Commit | Line | Data |
021c8622 |
1 | =pod |
2 | |
3 | =head1 CORE CONCEPTS |
4 | |
5 | =item authentication |
6 | |
7 | Authentication means ensuring the user is who they claim to be. |
8 | |
9 | =item authorization |
10 | |
11 | Once a user's identity is known and authenticated, we can decide what they are |
12 | authorized to do. |
13 | |
14 | =head2 Authentication |
15 | |
16 | The process of authenticating the user is, almost always, asking the user to |
17 | prove they know a secret that only the real user would know. |
18 | |
19 | Ways of proving you know a secret are: |
20 | |
21 | =over 4 |
22 | |
23 | =item cleartext password |
24 | |
25 | Just tell the secret to the other side to prove you know it. |
26 | |
27 | =item challange/response |
28 | |
29 | Prove you know the secret without revealing it, and without the possibility of |
30 | an attacker gaining access even if they get a copy of the data sent from the |
31 | client. |
32 | |
33 | =over 4 |
34 | |
35 | =item 1 |
36 | |
37 | The server sends some random data, unique for each request |
38 | |
39 | =item 2 |
40 | |
41 | The client combines the random data with the secret, and digests that |
42 | |
43 | =item 3 |
44 | |
45 | The server combines the data with the password as well, and makes sure the |
46 | digest is the same as what the user sent. |
47 | |
48 | =back |
49 | |
50 | Can possibly be implemented optionally using javascript on the client side if |
51 | it'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 | |
65 | Each user plays in several roles. Restricted actions can then check that the |
66 | user belongs to a certain role (for example, for the 'delete' action you might |
67 | want to ensure that a user is an 'editor'). |
68 | |
69 | Since users are allowed to be in an arbitrary amount of roles the logic is |
70 | simple and effective. |
71 | |
72 | =item ACLs |
73 | |
74 | Cascading on top of roles, and more tightly coupled with catalyst is the |
75 | possibility of access control lists in two namespaces: |
76 | |
77 | =over 4 |
78 | |
79 | =item * |
80 | |
81 | The perl package namespace |
82 | |
83 | =item * |
84 | |
85 | The mapped URI space |
86 | |
87 | =back |
88 | |
89 | For example, you could say that the controller MyApp::C::AdminPanel requires |
90 | the role 'admin'. |
91 | |
92 | This should resemble the way apache can provide access control for |
93 | C<< <Location ...> >> and C<< <Directory ...> >>. |
94 | |
95 | =back |
96 | |
97 | =head1 ASPECTS OF IMPLEMENTATION |
98 | |
99 | =head2 Authentication |
100 | |
101 | Authentication needs to take care of two things: |
102 | |
103 | =over 4 |
104 | |
105 | =item * |
106 | |
107 | Storage of the user data |
108 | |
109 | =item * |
110 | |
111 | Authentication of credentials against storage |
112 | |
113 | =back |
114 | |
115 | Ideally the two are decoupled, with the following call chain relationship: |
116 | |
117 | When application asks the authentication plugin to verify credentials, the |
118 | credential verification plugin asks storage retrieve the user based on the user |
119 | ID (the credential plugin knows what part of the credential is a user ID). |
120 | |
121 | A storage must return an object representing the user, whose API is irrelevant |
122 | here. |
123 | |
124 | It is up to the user to synchronize the user info storage with the credential |
125 | plugin, so that the value retrieved from storage is the value the credential |
126 | plugin expects. |
127 | |
128 | For 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 | |
141 | A situation where login/password authentication would have to be changed is if |
142 | the credential system uses /etc/passwd storage, for example. Since /etc/passwd |
143 | contains hashes of passwords, we can't compare the password directly. The |
144 | credential system must know to take care of this. |
145 | |
146 | The 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 | |
161 | Or less optimistically |
162 | |
163 | if ( $user->supports(password => "hashed") ) { |
164 | my $hash = $user->hashed_password; |
165 | ... |
166 | } |
167 | |
168 | C<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 | |
183 | Which just goes through a nested hash, populated by the implementation class. |
184 | |
185 | The structure of this hash should be converged by the community, and should |
186 | look 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 | |
204 | In the event that the storage backend implies a certain format, the credential |
205 | verification and storage plugins should ship together, for convenience. |
206 | |
207 | =head3 Testing for authentication |
208 | |
209 | Once authenticated the authentication plugin sets the C<user> accessor in the |
210 | context object to contain the user object. |
211 | |
212 | =head3 Stores are just models |
213 | |
214 | To make things more flexible, stores are really just models. |
215 | |
216 | To make things easy a "default" auth storage can be [automatically] selected |
217 | for 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 | |
231 | Ofcourse, %opts should not be just delegated blindly in the production |
232 | versions. Perhaps even L<Params::Validate> is in order. |
233 | |
234 | =head3 Integration with Catalyst::Plugin::Session |
235 | |
236 | If a session plugin is loaded (C<< $c->isa("Catalyst::Plugin::Session) >>), it |
237 | should also store the user ID in C<< $c->session->{user} >> unless |
238 | C<< $c->config->{authentication}{use_session} >> is a false value (true by |
239 | default). |
240 | |
241 | =head2 Authorization |
242 | |
243 | Authorization uses the same storage backend as authentication - that's why they |
244 | are decoupled. |
245 | |
246 | Like the credential verification part, the authorization system simply assumes |
247 | that the necesseary information is in the object representing the user. |
248 | |
249 | The API required should be strictly documented, in order to minimize confusion. |
250 | |
251 | For 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 | |
261 | Which must be arranged by the authentication storage plugin's user class. |
262 | |
263 | In order to facilitate this all storage plugins, either trivial ones, should |
264 | use factory methods for generating the user object. This allows the user to |
265 | override the method, replacing the user class with their own, extended class |
266 | that provides the necessary glue. |
267 | |
268 | =head1 DESIRED PLUGINS |
269 | |
270 | =head2 Credential Verification |
271 | |
272 | =over 4 |
273 | |
274 | =item login/password |
275 | |
276 | Requires a C<password> method from the user, checks for equality |
277 | |
278 | =item unix passwd |
279 | |
280 | Requires a C<crypted_passwd> method from the user, checks for equality after crypt |
281 | |
282 | =item challenge response |
283 | |
284 | pretends to be a login/password compatible plugin, and will fall back to it, |
285 | unless the client can use javascript to create a digest of the password and a |
286 | seed instead of sending the actual password. |
287 | |
288 | =item http auth |
289 | |
290 | Send 401 status code, read back authentication headers |
291 | |
292 | =item delegated |
293 | |
294 | a passive authentication plugin that checks whether the user logged in using |
295 | the engine's backend (e.g. apache) and provides a handle object representing |
296 | that user that is not tied to any storage. |
297 | |
298 | =head2 Storage |
299 | |
300 | =over 4 |
301 | |
302 | =item DBIC/CDBI |
303 | |
304 | User table, contains arbitrary fields and relationships. |
305 | |
306 | This should simply provide the interface for storage plugins based on a table |
307 | in config. |
308 | |
309 | =item LDAP |
310 | |
311 | FIXME I'm not quite sure how LDAP looks from an OO perspective. |
312 | |
313 | =item htpasswd |
314 | |
315 | Implies credential verification for the various types of passwords htpasswd |
316 | supports. |
317 | |
318 | Can 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 | |
327 | A subset of htpasswd, can use comment field for extended info like htpasswd. |
328 | |
329 | =item perl hash of objects |
330 | |
331 | a hash of user object keyed by user ID can prove very convenient for rapid |
332 | development. |
333 | |
334 | =back |
335 | |
336 | =head2 Authorization |
337 | |
338 | =over 4 |
339 | |
340 | =item roles |
341 | |
342 | =item ACLs |
343 | |
344 | A complex interface that layers on roles and automatically checks role |
345 | membership for certain areas of an application based on a perl data structure. |
346 | |
347 | =back |
348 | |
349 | =head1 GUIDELINES |
350 | |
351 | Avoid new formats - there are enough standard formats out there that we can |
352 | use. No need for XML, YAML, etc authentication support in the core of these plugins. |
353 | |
354 | These plugins can be supplemented by ::Simple namespaces that have the extra sugar. |
355 | |
356 | This is one of the reasons Authentication::Simple sucked so bad - it's file |
357 | parser was half baked, it was very inefficient, and about 10x as many lines of |
358 | code it could have been if the user simply had to put a perl hash of arrays in |
359 | the 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 | |
376 | This tree proposes a list the classes in the authentication/authorization |
377 | plugins, as well as their methods. |
378 | |
379 | It's purpose is to help synchronize the authen/authz efforts, and is not to be |
380 | considered 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 | |