File Coverage

blib/lib/OpenID/Login.pm
Criterion Covered Total %
statement 114 121 94.2
branch 29 50 58.0
condition 6 10 60.0
subroutine 19 19 100.0
pod 6 6 100.0
total 174 206 84.4


line stmt bran cond sub pod time code
1             package OpenID::Login;
2             {
3             $OpenID::Login::VERSION = '0.1.2';
4             }
5              
6             # ABSTRACT: A simple lightweight OpenID consumer with very few dependencies
7              
8 4     4   300796 use Moose;
  4         2185037  
  4         35  
9 4     4   28995 use Moose::Util::TypeConstraints;
  4         11  
  4         39  
10              
11 4     4   11443 use LWP::UserAgent;
  4         107543  
  4         111  
12 4     4   38 use Carp;
  4         6  
  4         320  
13 4     4   24 use URI::Escape;
  4         7  
  4         223  
14              
15 4     4   3261 use OpenID::Login::Extension;
  4         16  
  4         171  
16 4     4   2830 use OpenID::Login::Types;
  4         17  
  4         172  
17 4     4   2814 use OpenID::Login::Discover;
  4         16  
  4         155  
18 4     4   2748 use OpenID::Login::URI;
  4         10  
  4         5581  
19              
20              
21             my $TIMEOUT = 4;
22              
23              
24             has claimed_id => (
25             is => 'rw',
26             isa => 'Str',
27             );
28              
29              
30             has realm => (
31             is => 'rw',
32             isa => 'Str',
33             );
34              
35              
36             has ua => (
37             is => 'rw',
38             isa => 'LWP::UserAgent',
39             default => sub {
40             return LWP::UserAgent->new(
41             agent => sprintf 'OpenID-Login/%s ',
42             __PACKAGE__->VERSION,
43             timeout => $TIMEOUT,
44             max_redirect => 0,
45             );
46             },
47             );
48              
49              
50             has return_to => (
51             is => 'rw',
52             isa => 'Str',
53             );
54              
55              
56             has cgi => (
57             is => 'rw',
58             isa => duck_type( ['param'] ),
59             );
60              
61              
62             has cgi_params => (
63             is => 'ro',
64             isa => 'HashRef'
65             );
66              
67              
68             has extensions => (
69             is => 'rw',
70             isa => 'Extension_List',
71             coerce => 1,
72             );
73              
74              
75             has endpoint => (
76             is => 'rw',
77             isa => 'Str',
78             );
79              
80              
81             sub get_auth_url {
82 4     4 1 795 my $self = shift;
83              
84 4         12 my $endpoint = $self->get_openid_endpoint;
85 4 50       16 return unless $endpoint;
86              
87             #if the endpoint already contains params, put in a param separator ('&') otherwise start params ('?')
88 4 50       24 $endpoint .=
89             ( $endpoint =~ /\?/ )
90             ? '&'
91             : '?';
92 4         17 $endpoint .= $self->_get_request_parameters;
93              
94 4         15 return $endpoint;
95             }
96              
97              
98             sub get_openid_endpoint {
99 5     5 1 9 my $self = shift;
100              
101 5         161 my $identifier = normalize( $self->claimed_id );
102 5         219 my $endpoint = $self->endpoint;
103 5 50       17 if ( !$endpoint ) {
104 5         176 my $discoverer = OpenID::Login::Discover->new( ua => $self->ua, claimed_id => $identifier );
105 5         24 $endpoint = $discoverer->perform_discovery;
106             }
107 5         16 return $endpoint;
108             }
109              
110              
111             sub normalize {
112 12     12 1 4228 my ($uri) = @_;
113              
114 12 100       75 unless ( $uri =~ /^https?:\/\// ) {
115 1         6 $uri = sprintf q{http://%s}, $uri;
116             }
117              
118 12         31 $uri =~ s/\#.*$//; # remove fragment
119              
120 12         109 $uri = OpenID::Login::URI->normalize($uri);
121 12 50       58 return unless OpenID::Login::URI->is_uri($uri);
122 12         74 my $u = URI->new($uri)->canonical;
123 12   50     1712 $u->path( $u->path || '/' );
124 12 50 66     445 $u->port(undef) if $u->port == 80 || $u->port == 443;
125 12         1245 $u->fragment(undef);
126 12         133 return $u->as_string;
127             }
128              
129             sub _get_request_parameters {
130 4     4   9 my $self = shift;
131              
132 4 50       142 croak 'No return_to address provided' unless $self->return_to;
133 4         118 my $params = 'openid.mode=checkid_setup' . '&openid.ns=http://specs.openid.net/auth/2.0' . '&openid.claimed_id=http://specs.openid.net/auth/2.0/identifier_select' . '&openid.identity=http://specs.openid.net/auth/2.0/identifier_select' . '&openid.return_to=' . $self->return_to;
134              
135 4 50       125 if ( my $realm = $self->realm ) {
136 0         0 $params .= '&openid.realm=' . $realm;
137             }
138              
139 4         127 my $extensions = $self->extensions;
140 4 100 66     34 if ( $extensions && %$extensions ) {
141 3         20 $params .= '&' . $_->get_parameter_string() foreach map { $extensions->{$_} } sort keys %$extensions;
  6         34  
142             }
143              
144 4         20 return $params;
145             }
146              
147              
148             sub verify_auth {
149 1     1 1 7 my $self = shift;
150              
151 1 50       6 return if $self->_get_param('openid.mode') eq 'cancel';
152              
153 1         28 my $return_to = $self->return_to;
154 1         4 my $param_return_to = $self->_get_param('openid.return_to');
155 1 50       13 croak 'Return_to value must be set for validation purposes' unless $return_to;
156 1 50       5 croak sprintf q{Return_to parameter (%s) doesn't match provided value(%s)}, $param_return_to, $return_to unless $param_return_to eq $return_to;
157              
158 1         44 my $claimed_id = $self->claimed_id;
159 1         4 my $param_claimed_id = $self->_get_param('openid.claimed_id');
160 1 50       5 if ( !$claimed_id ) {
    0          
161 1         31 $self->claimed_id($param_claimed_id);
162             } elsif ( $claimed_id ne $param_claimed_id ) {
163 0         0 carp "Identity from parameters ($param_claimed_id) is not the same as the previously set claimed identity ($claimed_id); using the parameter version.";
164 0         0 $self->claimed_id($param_claimed_id);
165             }
166              
167 1         5 my $verify_endpoint = $self->get_openid_endpoint;
168 1 50       6 $verify_endpoint .=
169             ( $verify_endpoint =~ /\?/ )
170             ? '&'
171             : '?';
172 10         234 $verify_endpoint .= join '&', map {
173 1         3 my $param = $_;
174 10         19 my $val = $self->_get_param($param);
175 10 100       24 $val = 'check_authentication' if $param eq 'openid.mode';
176 10         34 sprintf '%s=%s', uri_escape($param), uri_escape($val);
177             } $self->_get_param;
178              
179 1         74 my $ua = $self->ua;
180              
181 1         4 my $response = $ua->get( $verify_endpoint, Accept => 'text/plain' );
182 1         438 my $response_data = _parse_direct_response($response);
183              
184 1 50       5 croak "Unexpected verification response namespace: $response_data->{ns}" unless $response_data->{ns} eq 'http://specs.openid.net/auth/2.0';
185              
186 1 50       3 return unless $response_data->{is_valid} eq 'true';
187 1         14 return $param_claimed_id;
188             }
189              
190             sub _parse_direct_response {
191 1     1   2 my $response = shift;
192              
193 1         6 my $response_content = $response->decoded_content;
194 1         408 my @lines = split /\n/, $response_content;
195 1         3 my %data = map { my ( $key, $value ) = split /:/, $_, 2; $key => $value } @lines;
  2         5  
  2         8  
196 1         4 return \%data;
197             }
198              
199              
200             sub get_extension {
201 4     4 1 1710 my $self = shift;
202 4         9 my $uri = shift;
203              
204 4         6 my $extension;
205              
206 4         150 my $extensions = $self->extensions;
207 4 100       15 if ($extensions) {
208 3         9 $extension = $extensions->{$uri};
209             }
210              
211 4 100       11 unless ($extension) {
212 1         35 $extension = OpenID::Login::Extension->new( uri => $uri, cgi => $self->cgi, cgi_params => $self->cgi_params );
213 1 50       6 $self->set_extension($extension) if $extension;
214             }
215 4         36 return $extension;
216             }
217              
218              
219             sub set_extension {
220 1     1 1 1 my $self = shift;
221 1         2 my $extension = shift;
222              
223 1   50     27 my $extensions = $self->extensions || {};
224 1         5 $extensions->{ $extension->{uri} } = $extension;
225 1         30 $self->extensions($extensions);
226             }
227              
228             sub _get_param {
229 14     14   15 my $self = shift;
230 14         14 my $param = shift;
231              
232 14 50       404 if ( my $cgi = $self->cgi ) {
    50          
233 0 0       0 if ($param) {
234 0         0 return $cgi->param($param);
235             } else {
236 0         0 return $cgi->param();
237             }
238             } elsif ( my $cgi_params = $self->cgi_params ) {
239 14 100       25 if ($param) {
240 13         33 return $cgi_params->{$param};
241             } else {
242 1         6 return keys %$cgi_params;
243             }
244             } else {
245 0           croak('Neither cgi nor cgi_params attributes have been provided (needed to verify OpenID parameters)');
246             }
247             }
248              
249 4     4   31 no Moose;
  4         11  
  4         38  
250             __PACKAGE__->meta->make_immutable;
251             1;
252              
253              
254              
255             =pod
256              
257             =head1 NAME
258              
259             OpenID::Login - A simple lightweight OpenID consumer with very few dependencies
260              
261             =head1 VERSION
262              
263             version 0.1.2
264              
265             =head1 SYNOPSIS
266              
267             Sending user to be authenticated:
268              
269             my $claimed_id = 'https://example.openssl.com';
270             my $o = OpenID::Login->new(claimed_id => $claimed_id, return_to => 'https://example.com/auth');
271             my $auth_url = $o->get_auth_url();
272              
273             Verifying the user was correctly authenticated:
274              
275             my $o = OpenID::Login->new(cgi => $cgi, return_to => 'https://example.com/auth');
276             my $id = $o->verify_auth();
277             # $id is the verified identity, or false if it wasn't verified (eg by the user handcrafting the url, or disallowing access)
278              
279             =head1 DESCRIPTION
280              
281             OpenID::Login is an simple alternative OpenID consumer to L<Net::OpenID::Consumer> with less dependencies especially not to
282             L<Crypt::DH::GMP> which itself has dependencies to libgmp, etc. and is sometimes difficult to install.
283              
284             This module only does XRDS discovery, no YARDIS and will only do OpenID in "dumb" aka stateless mode.
285              
286             OpenID::Login is inspired and based on L<Net::Google::FederatedLogin> written by Glenn Fowler.
287              
288             =head1 ATTRIBUTES
289              
290             =head2 claimed_id
291              
292             B<Required for L<"get_auth_url">:> The email address, or an OpenID URL of the identity to be checked.
293              
294             =head2 realm
295              
296             Optional field that is used to populate the openid.realm parameter.
297             If not provided the parameter will not be used (as opposed to being
298             calculated from the L<"return_to">" value).
299              
300             =head2 ua
301              
302             The useragent internally used for communications that the module needs to do.
303             If not provided, a new L<LWP::UserAgent> will be instantiated.
304              
305             =head2 return_to
306              
307             B<Required for L<"get_auth_url"> and L<"verify_auth">:> The URL the user should be
308             returned to after verifying their identity.
309              
310             =head2 cgi
311              
312             B<Required for L<"verify_auth">:> A CGI-like object (same param() method behaviour)
313             that is used to access the parameters that assert the identity has been verified. May optionally
314             be replaced by L<"cgi_params">.
315              
316             =head2 cgi_params
317              
318             B<Required for L<"verify_auth"> unless L<"cgi"> is supplied:> A hashref containing the cgi
319             parameters for verifying the identity.
320              
321             =head2 extensions
322              
323             Hashref of L<OpenID::Login::Extension> objects (keyed off the extension type URI).
324              
325             =head2 endpoint
326              
327             Static URI of the authorizing OpenID server. If set no XRDS discovery will be done.
328              
329             =head1 METHODS
330              
331             =head2 get_auth_url
332              
333             Gets the URL to send the user to where they can verify their identity.
334              
335             =head2 get_openid_endpoint
336              
337             Gets the unadorned OpenID authentication URL (like L<"get_auth_url">, but doesn't contain values specific to
338             this request (return_to, mode etc))
339              
340             =head2 normalize
341              
342             Normalizes and checks the claimed OpenID identifier for synthactical validity.
343              
344             =head2 verify_auth
345              
346             Checks if the user has been validated based on the parameters in the L<"cgi"> object,
347             and checks that these parameters do come from the correct OpenID provider (rather
348             than having been hand-crafted to appear to validate the identity). If the id is
349             successfully verified, it is returned (otherwise a false value is returned).
350              
351             =head2 get_extension
352              
353             Retrieve a single L<OpenID::Login::Extension> object, based on the type URI provided.
354             This method is most likely to be useful for handling the response to an OpenID request.
355              
356             =head2 set_extension
357              
358             Save an extension into the list of extensions for this login object
359              
360             =head1 AUTHOR
361              
362             Holger Eiboeck <realholgi@cpan.org>
363              
364             =head1 COPYRIGHT AND LICENSE
365              
366             This software is copyright (c) 2013 by Holger Eiboeck.
367              
368             This is free software; you can redistribute it and/or modify it under
369             the same terms as the Perl 5 programming language system itself.
370              
371             =cut
372              
373              
374             __END__
375              
376              
377