$user->supports("foo") will also return true for User::Hash if there is such a key
[catagits/Catalyst-Plugin-Authentication.git] / notes.pod
CommitLineData
021c8622 1=pod
2
3=head1 CORE CONCEPTS
4
5=item authentication
6
7Authentication means ensuring the user is who they claim to be.
8
9=item authorization
10
11Once a user's identity is known and authenticated, we can decide what they are
12authorized to do.
13
14=head2 Authentication
15
16The process of authenticating the user is, almost always, asking the user to
17prove they know a secret that only the real user would know.
18
19Ways of proving you know a secret are:
20
21=over 4
22
23=item cleartext password
24
25Just tell the secret to the other side to prove you know it.
26
27=item challange/response
28
29Prove you know the secret without revealing it, and without the possibility of
30an attacker gaining access even if they get a copy of the data sent from the
31client.
32
33=over 4
34
35=item 1
36
37The server sends some random data, unique for each request
38
39=item 2
40
41The client combines the random data with the secret, and digests that
42
43=item 3
44
45The server combines the data with the password as well, and makes sure the
46digest is the same as what the user sent.
47
48=back
49
50Can possibly be implemented optionally using javascript on the client side if
51it's enabled.
52
53=item public key cryptography
54
55...
56
57=back
58
59=head2 Authorization
60
61=over 4
62
63=item Role based classification
64
65Each user plays in several roles. Restricted actions can then check that the
66user belongs to a certain role (for example, for the 'delete' action you might
67want to ensure that a user is an 'editor').
68
69Since users are allowed to be in an arbitrary amount of roles the logic is
70simple and effective.
71
72=item ACLs
73
74Cascading on top of roles, and more tightly coupled with catalyst is the
75possibility of access control lists in two namespaces:
76
77=over 4
78
79=item *
80
81The perl package namespace
82
83=item *
84
85The mapped URI space
86
87=back
88
89For example, you could say that the controller MyApp::C::AdminPanel requires
90the role 'admin'.
91
92This should resemble the way apache can provide access control for
93C<< <Location ...> >> and C<< <Directory ...> >>.
94
95=back
96
97=head1 ASPECTS OF IMPLEMENTATION
98
99=head2 Authentication
100
101Authentication needs to take care of two things:
102
103=over 4
104
105=item *
106
107Storage of the user data
108
109=item *
110
111Authentication of credentials against storage
112
113=back
114
115Ideally the two are decoupled, with the following call chain relationship:
116
117When application asks the authentication plugin to verify credentials, the
118credential verification plugin asks storage retrieve the user based on the user
119ID (the credential plugin knows what part of the credential is a user ID).
120
121A storage must return an object representing the user, whose API is irrelevant
122here.
123
124It is up to the user to synchronize the user info storage with the credential
125plugin, so that the value retrieved from storage is the value the credential
126plugin expects.
127
128For example, the user/password credential plugin will look something like this:
129
130 sub authen_login {
131 my ( $c, %opts ) = @_;
132 my $user = $opts{user} || $c->auth_get_user( %opts ); # note that
133 # credential plugin methods should accept a user object if they are
134 # provided with one
135
136 if ( $user->password eq $opts->{password} ) {
137 ...
138 }
139 }
140
141A situation where login/password authentication would have to be changed is if
142the credential system uses /etc/passwd storage, for example. Since /etc/passwd
143contains hashes of passwords, we can't compare the password directly. The
144credential system must know to take care of this.
145
146The login/password plugin should probably be a bit more lax, making checks on
147$user to see what the user supports:
148
149 if (my $hash = eval { $user->hashed_password }) {
150 my $digest = Digest->new( $user->hash_algorithm );
151 ...
152 $digest->digest eq $hash; # yes, this is binay, not hex or whatever
153 } elsif (my $crypt = eval { $user->crypted_password }) {
154 return crypt( $password, $crypt ) eq $crypt;
155 } elsif (my $clear = eval { $user->password }) {
156 return $passwd eq $clear;
157 } else {
158 Catalyst::Exception->throw("password storage scheme possibilities exhausted");
159 }
160
161Or less optimistically
162
163 if ( $user->supports(password => "hashed") ) {
164 my $hash = $user->hashed_password;
165 ...
166 }
167
168C<supports> in the user base class should look like this:
169
170 sub supports {
171 my ( $self, @path ) = @_;
172
173 my $cursor = $self->_supports; # this is a nested hash
174
175 while (@path) {
176 ref $cursor or return undef;
177 $cursor = $cursor->{ shift @path };
178 }
179
180 return $cursor && !ref($cursor);
181 }
182
183Which just goes through a nested hash, populated by the implementation class.
184
185The structure of this hash should be converged by the community, and should
186look something like this for a plugin that supports everything I can think of:
187
188 {
189 password => {
190 hash => 1,
191 crypt => 1,
192 clear => 1,
193 },
194 key {
195 gpg => 1,
196 ssh => 1,
197 ...
198 },
199 secureid => 1,
200 kerberos => 1, # maybe this needs to be deeper? i dunno
201 },
202
203
204In the event that the storage backend implies a certain format, the credential
205verification and storage plugins should ship together, for convenience.
206
207=head3 Testing for authentication
208
209Once authenticated the authentication plugin sets the C<user> accessor in the
210context object to contain the user object.
211
212=head3 Stores are just models
213
214To make things more flexible, stores are really just models.
215
216To make things easy a "default" auth storage can be [automatically] selected
217for the user for the more common situation of one auth driver per app:
218
219 sub auth_get_user {
220 my ( $c, %opts ) = @_;
221
222 ( $opts->{store} || $c->find_auth_store )->auth_get_user( %opts );
223 }
224
225 sub find_auth_store {
226 my $c = shift;
227
228 $c->config->{auth}{default_store} || ...;
229 }
230
231Ofcourse, %opts should not be just delegated blindly in the production
232versions. Perhaps even L<Params::Validate> is in order.
233
234=head3 Integration with Catalyst::Plugin::Session
235
236If a session plugin is loaded (C<< $c->isa("Catalyst::Plugin::Session) >>), it
237should also store the user ID in C<< $c->session->{user} >> unless
238C<< $c->config->{authentication}{use_session} >> is a false value (true by
239default).
240
241=head2 Authorization
242
243Authorization uses the same storage backend as authentication - that's why they
244are decoupled.
245
246Like the credential verification part, the authorization system simply assumes
247that the necesseary information is in the object representing the user.
248
249The API required should be strictly documented, in order to minimize confusion.
250
251For example, the role authorization plugin may require a C<roles> method:
252
253 use Quantum::Superpositions qw/all any/;
254
255 sub authz_check_roles {
256 my ( $c, @required_roles ) = @_;
257
258 all(@required_roles) == $c->user->roles;
259 }
260
261Which must be arranged by the authentication storage plugin's user class.
262
263In order to facilitate this all storage plugins, either trivial ones, should
264use factory methods for generating the user object. This allows the user to
265override the method, replacing the user class with their own, extended class
266that provides the necessary glue.
267
268=head1 DESIRED PLUGINS
269
270=head2 Credential Verification
271
272=over 4
273
274=item login/password
275
276Requires a C<password> method from the user, checks for equality
277
278=item unix passwd
279
280Requires a C<crypted_passwd> method from the user, checks for equality after crypt
281
282=item challenge response
283
284pretends to be a login/password compatible plugin, and will fall back to it,
285unless the client can use javascript to create a digest of the password and a
286seed instead of sending the actual password.
287
288=item http auth
289
290Send 401 status code, read back authentication headers
291
292=item delegated
293
294a passive authentication plugin that checks whether the user logged in using
295the engine's backend (e.g. apache) and provides a handle object representing
296that user that is not tied to any storage.
297
298=head2 Storage
299
300=over 4
301
302=item DBIC/CDBI
303
304User table, contains arbitrary fields and relationships.
305
306This should simply provide the interface for storage plugins based on a table
307in config.
308
309=item LDAP
310
311FIXME I'm not quite sure how LDAP looks from an OO perspective.
312
313=item htpasswd
314
315Implies credential verification for the various types of passwords htpasswd
316supports.
317
318Can use extra info field for additional interface glue. For example:
319
320 sub roles {
321 my $self = shift;
322 split(",", $self->info);
323 }
324
325=item passwd
326
327A subset of htpasswd, can use comment field for extended info like htpasswd.
328
329=item perl hash of objects
330
331a hash of user object keyed by user ID can prove very convenient for rapid
332development.
333
334=back
335
336=head2 Authorization
337
338=over 4
339
340=item roles
341
342=item ACLs
343
344A complex interface that layers on roles and automatically checks role
345membership for certain areas of an application based on a perl data structure.
346
347=back
348
349=head1 GUIDELINES
350
351Avoid new formats - there are enough standard formats out there that we can
352use. No need for XML, YAML, etc authentication support in the core of these plugins.
353
354These plugins can be supplemented by ::Simple namespaces that have the extra sugar.
355
356This is one of the reasons Authentication::Simple sucked so bad - it's file
357parser was half baked, it was very inefficient, and about 10x as many lines of
358code it could have been if the user simply had to put a perl hash of arrays in
359the config.
360
361
362=head1 TODO
363
364=over 4
365
366=item Look into L<Apache::AuthCookie>
367
368 < kgftr|konobi> nothingmuch: Apache::AuthCookie has a really nice way of
369 dealing with multiple types of auth (like roles, groups, etc)
370
371=back
372
373
374=head1 API tree
375
376This tree proposes a list the classes in the authentication/authorization
377plugins, as well as their methods.
378
379It's purpose is to help synchronize the authen/authz efforts, and is not to be
380considered a formal spec.
381
382 * Authentication (umbrella plugin)
383 - set_authenticated( $user ) # set $c->user, and $c->session->{user_id} = $user->id
384 - logout # delete $c->user, delete $c->session->{user_id}
385 - user # currently logged in user, if any
386 - prepare (extended) # checks session to see if a user should be logged in
387 * User
388 - id (the value that can be used to get the user with)
389 - supports
390 method name, or hierarchy
391 The methods listed under a hierarchy mean the methods you can call
392 if $user->supports( qw/password clear/ ) then you can $user->password safely
393 * password
394 * clear
395 - password
396 * hashed
397 - hashed_password
398 - hash_algorithm
399 * crypted
400 - crypted_password
401 * roles
402 - roles
403 * ...
79653417 404 * Store (plugin)
405 the storage plugin is a thin wrapper around a store model, that
406 provides a notion of a default store for easy dwimming.
407 it's two methods are just dump delegations to $c->config->{authentication}{store}
408 - get_user( $id )
409 - user_supports( $id )
410 * DBIC
411 generates an object, puts it in $c->{authetnciation}{store}
412 this object inherits the model class but is not really a model,
413 instead it delegates to the real DBIC model under it
414 * ... # each store impl has a dual life as a plugin
415 * Store::...::Model
416 provides the actual storage model glue. For example, in DBIC get_user
417 will do a retrieve on the user table.
021c8622 418 - get_user( $id ) # $id is username, or other credential a user might type in
419 - user_supports
420 like calling ->supports on the user, but applies to all users
421 if one user supports X but another doesn't, the global check will say they both don't
422 * Minimal
423 A nested hash in $c->config->{authentication}{entries}
424 {
425 user_id => $user_obj || $hash_ref
426 }
427 Hash ref will auto instantiate into a user obj that provides a default
428 implementation of supports and some accessors, based on the keys
429 * DBIC
430 storage dependent
431 - roles # has_many rel
432 - password # column
433 - crypted_password # column
434 - hashed_password # column
435 - hash_algorithm # column
436 - ... # columns
437 * UNIX (Unix::PasswdFile, Unix::GroupFile)
438 provides simple role based authentication
439 implies the Password credential checker
440 - uid
441 - name
442 - crypted_password
443 - gid
444 - comment
445 - home
446 - shell
447 - groups/roles # aliases
448 - add_group/add_role
449 uses group password to temporary add a group/role (newgrp)
450 stores the added groups in the session data
451 * Htpasswd (Apache::Htpasswd)
452 has optional role based authentication in the info field
453 ($c->config->{authentication}{htpasswd}{role_delimiter} defaults to ',')
454 implies the Password credential checker
455 - name
456 - password # htpasswd -p
457 - crypted_password # htpasswd -d
458 - hashed_password # htpasswd -s, htpasswd -m
459 - hash_algorithm
460 * Credential (plugin)
461 all $user args can be either user objects, or user ids that will be queried from
462 $c->config->{authentication}{store}
463 * Password
464 - login( $user, $password )
465 tries hashed password, crypted password, and password based on caps of user obj
466 * CRAM (requires session)
467 challange is the session ID
468 response is hash( challange + ( password | crypted_password | hashed_password ) )
469 possibly backed by javascript on the client side
470 - authenticate_cram( $user, $response )
471 hashes $c->sessionid and password variant based on config
472 * SRP (probably useless, but at least take a look - SSL uses it)
473 possibly backed by javascript on the client side
474 * HTTP Auth (like Catalyst::Plugin::Authentication::CDBI::Basic does)
475 subclasses Credential::Password, and just calls login automatically from prepare
476 takes a value to $c->forward to in case the user isn't authenticated, to generate 401
477 * Apache
478 logs in a user without a store, by checking apache's authentication data
479 * Net (chansen)
480 all of these are Password subclasses
481 * FTP
482 * SMTP
483 * POP3
484 * Secure ID
485 * SSH (chansen)
486 * Store/Credential hybrid
487 * LDAP
488 storage dependent... querying the database is tied with credential checking
489 - roles (memberOf)
490 - ... (attributes)
491 * RADIUS (chansen)
492 * PAM (chansen)
493 * Service
494 * OpenID (chansen)
495 * Passport (hah!)
496 * TypeKey
497 * Sxip
498 * SAML
499 * LID
500 * SMB (chansen)
501 * Kerberos (chansen)
502 * SASL (Authen::SASL)
503 * Authorization (plugin)
504 * Roles
505 checks a user's membership in roles
506 uses has_roles( @roles ) if the user class has that method (assumed to be more efficient)
507 uses roles and compares on it's own otherwise
508 - check_user_roles( [ $user ], @roles )
509 returns boolean, whether $c->user (or supplied user) has specified roles
510 - assert_user_roles( [ $user ] @roles )
511 throws an exception if $c->user (or supplied user) doesn't have the specified roles
512 * ACL
513 allows to add ACL rules to sections of the catalyst app
514 ACLs apply to perl namespace (MyApp::C::Foo "contains" MyApp::C::Foo::Bar, etc)
515 or they can apply to URL space (/foo contains /bar)
516 implies $c->user
517 takes a value to $c->forward to in case the user isn't authorized (error page)
518 - restrict_access ( $arg_like_forward_takes, @roles ) # roles to allow
519 - deny_access ( $arg_like_forward_takes, @roles ) # roles to disallow
520 * SAML
521 * Login Form (plugin)
522 this is the plugin that will be used for CRAM stuff in javascript
523 http://pajhome.org.uk/crypt/ - bsd license hashes in js
524 http://pajhome.org.uk/crypt/md5/chaplogin.html
525 http://perl-md5-login.sourceforge.net/
526 it's just for convenience
527 - login_form( [ $store ] )
528 uses user_supports and isa checks on credentials to create a form
529 that makes sense for the store provided if no store is provided uses
530 $c->config->{authenticion}{store}
531 - process_login
532 using the same info about stores/credentials, will pick up the data
533 used to generate a form, and find out what form data to use to login
534 - prepare
535 if $c->config->{authentication}{auto_login} is on will call process_login automatically
536