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