| 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__ |