05 marzec 2010

ACL9 kompleksowe rozwiązanie autoryzacyjne dla aplikacji Internetowej

Kategorie:  RoR  Ruby  Web

Jak wykorzystać ACL9 do obsługi autoryzacji w połączeniu z Authlogic do obsługi uwierzytelnienia

W większości serwisów Internetowych deweloperzy nie muszą tworzyć kompleksowych rozwiązań autoryzacyjnych, wszystko czego im potrzeba to zarządzanie dostępem na podstawie prostego weryfikowania loginu i hasła. Jeśli użytkownik się zalogował ma dostęp do wszystkich funkcji serwisu, jeśli się nie zalogował, jedyne co może zrobić to zalogować się. W Ruby On Rails można to osiąnąc względnie prosto używając Authlogica. Jeśli prześledzisz screencast Ryan Bates'a dotyczący Authlogica nauczysz się jak to zrobić.

W projekcie nad którym pracowałem, potrzebowałem znacznie bardziej zaawansowanego rozwiązania. To czego potrzebowałem to:

  • możliwości kontrolowania dostępu opartego o różne role użytkowników
  • możliwości kontrolowania dostępu na poziomie akcji kontrolera
  • możliwości zmiany ról użytkowników w czasie wykonywania aplikacji
  • możliwości kontrolowania dostępu na poziomie modelu

Zdążyłem się już przyzwyczaić do tego, że w Ruby On Rails ktoś już napisał API, którego mogę użyć. Wszystko co musiałem zrobić to zainstalować wtyczkę autoryzacyjną ACL9:

script/plugin install git://github.com/be9/acl9.git

Postaram się tutaj pokazać jak pomocne jest API ACL9, poprzez zaprezentowanie małych fragmentów kodu z mojego projektu.

Pierwszą rzecz jaką musisz zrobić jest pozwolenia aby twój system przechwycił specjalny wyjątek, który zostanie wyrzucony kiedy wystąpi problem z autoryzacją. Możesz to zrobić dodając jedną linię kodu do twojego głównego kontrolera aplikacji.

rescue_from 'Acl9::AccessDenied', :with => :access_denied

Następnie musisz zdefiniować akcję access_denided w twoim kontrolerze aplikacji. Moja wygląda następująco:

Wyświetl kod źródłowy metody access_denided
  1. def access_denied
  2. if current_user
  3. flash[:error] = "Security problem!"
  4. respond_to do |format|
  5. format.html {redirect_to :controller => "szws", :action =>"denied"}
  6. format.js {
  7. render :update do |page|
  8. page.redirect_to :controller => "szws", :action =>"denied"
  9. end
  10. }
  11. end
  12. else
  13. flash[:notice] = "You have to be loged in to see this page"
  14. respond_to do |format|
  15. format.html {redirect_to login_path}
  16. format.js {
  17. render :update do |page|
  18. page.redirect_to login_path
  19. end
  20. }
  21. end
  22. end
  23. end

Jak widzisz ta akcja obsługuje dwa przypadki. Pierwszy kiedy użytkownik jest zalogowany i zgłoszony zostanie wyjątek, wyświetlimy użytkownikowi wiadomość flash z odpowiednim komunikatem i przekierujemy go do specjalnej akcji w jednym kontrolerów. Ta akcja wygeneruje stronę ze szczegółowym opisem błędu. Drugi jeśli użytkownik nie jest zalogowany (niedostępny jest obiekt current_user) wyświetlamy odpowiednią informację i przekierowujemy użytkownika do strony logowania.

Jak pewnie zauważyłeś mój system dzięki wykorzystaniu bloku kodu respond_to będzie poprawnie reagował na błędy autoryzacji generowane zarówno podczas normalnego żądania html jak i żądania typu xmlHTTPRequest generowanego przez wywołania Ajaxowe.

Co jest genialną rzeczą w ACL9 to bezpośrednia integracja z rozwiązaniem uwierzytelniającym, którego używam. Obie usługi wykorzystują obiekt current_user do zarządzania statusem logowania użytkownika. Możesz być zainteresowany otrzymaniem odpowiedzi na pytanie: Co należy zrobić kiedy chcesz sprawdzić dostęp użytkownika na podstawie jego statusu logowania? Spójrz na część kodu kontrolera user_session:

  1. access_control :debug => true do
  2. allow logged_in, :to => [:destroy]
  3. allow anonymous, :to => [:new, :create]
  4. end

Tutaj możesz zauważyć podstawową kontrole dostępu na podstawie statusu logowania. Ta mała część kodu jak jest napisane w API ACL9 stworzy poprzedzający filtr, który dokona sprawdzenia dostępu przez wykonaniem akcji naszego kontrolera. To co tu widzimy to to, że zalogowany użytkownik może wykonać akcję destroy, która obsługuje operację wylogowania, a dowolny niezalogowany użytkownik (anonymous) może wykonać akcje new i create, tak więc ma dostęp do strony logowania (akcja new) i może podjąć próbę zalogowania się (akcja create). Istnieje także jeszcze jedna przedefiniowana rola nazwana all. Jeśli użyjesz allow all to oznacza to, że operacje zawsze będą dostępne. Możesz także użyć deny zamiast allow aby upewnić się, że ktoś nie ma możliwości zrobienia czegoś. Zalecam wszystkim zatrzymanie :debug => true w fazie rozwijania projektu, ułatwi to znalezienie problemu jeśli coś nieoczekiwanego zacznie się dziać z częścią kodu odpowiedzialnego za autoryzację.

Spójrzmy teraz na autoryzację opartą o role. Rozpoczniemy od zajrzenia do kodu źródłowego mojego modulu użytkownika

Wyświetl kod źródłowy modelu user
  1. class User < ActiveRecord::Base
  2. #This is authentication subject
  3. acts_as_authentic do |c|
  4. c.validate_email_field = false
  5. c.validate_login_field = true
  6. #make sure that one workshop wont be able to have two users of the same name
  7. c.validations_scope = :workshop_id
  8. c.logged_in_timeout=30.minutes;
  9. c.maintain_sessions=false
  10. c.ignore_blank_passwords=false
  11. end
  12. #This is authorization subject
  13. acts_as_authorization_subject
  14. #This is also an authorization object
  15. acts_as_authorization_object
  16.  
  17. belongs_to :workshop
  18.  
  19. after_create :make_owner, :assign_security_roles
  20.  
  21. def self.find_user_in_workshop(login)
  22. workshop_unique=login[:workshop_unique]
  23. username = login[:username]
  24. workshop = Workshop.find_by_workshop_unique(workshop_unique)
  25. unless workshop.nil?
  26. find_by_username_and_workshop_id(username,workshop.id)
  27. else
  28. return nil
  29. end
  30. end
  31.  
  32. #Assign new user to standard security grups
  33. def assign_security_roles
  34. unless self.undeletable
  35. self.has_role! :warehouseman
  36. self.has_role! :dispatcher
  37. end
  38. end
  39.  
  40. #Make user owner of his data
  41. def make_owner
  42. self.has_role!(:owner,self)
  43. end
  44.  
  45. end

Pominiemy część kodu dotyczącą Authlogica i skupimy się na liniach używających API ACL9. Pierwszą rzeczą jaką widzimy jest acts_as_authorization_subject, to pozwala nam przypisywać role do dowolnego użytkownika. Druga rzecz to coś co uwielbiam w ACL9: acts_as_authorization_object, to pozwala nam przypisywać prawa dostępu na poziomie modelu. Dzięki temu możemy przypisać uprawnienia do dowolnej instancji danego obiektu. Jeśli spojrzysz głębiej w kod modelu użytkownika znajdziesz tam konkretne przykłady użycia. Metoda assign_security_roles pokazuje jak dopisać rolę do podmiotu autoryzacji. Możesz to zrobić poprzez proste wywołanie object.has_role! :role_name. Czynimy tutaj naszego użytkownika członkiem ról dispatcher i warehouseman. Metoda make_owner pokazuje jak zdefiniować autoryzację na poziomie modelu. Jest to również proste wywołanie: object.has_role!(:role_name,authorization_object). W ten sposób upewniam się, że każdy użytkownik będzie właścicielem swoich danych. To pozwoli mojemu użytkownikowi edytować jego własny profil bez względu na przynależność do ról systemowych. Jak widzisz obie metody są wywołane w call backu after_create w ten sposób każdy tworzony użytkownik będzie właścicielem swoich danych i będzie przypisany do domyślnych ról bezpieczeństwa.

Teraz możemy spojrzeć jak używać kontroli autoryzacji opartej o role w kontrolerach. Spójrz na kod ACL9 z mojego kontrolera users.

  1. access_control :debug => true do
  2. allow :admin, :manager
  3. allow :owner, :of => :user, :to => [:edituser,:update,:destroy,:changepasswd,:dochangepasswd]
  4. end

Najpierw pozwalamy na wykonanie wszystkich akcji dostępnych w naszym kontrolerze users rolom admin i manager. Następnie pozwalamy właścicielowi (owner) instancji obiektu user wykonywać tylko wymienione akcje. Ta część kodu pozwala naszym użytkownikom w pełnym zakresie zarządzać ich profilami. Jeśli potrzebujesz możesz także użyć wywołania except zamiast to definiując listę akcji.

Jeśli zabraniamy wykonywać pewnych zadań naszym użytkownikom, zdecydowanie lepiej jest nie wyświetlać im tych zadań. W ACL9 możesz osiągnąć to w prosty sposób. Spójrz na przykład menu poniżej:

Wyświetl kod źródłowy przykładu menu

Jak widzisz sprawdzanie ról może być wykonane poprzez proste wywołanie object.has_role? role_name. Authlogic zajmuje się tworzeniem i zarządzaniem obiektem current_user, tak więc dzięki ACL9 i Authlogicowi mogę wyświetlić użytkownikom menu zależnie od ich uprawnień w systemie.

Właśnie w ten sposób używam ACL9 w mojej aplikacji. Jest możliwe, że znajdziesz bardziej interesujące rzeczy w API, więc upewnij się, że je przeczytasz, zanim użyjesz tej świetnej wtyczki Railsów.

Sources




Komentarze

Jeśli znalazłeś jakieś błędy w powyższej informacji lub po prostu chcesz wypowiedzieć swoje zdanie na jej temat, będę wdzięczny za pozostawienie komentarza.
Wszystkie komentarze będą pokazywać się na stronie po tym jak zostaną zatwierdzone. Przepraszam za to ale chcę mieć pewność, że moja strona będzie wolna od obraźliwych lub wulgarnych treści. Nie mam nic przeciwko krytyce ale zrób to właściwie dobierając słowa.

Pozostaw komentarz