4e04addebf096ef63f214ef40628b9389d14811c
[catagits/Catalyst-Authentication-Credential-OAuth.git] / lib / Catalyst / Authentication / Credential / OAuth.pm
1 package Catalyst::Authentication::Credential::OAuth;
2 use Moose;
3 use MooseX::Types::Moose qw/ Bool HashRef /;
4 use MooseX::Types::Common::String qw/ NonEmptySimpleStr /;
5 use Net::OAuth;
6 #$Net::OAuth::PROTOCOL_VERSION = Net::OAuth::PROTOCOL_VERSION_1_0A;
7 use LWP::UserAgent;
8 use HTTP::Request::Common;
9 use String::Random qw/ random_string /;
10 use Catalyst::Exception ();
11 use namespace::autoclean;
12
13 our $VERSION = '0.04';
14
15 has debug => ( is => 'ro', isa => Bool );
16 has providers => ( is => 'ro', isa => HashRef, required => 1 );
17 has ua => ( is => 'ro', lazy_build => 1, init_arg => undef, isa => 'LWP::UserAgent' );
18
19 sub BUILDARGS {
20     my ($self, $config, $c, $realm) = @_;
21
22     return $config;
23 }
24
25 sub BUILD {
26     my ($self) = @_;
27     $self->ua; # Ensure lazy value is built.
28 }
29
30 sub _build_ua {
31     my $self = shift;
32
33     LWP::UserAgent->new;
34 }
35
36 sub authenticate {
37         my ($self, $c, $realm, $auth_info) = @_;
38
39     Catalyst::Exception->throw( "Provider is not defined." )
40         unless defined $auth_info->{provider} || defined $self->providers->{ $auth_info->{provider} };
41
42     my $provider = $self->providers->{ $auth_info->{provider} };
43
44     for ( qw/ consumer_key consumer_secret request_token_endpoint access_token_endpoint user_auth_endpoint extra_params / ) {
45         Catalyst::Exception->throw( $_ . " is not defined for provider ". $auth_info->{provider} )
46             unless $provider->{$_};
47     }
48
49     my %defaults = (
50         consumer_key => $provider->{consumer_key},
51         consumer_secret => $provider->{consumer_secret},
52         timestamp => time,
53         nonce => random_string( 'ccccccccccccccccccc' ),
54         request_method => 'GET',
55         signature_method => 'HMAC-SHA1',
56         oauth_version => '1.0a',
57         callback => $c->uri_for( $c->action, $c->req->captures, @{ $c->req->args } )->as_string,
58         ( defined $provider->{extra_params}
59           ? ( extra_params => $provider->{extra_params} )
60           : ()
61         ),
62     );
63
64         $c->log_debug( "authenticate() called from " . $c->request->uri ) if $self->debug;
65
66     my $oauth_token = $c->req->method eq 'GET'
67         ? $c->req->query_params->{oauth_token}
68         : $c->req->body_params->{oauth_token};
69
70         if( $oauth_token ) {
71
72                 my $response = Net::OAuth->response( 'user auth' )->from_hash( { %{$c->req->params}, %{$provider->{extra_params}} } );
73
74                 my $request = Net::OAuth->request( 'access token' )->new(
75                         %defaults,
76                         token => $response->token,
77                         token_secret => '',
78                         request_url => $provider->{access_token_endpoint},
79                         verifier => $c->req->params->{oauth_verifier},
80                 );
81                 $request->sign;
82
83                 my $ua_response = $self->ua->request( GET $request->to_url );
84                 Catalyst::Exception->throw( $ua_response->status_line.' '.$ua_response->content )
85                         unless $ua_response->is_success;
86
87                 $response = Net::OAuth->response( 'access token' )->from_post_body( $ua_response->content );
88
89                 my $user = +{
90                         token => $response->token,
91                         token_secret => $response->token_secret,
92                         extra_params => $response->extra_params
93                 };
94
95                 my $user_obj = $realm->find_user( $user, $c );
96
97                 return $user_obj if ref $user_obj;
98
99                 $c->log->debug( 'Verified OAuth identity failed' ) if $self->debug;
100
101                 return;
102         }
103         else {
104                 my $request = Net::OAuth->request( 'request token' )->new(
105                         %defaults,
106                         request_url => $provider->{request_token_endpoint}
107                 );
108                 $request->sign;
109
110                 my $ua_response = $self->ua->request( GET $request->to_url );
111
112                 Catalyst::Exception->throw( $ua_response->status_line.' '.$ua_response->content )
113                         unless $ua_response->is_success;
114
115                 my $response = Net::OAuth->response( 'request token' )->from_post_body( $ua_response->content );
116
117                 $request = Net::OAuth->request( 'user auth' )->new(
118                         %defaults,
119                         token => $response->token,
120                 );
121
122                 $c->res->redirect( $request->to_url( $provider->{user_auth_endpoint} ) );
123         }
124
125 }
126
127
128
129 1;
130
131
132 __END__
133
134 =head1 NAME
135
136 Catalyst::Authentication::Credential::OAuth - OAuth credential for Catalyst::Plugin::Authentication framework.
137
138 =head1 VERSION
139
140 0.02
141
142 =head1 SYNOPSIS
143
144 In MyApp.pm
145
146     use Catalyst qw/
147         Authentication
148         Session
149         Session::Store::FastMmap
150         Session::State::Cookie
151     /;
152
153
154 In myapp.conf
155
156     <Plugin::Authentication>
157         default_realm   oauth
158         <realms>
159             <oauth>
160                 <credential>
161                     class       OAuth
162                     <providers>
163                         <example.com>
164                             consumer_key             my_app_key
165                             consumer_secret          my_app_secret
166                             request_token_endpoint   http://example.com/oauth/request_token
167                             access_token_endpoint    http://example.com/oauth/access_token
168                             user_auth_endpoint       http://example.com/oauth/authorize
169                         </example.com>
170                     </providers>
171                 </credential>
172             </oauth>
173         </realms>
174     </Plugin::Authentication>
175
176
177 In controller code,
178
179     sub oauth : Local {
180         my ($self, $c) = @_;
181
182         if( $c->authenticate( { provider => 'example.com' } ) ) {
183             #do something with $c->user
184         }
185     }
186
187
188
189 =head1 USER METHODS
190
191 =over 4
192
193 =item $c->user->token
194
195 =item $c->user->token_secret
196
197 =item $c->user->extra_params - whatever other params the provider sends back
198
199 =back
200
201 =head1 AUTHOR
202
203 Cosmin Budrica E<lt>cosmin@sinapticode.comE<gt>
204
205 Bogdan Lucaciu E<lt>bogdan@sinapticode.comE<gt>
206
207 With contributions from:
208
209   Tomas Doran E<lt>bobtfish@bobtfish.netE</gt>
210
211
212 =head1 BUGS
213
214 Only tested with twitter
215
216 =head1 COPYRIGHT
217
218 Copyright (c) 2009 Sinapticode. All rights reserved
219
220 This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
221
222 =cut