X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?p=catagits%2FCatalyst-Manual.git;a=blobdiff_plain;f=lib%2FCatalyst%2FManual%2FTutorial%2FAuthorization.pod;h=fce6161619b09cb046ac8b392d37005ac6184207;hp=b59facf69416ee45665c48f25125ff12d8be3494;hb=3b1fa91be1d89d2297aa9e8e83462344d9cd9820;hpb=e075db0c03ded5b1d100852f9ba9c040e2499109 diff --git a/lib/Catalyst/Manual/Tutorial/Authorization.pod b/lib/Catalyst/Manual/Tutorial/Authorization.pod index b59facf..fce6161 100644 --- a/lib/Catalyst/Manual/Tutorial/Authorization.pod +++ b/lib/Catalyst/Manual/Tutorial/Authorization.pod @@ -1,11 +1,11 @@ =head1 NAME -Catalyst::Manual::Tutorial::Authorization - Catalyst Tutorial - Part 6: Authorization +Catalyst::Manual::Tutorial::Authorization - Catalyst Tutorial - Chapter 6: Authorization =head1 OVERVIEW -This is B for the Catalyst tutorial. +This is B for the Catalyst tutorial. L @@ -56,11 +56,12 @@ L =head1 DESCRIPTION -This part of the tutorial adds role-based authorization to the existing -authentication implemented in Part 5. It provides simple examples of -how to use roles in both TT templates and controller actions. The first -half looks at manually configured authorization. The second half looks -at how the ACL authorization plugin can simplify your code. +This chapter of the tutorial adds role-based authorization to the +existing authentication implemented in Chapter 5. It provides simple +examples of how to use roles in both TT templates and controller +actions. The first half looks at basic authorization concepts. The +second half looks at how moving your authorization code to your model +can simplify your code and make things easier to maintain. You can checkout the source code for this example from the catalyst subversion repository as per the instructions in @@ -69,92 +70,54 @@ L. =head1 BASIC AUTHORIZATION -In this section you learn how to manually configure authorization. +In this section you learn the basics of how authorization works under +Catalyst. =head2 Update Plugins to Include Support for Authorization Edit C and add C to the list: - __PACKAGE__->setup(qw/ - -Debug - ConfigLoader - Static::Simple - - StackTrace - - Authentication - Authorization::Roles - - Session - Session::Store::FastMmap - Session::State::Cookie - /; + # Load plugins + use Catalyst qw/-Debug + ConfigLoader + Static::Simple + + StackTrace + + Authentication + Authorization::Roles + + Session + Session::Store::FastMmap + Session::State::Cookie + /; B As discussed in MoreCatalystBasics, different versions of C have used a variety of methods to load the plugins. -You can put the plugins in the C statement if you prefer. - - -=head2 Add Config Information for Authorization - -Edit C and update it to match the following (the -C and C definitions are new): - - # rename this file to MyApp.yml and put a : in front of "name" if - # you want to use yaml like in old versions of Catalyst - name MyApp - - default_realm dbic - - - - # Note this first definition would be the same as setting - # __PACKAGE__->config->{authentication}->{realms}->{dbic} - # ->{credential} = 'Password' in lib/MyApp.pm - # - # Specify that we are going to do password-based auth - class Password - # This is the name of the field in the users table with the - # password stored in it - password_field password - # Switch to more secure hashed passwords - password_type hashed - # Use the SHA-1 hashing algorithm - password_hash_type SHA-1 - - - # Use DBIC to retrieve username, password & role information - class DBIx::Class - # This is the model object created by Catalyst::Model::DBIC - # from your schema (you created 'MyApp::Schema::User' but as - # the Catalyst startup debug messages show, it was loaded as - # 'MyApp::Model::DB::Users'). - # NOTE: Omit 'MyApp::Model' here just as you would when using - # '$c->model("DB::Users)' - user_class DB::Users - # This is the name of a many_to_many relation in the users - # object that points to the roles for that user - role_relation roles - # This is the name of field in the roles table that contains - # the role information - role_field role - - - - +You can put the plugins in the C statement if you +prefer. + +Once again (remain sharp, by now you should be getting the hang of things) +include this additional plugin as a new dependency in the Makefile.PL file +like this: + requires ( + ... + 'Catalyst::Plugin::Authorization::Roles' => '0', + ); =head2 Add Role-Specific Logic to the "Book List" Template Open C in your editor and add the following lines to the bottom of the file: + ...

Hello [% c.user.username %], you have the following roles:

    [% # Dump list of roles -%] - [% FOR role = c.user.roles %]
  • [% role %]
  • [% END %] + [% FOR role = c.user.role %]
  • [% role %]
  • [% END %]

@@ -168,7 +131,7 @@ lines to the bottom of the file: [% # Can also use $c->user->check_roles() to check authz -%] [% IF c.check_user_roles('admin') %] [% # Give admin users a link for 'create' %] - Admin Create + Admin Create [% END %]

@@ -176,7 +139,7 @@ This code displays a different combination of links depending on the roles assigned to the user. -=head2 Limit C to C Users +=head2 Limit Books::add to 'admin' Users C statements in TT templates simply control the output that is sent to the user's browser; it provides no real enforcement (if users know or @@ -205,28 +168,20 @@ updating C to match the following code: if ($c->check_user_roles('admin')) { # Call create() on the book model object. Pass the table # columns/field values we want to set as hash values - my $book = $c->model('DB::Books')->create({ + my $book = $c->model('DB::Book')->create({ title => $title, rating => $rating }); # Add a record to the join table for this book, mapping to # appropriate author - $book->add_to_book_authors({author_id => $author_id}); + $book->add_to_book_author({author_id => $author_id}); # Note: Above is a shortcut for this: - # $book->create_related('book_authors', {author_id => $author_id}); + # $book->create_related('book_author', {author_id => $author_id}); # Assign the Book object to the stash for display in the view $c->stash->{book} = $book; - # This is a hack to disable XSUB processing in Data::Dumper - # (it's used in the view). This is a work-around for a bug in - # the interaction of some versions or Perl, Data::Dumper & DBIC. - # You won't need this if you aren't using Data::Dumper (or if - # you are running DBIC 0.06001 or greater), but adding it doesn't - # hurt anything either. - $Data::Dumper::Useperl = 1; - # Set the TT template to use $c->stash->{template} = 'books/create_done.tt2'; } else { @@ -244,7 +199,7 @@ way to demonstrate that TT templates will not be used if the response body has already been set. In reality you would probably want to use a technique that maintains the visual continuity of your template layout (for example, using the "status" or "error" message feature added in -Part 3 or C to an action that shows an "unauthorized" page). +Chapter 3 or C to an action that shows an "unauthorized" page). B: If you want to keep your existing C method, you can create a new copy and comment out the original by making it look like a @@ -261,10 +216,10 @@ running) and restart it: Now trying going to L and you should be taken to the login page (you might have to C or -C your browser and/or click the "Logout" link on the book +C your browser and/or click the "User Logout" link on the book list page). Try logging in with both C and C (both use a password of C) and notice how the roles information -updates at the bottom of the "Book List" page. Also try the C +updates at the bottom of the "Book List" page. Also try the "User Logout" link on the book list page. Now the "url_create" URL will work if you are already logged in as user @@ -273,145 +228,122 @@ C. Try: http://localhost:3000/books/url_create/test/1/6 -while logged in as each user. Use one of the 'Logout' links (or go to +while logged in as each user. Use one of the "logout" links (or go to L in your browser directly) when you are done. -=head1 ENABLE ACL-BASED AUTHORIZATION - -This section takes a brief look at how the -L -plugin can automate much of the work required to perform role-based -authorization in a Catalyst application. - - -=head2 Add the C Plugin - -Open C in your editor and add the following plugin to the -C<__PACKAGE__-Esetup> statement: - - Authorization::ACL - -Note that the remaining C plugins from earlier sections -are not shown here, but they should still be included. - - -=head2 Add ACL Rules to the Application Class - -Open C in your editor and add the following B the -C<__PACKAGE__-Esetup> statement: - - # Authorization::ACL Rules - __PACKAGE__->deny_access_unless( - "/books/form_create", - [qw/admin/], - ); - __PACKAGE__->deny_access_unless( - "/books/form_create_do", - [qw/admin/], - ); - __PACKAGE__->allow_access_if( - "/books/delete", - [qw/user admin/], - ); - -Each of the three statements above comprises an ACL plugin "rule". The -first two rules only allow admin-level users to create new books using -the form (both the form itself and the data submission logic are -protected). The third statement allows both users and admins to delete -books; letting users delete but not create book entries may sound odd in -the "real world", but this is just an example. The C -action will continue to be protected by the "manually configured" -authorization created earlier in this part of the tutorial. - -The ACL plugin permits you to apply allow/deny logic in a variety of -ways. The following provides a basic overview of the capabilities: - -=over 4 - -=item * - -The ACL plugin only operates on the Catalyst "private namespace". You -are using the private namespace when you use C actions. C, -C, and C allow you to specify actions where the path and -the namespace differ -- the ACL plugin will not work in these cases. - -=item * - -Each rule is expressed in a separate -C<__PACKAGE__-Edeny_access_unless()> or -C<__PACKAGE__-Eallow_access_if()> line (there are several other -methods that can be used for more complex policies, see the C -portion of the -L -documentation for more details). - -=item * - -Each rule can contain multiple roles but only a single path. - -=item * - -The rules are tried in order (with the "most specific" rules tested -first), and processing stops at the first "match" where an allow or deny -is specified. Rules "fall through" if there is not a "match" (where a -"match" means the user has the specified role). If a "match" is found, -then processing stops there and the appropriate allow/deny action is -taken. - -=item * +=head1 ENABLE MODEL-BASED AUTHORIZATION -If none of the rules match, then access is allowed. +Hopefully it's fairly obvious that adding detailed permission checking +logic to our controllers and view templates isn't a very clean or +scalable way to build role-based permissions into out application. As +with many other aspects of MVC web development, the goal is to have +your controllers and views be an "thin" as possible, with all of the +"fancy business logic" built into your model. -=item * - -The rules currently need to be specified in the application class -C B the C<__PACKAGE__-Esetup;> line. - -=back +For example, let's add a method to our C Result Class to +check if a user is allowed to delete a book. Open +C and add the following method +(be sure to add it below the "C" line): + =head2 delete_allowed_by + + Can the specified user delete the current book? + + =cut + + sub delete_allowed_by { + my ($self, $user) = @_; + + # Only allow delete if user has 'admin' role + return $user->has_role('admin'); + } -=head2 Add a Method to Handle Access Violations +Here we call a C method on our user object, so we should add +this method to our Result Class. Open +C and add the following method below +the "C" line: -By default, -L -throws an exception when authorization fails. This will take the user -to the Catalyst debug screen, or a "Please come back later" message if -you are not using the C<-Debug> flag. This step uses the -C method in order to provide more appropriate feedback to -the user. + =head 2 has_role + + Check if a user has the specified role + + =cut + + use Perl6::Junction qw/any/; + sub has_role { + my ($self, $role) = @_; + + # Does this user posses the required role? + return any(map { $_->role } $self->roles) eq $role; + } -Open C in your editor and add the -following method: +Now we need to add some enforcement inside our controller. Open +C and update the C method to +match the following code: - =head2 access_denied + =head2 delete - Handle Catalyst::Plugin::Authorization::ACL access denied exceptions + Delete a book =cut - sub access_denied : Private { + sub delete :Chained('object') :PathPart('delete') :Args(0) { my ($self, $c) = @_; - # Set the error message - $c->stash->{error_msg} = 'Unauthorized!'; + # Check permissions + $c->detach('/error_noperms') + unless $c->stash->{object}->delete_allowed_by($c->user->get_object); - # Display the list - $c->forward('list'); + # Use the book object saved by 'object' and delete it along + # with related 'book_authors' entries + $c->stash->{object}->delete; + + # Use 'flash' to save information across requests until it's read + $c->flash->{status_msg} = "Book deleted"; + + # Redirect the user back to the list page + $c->response->redirect($c->uri_for($self->action_for('list'))); + } + +Here, we C to an error page if the user is lacking the +appropriate permissions. For this to work, we need to make +arrangements for the '/error_noperms' action to work. Open +C and add this method: + + =head2 error_noperms + + Permissions error screen + + =cut + + sub error_noperms :Chained('/') :PathPath('error_noperms') :Args(0) { + my ($self, $c) = @_; + + $c->stash->{template} = 'error_noperms.tt2'; } +And also add the template file by putting the following text into +C: + + Permission Denied + Then run the Catalyst development server script: $ script/myapp_server.pl -Log in as C. Once at the book list, click the "Create" link -to try the C action. You should receive a red -"Unauthorized!" error message at the top of the list. (Note that in -the example code the "Admin Create" link code in C -is inside an C statement that only displays the list to -admin-level users.) If you log in as C you should be able to -view the C form and add a new book. +Log in as C and create several new books using the C +feature: + + http://localhost:3000/books/url_create/Test/1/4 + +Then, while still logged in as C, click the "Delete" link next +to one of these books. The book should be removed and you should see +the usual green "Book deleted" message. Next, click the "User Logout" +link and log back in as C. Now try deleting one of the books. +You should be taken to the red "Permission Denied" message on our +error page. Use one of the 'Logout' links (or go to the L URL directly) when you are done.