| 1 | # LICENSE: You're free to distribute this under the same terms as Perl itself. |
|---|
| 2 | |
|---|
| 3 | use strict; |
|---|
| 4 | use Carp (); |
|---|
| 5 | use LWP::UserAgent; |
|---|
| 6 | use Storable; |
|---|
| 7 | |
|---|
| 8 | ############################################################################ |
|---|
| 9 | package Net::OpenID::Consumer; |
|---|
| 10 | |
|---|
| 11 | use vars qw($VERSION); |
|---|
| 12 | $VERSION = "1.03"; |
|---|
| 13 | |
|---|
| 14 | use fields ( |
|---|
| 15 | 'cache', # a Cache object to store HTTP responses and associations |
|---|
| 16 | 'ua', # LWP::UserAgent instance to use |
|---|
| 17 | 'args', # how to get at your args |
|---|
| 18 | 'message', # args interpreted as an IndirectMessage, if possible |
|---|
| 19 | 'consumer_secret', # scalar/subref |
|---|
| 20 | 'required_root', # the default required_root value, or undef |
|---|
| 21 | 'last_errcode', # last error code we got |
|---|
| 22 | 'last_errtext', # last error code we got |
|---|
| 23 | 'debug', # debug flag or codeblock |
|---|
| 24 | 'minimum_version', # The minimum protocol version to support |
|---|
| 25 | ); |
|---|
| 26 | |
|---|
| 27 | use Net::OpenID::ClaimedIdentity; |
|---|
| 28 | use Net::OpenID::VerifiedIdentity; |
|---|
| 29 | use Net::OpenID::Association; |
|---|
| 30 | use Net::OpenID::Yadis; |
|---|
| 31 | use Net::OpenID::IndirectMessage; |
|---|
| 32 | use Net::OpenID::URIFetch; |
|---|
| 33 | |
|---|
| 34 | use MIME::Base64 (); |
|---|
| 35 | use Digest::SHA1 (); |
|---|
| 36 | use Crypt::DH 0.05; |
|---|
| 37 | use Time::Local; |
|---|
| 38 | use HTTP::Request; |
|---|
| 39 | |
|---|
| 40 | sub new { |
|---|
| 41 | my Net::OpenID::Consumer $self = shift; |
|---|
| 42 | $self = fields::new( $self ) unless ref $self; |
|---|
| 43 | my %opts = @_; |
|---|
| 44 | |
|---|
| 45 | $opts{minimum_version} ||= 1; |
|---|
| 46 | |
|---|
| 47 | $self->{ua} = delete $opts{ua}; |
|---|
| 48 | $self->args ( delete $opts{args} ); |
|---|
| 49 | $self->cache ( delete $opts{cache} ); |
|---|
| 50 | $self->consumer_secret ( delete $opts{consumer_secret} ); |
|---|
| 51 | $self->required_root ( delete $opts{required_root} ); |
|---|
| 52 | $self->minimum_version ( delete $opts{minimum_version} ); |
|---|
| 53 | |
|---|
| 54 | $self->{debug} = delete $opts{debug}; |
|---|
| 55 | |
|---|
| 56 | Carp::croak("Unknown options: " . join(", ", keys %opts)) if %opts; |
|---|
| 57 | return $self; |
|---|
| 58 | } |
|---|
| 59 | |
|---|
| 60 | # NOTE: This method is here only to support the openid-test library. |
|---|
| 61 | # Don't call it from anywhere else, or you'll break when it gets |
|---|
| 62 | # removed. Instead, set the minimum_version property. |
|---|
| 63 | # FIXME: Can we just make openid-test set minimum_version and get |
|---|
| 64 | # rid of this? |
|---|
| 65 | sub disable_version_1 { |
|---|
| 66 | my $self = shift; |
|---|
| 67 | $self->{minimum_version} = 2.0; |
|---|
| 68 | } |
|---|
| 69 | |
|---|
| 70 | sub cache { &_getset; } |
|---|
| 71 | sub consumer_secret { &_getset; } |
|---|
| 72 | sub required_root { &_getset; } |
|---|
| 73 | sub minimum_version { &_getset; } |
|---|
| 74 | |
|---|
| 75 | sub _getset { |
|---|
| 76 | my Net::OpenID::Consumer $self = shift; |
|---|
| 77 | my $param = (caller(1))[3]; |
|---|
| 78 | $param =~ s/.+:://; |
|---|
| 79 | |
|---|
| 80 | if (@_) { |
|---|
| 81 | my $val = shift; |
|---|
| 82 | Carp::croak("Too many parameters") if @_; |
|---|
| 83 | $self->{$param} = $val; |
|---|
| 84 | } |
|---|
| 85 | return $self->{$param}; |
|---|
| 86 | } |
|---|
| 87 | |
|---|
| 88 | sub _debug { |
|---|
| 89 | my Net::OpenID::Consumer $self = shift; |
|---|
| 90 | return unless $self->{debug}; |
|---|
| 91 | |
|---|
| 92 | if (ref $self->{debug} eq "CODE") { |
|---|
| 93 | $self->{debug}->($_[0]); |
|---|
| 94 | } else { |
|---|
| 95 | print STDERR "[DEBUG Net::OpenID::Consumer] $_[0]\n"; |
|---|
| 96 | } |
|---|
| 97 | } |
|---|
| 98 | |
|---|
| 99 | # given something that can have GET arguments, returns a subref to get them: |
|---|
| 100 | # Apache |
|---|
| 101 | # Apache::Request |
|---|
| 102 | # CGI |
|---|
| 103 | # HASH of get args |
|---|
| 104 | # CODE returning get arg, given key |
|---|
| 105 | |
|---|
| 106 | # ... |
|---|
| 107 | |
|---|
| 108 | sub args { |
|---|
| 109 | my Net::OpenID::Consumer $self = shift; |
|---|
| 110 | |
|---|
| 111 | if (my $what = shift) { |
|---|
| 112 | unless (ref $what) { |
|---|
| 113 | return $self->{args} ? $self->{args}->($what) : Carp::croak("No args defined"); |
|---|
| 114 | } |
|---|
| 115 | else { |
|---|
| 116 | Carp::croak("Too many parameters") if @_; |
|---|
| 117 | my $message = Net::OpenID::IndirectMessage->new($what, ( |
|---|
| 118 | minimum_version => $self->minimum_version, |
|---|
| 119 | )); |
|---|
| 120 | $self->{message} = $message; |
|---|
| 121 | $self->{args} = $message ? $message->getter : sub { undef }; |
|---|
| 122 | } |
|---|
| 123 | } |
|---|
| 124 | $self->{args}; |
|---|
| 125 | } |
|---|
| 126 | |
|---|
| 127 | sub message { |
|---|
| 128 | my Net::OpenID::Consumer $self = shift; |
|---|
| 129 | if (my $key = shift) { |
|---|
| 130 | return $self->{message} ? $self->{message}->get($key) : undef; |
|---|
| 131 | } |
|---|
| 132 | else { |
|---|
| 133 | return $self->{message}; |
|---|
| 134 | } |
|---|
| 135 | } |
|---|
| 136 | |
|---|
| 137 | sub _message_mode { |
|---|
| 138 | my $message = $_[0]->message; |
|---|
| 139 | return $message ? $message->mode : undef; |
|---|
| 140 | } |
|---|
| 141 | |
|---|
| 142 | sub _message_version { |
|---|
| 143 | my $message = $_[0]->message; |
|---|
| 144 | return $message ? $message->protocol_version : undef; |
|---|
| 145 | } |
|---|
| 146 | |
|---|
| 147 | sub ua { |
|---|
| 148 | my Net::OpenID::Consumer $self = shift; |
|---|
| 149 | $self->{ua} = shift if @_; |
|---|
| 150 | Carp::croak("Too many parameters") if @_; |
|---|
| 151 | |
|---|
| 152 | # make default one on first access |
|---|
| 153 | unless ($self->{ua}) { |
|---|
| 154 | my $ua = $self->{ua} = LWP::UserAgent->new; |
|---|
| 155 | $ua->timeout(10); |
|---|
| 156 | } |
|---|
| 157 | |
|---|
| 158 | $self->{ua}; |
|---|
| 159 | } |
|---|
| 160 | |
|---|
| 161 | sub _fail { |
|---|
| 162 | my Net::OpenID::Consumer $self = shift; |
|---|
| 163 | my ($code, $text) = @_; |
|---|
| 164 | |
|---|
| 165 | $text ||= { |
|---|
| 166 | 'no_identity_server' => "The provided URL doesn't declare its OpenID identity server.", |
|---|
| 167 | 'empty_url' => "No URL entered.", |
|---|
| 168 | 'bogus_url' => "Invalid URL.", |
|---|
| 169 | 'no_head_tag' => "URL provided doesn't seem to have a head tag.", |
|---|
| 170 | 'url_fetch_err' => "Error fetching the provided URL.", |
|---|
| 171 | 'bad_mode' => "The openid.mode argument is not correct", |
|---|
| 172 | 'protocol_version_incorrect' => "The provided URL uses the wrong protocol version", |
|---|
| 173 | 'naive_verify_failed_return' => "Provider says signature is invalid", |
|---|
| 174 | 'naive_verify_failed_network' => "Could not contact provider to verify signature", |
|---|
| 175 | }->{$code}; |
|---|
| 176 | |
|---|
| 177 | $self->{last_errcode} = $code; |
|---|
| 178 | $self->{last_errtext} = $text; |
|---|
| 179 | |
|---|
| 180 | $self->_debug("fail($code) $text"); |
|---|
| 181 | wantarray ? () : undef; |
|---|
| 182 | } |
|---|
| 183 | |
|---|
| 184 | sub json_err { |
|---|
| 185 | my Net::OpenID::Consumer $self = shift; |
|---|
| 186 | return OpenID::util::js_dumper({ |
|---|
| 187 | err_code => $self->{last_errcode}, |
|---|
| 188 | err_text => $self->{last_errtext}, |
|---|
| 189 | }); |
|---|
| 190 | } |
|---|
| 191 | |
|---|
| 192 | sub err { |
|---|
| 193 | my Net::OpenID::Consumer $self = shift; |
|---|
| 194 | $self->{last_errcode} . ": " . $self->{last_errtext}; |
|---|
| 195 | } |
|---|
| 196 | |
|---|
| 197 | sub errcode { |
|---|
| 198 | my Net::OpenID::Consumer $self = shift; |
|---|
| 199 | $self->{last_errcode}; |
|---|
| 200 | } |
|---|
| 201 | |
|---|
| 202 | sub errtext { |
|---|
| 203 | my Net::OpenID::Consumer $self = shift; |
|---|
| 204 | $self->{last_errtext}; |
|---|
| 205 | } |
|---|
| 206 | |
|---|
| 207 | sub _get_url_contents { |
|---|
| 208 | my Net::OpenID::Consumer $self = shift; |
|---|
| 209 | my ($url, $final_url_ref, $hook) = @_; |
|---|
| 210 | $final_url_ref ||= do { my $dummy; \$dummy; }; |
|---|
| 211 | |
|---|
| 212 | my $res = Net::OpenID::URIFetch->fetch($url, $self, $hook); |
|---|
| 213 | |
|---|
| 214 | $$final_url_ref = $res->final_uri; |
|---|
| 215 | |
|---|
| 216 | return $res ? $res->content : undef; |
|---|
| 217 | } |
|---|
| 218 | |
|---|
| 219 | sub _find_semantic_info { |
|---|
| 220 | my Net::OpenID::Consumer $self = shift; |
|---|
| 221 | my $url = shift; |
|---|
| 222 | my $final_url_ref = shift; |
|---|
| 223 | |
|---|
| 224 | my $trim_hook = sub { |
|---|
| 225 | my $htmlref = shift; |
|---|
| 226 | # trim everything past the body. this is in case the user doesn't |
|---|
| 227 | # have a head document and somebody was able to inject their own |
|---|
| 228 | # head. -- brad choate |
|---|
| 229 | $$htmlref =~ s/<body\b.*//is; |
|---|
| 230 | }; |
|---|
| 231 | |
|---|
| 232 | my $doc = $self->_get_url_contents($url, $final_url_ref, $trim_hook) || ''; |
|---|
| 233 | |
|---|
| 234 | # find <head> content of document (notably: the first head, if an attacker |
|---|
| 235 | # has added others somehow) |
|---|
| 236 | return $self->_fail("no_head_tag", "Couldn't find OpenID servers due to no head tag") |
|---|
| 237 | unless $doc =~ m!<head[^>]*>(.*?)</head>!is; |
|---|
| 238 | my $head = $1; |
|---|
| 239 | |
|---|
| 240 | my $ret = { |
|---|
| 241 | 'openid.server' => undef, |
|---|
| 242 | 'openid.delegate' => undef, |
|---|
| 243 | 'foaf' => undef, |
|---|
| 244 | 'foaf.maker' => undef, |
|---|
| 245 | 'rss' => undef, |
|---|
| 246 | 'atom' => undef, |
|---|
| 247 | }; |
|---|
| 248 | |
|---|
| 249 | # analyze link/meta tags |
|---|
| 250 | while ($head =~ m!<(link|meta)\b([^>]+)>!g) { |
|---|
| 251 | my ($type, $val) = ($1, $2); |
|---|
| 252 | my $temp; |
|---|
| 253 | |
|---|
| 254 | # OpenID servers / delegated identities |
|---|
| 255 | # <link rel="openid.server" href="http://www.livejournal.com/misc/openid.bml" /> |
|---|
| 256 | if ($type eq "link" && |
|---|
| 257 | $val =~ /\brel=.openid\.(server|delegate)./i && ($temp = $1) && |
|---|
| 258 | $val =~ m!\bhref=[\"\']([^\"\']+)[\"\']!i) { |
|---|
| 259 | $ret->{"openid.$temp"} = $1; |
|---|
| 260 | next; |
|---|
| 261 | } |
|---|
| 262 | |
|---|
| 263 | # OpenID2 providers / local identifiers |
|---|
| 264 | # <link rel="openid2.provider" href="http://www.livejournal.com/misc/openid.bml" /> |
|---|
| 265 | if ($type eq "link" && |
|---|
| 266 | $val =~ /\brel=.openid2\.(provider|local_id)./i && ($temp = $1) && |
|---|
| 267 | $val =~ m!\bhref=[\"\']([^\"\']+)[\"\']!i) { |
|---|
| 268 | $ret->{"openid2.$temp"} = $1; |
|---|
| 269 | next; |
|---|
| 270 | } |
|---|
| 271 | |
|---|
| 272 | # FOAF documents |
|---|
| 273 | #<link rel="meta" type="application/rdf+xml" title="FOAF" href="http://brad.livejournal.com/data/foaf" /> |
|---|
| 274 | if ($type eq "link" && |
|---|
| 275 | $val =~ m!title=.foaf.!i && |
|---|
| 276 | $val =~ m!rel=.meta.!i && |
|---|
| 277 | $val =~ m!type=.application/rdf\+xml.!i && |
|---|
| 278 | $val =~ m!href=[\"\']([^\"\']+)[\"\']!i) { |
|---|
| 279 | $ret->{"foaf"} = $1; |
|---|
| 280 | next; |
|---|
| 281 | } |
|---|
| 282 | |
|---|
| 283 | # FOAF maker info |
|---|
| 284 | # <meta name="foaf:maker" content="foaf:mbox_sha1sum '4caa1d6f6203d21705a00a7aca86203e82a9cf7a'" /> |
|---|
| 285 | if ($type eq "meta" && |
|---|
| 286 | $val =~ m!name=.foaf:maker.!i && |
|---|
| 287 | $val =~ m!content=([\'\"])(.*?)\1!i) { |
|---|
| 288 | $ret->{"foaf.maker"} = $2; |
|---|
| 289 | next; |
|---|
| 290 | } |
|---|
| 291 | |
|---|
| 292 | if ($type eq "meta" && |
|---|
| 293 | $val =~ m!name=.foaf:maker.!i && |
|---|
| 294 | $val =~ m!content=([\'\"])(.*?)\1!i) { |
|---|
| 295 | $ret->{"foaf.maker"} = $2; |
|---|
| 296 | next; |
|---|
| 297 | } |
|---|
| 298 | |
|---|
| 299 | # RSS |
|---|
| 300 | # <link rel="alternate" type="application/rss+xml" title="RSS" href="http://www.livejournal.com/~brad/data/rss" /> |
|---|
| 301 | if ($type eq "link" && |
|---|
| 302 | $val =~ m!rel=.alternate.!i && |
|---|
| 303 | $val =~ m!type=.application/rss\+xml.!i && |
|---|
| 304 | $val =~ m!href=[\"\']([^\"\']+)[\"\']!i) { |
|---|
| 305 | $ret->{"rss"} = $1; |
|---|
| 306 | next; |
|---|
| 307 | } |
|---|
| 308 | |
|---|
| 309 | # Atom |
|---|
| 310 | # <link rel="alternate" type="application/atom+xml" title="Atom" href="http://www.livejournal.com/~brad/data/rss" /> |
|---|
| 311 | if ($type eq "link" && |
|---|
| 312 | $val =~ m!rel=.alternate.!i && |
|---|
| 313 | $val =~ m!type=.application/atom\+xml.!i && |
|---|
| 314 | $val =~ m!href=[\"\']([^\"\']+)[\"\']!i) { |
|---|
| 315 | $ret->{"atom"} = $1; |
|---|
| 316 | next; |
|---|
| 317 | } |
|---|
| 318 | } |
|---|
| 319 | |
|---|
| 320 | # map the 4 entities that the spec asks for |
|---|
| 321 | my $emap = { |
|---|
| 322 | 'lt' => '<', |
|---|
| 323 | 'gt' => '>', |
|---|
| 324 | 'quot' => '"', |
|---|
| 325 | 'amp' => '&', |
|---|
| 326 | }; |
|---|
| 327 | foreach my $k (keys %$ret) { |
|---|
| 328 | next unless $ret->{$k}; |
|---|
| 329 | $ret->{$k} =~ s/&(\w+);/$emap->{$1} || ""/eg; |
|---|
| 330 | } |
|---|
| 331 | |
|---|
| 332 | $self->_debug("semantic info ($url) = " . join(", ", map { $_.' => '.$ret->{$_} } keys %$ret)) if $self->{debug}; |
|---|
| 333 | |
|---|
| 334 | return $ret; |
|---|
| 335 | } |
|---|
| 336 | |
|---|
| 337 | sub _find_openid_server { |
|---|
| 338 | my Net::OpenID::Consumer $self = shift; |
|---|
| 339 | my $url = shift; |
|---|
| 340 | my $final_url_ref = shift; |
|---|
| 341 | |
|---|
| 342 | my $sem_info = $self->_find_semantic_info($url, $final_url_ref) or |
|---|
| 343 | return; |
|---|
| 344 | |
|---|
| 345 | return $self->_fail("no_identity_server") unless $sem_info->{"openid.server"}; |
|---|
| 346 | $sem_info->{"openid.server"}; |
|---|
| 347 | } |
|---|
| 348 | |
|---|
| 349 | sub is_server_response { |
|---|
| 350 | my Net::OpenID::Consumer $self = shift; |
|---|
| 351 | return $self->_message_mode ? 1 : 0; |
|---|
| 352 | } |
|---|
| 353 | |
|---|
| 354 | sub handle_server_response { |
|---|
| 355 | my Net::OpenID::Consumer $self = shift; |
|---|
| 356 | my %callbacks_in = @_; |
|---|
| 357 | my %callbacks = (); |
|---|
| 358 | |
|---|
| 359 | foreach my $cb (qw(not_openid setup_required cancelled verified error)) { |
|---|
| 360 | $callbacks{$cb} = delete($callbacks_in{$cb}) || sub { Carp::croak("No ".$cb." callback") }; |
|---|
| 361 | } |
|---|
| 362 | Carp::croak("Unknown callbacks ".join(',', keys %callbacks)) if %callbacks_in; |
|---|
| 363 | |
|---|
| 364 | unless ($self->is_server_response) { |
|---|
| 365 | return $callbacks{not_openid}->(); |
|---|
| 366 | } |
|---|
| 367 | |
|---|
| 368 | if (my $setup_url = $self->user_setup_url) { |
|---|
| 369 | return $callbacks{setup_required}->($setup_url); |
|---|
| 370 | } |
|---|
| 371 | elsif ($self->user_cancel) { |
|---|
| 372 | return $callbacks{cancelled}->(); |
|---|
| 373 | } |
|---|
| 374 | elsif (my $vident = $self->verified_identity) { |
|---|
| 375 | return $callbacks{verified}->($vident); |
|---|
| 376 | } |
|---|
| 377 | else { |
|---|
| 378 | return $callbacks{error}->($self->errcode, $self->errtext); |
|---|
| 379 | } |
|---|
| 380 | |
|---|
| 381 | } |
|---|
| 382 | |
|---|
| 383 | sub _discover_acceptable_endpoints { |
|---|
| 384 | my Net::OpenID::Consumer $self = shift; |
|---|
| 385 | my $url = shift; |
|---|
| 386 | my %opts = @_; |
|---|
| 387 | |
|---|
| 388 | # if return_early is set, we'll return as soon as we have enough |
|---|
| 389 | # information to determine the "primary" endpoint, and return |
|---|
| 390 | # that as the first (and possibly only) item in our response. |
|---|
| 391 | my $primary_only = delete $opts{primary_only} ? 1 : 0; |
|---|
| 392 | |
|---|
| 393 | my $force_version = delete $opts{force_version}; |
|---|
| 394 | |
|---|
| 395 | Carp::croak("Unknown option(s) ".join(', ', keys(%opts))) if %opts; |
|---|
| 396 | |
|---|
| 397 | # trim whitespace |
|---|
| 398 | $url =~ s/^\s+//; |
|---|
| 399 | $url =~ s/\s+$//; |
|---|
| 400 | return $self->_fail("empty_url", "Empty URL") unless $url; |
|---|
| 401 | |
|---|
| 402 | # do basic canonicalization |
|---|
| 403 | $url = "http://$url" if $url && $url !~ m!^\w+://!; |
|---|
| 404 | return $self->_fail("bogus_url", "Invalid URL") unless $url =~ m!^https?://!i; |
|---|
| 405 | # add a slash, if none exists |
|---|
| 406 | $url .= "/" unless $url =~ m!^https?://.+/!i; |
|---|
| 407 | |
|---|
| 408 | my @discovered_endpoints = (); |
|---|
| 409 | my $result = sub { |
|---|
| 410 | # We always prefer 2.0 endpoints to 1.1 ones, regardless of |
|---|
| 411 | # the priority chosen by the identifier. |
|---|
| 412 | return [ |
|---|
| 413 | (grep { $_->{version} == 2 } @discovered_endpoints), |
|---|
| 414 | (grep { $_->{version} == 1 } @discovered_endpoints), |
|---|
| 415 | ]; |
|---|
| 416 | }; |
|---|
| 417 | |
|---|
| 418 | # TODO: Support XRI too? |
|---|
| 419 | |
|---|
| 420 | # First we Yadis service discovery |
|---|
| 421 | my $yadis = Net::OpenID::Yadis->new(consumer => $self); |
|---|
| 422 | if ($yadis->discover($url)) { |
|---|
| 423 | # FIXME: Currently we don't ever do _find_semantic_info in the Yadis |
|---|
| 424 | # code path, so an extra redundant HTTP request is done later |
|---|
| 425 | # when the semantic info is accessed. |
|---|
| 426 | |
|---|
| 427 | my $final_url = $yadis->identity_url; |
|---|
| 428 | my @services = $yadis->services( |
|---|
| 429 | OpenID::util::version_2_xrds_service_url(), |
|---|
| 430 | OpenID::util::version_2_xrds_directed_service_url(), |
|---|
| 431 | OpenID::util::version_1_xrds_service_url(), |
|---|
| 432 | ); |
|---|
| 433 | my $version2 = OpenID::util::version_2_xrds_service_url(); |
|---|
| 434 | my $version1 = OpenID::util::version_1_xrds_service_url(); |
|---|
| 435 | my $version2_directed = OpenID::util::version_2_xrds_directed_service_url(); |
|---|
| 436 | |
|---|
| 437 | foreach my $service (@services) { |
|---|
| 438 | my $service_uris = $service->URI; |
|---|
| 439 | |
|---|
| 440 | # Service->URI seems to return all sorts of bizarre things, so let's |
|---|
| 441 | # normalize it to always be an arrayref. |
|---|
| 442 | if (ref($service_uris) eq 'ARRAY') { |
|---|
| 443 | my @sorted_id_servers = sort { |
|---|
| 444 | my $pa = $a->{priority}; |
|---|
| 445 | my $pb = $b->{priority}; |
|---|
| 446 | return 0 unless defined($pa) || defined($pb); |
|---|
| 447 | return -1 unless defined ($pb); |
|---|
| 448 | return 1 unless defined ($pa); |
|---|
| 449 | return $a->{priority} <=> $b->{priority} |
|---|
| 450 | } @$service_uris; |
|---|
| 451 | $service_uris = \@sorted_id_servers; |
|---|
| 452 | } |
|---|
| 453 | if (ref($service_uris) eq 'HASH') { |
|---|
| 454 | $service_uris = [ $service_uris->{content} ]; |
|---|
| 455 | } |
|---|
| 456 | unless (ref($service_uris)) { |
|---|
| 457 | $service_uris = [ $service_uris ]; |
|---|
| 458 | } |
|---|
| 459 | |
|---|
| 460 | my $delegate = undef; |
|---|
| 461 | my @versions = (); |
|---|
| 462 | |
|---|
| 463 | if (grep(/^${version2}$/, $service->Type)) { |
|---|
| 464 | # We have an OpenID 2.0 end-user identifier |
|---|
| 465 | $delegate = $service->extra_field("LocalID"); |
|---|
| 466 | push @versions, 2; |
|---|
| 467 | } |
|---|
| 468 | if (grep(/^${version1}$/, $service->Type)) { |
|---|
| 469 | # We have an OpenID 1.1 end-user identifier |
|---|
| 470 | $delegate = $service->extra_field("Delegate", "http://openid.net/xmlns/1.0"); |
|---|
| 471 | push @versions, 1; |
|---|
| 472 | } |
|---|
| 473 | |
|---|
| 474 | if (@versions) { |
|---|
| 475 | foreach my $version (@versions) { |
|---|
| 476 | next if defined($force_version) && $force_version != $version; |
|---|
| 477 | foreach my $uri (@$service_uris) { |
|---|
| 478 | push @discovered_endpoints, { |
|---|
| 479 | uri => $uri, |
|---|
| 480 | version => $version, |
|---|
| 481 | final_url => $final_url, |
|---|
| 482 | delegate => $delegate, |
|---|
| 483 | sem_info => undef, |
|---|
| 484 | mechanism => "Yadis", |
|---|
| 485 | }; |
|---|
| 486 | } |
|---|
| 487 | } |
|---|
| 488 | } |
|---|
| 489 | |
|---|
| 490 | if (grep(/^${version2_directed}$/, $service->Type)) { |
|---|
| 491 | # We have an OpenID 2.0 OP identifier (i.e. we're doing directed identity) |
|---|
| 492 | my $version = 2; |
|---|
| 493 | # In this case, the user's claimed identifier is a magic value |
|---|
| 494 | # and the actual identifier will be determined by the provider. |
|---|
| 495 | my $final_url = OpenID::util::version_2_identifier_select_url(); |
|---|
| 496 | my $delegate = OpenID::util::version_2_identifier_select_url(); |
|---|
| 497 | |
|---|
| 498 | foreach my $uri (@$service_uris) { |
|---|
| 499 | push @discovered_endpoints, { |
|---|
| 500 | uri => $uri, |
|---|
| 501 | version => $version, |
|---|
| 502 | final_url => $final_url, |
|---|
| 503 | delegate => $delegate, |
|---|
| 504 | sem_info => undef, |
|---|
| 505 | mechanism => "Yadis", |
|---|
| 506 | }; |
|---|
| 507 | } |
|---|
| 508 | } |
|---|
| 509 | |
|---|
| 510 | if ($primary_only && scalar(@discovered_endpoints)) { |
|---|
| 511 | # We've got at least one endpoint now, so return early |
|---|
| 512 | return $result->(); |
|---|
| 513 | } |
|---|
| 514 | } |
|---|
| 515 | } |
|---|
| 516 | |
|---|
| 517 | # Now HTML-based discovery, both 2.0- and 1.1-style. |
|---|
| 518 | { |
|---|
| 519 | my $final_url = undef; |
|---|
| 520 | my $sem_info = $self->_find_semantic_info($url, \$final_url); |
|---|
| 521 | |
|---|
| 522 | if ($sem_info) { |
|---|
| 523 | if ($sem_info->{"openid2.provider"}) { |
|---|
| 524 | unless (defined($force_version) && $force_version != 2) { |
|---|
| 525 | push @discovered_endpoints, { |
|---|
| 526 | uri => $sem_info->{"openid2.provider"}, |
|---|
| 527 | version => 2, |
|---|
| 528 | final_url => $final_url, |
|---|
| 529 | delegate => $sem_info->{"openid2.local_id"}, |
|---|
| 530 | sem_info => $sem_info, |
|---|
| 531 | mechanism => "HTML", |
|---|
| 532 | }; |
|---|
| 533 | } |
|---|
| 534 | } |
|---|
| 535 | if ($sem_info->{"openid.server"}) { |
|---|
| 536 | unless (defined($force_version) && $force_version != 1) { |
|---|
| 537 | push @discovered_endpoints, { |
|---|
| 538 | uri => $sem_info->{"openid.server"}, |
|---|
| 539 | version => 1, |
|---|
| 540 | final_url => $final_url, |
|---|
| 541 | delegate => $sem_info->{"openid.delegate"}, |
|---|
| 542 | sem_info => $sem_info, |
|---|
| 543 | mechanism => "HTML", |
|---|
| 544 | }; |
|---|
| 545 | } |
|---|
| 546 | } |
|---|
| 547 | } |
|---|
| 548 | } |
|---|
| 549 | |
|---|
| 550 | return $result->(); |
|---|
| 551 | |
|---|
| 552 | } |
|---|
| 553 | |
|---|
| 554 | # returns Net::OpenID::ClaimedIdentity |
|---|
| 555 | sub claimed_identity { |
|---|
| 556 | my Net::OpenID::Consumer $self = shift; |
|---|
| 557 | my $url = shift; |
|---|
| 558 | Carp::croak("Too many parameters") if @_; |
|---|
| 559 | |
|---|
| 560 | # trim whitespace |
|---|
| 561 | $url =~ s/^\s+//; |
|---|
| 562 | $url =~ s/\s+$//; |
|---|
| 563 | return $self->_fail("empty_url", "Empty URL") unless $url; |
|---|
| 564 | |
|---|
| 565 | # do basic canonicalization |
|---|
| 566 | $url = "http://$url" if $url && $url !~ m!^\w+://!; |
|---|
| 567 | return $self->_fail("bogus_url", "Invalid URL") unless $url =~ m!^https?://!i; |
|---|
| 568 | # add a slash, if none exists |
|---|
| 569 | $url .= "/" unless $url =~ m!^https?://.+/!i; |
|---|
| 570 | |
|---|
| 571 | my $endpoints = $self->_discover_acceptable_endpoints($url, primary_only => 1); |
|---|
| 572 | |
|---|
| 573 | if (ref($endpoints) && @$endpoints) { |
|---|
| 574 | foreach my $endpoint (@$endpoints) { |
|---|
| 575 | |
|---|
| 576 | next unless $endpoint->{version} >= $self->minimum_version; |
|---|
| 577 | |
|---|
| 578 | $self->_debug("Discovered version $endpoint->{version} endpoint at $endpoint->{uri} via $endpoint->{mechanism}"); |
|---|
| 579 | $self->_debug("Delegate is $endpoint->{delegate}") if $endpoint->{delegate}; |
|---|
| 580 | |
|---|
| 581 | return Net::OpenID::ClaimedIdentity->new( |
|---|
| 582 | identity => $endpoint->{final_url}, |
|---|
| 583 | server => $endpoint->{uri}, |
|---|
| 584 | consumer => $self, |
|---|
| 585 | delegate => $endpoint->{delegate}, |
|---|
| 586 | protocol_version => $endpoint->{version}, |
|---|
| 587 | semantic_info => $endpoint->{sem_info}, |
|---|
| 588 | ); |
|---|
| 589 | |
|---|
| 590 | } |
|---|
| 591 | |
|---|
| 592 | # If we've fallen out here, then none of the available services are of the required version. |
|---|
| 593 | return $self->_fail("protocol_version_incorrect"); |
|---|
| 594 | |
|---|
| 595 | } |
|---|
| 596 | else { |
|---|
| 597 | return $self->_fail("no_identity_server"); |
|---|
| 598 | } |
|---|
| 599 | |
|---|
| 600 | } |
|---|
| 601 | |
|---|
| 602 | sub user_cancel { |
|---|
| 603 | my Net::OpenID::Consumer $self = shift; |
|---|
| 604 | return $self->_message_mode eq "cancel"; |
|---|
| 605 | } |
|---|
| 606 | |
|---|
| 607 | sub user_setup_url { |
|---|
| 608 | my Net::OpenID::Consumer $self = shift; |
|---|
| 609 | my %opts = @_; |
|---|
| 610 | my $post_grant = delete $opts{'post_grant'}; |
|---|
| 611 | Carp::croak("Unknown options: " . join(", ", keys %opts)) if %opts; |
|---|
| 612 | |
|---|
| 613 | if ($self->_message_version == 1) { |
|---|
| 614 | return $self->_fail("bad_mode") unless $self->_message_mode eq "id_res"; |
|---|
| 615 | } |
|---|
| 616 | else { |
|---|
| 617 | return undef unless $self->_message_mode eq 'setup_needed'; |
|---|
| 618 | } |
|---|
| 619 | |
|---|
| 620 | my $setup_url = $self->message("user_setup_url"); |
|---|
| 621 | |
|---|
| 622 | OpenID::util::push_url_arg(\$setup_url, "openid.post_grant", $post_grant) |
|---|
| 623 | if $setup_url && $post_grant; |
|---|
| 624 | |
|---|
| 625 | return $setup_url; |
|---|
| 626 | } |
|---|
| 627 | |
|---|
| 628 | sub verified_identity { |
|---|
| 629 | my Net::OpenID::Consumer $self = shift; |
|---|
| 630 | my %opts = @_; |
|---|
| 631 | |
|---|
| 632 | my $rr = delete $opts{'required_root'} || $self->{required_root}; |
|---|
| 633 | Carp::croak("Unknown options: " . join(", ", keys %opts)) if %opts; |
|---|
| 634 | |
|---|
| 635 | return $self->_fail("bad_mode") unless $self->_message_mode eq "id_res"; |
|---|
| 636 | |
|---|
| 637 | # the asserted identity (the delegated one, if there is one, since the protocol |
|---|
| 638 | # knows nothing of the original URL) |
|---|
| 639 | my $a_ident = $self->message("identity") or return $self->_fail("no_identity"); |
|---|
| 640 | |
|---|
| 641 | my $sig64 = $self->message("sig") or return $self->_fail("no_sig"); |
|---|
| 642 | |
|---|
| 643 | # fix sig if the OpenID auth server failed to properly escape pluses (+) in the sig |
|---|
| 644 | $sig64 =~ s/ /+/g; |
|---|
| 645 | |
|---|
| 646 | my $returnto = $self->message("return_to") or return $self->_fail("no_return_to"); |
|---|
| 647 | my $signed = $self->message("signed"); |
|---|
| 648 | |
|---|
| 649 | my $possible_endpoints; |
|---|
| 650 | my $server; |
|---|
| 651 | my $claimed_identity; |
|---|
| 652 | |
|---|
| 653 | my $real_ident; |
|---|
| 654 | if ($self->_message_version == 1) { |
|---|
| 655 | $real_ident = $self->args("oic.identity") || $a_ident; |
|---|
| 656 | |
|---|
| 657 | # In version 1, we have to assume that the primary server |
|---|
| 658 | # found during discovery is the one sending us this message. |
|---|
| 659 | $possible_endpoints = $self->_discover_acceptable_endpoints($real_ident, force_version => 1); |
|---|
| 660 | |
|---|
| 661 | if ($possible_endpoints && @$possible_endpoints) { |
|---|
| 662 | $possible_endpoints = [ $possible_endpoints->[0] ]; |
|---|
| 663 | $server = $possible_endpoints->[0]{uri}; |
|---|
| 664 | } |
|---|
| 665 | else { |
|---|
| 666 | # We just fall out of here and bail out below for having no endpoints. |
|---|
| 667 | } |
|---|
| 668 | } |
|---|
| 669 | else { |
|---|
| 670 | $real_ident = $self->message("claimed_id") || $a_ident; |
|---|
| 671 | |
|---|
| 672 | # In version 2, the OP tells us its URL. |
|---|
| 673 | $server = $self->message("op_endpoint"); |
|---|
| 674 | $possible_endpoints = $self->_discover_acceptable_endpoints($real_ident, force_version => 2); |
|---|
| 675 | |
|---|
| 676 | # FIXME: It kinda sucks that the above will always do both Yadis and HTML discovery, even though |
|---|
| 677 | # in most cases only one will be in use. |
|---|
| 678 | } |
|---|
| 679 | |
|---|
| 680 | $self->_debug("Server is $server"); |
|---|
| 681 | |
|---|
| 682 | unless ($possible_endpoints && @$possible_endpoints) { |
|---|
| 683 | return $self->_fail("no_identity_server"); |
|---|
| 684 | } |
|---|
| 685 | |
|---|
| 686 | # check that returnto is for the right host |
|---|
| 687 | return $self->_fail("bogus_return_to") if $rr && $returnto !~ /^\Q$rr\E/; |
|---|
| 688 | |
|---|
| 689 | # check age/signature of return_to |
|---|
| 690 | my $now = time(); |
|---|
| 691 | { |
|---|
| 692 | my ($sig_time, $sig) = split(/\-/, $self->args("oic.time") || ""); |
|---|
| 693 | # complain if more than an hour since we sent them off |
|---|
| 694 | return $self->_fail("time_expired") if $sig_time < $now - 3600; |
|---|
| 695 | # also complain if the signature is from the future by more than 30 seconds, |
|---|
| 696 | # which compensates for potential clock drift between nodes in a web farm. |
|---|
| 697 | return $self->_fail("time_in_future") if $sig_time - 30 > $now; |
|---|
| 698 | # and check that the time isn't faked |
|---|
| 699 | my $c_secret = $self->_get_consumer_secret($sig_time); |
|---|
| 700 | my $good_sig = substr(OpenID::util::hmac_sha1_hex($sig_time, $c_secret), 0, 20); |
|---|
| 701 | return $self->_fail("time_bad_sig") unless $sig eq $good_sig; |
|---|
| 702 | } |
|---|
| 703 | |
|---|
| 704 | my $last_error = undef; |
|---|
| 705 | |
|---|
| 706 | foreach my $endpoint (@$possible_endpoints) { |
|---|
| 707 | my $final_url = $endpoint->{final_url}; |
|---|
| 708 | my $endpoint_uri = $endpoint->{uri}; |
|---|
| 709 | my $delegate = $endpoint->{delegate}; |
|---|
| 710 | |
|---|
| 711 | my $error = sub { |
|---|
| 712 | $self->_debug("$endpoint_uri not acceptable: ".$_[0]); |
|---|
| 713 | $last_error = $_[0]; |
|---|
| 714 | }; |
|---|
| 715 | |
|---|
| 716 | # The endpoint_uri must match our $server |
|---|
| 717 | if ($endpoint_uri ne $server) { |
|---|
| 718 | $error->("server_not_allowed"); |
|---|
| 719 | next; |
|---|
| 720 | } |
|---|
| 721 | |
|---|
| 722 | # OpenID 2.0 wants us to exclude the fragment part of the URL when doing equality checks |
|---|
| 723 | my $a_ident_nofragment = $a_ident; |
|---|
| 724 | my $real_ident_nofragment = $real_ident; |
|---|
| 725 | my $final_url_nofragment = $final_url; |
|---|
| 726 | if ($self->_message_version >= 2) { |
|---|
| 727 | $a_ident_nofragment =~ s/\#.*$//x; |
|---|
| 728 | $real_ident_nofragment =~ s/\#.*$//x; |
|---|
| 729 | $final_url_nofragment =~ s/\#.*$//x; |
|---|
| 730 | } |
|---|
| 731 | unless ($final_url_nofragment eq $real_ident_nofragment) { |
|---|
| 732 | $error->("unexpected_url_redirect"); |
|---|
| 733 | next; |
|---|
| 734 | } |
|---|
| 735 | |
|---|
| 736 | # Protocol version must match |
|---|
| 737 | unless ($endpoint->{version} == $self->_message_version) { |
|---|
| 738 | $error->("protocol_version_incorrect"); |
|---|
| 739 | next; |
|---|
| 740 | } |
|---|
| 741 | |
|---|
| 742 | # if openid.delegate was used, check that it was done correctly |
|---|
| 743 | if ($a_ident_nofragment ne $real_ident_nofragment) { |
|---|
| 744 | unless ($delegate eq $a_ident_nofragment) { |
|---|
| 745 | $error->("bogus_delegation"); |
|---|
| 746 | next; |
|---|
| 747 | } |
|---|
| 748 | } |
|---|
| 749 | |
|---|
| 750 | # If we've got this far then we've found the right endpoint. |
|---|
| 751 | |
|---|
| 752 | $claimed_identity = Net::OpenID::ClaimedIdentity->new( |
|---|
| 753 | identity => $endpoint->{final_url}, |
|---|
| 754 | server => $endpoint->{uri}, |
|---|
| 755 | consumer => $self, |
|---|
| 756 | delegate => $endpoint->{delegate}, |
|---|
| 757 | protocol_version => $endpoint->{version}, |
|---|
| 758 | semantic_info => $endpoint->{sem_info}, |
|---|
| 759 | ); |
|---|
| 760 | last; |
|---|
| 761 | |
|---|
| 762 | } |
|---|
| 763 | |
|---|
| 764 | unless ($claimed_identity) { |
|---|
| 765 | # We failed to find a good endpoint in the above loop, so |
|---|
| 766 | # lets bail out. |
|---|
| 767 | return $self->_fail($last_error); |
|---|
| 768 | } |
|---|
| 769 | |
|---|
| 770 | my $assoc_handle = $self->message("assoc_handle"); |
|---|
| 771 | |
|---|
| 772 | $self->_debug("verified_identity: assoc_handle: $assoc_handle"); |
|---|
| 773 | my $assoc = Net::OpenID::Association::handle_assoc($self, $server, $assoc_handle); |
|---|
| 774 | |
|---|
| 775 | my %signed_fields; # key (without openid.) -> value |
|---|
| 776 | |
|---|
| 777 | # Auth 2.0 requires certain keys to be signed. |
|---|
| 778 | if ($self->_message_version >= 2) { |
|---|
| 779 | my %signed_fields = map {$_ => 1} split /,/, $signed; |
|---|
| 780 | my %unsigned_fields; |
|---|
| 781 | # these fields must be signed unconditionally |
|---|
| 782 | foreach my $f (qw/op_endpoint return_to response_nonce assoc_handle/) { |
|---|
| 783 | $unsigned_fields{$f}++ if !$signed_fields{$f}; |
|---|
| 784 | } |
|---|
| 785 | # these fields must be signed if present |
|---|
| 786 | foreach my $f (qw/claimed_id identity/) { |
|---|
| 787 | next unless $self->args("openid.$f"); |
|---|
| 788 | $unsigned_fields{$f}++ if !$signed_fields{$f}; |
|---|
| 789 | } |
|---|
| 790 | if (%unsigned_fields) { |
|---|
| 791 | return $self->_fail( |
|---|
| 792 | "unsigned_field", |
|---|
| 793 | "Field(s) must be signed: " . join(", ", keys %unsigned_fields) |
|---|
| 794 | ); |
|---|
| 795 | } |
|---|
| 796 | } |
|---|
| 797 | |
|---|
| 798 | if ($assoc) { |
|---|
| 799 | $self->_debug("verified_identity: verifying with found association"); |
|---|
| 800 | |
|---|
| 801 | return $self->_fail("expired_association") |
|---|
| 802 | if $assoc->expired; |
|---|
| 803 | |
|---|
| 804 | # verify the token |
|---|
| 805 | my $token = ""; |
|---|
| 806 | foreach my $param (split(/,/, $signed)) { |
|---|
| 807 | my $val = $self->args("openid.$param"); |
|---|
| 808 | $token .= "$param:$val\n"; |
|---|
| 809 | $signed_fields{$param} = $val; |
|---|
| 810 | } |
|---|
| 811 | |
|---|
| 812 | my $good_sig = OpenID::util::b64(OpenID::util::hmac_sha1($token, $assoc->secret)); |
|---|
| 813 | return $self->_fail("signature_mismatch") unless $sig64 eq $good_sig; |
|---|
| 814 | |
|---|
| 815 | } else { |
|---|
| 816 | $self->_debug("verified_identity: verifying using HTTP (dumb mode)"); |
|---|
| 817 | # didn't find an association. have to do dumb consumer mode |
|---|
| 818 | # and check it with a POST |
|---|
| 819 | my %post = ( |
|---|
| 820 | "openid.mode" => "check_authentication", |
|---|
| 821 | "openid.assoc_handle" => $assoc_handle, |
|---|
| 822 | "openid.signed" => $signed, |
|---|
| 823 | "openid.sig" => $sig64, |
|---|
| 824 | ); |
|---|
| 825 | |
|---|
| 826 | # and copy in all signed parameters that we don't already have into %post |
|---|
| 827 | foreach my $param (split(/,/, $signed)) { |
|---|
| 828 | next unless $param =~ /^[\w\.]+$/; |
|---|
| 829 | my $val = $self->args('openid.'.$param); |
|---|
| 830 | $signed_fields{$param} = $val; |
|---|
| 831 | next if $post{"openid.$param"}; |
|---|
| 832 | $post{"openid.$param"} = $val; |
|---|
| 833 | } |
|---|
| 834 | |
|---|
| 835 | # if the server told us our handle as bogus, let's ask in our |
|---|
| 836 | # check_authentication mode whether that's true |
|---|
| 837 | if (my $ih = $self->message("invalidate_handle")) { |
|---|
| 838 | $post{"openid.invalidate_handle"} = $ih; |
|---|
| 839 | } |
|---|
| 840 | |
|---|
| 841 | my $req = HTTP::Request->new(POST => $server); |
|---|
| 842 | $req->header("Content-Type" => "application/x-www-form-urlencoded"); |
|---|
| 843 | $req->content(join("&", map { "$_=" . OpenID::util::eurl($post{$_}) } keys %post)); |
|---|
| 844 | |
|---|
| 845 | my $ua = $self->ua; |
|---|
| 846 | my $res = $ua->request($req); |
|---|
| 847 | |
|---|
| 848 | # uh, some failure, let's go into dumb mode? |
|---|
| 849 | return $self->_fail("naive_verify_failed_network") unless $res && $res->is_success; |
|---|
| 850 | |
|---|
| 851 | my $content = $res->content; |
|---|
| 852 | my %args = OpenID::util::parse_keyvalue($content); |
|---|
| 853 | |
|---|
| 854 | # delete the handle from our cache |
|---|
| 855 | if (my $ih = $args{'invalidate_handle'}) { |
|---|
| 856 | Net::OpenID::Association::invalidate_handle($self, $server, $ih); |
|---|
| 857 | } |
|---|
| 858 | |
|---|
| 859 | return $self->_fail("naive_verify_failed_return") unless |
|---|
| 860 | $args{'is_valid'} eq "true" || # protocol 1.1 |
|---|
| 861 | $args{'lifetime'} > 0; # DEPRECATED protocol 1.0 |
|---|
| 862 | } |
|---|
| 863 | |
|---|
| 864 | $self->_debug("verified identity! = $real_ident"); |
|---|
| 865 | |
|---|
| 866 | # verified! |
|---|
| 867 | return Net::OpenID::VerifiedIdentity->new( |
|---|
| 868 | claimed_identity => $claimed_identity, |
|---|
| 869 | consumer => $self, |
|---|
| 870 | signed_fields => \%signed_fields, |
|---|
| 871 | ); |
|---|
| 872 | } |
|---|
| 873 | |
|---|
| 874 | sub supports_consumer_secret { 1; } |
|---|
| 875 | |
|---|
| 876 | sub _get_consumer_secret { |
|---|
| 877 | my Net::OpenID::Consumer $self = shift; |
|---|
| 878 | my $time = shift; |
|---|
| 879 | |
|---|
| 880 | my $ss; |
|---|
| 881 | if (ref $self->{consumer_secret} eq "CODE") { |
|---|
| 882 | $ss = $self->{consumer_secret}; |
|---|
| 883 | } elsif ($self->{consumer_secret}) { |
|---|
| 884 | $ss = sub { return $self->{consumer_secret}; }; |
|---|
| 885 | } else { |
|---|
| 886 | Carp::croak("You haven't defined a consumer_secret value or subref.\n"); |
|---|
| 887 | } |
|---|
| 888 | |
|---|
| 889 | my $sec = $ss->($time); |
|---|
| 890 | Carp::croak("Consumer secret too long") if length($sec) > 255; |
|---|
| 891 | return $sec; |
|---|
| 892 | } |
|---|
| 893 | |
|---|
| 894 | package OpenID::util; |
|---|
| 895 | |
|---|
| 896 | use constant VERSION_1_NAMESPACE => "http://openid.net/signon/1.1"; |
|---|
| 897 | use constant VERSION_2_NAMESPACE => "http://specs.openid.net/auth/2.0"; |
|---|
| 898 | |
|---|
| 899 | # I guess this is a bit daft since constants are subs anyway, |
|---|
| 900 | # but whatever. |
|---|
| 901 | sub version_1_namespace { |
|---|
| 902 | return VERSION_1_NAMESPACE; |
|---|
| 903 | } |
|---|
| 904 | sub version_2_namespace { |
|---|
| 905 | return VERSION_2_NAMESPACE; |
|---|
| 906 | } |
|---|
| 907 | sub version_1_xrds_service_url { |
|---|
| 908 | return VERSION_1_NAMESPACE; |
|---|
| 909 | } |
|---|
| 910 | sub version_2_xrds_service_url { |
|---|
| 911 | return "http://specs.openid.net/auth/2.0/signon"; |
|---|
| 912 | } |
|---|
| 913 | sub version_2_xrds_directed_service_url { |
|---|
| 914 | return "http://specs.openid.net/auth/2.0/server"; |
|---|
| 915 | } |
|---|
| 916 | sub version_2_identifier_select_url { |
|---|
| 917 | return "http://specs.openid.net/auth/2.0/identifier_select"; |
|---|
| 918 | } |
|---|
| 919 | |
|---|
| 920 | # From Digest::HMAC |
|---|
| 921 | sub hmac_sha1_hex { |
|---|
| 922 | unpack("H*", &hmac_sha1); |
|---|
| 923 | } |
|---|
| 924 | sub hmac_sha1 { |
|---|
| 925 | hmac($_[0], $_[1], \&Digest::SHA1::sha1, 64); |
|---|
| 926 | } |
|---|
| 927 | sub hmac { |
|---|
| 928 | my($data, $key, $hash_func, $block_size) = @_; |
|---|
| 929 | $block_size ||= 64; |
|---|
| 930 | $key = &$hash_func($key) if length($key) > $block_size; |
|---|
| 931 | |
|---|
| 932 | my $k_ipad = $key ^ (chr(0x36) x $block_size); |
|---|
| 933 | my $k_opad = $key ^ (chr(0x5c) x $block_size); |
|---|
| 934 | |
|---|
| 935 | &$hash_func($k_opad, &$hash_func($k_ipad, $data)); |
|---|
| 936 | } |
|---|
| 937 | |
|---|
| 938 | sub parse_keyvalue { |
|---|
| 939 | my $reply = shift; |
|---|
| 940 | my %ret; |
|---|
| 941 | $reply =~ s/\r//g; |
|---|
| 942 | foreach (split /\n/, $reply) { |
|---|
| 943 | next unless /^(\S+?):(.*)/; |
|---|
| 944 | $ret{$1} = $2; |
|---|
| 945 | } |
|---|
| 946 | return %ret; |
|---|
| 947 | } |
|---|
| 948 | |
|---|
| 949 | sub ejs |
|---|
| 950 | { |
|---|
| 951 | my $a = $_[0]; |
|---|
| 952 | $a =~ s/[\"\'\\]/\\$&/g; |
|---|
| 953 | $a =~ s/\r?\n/\\n/gs; |
|---|
| 954 | $a =~ s/\r//; |
|---|
| 955 | return $a; |
|---|
| 956 | } |
|---|
| 957 | |
|---|
| 958 | # Data::Dumper for JavaScript |
|---|
| 959 | sub js_dumper { |
|---|
| 960 | my $obj = shift; |
|---|
| 961 | if (ref $obj eq "HASH") { |
|---|
| 962 | my $ret = "{"; |
|---|
| 963 | foreach my $k (keys %$obj) { |
|---|
| 964 | $ret .= "$k: " . js_dumper($obj->{$k}) . ","; |
|---|
| 965 | } |
|---|
| 966 | chop $ret; |
|---|
| 967 | $ret .= "}"; |
|---|
| 968 | return $ret; |
|---|
| 969 | } elsif (ref $obj eq "ARRAY") { |
|---|
| 970 | my $ret = "[" . join(", ", map { js_dumper($_) } @$obj) . "]"; |
|---|
| 971 | return $ret; |
|---|
| 972 | } else { |
|---|
| 973 | return $obj if $obj =~ /^\d+$/; |
|---|
| 974 | return "\"" . ejs($obj) . "\""; |
|---|
| 975 | } |
|---|
| 976 | } |
|---|
| 977 | |
|---|
| 978 | sub eurl |
|---|
| 979 | { |
|---|
| 980 | my $a = $_[0]; |
|---|
| 981 | $a =~ s/([^a-zA-Z0-9_\,\-.\/\\\: ])/uc sprintf("%%%02x",ord($1))/eg; |
|---|
| 982 | $a =~ tr/ /+/; |
|---|
| 983 | return $a; |
|---|
| 984 | } |
|---|
| 985 | |
|---|
| 986 | sub push_url_arg { |
|---|
| 987 | my $uref = shift; |
|---|
| 988 | $$uref =~ s/[&?]$//; |
|---|
| 989 | my $got_qmark = ($$uref =~ /\?/); |
|---|
| 990 | |
|---|
| 991 | while (@_) { |
|---|
| 992 | my $key = shift; |
|---|
| 993 | my $value = shift; |
|---|
| 994 | $$uref .= $got_qmark ? "&" : ($got_qmark = 1, "?"); |
|---|
| 995 | $$uref .= eurl($key) . "=" . eurl($value); |
|---|
| 996 | } |
|---|
| 997 | } |
|---|
| 998 | |
|---|
| 999 | sub push_openid2_url_arg { |
|---|
| 1000 | my $uref = shift; |
|---|
| 1001 | my %args = @_; |
|---|
| 1002 | push_url_arg($uref, |
|---|
| 1003 | 'openid.ns' => VERSION_2_NAMESPACE, |
|---|
| 1004 | map { |
|---|
| 1005 | 'openid.'.$_ => $args{$_} |
|---|
| 1006 | } keys %args, |
|---|
| 1007 | ); |
|---|
| 1008 | } |
|---|
| 1009 | |
|---|
| 1010 | sub time_to_w3c { |
|---|
| 1011 | my $time = shift || time(); |
|---|
| 1012 | my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = gmtime($time); |
|---|
| 1013 | $mon++; |
|---|
| 1014 | $year += 1900; |
|---|
| 1015 | |
|---|
| 1016 | return sprintf("%04d-%02d-%02dT%02d:%02d:%02dZ", |
|---|
| 1017 | $year, $mon, $mday, |
|---|
| 1018 | $hour, $min, $sec); |
|---|
| 1019 | } |
|---|
| 1020 | |
|---|
| 1021 | sub w3c_to_time { |
|---|
| 1022 | my $hms = shift; |
|---|
| 1023 | return 0 unless |
|---|
| 1024 | $hms =~ /^(\d{4,4})-(\d\d)-(\d\d)T(\d\d):(\d\d):(\d\d)Z$/; |
|---|
| 1025 | |
|---|
| 1026 | my $time; |
|---|
| 1027 | eval { |
|---|
| 1028 | $time = Time::Local::timegm($6, $5, $4, $3, $2 - 1, $1); |
|---|
| 1029 | }; |
|---|
| 1030 | return 0 if $@; |
|---|
| 1031 | return $time; |
|---|
| 1032 | } |
|---|
| 1033 | |
|---|
| 1034 | sub bi2bytes { |
|---|
| 1035 | my $bigint = shift; |
|---|
| 1036 | die "Can't deal with negative numbers" if $bigint->is_negative; |
|---|
| 1037 | |
|---|
| 1038 | my $bits = $bigint->as_bin; |
|---|
| 1039 | die unless $bits =~ s/^0b//; |
|---|
| 1040 | |
|---|
| 1041 | # prepend zeros to round to byte boundary, or to unset high bit |
|---|
| 1042 | my $prepend = (8 - length($bits) % 8) || ($bits =~ /^1/ ? 8 : 0); |
|---|
| 1043 | $bits = ("0" x $prepend) . $bits if $prepend; |
|---|
| 1044 | |
|---|
| 1045 | return pack("B*", $bits); |
|---|
| 1046 | } |
|---|
| 1047 | |
|---|
| 1048 | sub bi2arg { |
|---|
| 1049 | return b64(bi2bytes($_[0])); |
|---|
| 1050 | } |
|---|
| 1051 | |
|---|
| 1052 | sub b64 { |
|---|
| 1053 | my $val = MIME::Base64::encode_base64($_[0]); |
|---|
| 1054 | $val =~ s/\s+//g; |
|---|
| 1055 | return $val; |
|---|
| 1056 | } |
|---|
| 1057 | |
|---|
| 1058 | sub d64 { |
|---|
| 1059 | return MIME::Base64::decode_base64($_[0]); |
|---|
| 1060 | } |
|---|
| 1061 | |
|---|
| 1062 | sub bytes2bi { |
|---|
| 1063 | return Math::BigInt->new("0b" . unpack("B*", $_[0])); |
|---|
| 1064 | } |
|---|
| 1065 | |
|---|
| 1066 | sub arg2bi { |
|---|
| 1067 | return undef unless defined $_[0] and $_[0] ne ""; |
|---|
| 1068 | # don't acccept base-64 encoded numbers over 700 bytes. which means |
|---|
| 1069 | # those over 4200 bits. |
|---|
| 1070 | return Math::BigInt->new("0") if length($_[0]) > 700; |
|---|
| 1071 | return bytes2bi(MIME::Base64::decode_base64($_[0])); |
|---|
| 1072 | } |
|---|
| 1073 | |
|---|
| 1074 | |
|---|
| 1075 | __END__ |
|---|
| 1076 | |
|---|
| 1077 | =head1 NAME |
|---|
| 1078 | |
|---|
| 1079 | Net::OpenID::Consumer - library for consumers of OpenID identities |
|---|
| 1080 | |
|---|
| 1081 | =head1 SYNOPSIS |
|---|
| 1082 | |
|---|
| 1083 | use Net::OpenID::Consumer; |
|---|
| 1084 | |
|---|
| 1085 | my $csr = Net::OpenID::Consumer->new( |
|---|
| 1086 | ua => LWPx::ParanoidAgent->new, |
|---|
| 1087 | cache => Some::Cache->new, |
|---|
| 1088 | args => $cgi, |
|---|
| 1089 | consumer_secret => ..., |
|---|
| 1090 | required_root => "http://site.example.com/", |
|---|
| 1091 | ); |
|---|
| 1092 | |
|---|
| 1093 | # a user entered, say, "bradfitz.com" as their identity. The first |
|---|
| 1094 | # step is to fetch that page, parse it, and get a |
|---|
| 1095 | # Net::OpenID::ClaimedIdentity object: |
|---|
| 1096 | |
|---|
| 1097 | my $claimed_identity = $csr->claimed_identity("bradfitz.com"); |
|---|
| 1098 | |
|---|
| 1099 | # now your app has to send them at their identity server's endpoint |
|---|
| 1100 | # to get redirected to either a positive assertion that they own |
|---|
| 1101 | # that identity, or where they need to go to login/setup trust/etc. |
|---|
| 1102 | |
|---|
| 1103 | my $check_url = $claimed_identity->check_url( |
|---|
| 1104 | return_to => "http://example.com/openid-check.app?yourarg=val", |
|---|
| 1105 | trust_root => "http://example.com/", |
|---|
| 1106 | ); |
|---|
| 1107 | |
|---|
| 1108 | # so you send the user off there, and then they come back to |
|---|
| 1109 | # openid-check.app, then you see what the identity server said. |
|---|
| 1110 | |
|---|
| 1111 | # Either use callback-based API (recommended)... |
|---|
| 1112 | $csr->handle_server_response( |
|---|
| 1113 | not_openid => sub { |
|---|
| 1114 | die "Not an OpenID message"; |
|---|
| 1115 | }, |
|---|
| 1116 | setup_required => sub { |
|---|
| 1117 | my $setup_url = shift; |
|---|
| 1118 | # Redirect the user to $setup_url |
|---|
| 1119 | }, |
|---|
| 1120 | cancelled => sub { |
|---|
| 1121 | # Do something appropriate when the user hits "cancel" at the OP |
|---|
| 1122 | }, |
|---|
| 1123 | verified => sub { |
|---|
| 1124 | my $vident = shift; |
|---|
| 1125 | # Do something with the VerifiedIdentity object $vident |
|---|
| 1126 | }, |
|---|
| 1127 | error => sub { |
|---|
| 1128 | my $err = shift; |
|---|
| 1129 | die($err); |
|---|
| 1130 | }, |
|---|
| 1131 | ); |
|---|
| 1132 | |
|---|
| 1133 | # ... or handle the various cases yourself |
|---|
| 1134 | if (my $setup_url = $csr->user_setup_url) { |
|---|
| 1135 | # redirect/link/popup user to $setup_url |
|---|
| 1136 | } elsif ($csr->user_cancel) { |
|---|
| 1137 | # restore web app state to prior to check_url |
|---|
| 1138 | } elsif (my $vident = $csr->verified_identity) { |
|---|
| 1139 | my $verified_url = $vident->url; |
|---|
| 1140 | print "You are $verified_url !"; |
|---|
| 1141 | } else { |
|---|
| 1142 | die "Error validating identity: " . $csr->err; |
|---|
| 1143 | } |
|---|
| 1144 | |
|---|
| 1145 | |
|---|
| 1146 | =head1 DESCRIPTION |
|---|
| 1147 | |
|---|
| 1148 | This is the Perl API for (the consumer half of) OpenID, a distributed |
|---|
| 1149 | identity system based on proving you own a URL, which is then your |
|---|
| 1150 | identity. More information is available at: |
|---|
| 1151 | |
|---|
| 1152 | http://openid.net/ |
|---|
| 1153 | |
|---|
| 1154 | =head1 CONSTRUCTOR |
|---|
| 1155 | |
|---|
| 1156 | =over 4 |
|---|
| 1157 | |
|---|
| 1158 | =item C<new> |
|---|
| 1159 | |
|---|
| 1160 | my $csr = Net::OpenID::Consumer->new([ %opts ]); |
|---|
| 1161 | |
|---|
| 1162 | You can set the C<ua>, C<cache>, C<consumer_secret>, C<required_root>, |
|---|
| 1163 | C<minimum_version> and C<args> in the constructor. See the corresponding |
|---|
| 1164 | method descriptions below. |
|---|
| 1165 | |
|---|
| 1166 | =back |
|---|
| 1167 | |
|---|
| 1168 | =head1 METHODS |
|---|
| 1169 | |
|---|
| 1170 | =over 4 |
|---|
| 1171 | |
|---|
| 1172 | =item $csr->B<ua>($user_agent) |
|---|
| 1173 | |
|---|
| 1174 | =item $csr->B<ua> |
|---|
| 1175 | |
|---|
| 1176 | Getter/setter for the LWP::UserAgent (or subclass) instance which will |
|---|
| 1177 | be used when web donwloads are needed. It's highly recommended that |
|---|
| 1178 | you use LWPx::ParanoidAgent, or at least read its documentation so |
|---|
| 1179 | you're aware of why you should care. |
|---|
| 1180 | |
|---|
| 1181 | =item $csr->B<cache>($cache) |
|---|
| 1182 | |
|---|
| 1183 | =item $csr->B<cache> |
|---|
| 1184 | |
|---|
| 1185 | Getter/setter for the optional (but recommended!) cache instance you |
|---|
| 1186 | want to use for storing fetched parts of pages. (identity server |
|---|
| 1187 | public keys, and the E<lt>headE<gt> section of user's HTML pages) |
|---|
| 1188 | |
|---|
| 1189 | The $cache object can be anything that has a -E<gt>get($key) and |
|---|
| 1190 | -E<gt>set($key,$value) methods. See L<URI::Fetch> for more |
|---|
| 1191 | information. This cache object is just passed to L<URI::Fetch> |
|---|
| 1192 | directly. |
|---|
| 1193 | |
|---|
| 1194 | =item $nos->B<consumer_secret>($scalar) |
|---|
| 1195 | |
|---|
| 1196 | =item $nos->B<consumer_secret>($code) |
|---|
| 1197 | |
|---|
| 1198 | =item $code = $nos->B<consumer_secret>; ($secret) = $code->($time); |
|---|
| 1199 | |
|---|
| 1200 | The consumer secret is used to generate self-signed nonces for the |
|---|
| 1201 | return_to URL, to prevent spoofing. |
|---|
| 1202 | |
|---|
| 1203 | In the simplest (and least secure) form, you configure a static secret |
|---|
| 1204 | value with a scalar. If you use this method and change the scalar |
|---|
| 1205 | value, any outstanding requests from the last 30 seconds or so will fail. |
|---|
| 1206 | |
|---|
| 1207 | The more robust (but more complicated) form is to supply a subref that |
|---|
| 1208 | returns a secret based on the provided I<$time>, a unix timestamp. |
|---|
| 1209 | And if one doesn't exist for that time, create, store and return it |
|---|
| 1210 | (with appropriate locking so you never return different secrets for |
|---|
| 1211 | the same time.) |
|---|
| 1212 | |
|---|
| 1213 | Your secret may not exceed 255 characters. |
|---|
| 1214 | |
|---|
| 1215 | =item $csr->B<minimum_version>(2) |
|---|
| 1216 | |
|---|
| 1217 | =item $csr->B<minimum_version> |
|---|
| 1218 | |
|---|
| 1219 | Get or set the minimum OpenID protocol version supported. Currently |
|---|
| 1220 | the only useful value you can set here is 2, which will cause |
|---|
| 1221 | 1.1 identifiers to fail discovery with the error C<protocol_version_incorrect>. |
|---|
| 1222 | |
|---|
| 1223 | In most cases you'll want to allow both 1.1 and 2.0 identifiers, |
|---|
| 1224 | which is the default. If you want, you can set this property to 1 |
|---|
| 1225 | to make this behavior explicit. |
|---|
| 1226 | |
|---|
| 1227 | =item $csr->B<message>($key) |
|---|
| 1228 | |
|---|
| 1229 | Obtain a value from the message contained in the request arguments |
|---|
| 1230 | with the given key. This can only be used to obtain core arguments, |
|---|
| 1231 | not extension arguments. |
|---|
| 1232 | |
|---|
| 1233 | Call this method without a C<$key> argument to get a L<Net::OpenID::IndirectMessage> |
|---|
| 1234 | object representing the message. |
|---|
| 1235 | |
|---|
| 1236 | =item $csr->B<args>($ref) |
|---|
| 1237 | |
|---|
| 1238 | =item $csr->B<args>($param) |
|---|
| 1239 | |
|---|
| 1240 | =item $csr->B<args> |
|---|
| 1241 | |
|---|
| 1242 | Can be used in 1 of 3 ways: |
|---|
| 1243 | |
|---|
| 1244 | 1. Setting the way which the Consumer instances obtains GET parameters: |
|---|
| 1245 | |
|---|
| 1246 | $csr->args( $reference ) |
|---|
| 1247 | |
|---|
| 1248 | Where $reference is either a HASH ref, CODE ref, Apache $r, |
|---|
| 1249 | Apache::Request $apreq, or CGI.pm $cgi. If a CODE ref, the subref |
|---|
| 1250 | must return the value given one argument (the parameter to retrieve) |
|---|
| 1251 | |
|---|
| 1252 | If you pass in an Apache $r object, you must not have already called |
|---|
| 1253 | $r->content as the consumer module will want to get the request |
|---|
| 1254 | arguments out of here in the case of a POST request. |
|---|
| 1255 | |
|---|
| 1256 | 2. Get a paramater: |
|---|
| 1257 | |
|---|
| 1258 | my $foo = $csr->args("foo"); |
|---|
| 1259 | |
|---|
| 1260 | When given an unblessed scalar, it retrieves the value. It croaks if |
|---|
| 1261 | you haven't defined a way to get at the parameters. |
|---|
| 1262 | |
|---|
| 1263 | Most callers should instead use the C<message> method above, which |
|---|
| 1264 | abstracts away the need to understand OpenID's message serialization. |
|---|
| 1265 | |
|---|
| 1266 | 3. Get the getter: |
|---|
| 1267 | |
|---|
| 1268 | my $code = $csr->args; |
|---|
| 1269 | |
|---|
| 1270 | Without arguments, returns a subref that returns the value given a |
|---|
| 1271 | parameter name. |
|---|
| 1272 | |
|---|
| 1273 | Most callers should instead use the C<message> method above with no |
|---|
| 1274 | arguments, which returns an object from which extension attributes |
|---|
| 1275 | can be obtained by their documented namespace URI. |
|---|
| 1276 | |
|---|
| 1277 | =item $nos->B<required_root>($url_prefix) |
|---|
| 1278 | |
|---|
| 1279 | =item $url_prefix = $nos->B<required_root> |
|---|
| 1280 | |
|---|
| 1281 | If provided, this is the required string that all return_to URLs must |
|---|
| 1282 | start with. If it doesn't match, it'll be considered invalid (spoofed |
|---|
| 1283 | from another site) |
|---|
| 1284 | |
|---|
| 1285 | =item $csr->B<claimed_identity>($url) |
|---|
| 1286 | |
|---|
| 1287 | Given a user-entered $url (which could be missing http://, or have |
|---|
| 1288 | extra whitespace, etc), returns either a Net::OpenID::ClaimedIdentity |
|---|
| 1289 | object, or undef on failure. |
|---|
| 1290 | |
|---|
| 1291 | Note that this identity is NOT verified yet. It's only who the user |
|---|
| 1292 | claims they are, but they could be lying. |
|---|
| 1293 | |
|---|
| 1294 | If this method returns undef, you can rely on the following errors |
|---|
| 1295 | codes (from $csr->B<errcode>) to decide what to present to the user: |
|---|
| 1296 | |
|---|
| 1297 | =over 8 |
|---|
| 1298 | |
|---|
| 1299 | =item no_identity_server |
|---|
| 1300 | |
|---|
| 1301 | =item empty_url |
|---|
| 1302 | |
|---|
| 1303 | =item bogus_url |
|---|
| 1304 | |
|---|
| 1305 | =item no_head_tag |
|---|
| 1306 | |
|---|
| 1307 | =item url_fetch_err |
|---|
| 1308 | |
|---|
| 1309 | =back |
|---|
| 1310 | |
|---|
| 1311 | =item $csr->B<handle_server_response>( %callbacks ); |
|---|
| 1312 | |
|---|
| 1313 | When a request comes in that contains a response from an OpenID provider, |
|---|
| 1314 | figure out what it means and dispatch to an appropriate callback to handle |
|---|
| 1315 | the request. This is the callback-based alternative to explicitly calling |
|---|
| 1316 | the methods below in the correct sequence, and is recommended unless you |
|---|
| 1317 | need to do something strange. |
|---|
| 1318 | |
|---|
| 1319 | Anything you return from the selected callback function will be returned |
|---|
| 1320 | by this method verbatim. This is useful if the caller needs to return |
|---|
| 1321 | something different in each case. |
|---|
| 1322 | |
|---|
| 1323 | The available callbacks are: |
|---|
| 1324 | |
|---|
| 1325 | =over 8 |
|---|
| 1326 | |
|---|
| 1327 | =item B<not_openid> - the request isn't an OpenID response after all. |
|---|
| 1328 | |
|---|
| 1329 | =item B<setup_required>($setup_url) - the provider needs to present some UI to the user before it can respond. Send the user to the given URL by some means. |
|---|
| 1330 | |
|---|
| 1331 | =item B<cancelled> - the user cancelled the authentication request from the provider's UI |
|---|
| 1332 | |
|---|
| 1333 | =item B<verified>($verified_identity) - the user's identity has been successfully verified. A L<Net::OpenID::VerifiedIdentity> object is passed in. |
|---|
| 1334 | |
|---|
| 1335 | =item B<error>($errcode, $errmsg) - an error has occured. An error code and message are provided. |
|---|
| 1336 | |
|---|
| 1337 | =back |
|---|
| 1338 | |
|---|
| 1339 | =item $csr->B<user_setup_url>( [ %opts ] ) |
|---|
| 1340 | |
|---|
| 1341 | Returns the URL the user must return to in order to login, setup trust, |
|---|
| 1342 | or do whatever the identity server needs them to do in order to make |
|---|
| 1343 | the identity assertion which they previously initiated by entering |
|---|
| 1344 | their claimed identity URL. Returns undef if this setup URL isn't |
|---|
| 1345 | required, in which case you should ask for the verified_identity. |
|---|
| 1346 | |
|---|
| 1347 | The base URL this this function returns can be modified by using the |
|---|
| 1348 | following options in %opts: |
|---|
| 1349 | |
|---|
| 1350 | =over |
|---|
| 1351 | |
|---|
| 1352 | =item C<post_grant> |
|---|
| 1353 | |
|---|
| 1354 | What you're asking the identity server to do with the user after they |
|---|
| 1355 | setup trust. Can be either C<return> or C<close> to return the user |
|---|
| 1356 | back to the return_to URL, or close the browser window with |
|---|
| 1357 | JavaScript. If you don't specify, the behavior is undefined (probably |
|---|
| 1358 | the user gets a dead-end page with a link back to the return_to URL). |
|---|
| 1359 | In any case, the identity server can do whatever it wants, so don't |
|---|
| 1360 | depend on this. |
|---|
| 1361 | |
|---|
| 1362 | =back |
|---|
| 1363 | |
|---|
| 1364 | =item $csr->B<user_cancel> |
|---|
| 1365 | |
|---|
| 1366 | Returns true if the user declined to share their identity, false |
|---|
| 1367 | otherwise. (This function is literally one line: returns true if |
|---|
| 1368 | "openid.mode" eq "cancel") |
|---|
| 1369 | |
|---|
| 1370 | It's then your job to restore your app to where it was prior to |
|---|
| 1371 | redirecting them off to the user_setup_url, using the other query |
|---|
| 1372 | parameters that you'd sent along in your return_to URL. |
|---|
| 1373 | |
|---|
| 1374 | =item $csr->B<verified_identity>( [ %opts ] ) |
|---|
| 1375 | |
|---|
| 1376 | Returns a Net::OpenID::VerifiedIdentity object, or undef. |
|---|
| 1377 | Verification includes double-checking the reported identity URL |
|---|
| 1378 | declares the identity server, verifying the signature, etc. |
|---|
| 1379 | |
|---|
| 1380 | The options in %opts may contain: |
|---|
| 1381 | |
|---|
| 1382 | =over |
|---|
| 1383 | |
|---|
| 1384 | =item C<required_root> |
|---|
| 1385 | |
|---|
| 1386 | Sets the required_root just for this request. Values returns to its |
|---|
| 1387 | previous value afterwards. |
|---|
| 1388 | |
|---|
| 1389 | =back |
|---|
| 1390 | |
|---|
| 1391 | =item $csr->B<err> |
|---|
| 1392 | |
|---|
| 1393 | Returns the last error, in form "errcode: errtext" |
|---|
| 1394 | |
|---|
| 1395 | =item $csr->B<errcode> |
|---|
| 1396 | |
|---|
| 1397 | Returns the last error code. |
|---|
| 1398 | |
|---|
| 1399 | =item $csr->B<errtext> |
|---|
| 1400 | |
|---|
| 1401 | Returns the last error text. |
|---|
| 1402 | |
|---|
| 1403 | =item $csr->B<json_err> |
|---|
| 1404 | |
|---|
| 1405 | Returns the last error code/text in JSON format. |
|---|
| 1406 | |
|---|
| 1407 | =back |
|---|
| 1408 | |
|---|
| 1409 | =head1 COPYRIGHT |
|---|
| 1410 | |
|---|
| 1411 | This module is Copyright (c) 2005 Brad Fitzpatrick. |
|---|
| 1412 | All rights reserved. |
|---|
| 1413 | |
|---|
| 1414 | You may distribute under the terms of either the GNU General Public |
|---|
| 1415 | License or the Artistic License, as specified in the Perl README file. |
|---|
| 1416 | If you need more liberal licensing terms, please contact the |
|---|
| 1417 | maintainer. |
|---|
| 1418 | |
|---|
| 1419 | =head1 WARRANTY |
|---|
| 1420 | |
|---|
| 1421 | This is free software. IT COMES WITHOUT WARRANTY OF ANY KIND. |
|---|
| 1422 | |
|---|
| 1423 | =head1 MAILING LIST |
|---|
| 1424 | |
|---|
| 1425 | The Net::OpenID family of modules has a mailing list powered |
|---|
| 1426 | by Google Groups. For more information, see |
|---|
| 1427 | http://groups.google.com/group/openid-perl . |
|---|
| 1428 | |
|---|
| 1429 | =head1 SEE ALSO |
|---|
| 1430 | |
|---|
| 1431 | OpenID website: http://openid.net/ |
|---|
| 1432 | |
|---|
| 1433 | L<Net::OpenID::ClaimedIdentity> -- part of this module |
|---|
| 1434 | |
|---|
| 1435 | L<Net::OpenID::VerifiedIdentity> -- part of this module |
|---|
| 1436 | |
|---|
| 1437 | L<Net::OpenID::Server> -- another module, for acting like an OpenID server |
|---|
| 1438 | |
|---|
| 1439 | =head1 AUTHORS |
|---|
| 1440 | |
|---|
| 1441 | Brad Fitzpatrick <brad@danga.com> |
|---|
| 1442 | |
|---|
| 1443 | Tatsuhiko Miyagawa <miyagawa@sixapart.com> |
|---|
| 1444 | |
|---|
| 1445 | Martin Atkins <mart@degeneration.co.uk> |
|---|
| 1446 | |
|---|