root/trunk/extlib/Net/OpenID/VerifiedIdentity.pm @ 3531

Revision 3531, 10.5 kB (checked in by fumiakiy, 9 months ago)

Merged sockfish to trunk. "svn merge -r3114:3527 http://code.sixapart.com/svn/movabletype/branches/sockfish/ ."

Line 
1use strict;
2use Carp ();
3
4############################################################################
5package Net::OpenID::VerifiedIdentity;
6use fields (
7    'identity',  # the verified identity URL
8    'id_uri',  # the verified identity's URI object
9
10    'claimed_identity', # The ClaimedIdentity object that we've verified
11    'semantic_info',    # The "semantic info" (RSS URLs, etc) at the verified identity URL
12
13    'consumer',  # The Net::OpenID::Consumer module which created us
14
15    'signed_fields' ,  # hashref of key->value of things that were signed.  without "openid." prefix
16    'signed_message',  # the signed fields as an IndirectMessage object. Created when needed.
17);
18use URI;
19
20sub new {
21    my Net::OpenID::VerifiedIdentity $self = shift;
22    $self = fields::new( $self ) unless ref $self;
23    my %opts = @_;
24
25    $self->{'consumer'} = delete $opts{'consumer'};
26
27    if ($self->{'claimed_identity'} = delete $opts{'claimed_identity'}) {
28        $self->{identity} = $self->{claimed_identity}->claimed_url;
29        unless ($self->{'id_uri'} = URI->new($self->{identity})) {
30            return $self->{'consumer'}->_fail("invalid_uri");
31        }
32    }
33
34    for my $par (qw(signed_fields)) {
35        $self->$par(delete $opts{$par});
36    }
37
38    Carp::croak("Unknown options: " . join(", ", keys %opts)) if %opts;
39    return $self;
40}
41
42sub url {
43    my Net::OpenID::VerifiedIdentity $self = shift;
44    return $self->{'identity'};
45}
46
47sub display {
48    my Net::OpenID::VerifiedIdentity $self = shift;
49    return DisplayOfURL($self->{'identity'});
50}
51
52sub _semantic_info_hash {
53    my ($self) = @_;
54    return $self->{semantic_info} if $self->{semantic_info};
55    my $sem_info = $self->{claimed_identity}->semantic_info;
56    $self->{semantic_info} = {
57        'foaf' => $self->_identity_relative_uri($sem_info->{"foaf"}),
58        'foafmaker' => $sem_info->{"foaf.maker"},
59        'rss' => $self->_identity_relative_uri($sem_info->{"rss"}),
60        'atom' => $self->_identity_relative_uri($sem_info->{"atom"}),
61    };
62    return $self->{semantic_info};
63}
64
65sub _identity_relative_uri {
66    my $self = shift;
67    my $url = shift;
68
69    return $url if ref $url;
70    return undef unless $url;
71    return URI->new_abs($url, $self->{'id_uri'});
72}
73
74sub signed_fields { &_getset;        }
75
76sub foaf      { &_getset_semurl; }
77sub rss       { &_getset_semurl; }
78sub atom      { &_getset_semurl; }
79sub foafmaker     { &_getset_sem; }
80
81sub declared_foaf   { &_dec_semurl; }
82sub declared_rss    { &_dec_semurl; }
83sub declared_atom   { &_dec_semurl; }
84
85sub extension_fields {
86    my ($self, $ns_uri) = @_;
87    return $self->_extension_fields($ns_uri, $self->{consumer}->message);
88}
89
90sub signed_extension_fields {
91    my ($self, $ns_uri) = @_;
92
93    return $self->_extension_fields($ns_uri, $self->signed_message);
94}
95
96sub _extension_fields {
97    my ($self, $ns_uri, $args) = @_;
98
99    return $args->get_ext($ns_uri);
100}
101
102sub signed_message {
103    my ($self) = @_;
104
105    return $self->{signed_message} if $self->{signed_message};
106
107    # This is maybe a bit hacky.
108    # We need to synthesize an IndirectMessage object
109    # representing the signed fields, which means
110    # that we need to fake up the mandatory message
111    # arguments that probably weren't signed.
112
113    my %args = map { 'openid.'.$_ => $self->{signed_fields}{$_} } keys %{$self->{signed_fields}};
114
115    my $real_message = $self->{consumer}->message;
116    if ($real_message->protocol_version == 1) {
117        # OpenID 1.1 just needs a mode.
118        $args{'openid.mode'} = 'id_res';
119    }
120    else {
121        # OpenID 2.2 needs the namespace URI as well
122        $args{'openid.ns'} = 'http://specs.openid.net/auth/2.0';
123        $args{'openid.mode'} = 'id_res';
124    }
125
126    my $message = Net::OpenID::IndirectMessage->new(\%args);
127
128    return $self->{signed_message} = $message;
129}
130
131sub _getset {
132    my $self = shift;
133    my $param = (caller(1))[3];
134    $param =~ s/.+:://;
135
136    if (@_) {
137        my $val = shift;
138        Carp::croak("Too many parameters") if @_;
139        $self->{$param} = $val;
140    }
141    return $self->{$param};
142}
143
144sub _getset_sem {
145    my $self = shift;
146    my $param = (caller(1))[3];
147    $param =~ s/.+:://;
148
149    my $info = $self->_semantic_info_hash;
150
151    if (my $value = shift) {
152        Carp::croak("Too many parameters") if @_;
153        $info->{$param} = $value;
154    }
155    return $info->{$param};
156}
157
158sub _getset_semurl {
159    my $self = shift;
160    my $param = (caller(1))[3];
161    $param =~ s/.+:://;
162
163    my $info = $self->_semantic_info_hash;
164
165    if (my $surl = shift) {
166        Carp::croak("Too many parameters") if @_;
167
168        # TODO: make absolute URL from possibly relative one
169        my $abs = URI->new_abs($surl, $self->{'id_uri'});
170        $info->{$param} = $abs;
171    }
172
173    my $uri = $info->{$param};
174    return $uri && _url_is_under($self->{'id_uri'}, $uri) ? $uri->as_string : undef;
175}
176
177sub _dec_semurl {
178    my $self = shift;
179    my $param = (caller(1))[3];
180    $param =~ s/.+::declared_//;
181
182    my $info = $self->_semantic_info_hash;
183
184    my $uri = $info->{$param};
185    return $uri ? $uri->as_string : undef;
186}
187
188sub DisplayOfURL {
189    my $url = shift;
190    my $dev_mode = shift;
191
192    return $url unless
193        $url =~ m!^https?://([^/]+)(/.*)?$!;
194
195    my ($host, $path) = ($1, $2);
196    $host = lc($host);
197
198    if ($dev_mode) {
199        $host =~ s!^dev\.!!;
200        $host =~ s!:\d+!!;
201    }
202
203    $host =~ s/:.+//;
204    $host =~ s/^www\.//i;
205
206    if (length($path) <= 1) {
207        return $host;
208    }
209
210    # obvious username
211    if ($path =~ m!^/~([^/]+)/?$! ||
212        $path =~ m!^/(?:users?|members?)/([^/]+)/?$!) {
213        return "$1 [$host]";
214    }
215
216    if ($host =~ m!^profile\.(.+)!i) {
217        my $site = $1;
218        if ($path =~ m!^/([^/]+)/?$!) {
219            return "$1 [$site]";
220        }
221    }
222
223    return $url;
224}
225
226# FIXME: duplicated in Net::OpenID::Server
227sub _url_is_under {
228    my ($root, $test, $err_ref) = @_;
229
230    my $err = sub {
231        $$err_ref = shift if $err_ref;
232        return undef;
233    };
234
235    my $ru = ref $root ? $root : URI->new($root);
236    return $err->("invalid root scheme") unless $ru->scheme =~ /^https?$/;
237    my $tu = ref $test ? $test : URI->new($test);
238    return $err->("invalid test scheme") unless $tu->scheme =~ /^https?$/;
239    return $err->("schemes don't match") unless $ru->scheme eq $tu->scheme;
240    return $err->("ports don't match") unless $ru->port == $tu->port;
241
242    # check hostnames
243    my $ru_host = $ru->host;
244    my $tu_host = $tu->host;
245    my $wildcard_host = 0;
246    if ($ru_host =~ s!^\*\.!!) {
247        $wildcard_host = 1;
248    }
249    unless ($ru_host eq $tu_host) {
250        if ($wildcard_host) {
251            return $err->("host names don't match") unless
252                $tu_host =~ /\.\Q$ru_host\E$/;
253        } else {
254            return $err->("host names don't match");
255        }
256    }
257
258    # check paths
259    my $ru_path = $ru->path || "/";
260    my $tu_path = $tu->path || "/";
261    $ru_path .= "/" unless $ru_path =~ m!/$!;
262    $tu_path .= "/" unless $tu_path =~ m!/$!;
263    return $err->("path not a subpath") unless $tu_path =~ m!^\Q$ru_path\E!;
264
265    return 1;
266}
267
2681;
269
270__END__
271
272=head1 NAME
273
274Net::OpenID::VerifiedIdentity - object representing a verified OpenID identity
275
276=head1 SYNOPSIS
277
278  use Net::OpenID::Consumer;
279  my $csr = Net::OpenID::Consumer->new;
280  ....
281  my $vident = $csr->verified_identity
282    or die $csr->err;
283
284  my $url = $vident->url;
285
286
287=head1 DESCRIPTION
288
289After L<Net::OpenID::Consumer> verifies a user's identity and does the
290signature checks, it gives you this Net::OpenID::VerifiedIdentity
291object, from which you can learn more about the user.
292
293=head1 METHODS
294
295=over 4
296
297=item $vident->B<url>
298
299Returns the URL (as a scalar) that was verified.  (Remember, an OpenID
300is just a URL.)
301
302=item $vident->B<display>
303
304Returns the a short "display form" of the verified URL using a couple
305brain-dead patterns.  For instance, the identity
306"http://www.foo.com/~bob/" will map to "bob [foo.com]" The www. prefix
307is removed, as well as http, and a username is looked for, in either
308the tilde form, or "/users/USERNAME" or "/members/USERNAME".  If the
309path component is empty or just "/", then the display form is just the
310hostname, so "http://myblog.com/" is just "myblog.com".
311
312Suggestions for improving this function are welcome, but you'll probably
313get more satisfying results if you make use of the data returned by
314the Simple Registration (SREG) extension, which allows the user to
315choose a preferred nickname to use on your site.
316
317=item $vident->B<extension_fields>($ns_uri)
318
319Return the fields from the given extension namespace, if any, that
320were included in the assertion request. The fields are returned in
321a hashref.
322
323In most cases you'll probably want to use B<signed_extension_fields> instead,
324to avoid attacks where a man-in-the-middle alters the extension fields in transit.
325
326Note that for OpenID 1.1 transactions only Simple Registration (SREG) 1.1
327is supported.
328
329=item $vident->B<signed_extension_fields>($ns_uri)
330
331The same as B<extension_fields> except that only fields that were signed
332as part of the assertion are included in the returned hashref. For example,
333if you included a Simple Registration request in your initial message,
334you might fetch the results (if any) like this:
335
336    $sreg = $vident->signed_extension_fields(
337        'http://openid.net/extensions/sreg/1.1',
338    );
339
340An important gotcha to bear in mind is that for OpenID 2.0 responses
341no extension fields can be considered signed unless the corresponding
342extension namespace declaration is also signed. If that is not the case,
343this method will behave as if no extension fields for that URI were signed.
344
345=item $vident->B<rss>
346
347=item $vident->B<atom>
348
349=item $vident->B<foaf>
350
351=item $vident->B<declared_rss>
352
353=item $vident->B<declared_atom>
354
355=item $vident->B<declared_foaf>
356
357Returns the absolute URLs (as scalars) of the user's RSS, Atom, and
358FOAF XML documents that were also found in their HTML's E<lt>headE<gt>
359section.  The short versions will only return a URL if they're below
360the root URL that was verified.  If you want to get at the user's
361declared rss/atom/foaf, even if it's on a different host or parent
362directory, use the delcared_* versions, which don't have the additional
363checks.
364
3652005-05-24:  A future module will take a Net::OpenID::VerifiedIdentity
366object and create an OpenID profile object so you don't have to
367manually parse all those documents to get profile information.
368
369=item $vident->B<foafmaker>
370
371Returns the value of the C<foaf:maker> meta tag, if declared.
372
373=back
374
375=head1 COPYRIGHT, WARRANTY, AUTHOR
376
377See L<Net::OpenID::Consumer> for author, copyrignt and licensing information.
378
379=head1 SEE ALSO
380
381L<Net::OpenID::Consumer>
382
383L<Net::OpenID::ClaimedIdentity>
384
385L<Net::OpenID::Server>
386
387Website:  L<http://www.danga.com/openid/>
Note: See TracBrowser for help on using the browser.