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