line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
# LICENSE: You're free to distribute this under the same terms as Perl itself. |
2
|
|
|
|
|
|
|
|
3
|
6
|
|
|
6
|
|
115898
|
use strict; |
|
6
|
|
|
|
|
15
|
|
|
6
|
|
|
|
|
149
|
|
4
|
6
|
|
|
6
|
|
29
|
use Carp (); |
|
6
|
|
|
|
|
13
|
|
|
6
|
|
|
|
|
430
|
|
5
|
|
|
|
|
|
|
|
6
|
|
|
|
|
|
|
############################################################################ |
7
|
|
|
|
|
|
|
package Net::OpenID::Consumer; |
8
|
|
|
|
|
|
|
$Net::OpenID::Consumer::VERSION = '1.17'; |
9
|
|
|
|
|
|
|
|
10
|
|
|
|
|
|
|
use fields ( |
11
|
6
|
|
|
|
|
40
|
'cache', # Cache object to store HTTP responses, |
12
|
|
|
|
|
|
|
# associations, and nonces |
13
|
|
|
|
|
|
|
'ua', # LWP::UserAgent instance to use |
14
|
|
|
|
|
|
|
'args', # how to get at your args |
15
|
|
|
|
|
|
|
'message', # args interpreted as an IndirectMessage, if possible |
16
|
|
|
|
|
|
|
'consumer_secret', # scalar/subref |
17
|
|
|
|
|
|
|
'required_root', # the default required_root value, or undef |
18
|
|
|
|
|
|
|
'last_errcode', # last error code we got |
19
|
|
|
|
|
|
|
'last_errtext', # last error code we got |
20
|
|
|
|
|
|
|
'debug', # debug flag or codeblock |
21
|
|
|
|
|
|
|
'minimum_version', # The minimum protocol version to support |
22
|
|
|
|
|
|
|
'assoc_options', # options for establishing ID provider associations |
23
|
|
|
|
|
|
|
'nonce_options', # options for dealing with nonces |
24
|
6
|
|
|
6
|
|
6456
|
); |
|
6
|
|
|
|
|
10073
|
|
25
|
|
|
|
|
|
|
|
26
|
6
|
|
|
6
|
|
4205
|
use Net::OpenID::ClaimedIdentity; |
|
6
|
|
|
|
|
18
|
|
|
6
|
|
|
|
|
264
|
|
27
|
6
|
|
|
6
|
|
3649
|
use Net::OpenID::VerifiedIdentity; |
|
6
|
|
|
|
|
18
|
|
|
6
|
|
|
|
|
180
|
|
28
|
6
|
|
|
6
|
|
3938
|
use Net::OpenID::Association; |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
29
|
|
|
|
|
|
|
use Net::OpenID::Yadis; |
30
|
|
|
|
|
|
|
use Net::OpenID::IndirectMessage; |
31
|
|
|
|
|
|
|
use Net::OpenID::URIFetch; |
32
|
|
|
|
|
|
|
use Net::OpenID::Common; # To get the OpenID::util package |
33
|
|
|
|
|
|
|
|
34
|
|
|
|
|
|
|
use MIME::Base64 (); |
35
|
|
|
|
|
|
|
use Digest::SHA qw(hmac_sha1_hex); |
36
|
|
|
|
|
|
|
use Time::Local; |
37
|
|
|
|
|
|
|
use HTTP::Request; |
38
|
|
|
|
|
|
|
use LWP::UserAgent; |
39
|
|
|
|
|
|
|
use Storable; |
40
|
|
|
|
|
|
|
use JSON qw(encode_json); |
41
|
|
|
|
|
|
|
use URI::Escape qw(uri_escape_utf8); |
42
|
|
|
|
|
|
|
use HTML::Parser; |
43
|
|
|
|
|
|
|
|
44
|
|
|
|
|
|
|
sub new { |
45
|
|
|
|
|
|
|
my Net::OpenID::Consumer $self = shift; |
46
|
|
|
|
|
|
|
$self = fields::new( $self ) unless ref $self; |
47
|
|
|
|
|
|
|
my %opts = @_; |
48
|
|
|
|
|
|
|
|
49
|
|
|
|
|
|
|
$self->{ua} = delete $opts{ua}; |
50
|
|
|
|
|
|
|
$self->args ( delete $opts{args} ); |
51
|
|
|
|
|
|
|
$self->cache ( delete $opts{cache} ); |
52
|
|
|
|
|
|
|
$self->consumer_secret ( delete $opts{consumer_secret} ); |
53
|
|
|
|
|
|
|
$self->required_root ( delete $opts{required_root} ); |
54
|
|
|
|
|
|
|
$self->minimum_version ( delete $opts{minimum_version} ); |
55
|
|
|
|
|
|
|
$self->assoc_options ( delete $opts{assoc_options} ); |
56
|
|
|
|
|
|
|
$self->nonce_options ( delete $opts{nonce_options} ); |
57
|
|
|
|
|
|
|
|
58
|
|
|
|
|
|
|
$self->{debug} = delete $opts{debug}; |
59
|
|
|
|
|
|
|
|
60
|
|
|
|
|
|
|
Carp::croak("Unknown options: " . join(", ", keys %opts)) if %opts; |
61
|
|
|
|
|
|
|
return $self; |
62
|
|
|
|
|
|
|
} |
63
|
|
|
|
|
|
|
|
64
|
|
|
|
|
|
|
# NOTE: This method is here only to support the openid-test library. |
65
|
|
|
|
|
|
|
# Don't call it from anywhere else, or you'll break when it gets |
66
|
|
|
|
|
|
|
# removed. Instead, call minimum_version(2). |
67
|
|
|
|
|
|
|
# FIXME: Can we just make openid-test do that and get rid of this? |
68
|
|
|
|
|
|
|
sub disable_version_1 { |
69
|
|
|
|
|
|
|
$_[0]->minimum_version(2); |
70
|
|
|
|
|
|
|
} |
71
|
|
|
|
|
|
|
|
72
|
|
|
|
|
|
|
sub cache { &_getset; } |
73
|
|
|
|
|
|
|
sub consumer_secret { &_getset; } |
74
|
|
|
|
|
|
|
sub required_root { &_getset; } |
75
|
|
|
|
|
|
|
sub assoc_options { &_hashgetset } |
76
|
|
|
|
|
|
|
sub nonce_options { &_hashgetset } |
77
|
|
|
|
|
|
|
|
78
|
|
|
|
|
|
|
sub _getset { |
79
|
|
|
|
|
|
|
my Net::OpenID::Consumer $self = shift; |
80
|
|
|
|
|
|
|
my $param = (caller(1))[3]; |
81
|
|
|
|
|
|
|
$param =~ s/.+:://; |
82
|
|
|
|
|
|
|
|
83
|
|
|
|
|
|
|
if (@_) { |
84
|
|
|
|
|
|
|
my $val = shift; |
85
|
|
|
|
|
|
|
Carp::croak("Too many parameters") if @_; |
86
|
|
|
|
|
|
|
$self->{$param} = $val; |
87
|
|
|
|
|
|
|
} |
88
|
|
|
|
|
|
|
return $self->{$param}; |
89
|
|
|
|
|
|
|
} |
90
|
|
|
|
|
|
|
|
91
|
|
|
|
|
|
|
sub _hashgetset { |
92
|
|
|
|
|
|
|
my Net::OpenID::Consumer $self = shift; |
93
|
|
|
|
|
|
|
my $param = (caller(1))[3]; |
94
|
|
|
|
|
|
|
$param =~ s/.+:://; |
95
|
|
|
|
|
|
|
my $check_param = "_canonicalize_$param"; |
96
|
|
|
|
|
|
|
|
97
|
|
|
|
|
|
|
my $v; |
98
|
|
|
|
|
|
|
if (scalar(@_) == 1) { |
99
|
|
|
|
|
|
|
$v = shift; |
100
|
|
|
|
|
|
|
unless ($v) { |
101
|
|
|
|
|
|
|
$v = {}; |
102
|
|
|
|
|
|
|
} |
103
|
|
|
|
|
|
|
elsif (ref $v eq 'ARRAY') { |
104
|
|
|
|
|
|
|
$v = {@$v}; |
105
|
|
|
|
|
|
|
} |
106
|
|
|
|
|
|
|
elsif (ref $v) { |
107
|
|
|
|
|
|
|
# assume it's a hash and hope for the best |
108
|
|
|
|
|
|
|
$v = {%$v}; |
109
|
|
|
|
|
|
|
} |
110
|
|
|
|
|
|
|
else { |
111
|
|
|
|
|
|
|
Carp::croak("single argument must be HASH or ARRAY reference"); |
112
|
|
|
|
|
|
|
} |
113
|
|
|
|
|
|
|
$self->{$param} = $self->$check_param($v); |
114
|
|
|
|
|
|
|
} |
115
|
|
|
|
|
|
|
elsif (@_) { |
116
|
|
|
|
|
|
|
Carp::croak("odd number of parameters?") |
117
|
|
|
|
|
|
|
if scalar(@_)%2; |
118
|
|
|
|
|
|
|
$self->{$param} = $self->$check_param({@_}); |
119
|
|
|
|
|
|
|
} |
120
|
|
|
|
|
|
|
return $self->{$param}; |
121
|
|
|
|
|
|
|
} |
122
|
|
|
|
|
|
|
|
123
|
|
|
|
|
|
|
sub minimum_version { |
124
|
|
|
|
|
|
|
my Net::OpenID::Consumer $self = shift; |
125
|
|
|
|
|
|
|
|
126
|
|
|
|
|
|
|
if (@_) { |
127
|
|
|
|
|
|
|
my $minv = shift; |
128
|
|
|
|
|
|
|
Carp::croak("Too many parameters") if @_; |
129
|
|
|
|
|
|
|
$minv = 1 unless $minv && $minv > 1; |
130
|
|
|
|
|
|
|
$self->{minimum_version} = $minv; |
131
|
|
|
|
|
|
|
} |
132
|
|
|
|
|
|
|
return $self->{minimum_version}; |
133
|
|
|
|
|
|
|
} |
134
|
|
|
|
|
|
|
|
135
|
|
|
|
|
|
|
sub _canonicalize_assoc_options { return $_[1]; } |
136
|
|
|
|
|
|
|
|
137
|
|
|
|
|
|
|
sub _debug { |
138
|
|
|
|
|
|
|
my Net::OpenID::Consumer $self = shift; |
139
|
|
|
|
|
|
|
return unless $self->{debug}; |
140
|
|
|
|
|
|
|
|
141
|
|
|
|
|
|
|
if (ref $self->{debug} eq "CODE") { |
142
|
|
|
|
|
|
|
$self->{debug}->($_[0]); |
143
|
|
|
|
|
|
|
} else { |
144
|
|
|
|
|
|
|
print STDERR "[DEBUG Net::OpenID::Consumer] $_[0]\n"; |
145
|
|
|
|
|
|
|
} |
146
|
|
|
|
|
|
|
} |
147
|
|
|
|
|
|
|
|
148
|
|
|
|
|
|
|
# given something that can have GET arguments, returns a subref to get them: |
149
|
|
|
|
|
|
|
# Apache |
150
|
|
|
|
|
|
|
# Apache::Request |
151
|
|
|
|
|
|
|
# CGI |
152
|
|
|
|
|
|
|
# HASH of get args |
153
|
|
|
|
|
|
|
# CODE returning get arg, given key |
154
|
|
|
|
|
|
|
|
155
|
|
|
|
|
|
|
# ... |
156
|
|
|
|
|
|
|
|
157
|
|
|
|
|
|
|
sub args { |
158
|
|
|
|
|
|
|
my Net::OpenID::Consumer $self = shift; |
159
|
|
|
|
|
|
|
|
160
|
|
|
|
|
|
|
if (my $what = shift) { |
161
|
|
|
|
|
|
|
unless (ref $what) { |
162
|
|
|
|
|
|
|
return $self->{args} ? $self->{args}->($what) : Carp::croak("No args defined"); |
163
|
|
|
|
|
|
|
} |
164
|
|
|
|
|
|
|
Carp::croak("Too many parameters") if @_; |
165
|
|
|
|
|
|
|
|
166
|
|
|
|
|
|
|
# since we do not require field setters to be called in any particular order, |
167
|
|
|
|
|
|
|
# we cannot pass minimum_version here as it might change later. |
168
|
|
|
|
|
|
|
my $message = Net::OpenID::IndirectMessage->new($what); |
169
|
|
|
|
|
|
|
$self->{message} = $message; |
170
|
|
|
|
|
|
|
if ($message) { |
171
|
|
|
|
|
|
|
$self->{args} = $message->getter; |
172
|
|
|
|
|
|
|
|
173
|
|
|
|
|
|
|
# handle OpenID 2.0 'error' mode |
174
|
|
|
|
|
|
|
# (may as well do this here; we may not get another chance |
175
|
|
|
|
|
|
|
# since handle_server_response is not a required part of the API) |
176
|
|
|
|
|
|
|
if ($message->protocol_version >= 2 && $message->mode eq 'error') { |
177
|
|
|
|
|
|
|
$self->_fail('provider_error',$message->get('error')); |
178
|
|
|
|
|
|
|
} |
179
|
|
|
|
|
|
|
} |
180
|
|
|
|
|
|
|
else { |
181
|
|
|
|
|
|
|
$self->{args} = sub { undef }; |
182
|
|
|
|
|
|
|
} |
183
|
|
|
|
|
|
|
} |
184
|
|
|
|
|
|
|
$self->{args}; |
185
|
|
|
|
|
|
|
} |
186
|
|
|
|
|
|
|
|
187
|
|
|
|
|
|
|
sub message { |
188
|
|
|
|
|
|
|
my Net::OpenID::Consumer $self = shift; |
189
|
|
|
|
|
|
|
my $message = $self->{message}; |
190
|
|
|
|
|
|
|
return undef |
191
|
|
|
|
|
|
|
unless $message && |
192
|
|
|
|
|
|
|
($self->{minimum_version} <= $message->protocol_version); |
193
|
|
|
|
|
|
|
|
194
|
|
|
|
|
|
|
if (@_) { |
195
|
|
|
|
|
|
|
return $message->get($_[0]); |
196
|
|
|
|
|
|
|
} |
197
|
|
|
|
|
|
|
else { |
198
|
|
|
|
|
|
|
return $message; |
199
|
|
|
|
|
|
|
} |
200
|
|
|
|
|
|
|
} |
201
|
|
|
|
|
|
|
|
202
|
|
|
|
|
|
|
sub _message_mode_is { |
203
|
|
|
|
|
|
|
return (($_[0]->message('mode')||' ') eq $_[1]); |
204
|
|
|
|
|
|
|
} |
205
|
|
|
|
|
|
|
|
206
|
|
|
|
|
|
|
sub _message_version { |
207
|
|
|
|
|
|
|
my $message = $_[0]->message; |
208
|
|
|
|
|
|
|
return $message ? $message->protocol_version : 0; |
209
|
|
|
|
|
|
|
} |
210
|
|
|
|
|
|
|
|
211
|
|
|
|
|
|
|
sub ua { |
212
|
|
|
|
|
|
|
my Net::OpenID::Consumer $self = shift; |
213
|
|
|
|
|
|
|
$self->{ua} = shift if @_; |
214
|
|
|
|
|
|
|
Carp::croak("Too many parameters") if @_; |
215
|
|
|
|
|
|
|
|
216
|
|
|
|
|
|
|
# make default one on first access |
217
|
|
|
|
|
|
|
unless ($self->{ua}) { |
218
|
|
|
|
|
|
|
my $ua = $self->{ua} = LWP::UserAgent->new; |
219
|
|
|
|
|
|
|
$ua->timeout(10); |
220
|
|
|
|
|
|
|
} |
221
|
|
|
|
|
|
|
|
222
|
|
|
|
|
|
|
$self->{ua}; |
223
|
|
|
|
|
|
|
} |
224
|
|
|
|
|
|
|
|
225
|
|
|
|
|
|
|
our %Error_text = |
226
|
|
|
|
|
|
|
( |
227
|
|
|
|
|
|
|
'bad_mode' => "The openid.mode argument is not correct", |
228
|
|
|
|
|
|
|
'bogus_delegation' => "Asserted identity does not match claimed_id or local_id.", |
229
|
|
|
|
|
|
|
'bogus_return_to' => "Return URL does not match required_root.", |
230
|
|
|
|
|
|
|
'bogus_url' => "URL scheme must be http: or https:", |
231
|
|
|
|
|
|
|
'empty_url' => "No URL entered.", |
232
|
|
|
|
|
|
|
'expired_association' => "Association between ID provider and relying party has expired.", |
233
|
|
|
|
|
|
|
'naive_verify_failed_network' => "Could not contact ID provider to verify response.", |
234
|
|
|
|
|
|
|
'naive_verify_failed_return' => "Direct contact invalidated ID provider response.", |
235
|
|
|
|
|
|
|
'no_identity' => "Identity is missing from ID provider response.", |
236
|
|
|
|
|
|
|
'no_identity_server' => "Could not determine ID provider from URL.", |
237
|
|
|
|
|
|
|
'no_return_to' => "Return URL is missing from ID provider response.", |
238
|
|
|
|
|
|
|
'no_sig' => "Signature is missing from ID provider response.", |
239
|
|
|
|
|
|
|
'protocol_version_incorrect' => "ID provider does not support minimum protocol version", |
240
|
|
|
|
|
|
|
'provider_error' => "ID provider-specific error", |
241
|
|
|
|
|
|
|
'server_not_allowed' => "None of the discovered endpoints matches op_endpoint.", |
242
|
|
|
|
|
|
|
'signature_mismatch' => "Prior association invalidated ID provider response.", |
243
|
|
|
|
|
|
|
'time_bad_sig' => "Return_to signature is not valid.", |
244
|
|
|
|
|
|
|
'time_expired' => "Return_to signature is stale.", |
245
|
|
|
|
|
|
|
'time_in_future' => "Return_to signature is from the future.", |
246
|
|
|
|
|
|
|
'unexpected_url_redirect' => "Discovery for the given ID ended up at the wrong place", |
247
|
|
|
|
|
|
|
'unsigned_field' => sub { "Field(s) must be signed: " . join(", ", @_) }, |
248
|
|
|
|
|
|
|
'nonce_missing' => "Response_nonce is missing from ID provider response.", |
249
|
|
|
|
|
|
|
'nonce_reused' => 'Re-used response_nonce; possible replay attempt.', |
250
|
|
|
|
|
|
|
'nonce_stale' => 'Stale response_nonce; could have been used before.', |
251
|
|
|
|
|
|
|
'nonce_format' => 'Bad timestamp format in response_nonce.', |
252
|
|
|
|
|
|
|
'nonce_future' => 'Provider clock is too far forward.', |
253
|
|
|
|
|
|
|
|
254
|
|
|
|
|
|
|
# no longer used as of 1.11 |
255
|
|
|
|
|
|
|
# 'no_head_tag' => "Could not determine ID provider; URL document has no .", |
256
|
|
|
|
|
|
|
# 'url_fetch_err' => "Error fetching the provided URL.", |
257
|
|
|
|
|
|
|
|
258
|
|
|
|
|
|
|
); |
259
|
|
|
|
|
|
|
|
260
|
|
|
|
|
|
|
sub _fail { |
261
|
|
|
|
|
|
|
my Net::OpenID::Consumer $self = shift; |
262
|
|
|
|
|
|
|
my ($code, $text, @params) = @_; |
263
|
|
|
|
|
|
|
|
264
|
|
|
|
|
|
|
# 'bad_mode' is only an error if we survive to the end of |
265
|
|
|
|
|
|
|
# .mode dispatch without having figured out what to do; |
266
|
|
|
|
|
|
|
# it should not overwrite other errors. |
267
|
|
|
|
|
|
|
unless ($self->{last_errcode} && $code eq 'bad_mode') { |
268
|
|
|
|
|
|
|
$text ||= $Error_text{$code}; |
269
|
|
|
|
|
|
|
$text = $text->(@params) if ref($text) && ref($text) eq 'CODE'; |
270
|
|
|
|
|
|
|
$self->{last_errcode} = $code; |
271
|
|
|
|
|
|
|
$self->{last_errtext} = $text; |
272
|
|
|
|
|
|
|
$self->_debug("fail($code) $text"); |
273
|
|
|
|
|
|
|
} |
274
|
|
|
|
|
|
|
wantarray ? () : undef; |
275
|
|
|
|
|
|
|
} |
276
|
|
|
|
|
|
|
|
277
|
|
|
|
|
|
|
sub json_err { |
278
|
|
|
|
|
|
|
my Net::OpenID::Consumer $self = shift; |
279
|
|
|
|
|
|
|
return encode_json({ |
280
|
|
|
|
|
|
|
err_code => $self->{last_errcode}, |
281
|
|
|
|
|
|
|
err_text => $self->{last_errtext}, |
282
|
|
|
|
|
|
|
}); |
283
|
|
|
|
|
|
|
} |
284
|
|
|
|
|
|
|
|
285
|
|
|
|
|
|
|
sub err { |
286
|
|
|
|
|
|
|
my Net::OpenID::Consumer $self = shift; |
287
|
|
|
|
|
|
|
$self->{last_errcode} . ": " . $self->{last_errtext}; |
288
|
|
|
|
|
|
|
} |
289
|
|
|
|
|
|
|
|
290
|
|
|
|
|
|
|
sub errcode { |
291
|
|
|
|
|
|
|
my Net::OpenID::Consumer $self = shift; |
292
|
|
|
|
|
|
|
$self->{last_errcode}; |
293
|
|
|
|
|
|
|
} |
294
|
|
|
|
|
|
|
|
295
|
|
|
|
|
|
|
sub errtext { |
296
|
|
|
|
|
|
|
my Net::OpenID::Consumer $self = shift; |
297
|
|
|
|
|
|
|
$self->{last_errtext}; |
298
|
|
|
|
|
|
|
} |
299
|
|
|
|
|
|
|
|
300
|
|
|
|
|
|
|
# make sure you change the $prefix every time you change the $hook format |
301
|
|
|
|
|
|
|
# so that when user installs a new version and the old cache server is |
302
|
|
|
|
|
|
|
# still running the old cache entries won't confuse things. |
303
|
|
|
|
|
|
|
sub _get_url_contents { |
304
|
|
|
|
|
|
|
my Net::OpenID::Consumer $self = shift; |
305
|
|
|
|
|
|
|
my ($url, $final_url_ref, $hook, $prefix) = @_; |
306
|
|
|
|
|
|
|
$final_url_ref ||= do { my $dummy; \$dummy; }; |
307
|
|
|
|
|
|
|
|
308
|
|
|
|
|
|
|
my $res = Net::OpenID::URIFetch->fetch($url, $self, $hook, $prefix); |
309
|
|
|
|
|
|
|
|
310
|
|
|
|
|
|
|
$$final_url_ref = $res->final_uri; |
311
|
|
|
|
|
|
|
|
312
|
|
|
|
|
|
|
return $res ? $res->content : undef; |
313
|
|
|
|
|
|
|
} |
314
|
|
|
|
|
|
|
|
315
|
|
|
|
|
|
|
|
316
|
|
|
|
|
|
|
# List of head elements that matter for HTTP discovery. |
317
|
|
|
|
|
|
|
# Each entry defines a key+value that will appear in the |
318
|
|
|
|
|
|
|
# _find_semantic_info hash if the specified element exists |
319
|
|
|
|
|
|
|
# [ |
320
|
|
|
|
|
|
|
# FSI_KEY -- key name |
321
|
|
|
|
|
|
|
# TAG_NAME -- must be 'link' or 'meta' |
322
|
|
|
|
|
|
|
# |
323
|
|
|
|
|
|
|
# ELT_VALUES -- string (default = FSI_KEY) |
324
|
|
|
|
|
|
|
# what join(';',values of ELT_KEYS) has to match |
325
|
|
|
|
|
|
|
# in order for a given html element to provide |
326
|
|
|
|
|
|
|
# the value for FSI_KEY |
327
|
|
|
|
|
|
|
# |
328
|
|
|
|
|
|
|
# ELT_KEYS -- list-ref of html attribute names |
329
|
|
|
|
|
|
|
# default = ['rel'] for |
330
|
|
|
|
|
|
|
# default = ['name'] for |
331
|
|
|
|
|
|
|
# |
332
|
|
|
|
|
|
|
# FSI_VALUE -- name of html attribute where value lives |
333
|
|
|
|
|
|
|
# default = 'href' for |
334
|
|
|
|
|
|
|
# default = 'content' for |
335
|
|
|
|
|
|
|
# ] |
336
|
|
|
|
|
|
|
# |
337
|
|
|
|
|
|
|
our @HTTP_discovery_link_meta_tags = |
338
|
|
|
|
|
|
|
map { |
339
|
|
|
|
|
|
|
my ($fsi_key, $tag, $elt_value, $elt_keys, $fsi_value) = @{$_}; |
340
|
|
|
|
|
|
|
[$fsi_key, $tag, |
341
|
|
|
|
|
|
|
$elt_value || $fsi_key, |
342
|
|
|
|
|
|
|
$elt_keys || [$tag eq 'link' ? 'rel' : 'name'], |
343
|
|
|
|
|
|
|
$fsi_value || ($tag eq 'link' ? 'href' : 'content'), |
344
|
|
|
|
|
|
|
] |
345
|
|
|
|
|
|
|
} |
346
|
|
|
|
|
|
|
# OpenID providers / delegated identities |
347
|
|
|
|
|
|
|
#
|
348
|
|
|
|
|
|
|
# href="http://www.livejournal.com/misc/openid.bml" /> |
349
|
|
|
|
|
|
|
#
|
350
|
|
|
|
|
|
|
# href="whatever" /> |
351
|
|
|
|
|
|
|
# |
352
|
|
|
|
|
|
|
[qw(openid.server link)], # 'openid.server' => ['rel'], 'href' |
353
|
|
|
|
|
|
|
[qw(openid.delegate link)], |
354
|
|
|
|
|
|
|
|
355
|
|
|
|
|
|
|
# OpenID2 providers / local identifiers |
356
|
|
|
|
|
|
|
#
|
357
|
|
|
|
|
|
|
# href="http://www.livejournal.com/misc/openid.bml" /> |
358
|
|
|
|
|
|
|
# |
359
|
|
|
|
|
|
|
# |
360
|
|
|
|
|
|
|
[qw(openid2.provider link)], |
361
|
|
|
|
|
|
|
[qw(openid2.local_id link)], |
362
|
|
|
|
|
|
|
|
363
|
|
|
|
|
|
|
# FOAF maker info |
364
|
|
|
|
|
|
|
#
|
365
|
|
|
|
|
|
|
# content="foaf:mbox_sha1sum '4caa1d6f6203d21705a00a7aca86203e82a9cf7a'"/> |
366
|
|
|
|
|
|
|
# |
367
|
|
|
|
|
|
|
[qw(foaf.maker meta foaf:maker)], # == .name |
368
|
|
|
|
|
|
|
|
369
|
|
|
|
|
|
|
# FOAF documents |
370
|
|
|
|
|
|
|
#
|
371
|
|
|
|
|
|
|
# href="http://brad.livejournal.com/data/foaf" /> |
372
|
|
|
|
|
|
|
# |
373
|
|
|
|
|
|
|
[qw(foaf link), 'meta;foaf;application/rdf+xml' => [qw(rel title type)]], |
374
|
|
|
|
|
|
|
|
375
|
|
|
|
|
|
|
# RSS |
376
|
|
|
|
|
|
|
#
|
377
|
|
|
|
|
|
|
# href="http://www.livejournal.com/~brad/data/rss" /> |
378
|
|
|
|
|
|
|
# |
379
|
|
|
|
|
|
|
[qw(rss link), 'alternate;application/rss+xml' => [qw(rel type)]], |
380
|
|
|
|
|
|
|
|
381
|
|
|
|
|
|
|
# Atom |
382
|
|
|
|
|
|
|
#
|
383
|
|
|
|
|
|
|
# href="http://www.livejournal.com/~brad/data/rss" /> |
384
|
|
|
|
|
|
|
# |
385
|
|
|
|
|
|
|
[qw(atom link), 'alternate;application/atom+xml' => [qw(rel type)]], |
386
|
|
|
|
|
|
|
; |
387
|
|
|
|
|
|
|
|
388
|
|
|
|
|
|
|
sub _document_to_semantic_info { |
389
|
|
|
|
|
|
|
my $doc = shift; |
390
|
|
|
|
|
|
|
my $info = {}; |
391
|
|
|
|
|
|
|
|
392
|
|
|
|
|
|
|
my $elts = OpenID::util::html_extract_linkmetas($doc); |
393
|
|
|
|
|
|
|
for (@HTTP_discovery_link_meta_tags) { |
394
|
|
|
|
|
|
|
my ($key, $tag, $elt_value, $elt_keys, $vattrib) = @$_; |
395
|
|
|
|
|
|
|
for my $lm (@{$elts->{$tag}}) { |
396
|
|
|
|
|
|
|
$info->{$key} = $lm->{$vattrib} |
397
|
|
|
|
|
|
|
if $elt_value eq join ';', map {lc($lm->{$_}||'')} @$elt_keys; |
398
|
|
|
|
|
|
|
} |
399
|
|
|
|
|
|
|
} |
400
|
|
|
|
|
|
|
return $info; |
401
|
|
|
|
|
|
|
} |
402
|
|
|
|
|
|
|
|
403
|
|
|
|
|
|
|
sub _find_semantic_info { |
404
|
|
|
|
|
|
|
my Net::OpenID::Consumer $self = shift; |
405
|
|
|
|
|
|
|
my $url = shift; |
406
|
|
|
|
|
|
|
my $final_url_ref = shift; |
407
|
|
|
|
|
|
|
|
408
|
|
|
|
|
|
|
my $doc = $self->_get_url_contents($url, $final_url_ref); |
409
|
|
|
|
|
|
|
my $info = _document_to_semantic_info($doc); |
410
|
|
|
|
|
|
|
$self->_debug("semantic info ($url) = " . join(", ", map { $_.' => '.$info->{$_} } keys %$info)) if $self->{debug}; |
411
|
|
|
|
|
|
|
|
412
|
|
|
|
|
|
|
return $info; |
413
|
|
|
|
|
|
|
} |
414
|
|
|
|
|
|
|
|
415
|
|
|
|
|
|
|
sub _find_openid_server { |
416
|
|
|
|
|
|
|
my Net::OpenID::Consumer $self = shift; |
417
|
|
|
|
|
|
|
my $url = shift; |
418
|
|
|
|
|
|
|
my $final_url_ref = shift; |
419
|
|
|
|
|
|
|
|
420
|
|
|
|
|
|
|
my $sem_info = $self->_find_semantic_info($url, $final_url_ref) or |
421
|
|
|
|
|
|
|
return; |
422
|
|
|
|
|
|
|
|
423
|
|
|
|
|
|
|
return $self->_fail("no_identity_server") unless $sem_info->{"openid.server"}; |
424
|
|
|
|
|
|
|
$sem_info->{"openid.server"}; |
425
|
|
|
|
|
|
|
} |
426
|
|
|
|
|
|
|
|
427
|
|
|
|
|
|
|
sub is_server_response { |
428
|
|
|
|
|
|
|
my Net::OpenID::Consumer $self = shift; |
429
|
|
|
|
|
|
|
return $self->message ? 1 : 0; |
430
|
|
|
|
|
|
|
} |
431
|
|
|
|
|
|
|
|
432
|
|
|
|
|
|
|
my $_warned_about_setup_required = 0; |
433
|
|
|
|
|
|
|
sub handle_server_response { |
434
|
|
|
|
|
|
|
my Net::OpenID::Consumer $self = shift; |
435
|
|
|
|
|
|
|
my %callbacks_in = @_; |
436
|
|
|
|
|
|
|
my %callbacks = (); |
437
|
|
|
|
|
|
|
|
438
|
|
|
|
|
|
|
foreach my $cb (qw(not_openid cancelled verified error)) { |
439
|
|
|
|
|
|
|
$callbacks{$cb} = delete($callbacks_in{$cb}) || sub { Carp::croak("No ".$cb." callback") }; |
440
|
|
|
|
|
|
|
} |
441
|
|
|
|
|
|
|
|
442
|
|
|
|
|
|
|
# backwards compatibility: |
443
|
|
|
|
|
|
|
# 'setup_needed' is expected as of 1.04 |
444
|
|
|
|
|
|
|
# 'setup_required' is deprecated but allowed in its place, |
445
|
|
|
|
|
|
|
my $found_setup_callback = 0; |
446
|
|
|
|
|
|
|
foreach my $cb (qw(setup_needed setup_required)) { |
447
|
|
|
|
|
|
|
$callbacks{$cb} = delete($callbacks_in{$cb}) and $found_setup_callback++; |
448
|
|
|
|
|
|
|
} |
449
|
|
|
|
|
|
|
Carp::croak($found_setup_callback > 1 |
450
|
|
|
|
|
|
|
? "Cannot have both setup_needed and setup_required" |
451
|
|
|
|
|
|
|
: "No setup_needed callback") |
452
|
|
|
|
|
|
|
unless $found_setup_callback == 1; |
453
|
|
|
|
|
|
|
|
454
|
|
|
|
|
|
|
if (warnings::enabled('deprecated') && |
455
|
|
|
|
|
|
|
$callbacks{setup_required} && |
456
|
|
|
|
|
|
|
!$_warned_about_setup_required++ |
457
|
|
|
|
|
|
|
) { |
458
|
|
|
|
|
|
|
warnings::warn |
459
|
|
|
|
|
|
|
("deprecated", |
460
|
|
|
|
|
|
|
"'setup_required' callback is deprecated, use 'setup_needed'"); |
461
|
|
|
|
|
|
|
} |
462
|
|
|
|
|
|
|
|
463
|
|
|
|
|
|
|
Carp::croak("Unknown callbacks: ".join(',', keys %callbacks_in)) |
464
|
|
|
|
|
|
|
if %callbacks_in; |
465
|
|
|
|
|
|
|
|
466
|
|
|
|
|
|
|
unless ($self->is_server_response) { |
467
|
|
|
|
|
|
|
return $callbacks{not_openid}->(); |
468
|
|
|
|
|
|
|
} |
469
|
|
|
|
|
|
|
|
470
|
|
|
|
|
|
|
if ($self->setup_needed) { |
471
|
|
|
|
|
|
|
return $callbacks{setup_needed}->() |
472
|
|
|
|
|
|
|
unless ($callbacks{setup_required}); |
473
|
|
|
|
|
|
|
|
474
|
|
|
|
|
|
|
my $setup_url = $self->user_setup_url; |
475
|
|
|
|
|
|
|
return $callbacks{setup_required}->($setup_url) |
476
|
|
|
|
|
|
|
if $setup_url; |
477
|
|
|
|
|
|
|
# otherwise FALL THROUGH to preserve prior behavior, |
478
|
|
|
|
|
|
|
# Even though this is broken, old clients could have |
479
|
|
|
|
|
|
|
# put a workaround into the 'error' callback to handle |
480
|
|
|
|
|
|
|
# the setup_needed+(setup_url=undef) case |
481
|
|
|
|
|
|
|
} |
482
|
|
|
|
|
|
|
|
483
|
|
|
|
|
|
|
if ($self->user_cancel) { |
484
|
|
|
|
|
|
|
return $callbacks{cancelled}->(); |
485
|
|
|
|
|
|
|
} |
486
|
|
|
|
|
|
|
elsif (my $vident = $self->verified_identity) { |
487
|
|
|
|
|
|
|
return $callbacks{verified}->($vident); |
488
|
|
|
|
|
|
|
} |
489
|
|
|
|
|
|
|
else { |
490
|
|
|
|
|
|
|
return $callbacks{error}->($self->errcode, $self->errtext); |
491
|
|
|
|
|
|
|
} |
492
|
|
|
|
|
|
|
|
493
|
|
|
|
|
|
|
} |
494
|
|
|
|
|
|
|
|
495
|
|
|
|
|
|
|
sub _canonicalize_id_url { |
496
|
|
|
|
|
|
|
my Net::OpenID::Consumer $self = shift; |
497
|
|
|
|
|
|
|
my $url = shift; |
498
|
|
|
|
|
|
|
|
499
|
|
|
|
|
|
|
# trim whitespace |
500
|
|
|
|
|
|
|
$url =~ s/^\s+//; |
501
|
|
|
|
|
|
|
$url =~ s/\s+$//; |
502
|
|
|
|
|
|
|
return $self->_fail("empty_url") unless $url; |
503
|
|
|
|
|
|
|
|
504
|
|
|
|
|
|
|
# add scheme |
505
|
|
|
|
|
|
|
$url = "http://$url" if $url && $url !~ m!^\w+://!; |
506
|
|
|
|
|
|
|
return $self->_fail("bogus_url") unless $url =~ m!^https?://!i; |
507
|
|
|
|
|
|
|
|
508
|
|
|
|
|
|
|
# make sure there is a slash after the hostname |
509
|
|
|
|
|
|
|
$url .= "/" unless $url =~ m!^https?://.+/!i; |
510
|
|
|
|
|
|
|
return $url; |
511
|
|
|
|
|
|
|
} |
512
|
|
|
|
|
|
|
|
513
|
|
|
|
|
|
|
# always returns a listref; might be empty, though |
514
|
|
|
|
|
|
|
sub _discover_acceptable_endpoints { |
515
|
|
|
|
|
|
|
my Net::OpenID::Consumer $self = shift; |
516
|
|
|
|
|
|
|
my $url = shift; #already canonicalized ID url |
517
|
|
|
|
|
|
|
my %opts = @_; |
518
|
|
|
|
|
|
|
|
519
|
|
|
|
|
|
|
# if return_early is set, we'll return as soon as we have enough |
520
|
|
|
|
|
|
|
# information to determine the "primary" endpoint, and return |
521
|
|
|
|
|
|
|
# that as the first (and possibly only) item in our response. |
522
|
|
|
|
|
|
|
my $primary_only = delete $opts{primary_only} ? 1 : 0; |
523
|
|
|
|
|
|
|
|
524
|
|
|
|
|
|
|
# if force_version is set, we only return endpoints that have |
525
|
|
|
|
|
|
|
# that have {version} == $force_version |
526
|
|
|
|
|
|
|
my $force_version = delete $opts{force_version}; |
527
|
|
|
|
|
|
|
|
528
|
|
|
|
|
|
|
Carp::croak("Unknown option(s) ".join(', ', keys(%opts))) if %opts; |
529
|
|
|
|
|
|
|
|
530
|
|
|
|
|
|
|
my @discovered_endpoints = (); |
531
|
|
|
|
|
|
|
my $result = sub { |
532
|
|
|
|
|
|
|
# We always prefer 2.0 endpoints to 1.1 ones, regardless of |
533
|
|
|
|
|
|
|
# the priority chosen by the identifier. |
534
|
|
|
|
|
|
|
return [ |
535
|
|
|
|
|
|
|
(grep { $_->{version} == 2 } @discovered_endpoints), |
536
|
|
|
|
|
|
|
(grep { $_->{version} == 1 } @discovered_endpoints), |
537
|
|
|
|
|
|
|
]; |
538
|
|
|
|
|
|
|
}; |
539
|
|
|
|
|
|
|
|
540
|
|
|
|
|
|
|
# TODO: Support XRI too? |
541
|
|
|
|
|
|
|
|
542
|
|
|
|
|
|
|
# First we Yadis service discovery |
543
|
|
|
|
|
|
|
my $yadis = Net::OpenID::Yadis->new(consumer => $self); |
544
|
|
|
|
|
|
|
if ($yadis->discover($url)) { |
545
|
|
|
|
|
|
|
# FIXME: Currently we don't ever do _find_semantic_info in the Yadis |
546
|
|
|
|
|
|
|
# code path, so an extra redundant HTTP request is done later |
547
|
|
|
|
|
|
|
# when the semantic info is accessed. |
548
|
|
|
|
|
|
|
|
549
|
|
|
|
|
|
|
my $final_url = $yadis->identity_url; |
550
|
|
|
|
|
|
|
my @services = $yadis->services( |
551
|
|
|
|
|
|
|
OpenID::util::version_2_xrds_service_url(), |
552
|
|
|
|
|
|
|
OpenID::util::version_2_xrds_directed_service_url(), |
553
|
|
|
|
|
|
|
OpenID::util::version_1_xrds_service_url(), |
554
|
|
|
|
|
|
|
); |
555
|
|
|
|
|
|
|
my $version2 = OpenID::util::version_2_xrds_service_url(); |
556
|
|
|
|
|
|
|
my $version1 = OpenID::util::version_1_xrds_service_url(); |
557
|
|
|
|
|
|
|
my $version2_directed = OpenID::util::version_2_xrds_directed_service_url(); |
558
|
|
|
|
|
|
|
|
559
|
|
|
|
|
|
|
foreach my $service (@services) { |
560
|
|
|
|
|
|
|
my $service_uris = $service->URI; |
561
|
|
|
|
|
|
|
|
562
|
|
|
|
|
|
|
# Service->URI seems to return all sorts of bizarre things, so let's |
563
|
|
|
|
|
|
|
# normalize it to always be an arrayref. |
564
|
|
|
|
|
|
|
if (ref($service_uris) eq 'ARRAY') { |
565
|
|
|
|
|
|
|
my @sorted_id_servers = sort { |
566
|
|
|
|
|
|
|
my $pa = $a->{priority}; |
567
|
|
|
|
|
|
|
my $pb = $b->{priority}; |
568
|
|
|
|
|
|
|
defined($pb) <=> defined($pa) |
569
|
|
|
|
|
|
|
|| (defined($pa) ? ($pa <=> $pb) : 0) |
570
|
|
|
|
|
|
|
} @$service_uris; |
571
|
|
|
|
|
|
|
$service_uris = \@sorted_id_servers; |
572
|
|
|
|
|
|
|
} |
573
|
|
|
|
|
|
|
if (ref($service_uris) eq 'HASH') { |
574
|
|
|
|
|
|
|
$service_uris = [ $service_uris->{content} ]; |
575
|
|
|
|
|
|
|
} |
576
|
|
|
|
|
|
|
unless (ref($service_uris)) { |
577
|
|
|
|
|
|
|
$service_uris = [ $service_uris ]; |
578
|
|
|
|
|
|
|
} |
579
|
|
|
|
|
|
|
|
580
|
|
|
|
|
|
|
my $delegate = undef; |
581
|
|
|
|
|
|
|
my @versions = (); |
582
|
|
|
|
|
|
|
|
583
|
|
|
|
|
|
|
if (grep(/^${version2}$/, $service->Type)) { |
584
|
|
|
|
|
|
|
# We have an OpenID 2.0 end-user identifier |
585
|
|
|
|
|
|
|
$delegate = $service->extra_field("LocalID"); |
586
|
|
|
|
|
|
|
push @versions, 2; |
587
|
|
|
|
|
|
|
} |
588
|
|
|
|
|
|
|
if (grep(/^${version1}$/, $service->Type)) { |
589
|
|
|
|
|
|
|
# We have an OpenID 1.1 end-user identifier |
590
|
|
|
|
|
|
|
$delegate = $service->extra_field("Delegate", "http://openid.net/xmlns/1.0"); |
591
|
|
|
|
|
|
|
push @versions, 1; |
592
|
|
|
|
|
|
|
} |
593
|
|
|
|
|
|
|
|
594
|
|
|
|
|
|
|
if (@versions) { |
595
|
|
|
|
|
|
|
foreach my $version (@versions) { |
596
|
|
|
|
|
|
|
next if defined($force_version) && $force_version != $version; |
597
|
|
|
|
|
|
|
foreach my $uri (@$service_uris) { |
598
|
|
|
|
|
|
|
push @discovered_endpoints, { |
599
|
|
|
|
|
|
|
uri => $uri, |
600
|
|
|
|
|
|
|
version => $version, |
601
|
|
|
|
|
|
|
final_url => $final_url, |
602
|
|
|
|
|
|
|
delegate => $delegate, |
603
|
|
|
|
|
|
|
sem_info => undef, |
604
|
|
|
|
|
|
|
mechanism => "Yadis", |
605
|
|
|
|
|
|
|
}; |
606
|
|
|
|
|
|
|
} |
607
|
|
|
|
|
|
|
} |
608
|
|
|
|
|
|
|
} |
609
|
|
|
|
|
|
|
|
610
|
|
|
|
|
|
|
if (((!defined($force_version)) || $force_version == 2) |
611
|
|
|
|
|
|
|
&& grep(/^${version2_directed}$/, $service->Type)) { |
612
|
|
|
|
|
|
|
|
613
|
|
|
|
|
|
|
# We have an OpenID 2.0 OP identifier (i.e. we're doing directed identity) |
614
|
|
|
|
|
|
|
my $version = 2; |
615
|
|
|
|
|
|
|
# In this case, the user's claimed identifier is a magic value |
616
|
|
|
|
|
|
|
# and the actual identifier will be determined by the provider. |
617
|
|
|
|
|
|
|
my $final_url = OpenID::util::version_2_identifier_select_url(); |
618
|
|
|
|
|
|
|
my $delegate = OpenID::util::version_2_identifier_select_url(); |
619
|
|
|
|
|
|
|
|
620
|
|
|
|
|
|
|
foreach my $uri (@$service_uris) { |
621
|
|
|
|
|
|
|
push @discovered_endpoints, { |
622
|
|
|
|
|
|
|
uri => $uri, |
623
|
|
|
|
|
|
|
version => $version, |
624
|
|
|
|
|
|
|
final_url => $final_url, |
625
|
|
|
|
|
|
|
delegate => $delegate, |
626
|
|
|
|
|
|
|
sem_info => undef, |
627
|
|
|
|
|
|
|
mechanism => "Yadis", |
628
|
|
|
|
|
|
|
}; |
629
|
|
|
|
|
|
|
} |
630
|
|
|
|
|
|
|
} |
631
|
|
|
|
|
|
|
|
632
|
|
|
|
|
|
|
if ($primary_only && scalar(@discovered_endpoints)) { |
633
|
|
|
|
|
|
|
# We've got at least one endpoint now, so return early |
634
|
|
|
|
|
|
|
return $result->(); |
635
|
|
|
|
|
|
|
} |
636
|
|
|
|
|
|
|
} |
637
|
|
|
|
|
|
|
} |
638
|
|
|
|
|
|
|
|
639
|
|
|
|
|
|
|
# Now HTML-based discovery, both 2.0- and 1.1-style. |
640
|
|
|
|
|
|
|
{ |
641
|
|
|
|
|
|
|
my $final_url = undef; |
642
|
|
|
|
|
|
|
my $sem_info = $self->_find_semantic_info($url, \$final_url); |
643
|
|
|
|
|
|
|
|
644
|
|
|
|
|
|
|
if ($sem_info) { |
645
|
|
|
|
|
|
|
if ($sem_info->{"openid2.provider"}) { |
646
|
|
|
|
|
|
|
unless (defined($force_version) && $force_version != 2) { |
647
|
|
|
|
|
|
|
push @discovered_endpoints, { |
648
|
|
|
|
|
|
|
uri => $sem_info->{"openid2.provider"}, |
649
|
|
|
|
|
|
|
version => 2, |
650
|
|
|
|
|
|
|
final_url => $final_url, |
651
|
|
|
|
|
|
|
delegate => $sem_info->{"openid2.local_id"}, |
652
|
|
|
|
|
|
|
sem_info => $sem_info, |
653
|
|
|
|
|
|
|
mechanism => "HTML", |
654
|
|
|
|
|
|
|
}; |
655
|
|
|
|
|
|
|
} |
656
|
|
|
|
|
|
|
} |
657
|
|
|
|
|
|
|
if ($sem_info->{"openid.server"}) { |
658
|
|
|
|
|
|
|
unless (defined($force_version) && $force_version != 1) { |
659
|
|
|
|
|
|
|
push @discovered_endpoints, { |
660
|
|
|
|
|
|
|
uri => $sem_info->{"openid.server"}, |
661
|
|
|
|
|
|
|
version => 1, |
662
|
|
|
|
|
|
|
final_url => $final_url, |
663
|
|
|
|
|
|
|
delegate => $sem_info->{"openid.delegate"}, |
664
|
|
|
|
|
|
|
sem_info => $sem_info, |
665
|
|
|
|
|
|
|
mechanism => "HTML", |
666
|
|
|
|
|
|
|
}; |
667
|
|
|
|
|
|
|
} |
668
|
|
|
|
|
|
|
} |
669
|
|
|
|
|
|
|
} |
670
|
|
|
|
|
|
|
} |
671
|
|
|
|
|
|
|
|
672
|
|
|
|
|
|
|
return $result->(); |
673
|
|
|
|
|
|
|
|
674
|
|
|
|
|
|
|
} |
675
|
|
|
|
|
|
|
|
676
|
|
|
|
|
|
|
# returns Net::OpenID::ClaimedIdentity |
677
|
|
|
|
|
|
|
sub claimed_identity { |
678
|
|
|
|
|
|
|
my Net::OpenID::Consumer $self = shift; |
679
|
|
|
|
|
|
|
my $url = shift; |
680
|
|
|
|
|
|
|
Carp::croak("Too many parameters") if @_; |
681
|
|
|
|
|
|
|
|
682
|
|
|
|
|
|
|
return unless $url = $self->_canonicalize_id_url($url); |
683
|
|
|
|
|
|
|
|
684
|
|
|
|
|
|
|
my $endpoints = $self->_discover_acceptable_endpoints($url, primary_only => 1); |
685
|
|
|
|
|
|
|
|
686
|
|
|
|
|
|
|
if (@$endpoints) { |
687
|
|
|
|
|
|
|
foreach my $endpoint (@$endpoints) { |
688
|
|
|
|
|
|
|
|
689
|
|
|
|
|
|
|
next unless $endpoint->{version} >= $self->minimum_version; |
690
|
|
|
|
|
|
|
|
691
|
|
|
|
|
|
|
$self->_debug("Discovered version $endpoint->{version} endpoint at $endpoint->{uri} via $endpoint->{mechanism}"); |
692
|
|
|
|
|
|
|
$self->_debug("Delegate is $endpoint->{delegate}") if $endpoint->{delegate}; |
693
|
|
|
|
|
|
|
|
694
|
|
|
|
|
|
|
return Net::OpenID::ClaimedIdentity->new( |
695
|
|
|
|
|
|
|
identity => $endpoint->{final_url}, |
696
|
|
|
|
|
|
|
server => $endpoint->{uri}, |
697
|
|
|
|
|
|
|
consumer => $self, |
698
|
|
|
|
|
|
|
delegate => $endpoint->{delegate}, |
699
|
|
|
|
|
|
|
protocol_version => $endpoint->{version}, |
700
|
|
|
|
|
|
|
semantic_info => $endpoint->{sem_info}, |
701
|
|
|
|
|
|
|
); |
702
|
|
|
|
|
|
|
|
703
|
|
|
|
|
|
|
} |
704
|
|
|
|
|
|
|
|
705
|
|
|
|
|
|
|
# If we've fallen out here, then none of the available services are of the required version. |
706
|
|
|
|
|
|
|
return $self->_fail("protocol_version_incorrect"); |
707
|
|
|
|
|
|
|
|
708
|
|
|
|
|
|
|
} |
709
|
|
|
|
|
|
|
else { |
710
|
|
|
|
|
|
|
return $self->_fail("no_identity_server"); |
711
|
|
|
|
|
|
|
} |
712
|
|
|
|
|
|
|
|
713
|
|
|
|
|
|
|
} |
714
|
|
|
|
|
|
|
|
715
|
|
|
|
|
|
|
sub user_cancel { |
716
|
|
|
|
|
|
|
my Net::OpenID::Consumer $self = shift; |
717
|
|
|
|
|
|
|
return $self->_message_mode_is("cancel"); |
718
|
|
|
|
|
|
|
} |
719
|
|
|
|
|
|
|
|
720
|
|
|
|
|
|
|
sub setup_needed { |
721
|
|
|
|
|
|
|
my Net::OpenID::Consumer $self = shift; |
722
|
|
|
|
|
|
|
if ($self->_message_version == 1) { |
723
|
|
|
|
|
|
|
return $self->_message_mode_is("id_res") && $self->message("user_setup_url"); |
724
|
|
|
|
|
|
|
} |
725
|
|
|
|
|
|
|
else { |
726
|
|
|
|
|
|
|
return $self->_message_mode_is('setup_needed'); |
727
|
|
|
|
|
|
|
} |
728
|
|
|
|
|
|
|
} |
729
|
|
|
|
|
|
|
|
730
|
|
|
|
|
|
|
sub user_setup_url { |
731
|
|
|
|
|
|
|
my Net::OpenID::Consumer $self = shift; |
732
|
|
|
|
|
|
|
my %opts = @_; |
733
|
|
|
|
|
|
|
my $post_grant = delete $opts{'post_grant'}; |
734
|
|
|
|
|
|
|
Carp::croak("Unknown options: " . join(", ", keys %opts)) if %opts; |
735
|
|
|
|
|
|
|
|
736
|
|
|
|
|
|
|
if ($self->_message_version == 1) { |
737
|
|
|
|
|
|
|
return $self->_fail("bad_mode") unless $self->_message_mode_is("id_res"); |
738
|
|
|
|
|
|
|
} |
739
|
|
|
|
|
|
|
else { |
740
|
|
|
|
|
|
|
return undef unless $self->_message_mode_is('setup_needed'); |
741
|
|
|
|
|
|
|
} |
742
|
|
|
|
|
|
|
my $setup_url = $self->message("user_setup_url"); |
743
|
|
|
|
|
|
|
|
744
|
|
|
|
|
|
|
OpenID::util::push_url_arg(\$setup_url, "openid.post_grant", $post_grant) |
745
|
|
|
|
|
|
|
if $setup_url && $post_grant; |
746
|
|
|
|
|
|
|
|
747
|
|
|
|
|
|
|
return $setup_url; |
748
|
|
|
|
|
|
|
} |
749
|
|
|
|
|
|
|
|
750
|
|
|
|
|
|
|
sub verified_identity { |
751
|
|
|
|
|
|
|
my Net::OpenID::Consumer $self = shift; |
752
|
|
|
|
|
|
|
my %opts = @_; |
753
|
|
|
|
|
|
|
|
754
|
|
|
|
|
|
|
my $rr = delete $opts{'required_root'} || $self->{required_root}; |
755
|
|
|
|
|
|
|
Carp::croak("Unknown options: " . join(", ", keys %opts)) if %opts; |
756
|
|
|
|
|
|
|
|
757
|
|
|
|
|
|
|
return $self->_fail("bad_mode") unless $self->_message_mode_is("id_res"); |
758
|
|
|
|
|
|
|
|
759
|
|
|
|
|
|
|
# the asserted identity (the delegated one, if there is one, since the protocol |
760
|
|
|
|
|
|
|
# knows nothing of the original URL) |
761
|
|
|
|
|
|
|
my $a_ident = $self->message("identity") or return $self->_fail("no_identity"); |
762
|
|
|
|
|
|
|
|
763
|
|
|
|
|
|
|
my $sig64 = $self->message("sig") or return $self->_fail("no_sig"); |
764
|
|
|
|
|
|
|
|
765
|
|
|
|
|
|
|
# fix sig if the OpenID provider failed to properly escape pluses (+) in the sig |
766
|
|
|
|
|
|
|
$sig64 =~ s/ /+/g; |
767
|
|
|
|
|
|
|
|
768
|
|
|
|
|
|
|
my $returnto = $self->message("return_to") or return $self->_fail("no_return_to"); |
769
|
|
|
|
|
|
|
my $signed = $self->message("signed"); |
770
|
|
|
|
|
|
|
|
771
|
|
|
|
|
|
|
my $possible_endpoints; |
772
|
|
|
|
|
|
|
my $server; |
773
|
|
|
|
|
|
|
my $claimed_identity; |
774
|
|
|
|
|
|
|
|
775
|
|
|
|
|
|
|
my $real_ident = |
776
|
|
|
|
|
|
|
($self->_message_version == 1 |
777
|
|
|
|
|
|
|
? $self->args("oic.identity") |
778
|
|
|
|
|
|
|
: $self->message("claimed_id") |
779
|
|
|
|
|
|
|
) || $a_ident; |
780
|
|
|
|
|
|
|
my $real_canon = $self->_canonicalize_id_url($real_ident); |
781
|
|
|
|
|
|
|
|
782
|
|
|
|
|
|
|
return $self->_fail("no_identity_server") |
783
|
|
|
|
|
|
|
unless ($real_canon |
784
|
|
|
|
|
|
|
&& @{ |
785
|
|
|
|
|
|
|
$possible_endpoints = |
786
|
|
|
|
|
|
|
$self->_discover_acceptable_endpoints |
787
|
|
|
|
|
|
|
($real_canon, force_version => $self->_message_version) |
788
|
|
|
|
|
|
|
}); |
789
|
|
|
|
|
|
|
# FIXME: It kinda sucks that the above will always do both Yadis and HTML discovery, even though |
790
|
|
|
|
|
|
|
# in most cases only one will be in use. |
791
|
|
|
|
|
|
|
|
792
|
|
|
|
|
|
|
if ($self->_message_version == 1) { |
793
|
|
|
|
|
|
|
# In version 1, we have to assume that the primary server |
794
|
|
|
|
|
|
|
# found during discovery is the one sending us this message. |
795
|
|
|
|
|
|
|
splice(@$possible_endpoints,1); |
796
|
|
|
|
|
|
|
$server = $possible_endpoints->[0]->{uri}; |
797
|
|
|
|
|
|
|
$self->_debug("Server is $server"); |
798
|
|
|
|
|
|
|
} |
799
|
|
|
|
|
|
|
else { |
800
|
|
|
|
|
|
|
# In version 2, the OpenID provider tells us its URL. |
801
|
|
|
|
|
|
|
$server = $self->message("op_endpoint"); |
802
|
|
|
|
|
|
|
$self->_debug("Server is $server"); |
803
|
|
|
|
|
|
|
# but make sure that URL matches one of the discovered ones. |
804
|
|
|
|
|
|
|
@$possible_endpoints = |
805
|
|
|
|
|
|
|
grep {$_->{uri} eq $server} @$possible_endpoints |
806
|
|
|
|
|
|
|
or return $self->_fail("server_not_allowed"); |
807
|
|
|
|
|
|
|
} |
808
|
|
|
|
|
|
|
|
809
|
|
|
|
|
|
|
# check that returnto is for the right host |
810
|
|
|
|
|
|
|
return $self->_fail("bogus_return_to") if $rr && $returnto !~ /^\Q$rr\E/; |
811
|
|
|
|
|
|
|
|
812
|
|
|
|
|
|
|
my $now = time(); |
813
|
|
|
|
|
|
|
|
814
|
|
|
|
|
|
|
# check that we have not seen response_nonce before |
815
|
|
|
|
|
|
|
my $response_nonce = $self->message("response_nonce"); |
816
|
|
|
|
|
|
|
unless ($response_nonce) { |
817
|
|
|
|
|
|
|
# 1.0/1.1 does not require nonces |
818
|
|
|
|
|
|
|
return $self->_fail("nonce_missing") |
819
|
|
|
|
|
|
|
if $self->_message_version >= 2; |
820
|
|
|
|
|
|
|
} |
821
|
|
|
|
|
|
|
else { |
822
|
|
|
|
|
|
|
return unless $self->_nonce_check_succeeds($now, $server, $response_nonce); |
823
|
|
|
|
|
|
|
} |
824
|
|
|
|
|
|
|
|
825
|
|
|
|
|
|
|
# check age/signature of return_to |
826
|
|
|
|
|
|
|
{ |
827
|
|
|
|
|
|
|
my ($sig_time, $sig) = split(/\-/, $self->args("oic.time") || ""); |
828
|
|
|
|
|
|
|
# complain if more than an hour since we sent them off |
829
|
|
|
|
|
|
|
return $self->_fail("time_expired") if $sig_time < $now - 3600; |
830
|
|
|
|
|
|
|
# also complain if the signature is from the future by more than 30 seconds, |
831
|
|
|
|
|
|
|
# which compensates for potential clock drift between nodes in a web farm. |
832
|
|
|
|
|
|
|
return $self->_fail("time_in_future") if $sig_time - 30 > $now; |
833
|
|
|
|
|
|
|
# and check that the time isn't faked |
834
|
|
|
|
|
|
|
my $c_secret = $self->_get_consumer_secret($sig_time); |
835
|
|
|
|
|
|
|
my $good_sig = substr(hmac_sha1_hex($sig_time, $c_secret), 0, 20); |
836
|
|
|
|
|
|
|
return $self->_fail("time_bad_sig") unless OpenID::util::timing_indep_eq($sig, $good_sig); |
837
|
|
|
|
|
|
|
} |
838
|
|
|
|
|
|
|
|
839
|
|
|
|
|
|
|
my $last_error = undef; |
840
|
|
|
|
|
|
|
my $error = sub { |
841
|
|
|
|
|
|
|
$self->_debug("$server not acceptable: ".$_[0]); |
842
|
|
|
|
|
|
|
$last_error = $_[0]; |
843
|
|
|
|
|
|
|
}; |
844
|
|
|
|
|
|
|
|
845
|
|
|
|
|
|
|
foreach my $endpoint (@$possible_endpoints) { |
846
|
|
|
|
|
|
|
# Known: |
847
|
|
|
|
|
|
|
# $endpoint->{version} == $self->_message_version |
848
|
|
|
|
|
|
|
# $endpoint->{uri} == $server |
849
|
|
|
|
|
|
|
|
850
|
|
|
|
|
|
|
my $final_url = $endpoint->{final_url}; |
851
|
|
|
|
|
|
|
my $delegate = $endpoint->{delegate}; |
852
|
|
|
|
|
|
|
|
853
|
|
|
|
|
|
|
# OpenID 2.0 wants us to exclude the fragment part of the URL when doing equality checks |
854
|
|
|
|
|
|
|
my $a_ident_nofragment = $a_ident; |
855
|
|
|
|
|
|
|
my $real_ident_nofragment = $real_ident; |
856
|
|
|
|
|
|
|
my $final_url_nofragment = $final_url; |
857
|
|
|
|
|
|
|
if ($self->_message_version >= 2) { |
858
|
|
|
|
|
|
|
$a_ident_nofragment =~ s/\#.*$//x; |
859
|
|
|
|
|
|
|
$real_ident_nofragment =~ s/\#.*$//x; |
860
|
|
|
|
|
|
|
$final_url_nofragment =~ s/\#.*$//x; |
861
|
|
|
|
|
|
|
} |
862
|
|
|
|
|
|
|
unless ($final_url_nofragment eq $real_ident_nofragment) { |
863
|
|
|
|
|
|
|
$error->("unexpected_url_redirect"); |
864
|
|
|
|
|
|
|
next; |
865
|
|
|
|
|
|
|
} |
866
|
|
|
|
|
|
|
|
867
|
|
|
|
|
|
|
# if openid.delegate was used, check that it was done correctly |
868
|
|
|
|
|
|
|
if ($a_ident_nofragment ne $real_ident_nofragment) { |
869
|
|
|
|
|
|
|
unless ($delegate eq $a_ident_nofragment) { |
870
|
|
|
|
|
|
|
$error->("bogus_delegation"); |
871
|
|
|
|
|
|
|
next; |
872
|
|
|
|
|
|
|
} |
873
|
|
|
|
|
|
|
} |
874
|
|
|
|
|
|
|
|
875
|
|
|
|
|
|
|
# If we've got this far then we've found the right endpoint. |
876
|
|
|
|
|
|
|
|
877
|
|
|
|
|
|
|
$claimed_identity = Net::OpenID::ClaimedIdentity->new( |
878
|
|
|
|
|
|
|
identity => $endpoint->{final_url}, |
879
|
|
|
|
|
|
|
server => $endpoint->{uri}, |
880
|
|
|
|
|
|
|
consumer => $self, |
881
|
|
|
|
|
|
|
delegate => $endpoint->{delegate}, |
882
|
|
|
|
|
|
|
protocol_version => $endpoint->{version}, |
883
|
|
|
|
|
|
|
semantic_info => $endpoint->{sem_info}, |
884
|
|
|
|
|
|
|
); |
885
|
|
|
|
|
|
|
last; |
886
|
|
|
|
|
|
|
|
887
|
|
|
|
|
|
|
} |
888
|
|
|
|
|
|
|
|
889
|
|
|
|
|
|
|
unless ($claimed_identity) { |
890
|
|
|
|
|
|
|
# We failed to find a good endpoint in the above loop, so |
891
|
|
|
|
|
|
|
# lets bail out. |
892
|
|
|
|
|
|
|
return $self->_fail($last_error); |
893
|
|
|
|
|
|
|
} |
894
|
|
|
|
|
|
|
|
895
|
|
|
|
|
|
|
my $assoc_handle = $self->message("assoc_handle"); |
896
|
|
|
|
|
|
|
|
897
|
|
|
|
|
|
|
$self->_debug("verified_identity: assoc_handle" . |
898
|
|
|
|
|
|
|
($assoc_handle ? ": $assoc_handle" : " missing")); |
899
|
|
|
|
|
|
|
my $assoc = Net::OpenID::Association::handle_assoc($self, $server, $assoc_handle); |
900
|
|
|
|
|
|
|
|
901
|
|
|
|
|
|
|
my @signed_fields = grep {m/^[\w\.]+$/} split(/,/, $signed); |
902
|
|
|
|
|
|
|
my %signed_value = map {$_,$self->args("openid.$_")} @signed_fields; |
903
|
|
|
|
|
|
|
|
904
|
|
|
|
|
|
|
# Auth 2.0 requires certain keys to be signed. |
905
|
|
|
|
|
|
|
if ($self->_message_version >= 2) { |
906
|
|
|
|
|
|
|
my %unsigned; |
907
|
|
|
|
|
|
|
# these fields must be signed unconditionally |
908
|
|
|
|
|
|
|
foreach my $f (qw/op_endpoint return_to response_nonce assoc_handle/) { |
909
|
|
|
|
|
|
|
$unsigned{$f}++ unless exists $signed_value{$f}; |
910
|
|
|
|
|
|
|
} |
911
|
|
|
|
|
|
|
# these fields must be signed if present |
912
|
|
|
|
|
|
|
foreach my $f (qw/claimed_id identity/) { |
913
|
|
|
|
|
|
|
$unsigned{$f}++ |
914
|
|
|
|
|
|
|
if $self->args("openid.$f") && !exists $signed_value{$f}; |
915
|
|
|
|
|
|
|
} |
916
|
|
|
|
|
|
|
if (%unsigned) { |
917
|
|
|
|
|
|
|
return $self->_fail("unsigned_field", undef, keys %unsigned); |
918
|
|
|
|
|
|
|
} |
919
|
|
|
|
|
|
|
} |
920
|
|
|
|
|
|
|
|
921
|
|
|
|
|
|
|
if ($assoc) { |
922
|
|
|
|
|
|
|
$self->_debug("verified_identity: verifying with found association"); |
923
|
|
|
|
|
|
|
|
924
|
|
|
|
|
|
|
return $self->_fail("expired_association") |
925
|
|
|
|
|
|
|
if $assoc->expired; |
926
|
|
|
|
|
|
|
|
927
|
|
|
|
|
|
|
# verify the token |
928
|
|
|
|
|
|
|
my $token = join '',map {"$_:$signed_value{$_}\n"} @signed_fields; |
929
|
|
|
|
|
|
|
|
930
|
|
|
|
|
|
|
utf8::encode($token); |
931
|
|
|
|
|
|
|
my $good_sig = $assoc->generate_signature($token); |
932
|
|
|
|
|
|
|
return $self->_fail("signature_mismatch") unless OpenID::util::timing_indep_eq($sig64, $good_sig); |
933
|
|
|
|
|
|
|
|
934
|
|
|
|
|
|
|
} else { |
935
|
|
|
|
|
|
|
$self->_debug("verified_identity: verifying using HTTP (dumb mode)"); |
936
|
|
|
|
|
|
|
# didn't find an association. have to do dumb consumer mode |
937
|
|
|
|
|
|
|
# and check it with a POST |
938
|
|
|
|
|
|
|
my %post; |
939
|
|
|
|
|
|
|
my @mkeys; |
940
|
|
|
|
|
|
|
if ($self->_message_version >= 2 |
941
|
|
|
|
|
|
|
&& (@mkeys = $self->message->all_parameters)) { |
942
|
|
|
|
|
|
|
# OpenID 2.0: copy *EVERYTHING*, not just signed parameters. |
943
|
|
|
|
|
|
|
# (XXX: Do we need to copy non "openid." parameters as well? |
944
|
|
|
|
|
|
|
# For now, assume if provider is sending them, there is a reason) |
945
|
|
|
|
|
|
|
%post = map {$_ eq 'openid.mode' ? () : ($_, $self->args($_)) } @mkeys; |
946
|
|
|
|
|
|
|
} |
947
|
|
|
|
|
|
|
else { |
948
|
|
|
|
|
|
|
# OpenID 1.1 *OR* legacy client did not provide a proper |
949
|
|
|
|
|
|
|
# enumerator; in the latter case under 2.0 we have no |
950
|
|
|
|
|
|
|
# choice but to send a partial (1.1-style) |
951
|
|
|
|
|
|
|
# check_authentication request and hope for the best. |
952
|
|
|
|
|
|
|
|
953
|
|
|
|
|
|
|
%post = ( |
954
|
|
|
|
|
|
|
"openid.assoc_handle" => $assoc_handle, |
955
|
|
|
|
|
|
|
"openid.signed" => $signed, |
956
|
|
|
|
|
|
|
"openid.sig" => $sig64, |
957
|
|
|
|
|
|
|
); |
958
|
|
|
|
|
|
|
|
959
|
|
|
|
|
|
|
if ($self->_message_version >= 2) { |
960
|
|
|
|
|
|
|
$post{'openid.ns'} = OpenID::util::VERSION_2_NAMESPACE(); |
961
|
|
|
|
|
|
|
} |
962
|
|
|
|
|
|
|
|
963
|
|
|
|
|
|
|
# and copy in all signed parameters that we don't already have into %post |
964
|
|
|
|
|
|
|
$post{"openid.$_"} = $signed_value{$_} |
965
|
|
|
|
|
|
|
foreach grep {!exists $post{"openid.$_"}} @signed_fields; |
966
|
|
|
|
|
|
|
|
967
|
|
|
|
|
|
|
# if the provider told us our handle as bogus, let's ask in our |
968
|
|
|
|
|
|
|
# check_authentication mode whether that's true |
969
|
|
|
|
|
|
|
if (my $ih = $self->message("invalidate_handle")) { |
970
|
|
|
|
|
|
|
$post{"openid.invalidate_handle"} = $ih; |
971
|
|
|
|
|
|
|
} |
972
|
|
|
|
|
|
|
} |
973
|
|
|
|
|
|
|
$post{"openid.mode"} = "check_authentication"; |
974
|
|
|
|
|
|
|
|
975
|
|
|
|
|
|
|
my $req = HTTP::Request->new(POST => $server); |
976
|
|
|
|
|
|
|
$req->header("Content-Type" => "application/x-www-form-urlencoded"); |
977
|
|
|
|
|
|
|
$req->content(join("&", map { "$_=" . uri_escape_utf8($post{$_}) } keys %post)); |
978
|
|
|
|
|
|
|
|
979
|
|
|
|
|
|
|
my $ua = $self->ua; |
980
|
|
|
|
|
|
|
my $res = $ua->request($req); |
981
|
|
|
|
|
|
|
return $self->_fail("naive_verify_failed_network") |
982
|
|
|
|
|
|
|
unless $res && $res->is_success; |
983
|
|
|
|
|
|
|
|
984
|
|
|
|
|
|
|
my $content = $res->content; |
985
|
|
|
|
|
|
|
my %args = OpenID::util::parse_keyvalue($content); |
986
|
|
|
|
|
|
|
|
987
|
|
|
|
|
|
|
# delete the handle from our cache |
988
|
|
|
|
|
|
|
if (my $ih = $args{'invalidate_handle'}) { |
989
|
|
|
|
|
|
|
Net::OpenID::Association::invalidate_handle($self, $server, $ih); |
990
|
|
|
|
|
|
|
} |
991
|
|
|
|
|
|
|
|
992
|
|
|
|
|
|
|
return $self->_fail("naive_verify_failed_return") unless |
993
|
|
|
|
|
|
|
$args{'is_valid'} eq "true" || # protocol 1.1 |
994
|
|
|
|
|
|
|
$args{'lifetime'} > 0; # DEPRECATED protocol 1.0 |
995
|
|
|
|
|
|
|
} |
996
|
|
|
|
|
|
|
|
997
|
|
|
|
|
|
|
$self->_debug("verified identity! = $real_ident"); |
998
|
|
|
|
|
|
|
|
999
|
|
|
|
|
|
|
# verified! |
1000
|
|
|
|
|
|
|
return Net::OpenID::VerifiedIdentity->new( |
1001
|
|
|
|
|
|
|
claimed_identity => $claimed_identity, |
1002
|
|
|
|
|
|
|
consumer => $self, |
1003
|
|
|
|
|
|
|
signed_fields => \%signed_value, |
1004
|
|
|
|
|
|
|
); |
1005
|
|
|
|
|
|
|
} |
1006
|
|
|
|
|
|
|
|
1007
|
|
|
|
|
|
|
sub supports_consumer_secret { 1; } |
1008
|
|
|
|
|
|
|
|
1009
|
|
|
|
|
|
|
sub _get_consumer_secret { |
1010
|
|
|
|
|
|
|
my Net::OpenID::Consumer $self = shift; |
1011
|
|
|
|
|
|
|
my $time = shift; |
1012
|
|
|
|
|
|
|
|
1013
|
|
|
|
|
|
|
my $ss; |
1014
|
|
|
|
|
|
|
if (ref $self->{consumer_secret} eq "CODE") { |
1015
|
|
|
|
|
|
|
$ss = $self->{consumer_secret}; |
1016
|
|
|
|
|
|
|
} elsif ($self->{consumer_secret}) { |
1017
|
|
|
|
|
|
|
$ss = sub { return $self->{consumer_secret}; }; |
1018
|
|
|
|
|
|
|
} else { |
1019
|
|
|
|
|
|
|
Carp::croak("You haven't defined a consumer_secret value or subref.\n"); |
1020
|
|
|
|
|
|
|
} |
1021
|
|
|
|
|
|
|
|
1022
|
|
|
|
|
|
|
my $sec = $ss->($time); |
1023
|
|
|
|
|
|
|
Carp::croak("Consumer secret too long") if length($sec) > 255; |
1024
|
|
|
|
|
|
|
return $sec; |
1025
|
|
|
|
|
|
|
} |
1026
|
|
|
|
|
|
|
|
1027
|
|
|
|
|
|
|
our $nonce_default_delay = 1200; |
1028
|
|
|
|
|
|
|
our $nonce_default_skew = 300; |
1029
|
|
|
|
|
|
|
|
1030
|
|
|
|
|
|
|
sub _canonicalize_nonce_options { |
1031
|
|
|
|
|
|
|
my Net::OpenID::Consumer $self = shift; |
1032
|
|
|
|
|
|
|
my $o = shift; |
1033
|
|
|
|
|
|
|
my ($no_check,$ignore_time,$lifetime,$window,$start,$skew,$timecop) = |
1034
|
|
|
|
|
|
|
delete @{$o}{qw(no_check ignore_time lifetime window start skew timecop)}; |
1035
|
|
|
|
|
|
|
Carp::croak("Unrecognized nonce_options: ".join(',',keys %$o)) |
1036
|
|
|
|
|
|
|
if keys %$o; |
1037
|
|
|
|
|
|
|
|
1038
|
|
|
|
|
|
|
return +{ no_check => 1 } |
1039
|
|
|
|
|
|
|
if ($no_check); |
1040
|
|
|
|
|
|
|
|
1041
|
|
|
|
|
|
|
return +{ window => 0, |
1042
|
|
|
|
|
|
|
lifetime => ($lifetime && $lifetime > 0 ? $lifetime : 0), |
1043
|
|
|
|
|
|
|
} |
1044
|
|
|
|
|
|
|
if ($ignore_time); |
1045
|
|
|
|
|
|
|
|
1046
|
|
|
|
|
|
|
$window = |
1047
|
|
|
|
|
|
|
defined($lifetime) ? $lifetime : |
1048
|
|
|
|
|
|
|
$nonce_default_delay + 2*(defined($skew) && $skew > $nonce_default_skew |
1049
|
|
|
|
|
|
|
? $skew : $nonce_default_skew) |
1050
|
|
|
|
|
|
|
unless (defined($window)); |
1051
|
|
|
|
|
|
|
|
1052
|
|
|
|
|
|
|
$lifetime = $window |
1053
|
|
|
|
|
|
|
unless (defined($lifetime)); |
1054
|
|
|
|
|
|
|
|
1055
|
|
|
|
|
|
|
$lifetime = 0 if $lifetime < 0; |
1056
|
|
|
|
|
|
|
$window = 0 if $window < 0; |
1057
|
|
|
|
|
|
|
|
1058
|
|
|
|
|
|
|
$skew = $window < 2*$nonce_default_skew ? $window/2 : $nonce_default_skew |
1059
|
|
|
|
|
|
|
unless (defined($skew)); |
1060
|
|
|
|
|
|
|
|
1061
|
|
|
|
|
|
|
Carp::croak("Unrecognized nonce_options: ".join(',',keys %$o)) |
1062
|
|
|
|
|
|
|
if keys %$o; |
1063
|
|
|
|
|
|
|
|
1064
|
|
|
|
|
|
|
return |
1065
|
|
|
|
|
|
|
+{ |
1066
|
|
|
|
|
|
|
window => $window, |
1067
|
|
|
|
|
|
|
lifetime => $lifetime, |
1068
|
|
|
|
|
|
|
skew => $skew, |
1069
|
|
|
|
|
|
|
defined($start) ? (start => $start) : (), |
1070
|
|
|
|
|
|
|
}; |
1071
|
|
|
|
|
|
|
} |
1072
|
|
|
|
|
|
|
|
1073
|
|
|
|
|
|
|
# The contract: |
1074
|
|
|
|
|
|
|
# IF the provider adheres to protocol and is properly configured |
1075
|
|
|
|
|
|
|
# which, for our purposes here means |
1076
|
|
|
|
|
|
|
# (1) it sends properly formatted nonces |
1077
|
|
|
|
|
|
|
# that reflect provider clock time and |
1078
|
|
|
|
|
|
|
# (2) provider clock is not skewed from our own by more than |
1079
|
|
|
|
|
|
|
# (the maximum acceptable) |
1080
|
|
|
|
|
|
|
# AND |
1081
|
|
|
|
|
|
|
# we have a cache that can reliably hold onto entries |
1082
|
|
|
|
|
|
|
# for at least seconds |
1083
|
|
|
|
|
|
|
# THEN we must not accept a duplicate nonce. |
1084
|
|
|
|
|
|
|
# |
1085
|
|
|
|
|
|
|
# Preconditions imply that no message with this nonce will be received |
1086
|
|
|
|
|
|
|
# prior to - (i.e., provider clock is running |
1087
|
|
|
|
|
|
|
# maximally fast and there is no transmission delay). If our cache |
1088
|
|
|
|
|
|
|
# start time is prior to this and the lifetime of cache entries is |
1089
|
|
|
|
|
|
|
# long enough, then we can know for certain that it's not a duplicate, |
1090
|
|
|
|
|
|
|
# otherwise we do not and therefore must reject it. |
1091
|
|
|
|
|
|
|
# |
1092
|
|
|
|
|
|
|
# If we detect an instance where preconditions do not hold, there is |
1093
|
|
|
|
|
|
|
# not much we can do: rejecting nonces in this case will not make the |
1094
|
|
|
|
|
|
|
# protocol more secure. As long as the provider's clock is skewed too |
1095
|
|
|
|
|
|
|
# far forward, an attacker will be able to take advantage of it. Best |
1096
|
|
|
|
|
|
|
# we can do is issue warnings, which is the point of 'timecop', but if |
1097
|
|
|
|
|
|
|
# there's no place to send the warnings, then it's a waste of time. |
1098
|
|
|
|
|
|
|
# |
1099
|
|
|
|
|
|
|
sub _nonce_check_succeeds { |
1100
|
|
|
|
|
|
|
my Net::OpenID::Consumer $self = shift; |
1101
|
|
|
|
|
|
|
my ($now, $uri, $nonce) = @_; |
1102
|
|
|
|
|
|
|
|
1103
|
|
|
|
|
|
|
my $o = $self->nonce_options; |
1104
|
|
|
|
|
|
|
my $cache = $self->cache; |
1105
|
|
|
|
|
|
|
return 1 |
1106
|
|
|
|
|
|
|
if $o->{no_check} || !$cache; |
1107
|
|
|
|
|
|
|
|
1108
|
|
|
|
|
|
|
my $cache_key = "nonce:$uri:$nonce"; |
1109
|
|
|
|
|
|
|
|
1110
|
|
|
|
|
|
|
return $self->_fail('nonce_reused') if ($cache->get($cache_key)); |
1111
|
|
|
|
|
|
|
$cache->set($cache_key, 1, |
1112
|
|
|
|
|
|
|
($o->{lifetime} ? ($now + $o->{lifetime}) : ())); |
1113
|
|
|
|
|
|
|
|
1114
|
|
|
|
|
|
|
return 1 |
1115
|
|
|
|
|
|
|
unless $o->{window} || $o->{start}; |
1116
|
|
|
|
|
|
|
|
1117
|
|
|
|
|
|
|
# parse RFC3336 timestamp restricted as per 10.1 |
1118
|
|
|
|
|
|
|
my ($year,$mon,$day,$hour,$min,$sec) = |
1119
|
|
|
|
|
|
|
$nonce =~ m/^([0-9]{4})-([0-9]{2})-([0-9]{2})T([0-9]{2}):([0-9]{2}):([0-9]{2})Z/ |
1120
|
|
|
|
|
|
|
or return $self->_fail('nonce_format'); |
1121
|
|
|
|
|
|
|
|
1122
|
|
|
|
|
|
|
# $nonce_time is a lower bound on when the nonce could have been |
1123
|
|
|
|
|
|
|
# received according to our clock |
1124
|
|
|
|
|
|
|
my $nonce_time = eval { timegm($sec,$min,$hour,$day,$mon-1,$year) - $o->{skew} }; |
1125
|
|
|
|
|
|
|
return $self->_fail('nonce_format') if $@; |
1126
|
|
|
|
|
|
|
|
1127
|
|
|
|
|
|
|
# nonces from the future indicate misconfigured providers |
1128
|
|
|
|
|
|
|
# that we can do nothing about except give warnings |
1129
|
|
|
|
|
|
|
return !$o->{timecop} || $self->_fail('nonce_future') |
1130
|
|
|
|
|
|
|
if ($now < $nonce_time); |
1131
|
|
|
|
|
|
|
|
1132
|
|
|
|
|
|
|
# the check that matters |
1133
|
|
|
|
|
|
|
return $self->_fail('nonce_stale') |
1134
|
|
|
|
|
|
|
if ($o->{window} && $nonce_time < $now - $o->{window}) |
1135
|
|
|
|
|
|
|
|| ($o->{start} && $nonce_time < $o->{start}); |
1136
|
|
|
|
|
|
|
|
1137
|
|
|
|
|
|
|
# win |
1138
|
|
|
|
|
|
|
return 1; |
1139
|
|
|
|
|
|
|
} |
1140
|
|
|
|
|
|
|
|
1141
|
|
|
|
|
|
|
|
1142
|
|
|
|
|
|
|
|
1143
|
|
|
|
|
|
|
1; |
1144
|
|
|
|
|
|
|
__END__ |