| 1 | use strict; |
|---|
| 2 | use Carp (); |
|---|
| 3 | |
|---|
| 4 | ############################################################################ |
|---|
| 5 | package Net::OpenID::ClaimedIdentity; |
|---|
| 6 | use fields ( |
|---|
| 7 | 'identity', # the canonical URL that was found, following redirects |
|---|
| 8 | 'server', # author-identity identity server endpoint |
|---|
| 9 | 'consumer', # ref up to the Net::OpenID::Consumer which generated us |
|---|
| 10 | 'delegate', # the delegated URL actually asserted by the server |
|---|
| 11 | 'protocol_version', # The version of the OpenID Authentication Protocol that is used |
|---|
| 12 | 'semantic_info', # Stuff that we've discovered in the identifier page's metadata |
|---|
| 13 | 'extension_args', # Extension arguments that the caller wants to add to the request |
|---|
| 14 | ); |
|---|
| 15 | |
|---|
| 16 | sub new { |
|---|
| 17 | my Net::OpenID::ClaimedIdentity $self = shift; |
|---|
| 18 | $self = fields::new( $self ) unless ref $self; |
|---|
| 19 | my %opts = @_; |
|---|
| 20 | for my $f (qw( identity server consumer delegate protocol_version semantic_info )) { |
|---|
| 21 | $self->{$f} = delete $opts{$f}; |
|---|
| 22 | } |
|---|
| 23 | |
|---|
| 24 | $self->{protocol_version} ||= 1; |
|---|
| 25 | unless ($self->{protocol_version} == 1 || $self->{protocol_version} == 2) { |
|---|
| 26 | Carp::croak("Unsupported protocol version"); |
|---|
| 27 | } |
|---|
| 28 | |
|---|
| 29 | # lowercase the scheme and hostname |
|---|
| 30 | $self->{'identity'} =~ s!^(https?://.+?)(/(?:.*))?$!lc($1) . $2!ie; |
|---|
| 31 | |
|---|
| 32 | $self->{extension_args} = {}; |
|---|
| 33 | |
|---|
| 34 | Carp::croak("unknown options: " . join(", ", keys %opts)) if %opts; |
|---|
| 35 | return $self; |
|---|
| 36 | } |
|---|
| 37 | |
|---|
| 38 | sub claimed_url { |
|---|
| 39 | my Net::OpenID::ClaimedIdentity $self = shift; |
|---|
| 40 | Carp::croak("Too many parameters") if @_; |
|---|
| 41 | return $self->{'identity'}; |
|---|
| 42 | } |
|---|
| 43 | |
|---|
| 44 | sub delegated_url { |
|---|
| 45 | my Net::OpenID::ClaimedIdentity $self = shift; |
|---|
| 46 | Carp::croak("Too many parameters") if @_; |
|---|
| 47 | return $self->{'delegate'}; |
|---|
| 48 | } |
|---|
| 49 | |
|---|
| 50 | sub identity_server { |
|---|
| 51 | my Net::OpenID::ClaimedIdentity $self = shift; |
|---|
| 52 | Carp::croak("Too many parameters") if @_; |
|---|
| 53 | return $self->{server}; |
|---|
| 54 | } |
|---|
| 55 | |
|---|
| 56 | sub protocol_version { |
|---|
| 57 | my Net::OpenID::ClaimedIdentity $self = shift; |
|---|
| 58 | Carp::croak("Too many parameters") if @_; |
|---|
| 59 | return $self->{protocol_version}; |
|---|
| 60 | } |
|---|
| 61 | |
|---|
| 62 | sub semantic_info { |
|---|
| 63 | my Net::OpenID::ClaimedIdentity $self = shift; |
|---|
| 64 | Carp::croak("Too many parameters") if @_; |
|---|
| 65 | return $self->{semantic_info} if $self->{semantic_info}; |
|---|
| 66 | my $final_url = ''; |
|---|
| 67 | my $info = $self->{consumer}->_find_semantic_info($self->claimed_url, \$final_url); |
|---|
| 68 | # Don't return anything if the URL has changed. Something bad may be happening. |
|---|
| 69 | $info = {} if $final_url ne $self->claimed_url; |
|---|
| 70 | return $self->{semantic_info} = $info; |
|---|
| 71 | } |
|---|
| 72 | |
|---|
| 73 | sub set_extension_args { |
|---|
| 74 | my Net::OpenID::ClaimedIdentity $self = shift; |
|---|
| 75 | my $ext_uri = shift; |
|---|
| 76 | my $args = shift; |
|---|
| 77 | Carp::croak("Too many parameters") if @_; |
|---|
| 78 | Carp::croak("No extension URI given") unless $ext_uri; |
|---|
| 79 | Carp::croak("Expecting hashref of args") if defined($args) && ref $args ne 'HASH'; |
|---|
| 80 | |
|---|
| 81 | $self->{extension_args}{$ext_uri} = $args; |
|---|
| 82 | } |
|---|
| 83 | |
|---|
| 84 | sub check_url { |
|---|
| 85 | my Net::OpenID::ClaimedIdentity $self = shift; |
|---|
| 86 | my (%opts) = @_; |
|---|
| 87 | |
|---|
| 88 | my $return_to = delete $opts{'return_to'}; |
|---|
| 89 | my $trust_root = delete $opts{'trust_root'}; |
|---|
| 90 | my $delayed_ret = delete $opts{'delayed_return'}; |
|---|
| 91 | my $force_reassociate = delete $opts{'force_reassociate'}; |
|---|
| 92 | my $use_assoc_handle = delete $opts{'use_assoc_handle'}; |
|---|
| 93 | my $actually_return_association = delete $opts{'actually_return_association'}; |
|---|
| 94 | |
|---|
| 95 | Carp::croak("Unknown options: " . join(", ", keys %opts)) if %opts; |
|---|
| 96 | Carp::croak("Invalid/missing return_to") unless $return_to =~ m!^https?://!; |
|---|
| 97 | |
|---|
| 98 | my $csr = $self->{consumer}; |
|---|
| 99 | |
|---|
| 100 | my $ident_server = $self->{server} or |
|---|
| 101 | Carp::croak("No identity server"); |
|---|
| 102 | |
|---|
| 103 | # get an assoc (or undef for dumb mode) |
|---|
| 104 | my $assoc; |
|---|
| 105 | if ($use_assoc_handle) { |
|---|
| 106 | $assoc = Net::OpenID::Association::handle_assoc($csr, $ident_server, $use_assoc_handle); |
|---|
| 107 | } else { |
|---|
| 108 | $assoc = Net::OpenID::Association::server_assoc($csr, $ident_server, $force_reassociate, ( |
|---|
| 109 | protocol_version => $self->protocol_version, |
|---|
| 110 | )); |
|---|
| 111 | } |
|---|
| 112 | |
|---|
| 113 | # for the openid-test project: (doing interop testing) |
|---|
| 114 | if ($actually_return_association) { |
|---|
| 115 | return $assoc; |
|---|
| 116 | } |
|---|
| 117 | |
|---|
| 118 | my $identity_arg = $self->{'delegate'} || $self->{'identity'}; |
|---|
| 119 | |
|---|
| 120 | # make a note back to ourselves that we're using a delegate |
|---|
| 121 | # but only in the 1.1 case because 2.0 has a core field for this |
|---|
| 122 | if ($self->{'delegate'} && $self->protocol_version == 1) { |
|---|
| 123 | OpenID::util::push_url_arg(\$return_to, |
|---|
| 124 | "oic.identity", $self->{identity}); |
|---|
| 125 | } |
|---|
| 126 | |
|---|
| 127 | # add a HMAC-signed time so we can verify the return_to URL wasn't spoofed |
|---|
| 128 | my $sig_time = time(); |
|---|
| 129 | my $c_secret = $csr->_get_consumer_secret($sig_time); |
|---|
| 130 | my $sig = substr(OpenID::util::hmac_sha1_hex($sig_time, $c_secret), 0, 20); |
|---|
| 131 | OpenID::util::push_url_arg(\$return_to, |
|---|
| 132 | "oic.time", "${sig_time}-$sig"); |
|---|
| 133 | |
|---|
| 134 | my $curl = $ident_server; |
|---|
| 135 | if ($self->protocol_version == 1) { |
|---|
| 136 | OpenID::util::push_url_arg(\$curl, |
|---|
| 137 | "openid.mode" => ($delayed_ret ? "checkid_setup" : "checkid_immediate"), |
|---|
| 138 | "openid.identity" => $identity_arg, |
|---|
| 139 | "openid.return_to" => $return_to, |
|---|
| 140 | |
|---|
| 141 | ($trust_root ? ( |
|---|
| 142 | "openid.trust_root" => $trust_root |
|---|
| 143 | ) : ()), |
|---|
| 144 | |
|---|
| 145 | ($assoc ? ( |
|---|
| 146 | "openid.assoc_handle" => $assoc->handle |
|---|
| 147 | ) : ()), |
|---|
| 148 | ); |
|---|
| 149 | } |
|---|
| 150 | elsif ($self->protocol_version == 2) { |
|---|
| 151 | # NOTE: OpenID Auth 2.0 uses different terminology for a bunch |
|---|
| 152 | # of things than 1.1 did. This library still uses the 1.1 terminology |
|---|
| 153 | # in its API. |
|---|
| 154 | OpenID::util::push_openid2_url_arg(\$curl, |
|---|
| 155 | "mode" => ($delayed_ret ? "checkid_setup" : "checkid_immediate"), |
|---|
| 156 | "claimed_id" => $self->claimed_url, |
|---|
| 157 | "identity" => $identity_arg, |
|---|
| 158 | "return_to" => $return_to, |
|---|
| 159 | |
|---|
| 160 | ($trust_root ? ( |
|---|
| 161 | "realm" => $trust_root |
|---|
| 162 | ) : ()), |
|---|
| 163 | |
|---|
| 164 | ($assoc ? ( |
|---|
| 165 | "assoc_handle" => $assoc->handle |
|---|
| 166 | ) : ()), |
|---|
| 167 | ); |
|---|
| 168 | } |
|---|
| 169 | |
|---|
| 170 | # Finally we add in the extension arguments, if any |
|---|
| 171 | my %ext_url_args = (); |
|---|
| 172 | my $ext_idx = 1; |
|---|
| 173 | foreach my $ext_uri (keys %{$self->{extension_args}}) { |
|---|
| 174 | my $ext_alias; |
|---|
| 175 | |
|---|
| 176 | if ($self->protocol_version >= 2) { |
|---|
| 177 | $ext_alias = 'e'.($ext_idx++); |
|---|
| 178 | $ext_url_args{'openid.ns.'.$ext_alias} = $ext_uri; |
|---|
| 179 | } |
|---|
| 180 | else { |
|---|
| 181 | # For OpenID 1.1 only the "SREG" extension is allowed, |
|---|
| 182 | # and it must use the "openid.sreg." prefix. |
|---|
| 183 | next unless $ext_uri eq "http://openid.net/extensions/sreg/1.1"; |
|---|
| 184 | $ext_alias = "sreg"; |
|---|
| 185 | } |
|---|
| 186 | |
|---|
| 187 | foreach my $k (keys %{$self->{extension_args}{$ext_uri}}) { |
|---|
| 188 | $ext_url_args{'openid.'.$ext_alias.'.'.$k} = $self->{extension_args}{$ext_uri}{$k}; |
|---|
| 189 | } |
|---|
| 190 | } |
|---|
| 191 | OpenID::util::push_url_arg(\$curl, %ext_url_args) if %ext_url_args; |
|---|
| 192 | |
|---|
| 193 | $self->{consumer}->_debug("check_url for (del=$self->{delegate}, id=$self->{identity}) = $curl"); |
|---|
| 194 | return $curl; |
|---|
| 195 | } |
|---|
| 196 | |
|---|
| 197 | |
|---|
| 198 | 1; |
|---|
| 199 | |
|---|
| 200 | __END__ |
|---|
| 201 | |
|---|
| 202 | =head1 NAME |
|---|
| 203 | |
|---|
| 204 | Net::OpenID::ClaimedIdentity - a not-yet-verified OpenID identity |
|---|
| 205 | |
|---|
| 206 | =head1 SYNOPSIS |
|---|
| 207 | |
|---|
| 208 | use Net::OpenID::Consumer; |
|---|
| 209 | my $csr = Net::OpenID::Consumer->new; |
|---|
| 210 | .... |
|---|
| 211 | my $cident = $csr->claimed_identity("bradfitz.com") |
|---|
| 212 | or die $csr->err; |
|---|
| 213 | |
|---|
| 214 | if ($AJAX_mode) { |
|---|
| 215 | my $url = $cident->claimed_url; |
|---|
| 216 | my $openid_server = $cident->identity_server; |
|---|
| 217 | # ... return JSON with those to user agent (whose request was |
|---|
| 218 | # XMLHttpRequest, probably) |
|---|
| 219 | } |
|---|
| 220 | |
|---|
| 221 | if ($CLASSIC_mode) { |
|---|
| 222 | my $check_url = $cident->check_url( |
|---|
| 223 | delayed_return => 1, |
|---|
| 224 | return_to => "http://example.com/get-identity.app", |
|---|
| 225 | trust_root => "http://*.example.com/", |
|---|
| 226 | ); |
|---|
| 227 | WebApp::redirect($check_url); |
|---|
| 228 | } |
|---|
| 229 | |
|---|
| 230 | =head1 DESCRIPTION |
|---|
| 231 | |
|---|
| 232 | After L<Net::OpenID::Consumer> crawls a user's declared identity URL |
|---|
| 233 | and finds openid.server link tags in the HTML head, you get this |
|---|
| 234 | object. It represents an identity that can be verified with OpenID |
|---|
| 235 | (the link tags are present), but hasn't been actually verified yet. |
|---|
| 236 | |
|---|
| 237 | =head1 METHODS |
|---|
| 238 | |
|---|
| 239 | =over 4 |
|---|
| 240 | |
|---|
| 241 | =item $url = $cident->B<claimed_url> |
|---|
| 242 | |
|---|
| 243 | The URL, now canonicalized, that the user claims to own. You can't |
|---|
| 244 | know whether or not they do own it yet until you send them off to the |
|---|
| 245 | check_url, though. |
|---|
| 246 | |
|---|
| 247 | =item $id_server = $cident->B<identity_server> |
|---|
| 248 | |
|---|
| 249 | Returns the identity server that will assert whether or not this |
|---|
| 250 | claimed identity is valid, and sign a message saying so. |
|---|
| 251 | |
|---|
| 252 | =item $url = $cident->B<delegated_url> |
|---|
| 253 | |
|---|
| 254 | If the claimed URL is using delegation, this returns the delegated identity that will |
|---|
| 255 | actually be sent to the identity server. |
|---|
| 256 | |
|---|
| 257 | =item $version = $cident->B<protocol_version> |
|---|
| 258 | |
|---|
| 259 | Determines whether this identifier is to be verified by OpenID 1.1 |
|---|
| 260 | or by OpenID 2.0. Returns C<1> or C<2> respectively. This will |
|---|
| 261 | affect the way the C<check_url> is constructed. |
|---|
| 262 | |
|---|
| 263 | =item $cident->B<set_extension_args>($ns_uri, $args) |
|---|
| 264 | |
|---|
| 265 | If called before you access C<check_url>, the arguments given in the hashref |
|---|
| 266 | $args will be added to the request in the given extension namespace. |
|---|
| 267 | For example, to use the Simple Registration (SREG) extension: |
|---|
| 268 | |
|---|
| 269 | $cident->set_extension_args( |
|---|
| 270 | 'http://openid.net/extensions/sreg/1.1', |
|---|
| 271 | { |
|---|
| 272 | required => 'email', |
|---|
| 273 | optional => 'fullname,nickname', |
|---|
| 274 | policy_url => 'http://example.com/privacypolicy.html', |
|---|
| 275 | }, |
|---|
| 276 | ); |
|---|
| 277 | |
|---|
| 278 | Note that when making an OpenID 1.1 request, only the Simple Registration |
|---|
| 279 | extension is supported. There was no general extension mechanism defined |
|---|
| 280 | in OpenID 1.1, so SREG (with the namespace URI as in the example above) |
|---|
| 281 | is supported as a special case. All other extension namespaces will |
|---|
| 282 | be silently ignored when making a 1.1 request. |
|---|
| 283 | |
|---|
| 284 | =item $url = $cident->B<check_url>( %opts ) |
|---|
| 285 | |
|---|
| 286 | Makes the URL that you have to somehow send the user to in order to |
|---|
| 287 | validate their identity. The options to put in %opts are: |
|---|
| 288 | |
|---|
| 289 | =over |
|---|
| 290 | |
|---|
| 291 | =item C<return_to> |
|---|
| 292 | |
|---|
| 293 | The URL that the identity server should redirect the user with either |
|---|
| 294 | a verified identity signature -or- a user_setup_url (if the assertion |
|---|
| 295 | couldn't be made). This URL may contain query parameters, and the |
|---|
| 296 | identity server must preserve them. |
|---|
| 297 | |
|---|
| 298 | =item C<trust_root> |
|---|
| 299 | |
|---|
| 300 | The URL that you want the user to actually see and declare trust for. |
|---|
| 301 | Your C<return_to> URL must be at or below your trust_root. Sending |
|---|
| 302 | the trust_root is optional, and defaults to your C<return_to> value, |
|---|
| 303 | but it's highly recommended (and prettier for users) to see a simple |
|---|
| 304 | trust_root. Note that the trust root may contain a wildcard at the |
|---|
| 305 | beginning of the host, like C<http://*.example.com/> |
|---|
| 306 | |
|---|
| 307 | =item C<delayed_return> |
|---|
| 308 | |
|---|
| 309 | If set to a true value, the check_url returned will indicate to the |
|---|
| 310 | user's identity server that it has permission to control the user's |
|---|
| 311 | user-agent for awhile, giving them real pages (not just redirects) and |
|---|
| 312 | lets them bounce around the identity server site for awhile until |
|---|
| 313 | the requested assertion can be made, and they can finally be redirected |
|---|
| 314 | back to your return_to URL above. |
|---|
| 315 | |
|---|
| 316 | The default value, false, means that the identity server will |
|---|
| 317 | immediately return to your return_to URL with either a "yes" or "no" |
|---|
| 318 | answer. In the "no" case, you'll instead have control of what to do, |
|---|
| 319 | and you'll be sent the identity server's user_setup_url where you'll |
|---|
| 320 | have to somehow send the user (be it link, redirect, or pop-up |
|---|
| 321 | window). |
|---|
| 322 | |
|---|
| 323 | When writing a dynamic "AJAX"-style application, you can't use |
|---|
| 324 | delayed_return because the remote site can't usefully take control of |
|---|
| 325 | a 1x1 pixel hidden IFRAME, so you'll need to get the user_setup_url |
|---|
| 326 | and present it to the user somehow. |
|---|
| 327 | |
|---|
| 328 | =back |
|---|
| 329 | |
|---|
| 330 | =back |
|---|
| 331 | |
|---|
| 332 | =head1 COPYRIGHT, WARRANTY, AUTHOR |
|---|
| 333 | |
|---|
| 334 | See L<Net::OpenID::Consumer> for author, copyrignt and licensing information. |
|---|
| 335 | |
|---|
| 336 | =head1 SEE ALSO |
|---|
| 337 | |
|---|
| 338 | L<Net::OpenID::Consumer> |
|---|
| 339 | |
|---|
| 340 | L<Net::OpenID::VerifiedIdentity> |
|---|
| 341 | |
|---|
| 342 | L<Net::OpenID::Server> |
|---|
| 343 | |
|---|
| 344 | Website: L<http://www.openid.net/> |
|---|
| 345 | |
|---|