initial commit
[catagits/Catalyst-Authentication-Credential-OpenID.git] / lib / Catalyst / Authentication / Credential / toss
1 package Catalyst::Authentication::Credential::OpenID;
2 use base "Class::Accessor::Fast";
3
4 BEGIN {
5     __PACKAGE__->mk_accessors(qw/ _config realm debug secret /);
6 }
7
8 use strict;
9 use warnings;
10 our $VERSION = '0.01';
11
12 use Net::OpenID::Consumer;
13 use LWPx::ParanoidAgent;
14 use UNIVERSAL::require;
15 use Catalyst::Exception ();
16
17 use Data::Dumper;
18
19 sub new : method {
20     my ( $class, $config, $c, $realm ) = @_;
21     my $self = { _config => { %{ $config },
22                               %{ $realm->{config} }
23                           }
24                  };
25     bless $self, $class;
26
27     # 2.0 "SHOULD"
28     $self->_config->{openid_field} ||= "openid_identifier"; # 2.0 SHOULD
29
30     $self->debug( $self->_config->{debug} );
31
32     my $secret = $self->_config->{consumer_secret} ||= join("+",
33                                                             sort %{ $c->config },
34                                                             __PACKAGE__,
35                                                             $VERSION);
36
37     $c->log->debug("Setting consumer secret: " . $secret) if $self->debug;
38     $self->secret( $secret );
39     return $self;
40 }
41
42 sub authenticate {
43     my ( $self, $c, $realm, $authinfo ) = @_;
44
45     $c->log->debug("authenticate() called from " . $c->request->uri) if $self->debug;
46
47     my $field = $self->{_config}->{openid_field};
48
49     my $claimed_uri = $authinfo->{ $field };
50     $claimed_uri ||= $c->req->params->{ $field };
51
52     $c->log->debug(Dumper $self->_config->{ua_args});
53
54     my $csr = Net::OpenID::Consumer->new(
55         ua => ($self->_config->{ua} || "LWPx::ParanoidAgent")->new(%{$self->_config->{ua_args} || {}}),
56         args => $c->req->params,
57         consumer_secret => sub { $self->secret },
58     );
59
60     if ( $claimed_uri )
61     {
62         my $current = $c->req->uri;
63         $current->query(undef); # no query
64         my $identity = $csr->claimed_identity($claimed_uri)
65             or Catalyst::Exception->throw($csr->err);
66         my $check_url = $identity->check_url(
67             return_to  => $current . '?openid-check=1',
68             trust_root => $current,
69             delayed_return => 1,
70         );
71         $c->res->redirect($check_url);
72         return 0;
73     }
74     elsif ( $c->req->params->{'openid-check'} )
75     {
76         if ( my $setup_url = $csr->user_setup_url )
77         {
78             $c->res->redirect($setup_url);
79             return;
80         }
81         elsif ( $csr->user_cancel )
82         {
83             return;
84         }
85         elsif ( my $identity = $csr->verified_identity )
86         {
87             my $user = +{ map { $_ => scalar $identity->$_ }
88                 qw( url display rss atom foaf declared_rss declared_atom declared_foaf foafmaker ) };
89
90             my $user_obj = $realm->find_user($user, $c);
91
92             if ( ref $user_obj )
93             {
94                 return $user_obj;
95             }
96             else
97             {
98                 $c->log->debug("Verified OpenID identity failed to load with find_user; bad user_class? Try 'Null.'") if $c->debug;
99                 return;
100             }
101         }
102         else
103         {
104             Catalyst::Exception->throw("Error validating identity: " .
105                                        $csr->err);
106         }
107     }
108     else
109     {
110         return;
111     }
112 }
113
114 1;
115
116 __END__
117
118
119 sub setup {
120     my $c = shift;
121     my $config = $c->config->{authentication}->{openid} ||= {};
122     ( $config->{user_class}
123         ||=  "Catalyst::Plugin::Authentication::User::Hash" )->require;
124     $c->NEXT::setup(@_);
125 }
126
127 sub authenticate_openid {
128     my($c, $uri) = @_;
129
130     my $config = $c->config->{authentication}->{openid};
131
132     my $csr = Net::OpenID::Consumer->new(
133         ua => LWPx::ParanoidAgent->new,
134         args => $c->req->params,
135         consumer_secret => sub { $_[0] },
136     );
137
138     my @try_params = qw( openid_url openid_identifier claimed_uri );
139     if ($uri ||= (grep defined, @{$c->req->params}{@try_params})[0]) {
140         my $current = $c->req->uri;
141         $current->query(undef); # no query
142         my $identity = $csr->claimed_identity($uri)
143             or Catalyst::Exception->throw($csr->err);
144         my $check_url = $identity->check_url(
145             return_to  => $current . '?openid-check=1',
146             trust_root => $current,
147             delayed_return => 1,
148         );
149         $c->res->redirect($check_url);
150         return 0;
151     } elsif ($c->req->param('openid-check')) {
152         if (my $setup_url = $csr->user_setup_url) {
153             $c->res->redirect($setup_url);
154             return 0;
155         } elsif ($csr->user_cancel) {
156             return 0;
157         } elsif (my $identity = $csr->verified_identity) {
158             my $user = +{ map { $_ => scalar $identity->$_ }
159                 qw( url display rss atom foaf declared_rss declared_atom declared_foaf foafmaker ) };
160
161             my $store = $config->{store} || $c->default_auth_store;
162             if ( $store
163                  and my $store_user
164                  = $store->get_user( $user->{url}, $user ) ) {
165                 $c->set_authenticated($store_user);
166             } else {
167                 $user = $config->{user_class}->new($user);
168                 $c->set_authenticated($user);
169             }
170             return 1;
171         } else {
172             Catalyst::Exception->throw("Error validating identity: " .
173                 $csr->err);
174         }
175     } else {
176         return 0;
177     }
178 }
179
180 1;
181 __END__
182
183 =for stopwords
184     Flickr
185     OpenID
186     TypeKey
187     app
188     auth
189     callback
190     foaf
191     foafmaker
192     plugins
193     rss
194     url
195     URI
196
197 =head1 NAME
198
199 Catalyst::Plugin::Authentication::Credential::OpenID - OpenID credential for Catalyst::Auth framework
200
201 =head1 SYNOPSIS
202
203   use Catalyst qw/
204     Authentication
205     Authentication::Credential::OpenID
206     Session
207     Session::Store::FastMmap
208     Session::State::Cookie
209   /;
210
211   # MyApp.yaml -- optional
212   authentication:
213     openid:
214       use_session: 1
215       user_class: MyApp::M::User::OpenID
216
217   # whatever in your Controller pm
218   sub default : Private {
219       my($self, $c) = @_;
220       if ($c->user_exists) { ... }
221   }
222
223   sub signin_openid : Local {
224       my($self, $c) = @_;
225
226       if ($c->authenticate_openid) {
227           $c->res->redirect( $c->uri_for('/') );
228       }
229   }
230
231   # foo.tt
232   <form action="[% c.uri_for('/signin_openid') %]" method="GET">
233   <input type="text" name="openid_url" class="openid" />
234   <input type="submit" value="Sign in with OpenID" />
235   </form>
236
237 =head1 DESCRIPTION
238
239 Catalyst::Plugin::Authentication::Credential::OpenID is an OpenID
240 credential for Catalyst::Plugin::Authentication framework.
241
242 =head1 METHODS
243
244 =over 4
245
246 =item authenticate_openid
247
248   $c->authenticate_openid;
249
250 Call this method in the action you'd like to authenticate the user via
251 OpenID. Returns 0 if auth is not successful, and 1 if user is
252 authenticated.
253
254 User class specified with I<user_class> config, which defaults to
255 Catalyst::Plugin::Authentication::User::Hash, will be instantiated
256 with the following parameters.
257
258 By default, L<authenticate_openid> method looks for claimed URI
259 parameter from the form field named C<openid_url>,
260 C<openid_identifier> or C<claimed_uri>. If you want to use another
261 form field name, call it like:
262
263   $c->authenticate_openid( $c->req->param('myopenid_param') );
264
265 =over 8
266
267 =item url
268
269 =item display
270
271 =item rss
272
273 =item atom
274
275 =item foaf
276
277 =item declared_rss
278
279 =item declared_atom
280
281 =item declared_foaf
282
283 =item foafmaker
284
285 =back
286
287 See L<Net::OpenID::VerifiedIdentity> for details.
288
289 =back
290
291 =head1 DIFFERENCE WITH Authentication::OpenID
292
293 There's already Catalyst::Plugin::Authentication::OpenID
294 (Auth::OpenID) and this plugin tries to deprecate it.
295
296 =over 4
297
298 =item *
299
300 Don't use this plugin with Auth::OpenID since method names will
301 conflict and your app won't work.
302
303 =item *
304
305 Auth::OpenID uses your root path (/) as an authentication callback but
306 this plugin uses the current path, which means you can use this plugin
307 with other Credential plugins, like Flickr or TypeKey.
308
309 =item *
310
311 This plugin is NOT a drop-in replacement for Auth::OpenID, but your
312 app needs only slight modifications to work with this one.
313
314 =item *
315
316 This plugin is based on Catalyst authentication framework, which means
317 you can specify I<user_class> or I<auth_store> in your app config and
318 this modules does the right thing, like other Credential modules. This
319 crates new User object if authentication is successful, and works with
320 Session too.
321
322 =back
323
324 =head1 AUTHOR
325
326 Six Apart, Ltd. E<lt>cpan@sixapart.comE<gt>
327
328 =head1 LICENSE
329
330 This library is free software; you can redistribute it and/or modify
331 it under the same terms as Perl itself.
332
333 =head1 SEE ALSO
334
335 L<Catalyst::Plugin::Authentication::OpenID>, L<Catalyst::Plugin::Authentication::Credential::Flickr>
336
337 =cut