=pod =head1 CORE CONCEPTS =item authentication Authentication means ensuring the user is who they claim to be. =item authorization Once a user's identity is known and authenticated, we can decide what they are authorized to do. =head2 Authentication The process of authenticating the user is, almost always, asking the user to prove they know a secret that only the real user would know. Ways of proving you know a secret are: =over 4 =item cleartext password Just tell the secret to the other side to prove you know it. =item challange/response Prove you know the secret without revealing it, and without the possibility of an attacker gaining access even if they get a copy of the data sent from the client. =over 4 =item 1 The server sends some random data, unique for each request =item 2 The client combines the random data with the secret, and digests that =item 3 The server combines the data with the password as well, and makes sure the digest is the same as what the user sent. =back Can possibly be implemented optionally using javascript on the client side if it's enabled. =item public key cryptography ... =back =head2 Authorization =over 4 =item Role based classification Each user plays in several roles. Restricted actions can then check that the user belongs to a certain role (for example, for the 'delete' action you might want to ensure that a user is an 'editor'). Since users are allowed to be in an arbitrary amount of roles the logic is simple and effective. =item ACLs Cascading on top of roles, and more tightly coupled with catalyst is the possibility of access control lists in two namespaces: =over 4 =item * The perl package namespace =item * The mapped URI space =back For example, you could say that the controller MyApp::C::AdminPanel requires the role 'admin'. This should resemble the way apache can provide access control for C<< >> and C<< >>. =back =head1 ASPECTS OF IMPLEMENTATION =head2 Authentication Authentication needs to take care of two things: =over 4 =item * Storage of the user data =item * Authentication of credentials against storage =back Ideally the two are decoupled, with the following call chain relationship: When application asks the authentication plugin to verify credentials, the credential verification plugin asks storage retrieve the user based on the user ID (the credential plugin knows what part of the credential is a user ID). A storage must return an object representing the user, whose API is irrelevant here. It is up to the user to synchronize the user info storage with the credential plugin, so that the value retrieved from storage is the value the credential plugin expects. For example, the user/password credential plugin will look something like this: sub authen_login { my ( $c, %opts ) = @_; my $user = $opts{user} || $c->auth_get_user( %opts ); # note that # credential plugin methods should accept a user object if they are # provided with one if ( $user->password eq $opts->{password} ) { ... } } A situation where login/password authentication would have to be changed is if the credential system uses /etc/passwd storage, for example. Since /etc/passwd contains hashes of passwords, we can't compare the password directly. The credential system must know to take care of this. The login/password plugin should probably be a bit more lax, making checks on $user to see what the user supports: if (my $hash = eval { $user->hashed_password }) { my $digest = Digest->new( $user->hash_algorithm ); ... $digest->digest eq $hash; # yes, this is binay, not hex or whatever } elsif (my $crypt = eval { $user->crypted_password }) { return crypt( $password, $crypt ) eq $crypt; } elsif (my $clear = eval { $user->password }) { return $passwd eq $clear; } else { Catalyst::Exception->throw("password storage scheme possibilities exhausted"); } Or less optimistically if ( $user->supports(password => "hashed") ) { my $hash = $user->hashed_password; ... } C in the user base class should look like this: sub supports { my ( $self, @path ) = @_; my $cursor = $self->_supports; # this is a nested hash while (@path) { ref $cursor or return undef; $cursor = $cursor->{ shift @path }; } return $cursor && !ref($cursor); } Which just goes through a nested hash, populated by the implementation class. The structure of this hash should be converged by the community, and should look something like this for a plugin that supports everything I can think of: { password => { hash => 1, crypt => 1, clear => 1, }, key { gpg => 1, ssh => 1, ... }, secureid => 1, kerberos => 1, # maybe this needs to be deeper? i dunno }, In the event that the storage backend implies a certain format, the credential verification and storage plugins should ship together, for convenience. =head3 Testing for authentication Once authenticated the authentication plugin sets the C accessor in the context object to contain the user object. =head3 Stores are just models To make things more flexible, stores are really just models. To make things easy a "default" auth storage can be [automatically] selected for the user for the more common situation of one auth driver per app: sub auth_get_user { my ( $c, %opts ) = @_; ( $opts->{store} || $c->find_auth_store )->auth_get_user( %opts ); } sub find_auth_store { my $c = shift; $c->config->{auth}{default_store} || ...; } Ofcourse, %opts should not be just delegated blindly in the production versions. Perhaps even L is in order. =head3 Integration with Catalyst::Plugin::Session If a session plugin is loaded (C<< $c->isa("Catalyst::Plugin::Session) >>), it should also store the user ID in C<< $c->session->{user} >> unless C<< $c->config->{authentication}{use_session} >> is a false value (true by default). =head2 Authorization Authorization uses the same storage backend as authentication - that's why they are decoupled. Like the credential verification part, the authorization system simply assumes that the necesseary information is in the object representing the user. The API required should be strictly documented, in order to minimize confusion. For example, the role authorization plugin may require a C method: use Quantum::Superpositions qw/all any/; sub authz_check_roles { my ( $c, @required_roles ) = @_; all(@required_roles) == $c->user->roles; } Which must be arranged by the authentication storage plugin's user class. In order to facilitate this all storage plugins, either trivial ones, should use factory methods for generating the user object. This allows the user to override the method, replacing the user class with their own, extended class that provides the necessary glue. =head1 DESIRED PLUGINS =head2 Credential Verification =over 4 =item login/password Requires a C method from the user, checks for equality =item unix passwd Requires a C method from the user, checks for equality after crypt =item challenge response pretends to be a login/password compatible plugin, and will fall back to it, unless the client can use javascript to create a digest of the password and a seed instead of sending the actual password. =item http auth Send 401 status code, read back authentication headers =item delegated a passive authentication plugin that checks whether the user logged in using the engine's backend (e.g. apache) and provides a handle object representing that user that is not tied to any storage. =head2 Storage =over 4 =item DBIC/CDBI User table, contains arbitrary fields and relationships. This should simply provide the interface for storage plugins based on a table in config. =item LDAP FIXME I'm not quite sure how LDAP looks from an OO perspective. =item htpasswd Implies credential verification for the various types of passwords htpasswd supports. Can use extra info field for additional interface glue. For example: sub roles { my $self = shift; split(",", $self->info); } =item passwd A subset of htpasswd, can use comment field for extended info like htpasswd. =item perl hash of objects a hash of user object keyed by user ID can prove very convenient for rapid development. =back =head2 Authorization =over 4 =item roles =item ACLs A complex interface that layers on roles and automatically checks role membership for certain areas of an application based on a perl data structure. =back =head1 GUIDELINES Avoid new formats - there are enough standard formats out there that we can use. No need for XML, YAML, etc authentication support in the core of these plugins. These plugins can be supplemented by ::Simple namespaces that have the extra sugar. This is one of the reasons Authentication::Simple sucked so bad - it's file parser was half baked, it was very inefficient, and about 10x as many lines of code it could have been if the user simply had to put a perl hash of arrays in the config. =head1 TODO =over 4 =item Look into L < kgftr|konobi> nothingmuch: Apache::AuthCookie has a really nice way of dealing with multiple types of auth (like roles, groups, etc) =back =head1 API tree This tree proposes a list the classes in the authentication/authorization plugins, as well as their methods. It's purpose is to help synchronize the authen/authz efforts, and is not to be considered a formal spec. * Authentication (umbrella plugin) - set_authenticated( $user ) # set $c->user, and $c->session->{user_id} = $user->id - logout # delete $c->user, delete $c->session->{user_id} - user # currently logged in user, if any - prepare (extended) # checks session to see if a user should be logged in * User - id (the value that can be used to get the user with) - supports method name, or hierarchy The methods listed under a hierarchy mean the methods you can call if $user->supports( qw/password clear/ ) then you can $user->password safely * password * clear - password * hashed - hashed_password - hash_algorithm * crypted - crypted_password * roles - roles * ... * Store (plugin) the storage plugin is a thin wrapper around a store model, that provides a notion of a default store for easy dwimming. it's two methods are just dump delegations to $c->config->{authentication}{store} - get_user( $id ) - user_supports( $id ) * DBIC generates an object, puts it in $c->{authetnciation}{store} this object inherits the model class but is not really a model, instead it delegates to the real DBIC model under it * ... # each store impl has a dual life as a plugin * Store::...::Model provides the actual storage model glue. For example, in DBIC get_user will do a retrieve on the user table. - get_user( $id ) # $id is username, or other credential a user might type in - user_supports like calling ->supports on the user, but applies to all users if one user supports X but another doesn't, the global check will say they both don't * Minimal A nested hash in $c->config->{authentication}{entries} { user_id => $user_obj || $hash_ref } Hash ref will auto instantiate into a user obj that provides a default implementation of supports and some accessors, based on the keys * DBIC storage dependent - roles # has_many rel - password # column - crypted_password # column - hashed_password # column - hash_algorithm # column - ... # columns * UNIX (Unix::PasswdFile, Unix::GroupFile) provides simple role based authentication implies the Password credential checker - uid - name - crypted_password - gid - comment - home - shell - groups/roles # aliases - add_group/add_role uses group password to temporary add a group/role (newgrp) stores the added groups in the session data * Htpasswd (Apache::Htpasswd) has optional role based authentication in the info field ($c->config->{authentication}{htpasswd}{role_delimiter} defaults to ',') implies the Password credential checker - name - password # htpasswd -p - crypted_password # htpasswd -d - hashed_password # htpasswd -s, htpasswd -m - hash_algorithm * Credential (plugin) all $user args can be either user objects, or user ids that will be queried from $c->config->{authentication}{store} * Password - login( $user, $password ) tries hashed password, crypted password, and password based on caps of user obj * CRAM (requires session) challange is the session ID response is hash( challange + ( password | crypted_password | hashed_password ) ) possibly backed by javascript on the client side - authenticate_cram( $user, $response ) hashes $c->sessionid and password variant based on config * SRP (probably useless, but at least take a look - SSL uses it) possibly backed by javascript on the client side * HTTP Auth (like Catalyst::Plugin::Authentication::CDBI::Basic does) subclasses Credential::Password, and just calls login automatically from prepare takes a value to $c->forward to in case the user isn't authenticated, to generate 401 * Apache logs in a user without a store, by checking apache's authentication data * Net (chansen) all of these are Password subclasses * FTP * SMTP * POP3 * Secure ID * SSH (chansen) * Store/Credential hybrid * LDAP storage dependent... querying the database is tied with credential checking - roles (memberOf) - ... (attributes) * RADIUS (chansen) * PAM (chansen) * Service * OpenID (chansen) * Passport (hah!) * TypeKey * Sxip * SAML * LID * SMB (chansen) * Kerberos (chansen) * SASL (Authen::SASL) * Authorization (plugin) * Roles checks a user's membership in roles uses has_roles( @roles ) if the user class has that method (assumed to be more efficient) uses roles and compares on it's own otherwise - check_user_roles( [ $user ], @roles ) returns boolean, whether $c->user (or supplied user) has specified roles - assert_user_roles( [ $user ] @roles ) throws an exception if $c->user (or supplied user) doesn't have the specified roles * ACL allows to add ACL rules to sections of the catalyst app ACLs apply to perl namespace (MyApp::C::Foo "contains" MyApp::C::Foo::Bar, etc) or they can apply to URL space (/foo contains /bar) implies $c->user takes a value to $c->forward to in case the user isn't authorized (error page) - restrict_access ( $arg_like_forward_takes, @roles ) # roles to allow - deny_access ( $arg_like_forward_takes, @roles ) # roles to disallow * SAML * Login Form (plugin) this is the plugin that will be used for CRAM stuff in javascript http://pajhome.org.uk/crypt/ - bsd license hashes in js http://pajhome.org.uk/crypt/md5/chaplogin.html http://perl-md5-login.sourceforge.net/ it's just for convenience - login_form( [ $store ] ) uses user_supports and isa checks on credentials to create a form that makes sense for the store provided if no store is provided uses $c->config->{authenticion}{store} - process_login using the same info about stores/credentials, will pick up the data used to generate a form, and find out what form data to use to login - prepare if $c->config->{authentication}{auto_login} is on will call process_login automatically