ruby-openid 1.1.4 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/INSTALL +0 -9
- data/README +21 -22
- data/UPGRADE +117 -0
- data/admin/runtests.rb +36 -0
- data/examples/README +13 -21
- data/examples/active_record_openid_store/README +8 -3
- data/examples/active_record_openid_store/XXX_add_open_id_store_to_db.rb +4 -8
- data/examples/active_record_openid_store/XXX_upgrade_open_id_store.rb +26 -0
- data/examples/active_record_openid_store/lib/association.rb +2 -0
- data/examples/active_record_openid_store/lib/openid_ar_store.rb +22 -47
- data/examples/active_record_openid_store/test/store_test.rb +78 -48
- data/examples/discover +46 -0
- data/examples/{rails_server → rails_openid}/README +0 -0
- data/examples/{rails_server → rails_openid}/Rakefile +0 -0
- data/examples/{rails_server → rails_openid}/app/controllers/application.rb +0 -0
- data/examples/rails_openid/app/controllers/consumer_controller.rb +115 -0
- data/examples/{rails_server → rails_openid}/app/controllers/login_controller.rb +10 -2
- data/examples/rails_openid/app/controllers/server_controller.rb +265 -0
- data/examples/{rails_server → rails_openid}/app/helpers/application_helper.rb +0 -0
- data/examples/{rails_server → rails_openid}/app/helpers/login_helper.rb +0 -0
- data/examples/{rails_server → rails_openid}/app/helpers/server_helper.rb +0 -0
- data/examples/rails_openid/app/views/consumer/index.rhtml +81 -0
- data/examples/rails_openid/app/views/consumer/start.rhtml +8 -0
- data/examples/{rails_server → rails_openid}/app/views/layouts/server.rhtml +0 -0
- data/examples/{rails_server → rails_openid}/app/views/login/index.rhtml +1 -1
- data/examples/rails_openid/app/views/server/decide.rhtml +26 -0
- data/examples/{rails_server → rails_openid}/config/boot.rb +0 -0
- data/examples/{rails_server → rails_openid}/config/database.yml +0 -0
- data/examples/{rails_server → rails_openid}/config/environment.rb +0 -0
- data/examples/{rails_server → rails_openid}/config/environments/development.rb +0 -0
- data/examples/{rails_server → rails_openid}/config/environments/production.rb +0 -0
- data/examples/{rails_server → rails_openid}/config/environments/test.rb +0 -0
- data/examples/{rails_server → rails_openid}/config/routes.rb +2 -1
- data/examples/{rails_server → rails_openid}/doc/README_FOR_APP +0 -0
- data/examples/{rails_server → rails_openid}/public/404.html +0 -0
- data/examples/{rails_server → rails_openid}/public/500.html +0 -0
- data/examples/{rails_server → rails_openid}/public/dispatch.cgi +0 -0
- data/examples/{rails_server → rails_openid}/public/dispatch.fcgi +0 -0
- data/examples/{rails_server → rails_openid}/public/dispatch.rb +0 -0
- data/examples/{rails_server → rails_openid}/public/favicon.ico +0 -0
- data/examples/rails_openid/public/images/openid_login_bg.gif +0 -0
- data/examples/{rails_server → rails_openid}/public/javascripts/controls.js +0 -0
- data/examples/{rails_server → rails_openid}/public/javascripts/dragdrop.js +0 -0
- data/examples/{rails_server → rails_openid}/public/javascripts/effects.js +0 -0
- data/examples/{rails_server → rails_openid}/public/javascripts/prototype.js +0 -0
- data/examples/{rails_server → rails_openid}/public/robots.txt +0 -0
- data/examples/{rails_server → rails_openid}/script/about +0 -0
- data/examples/{rails_server → rails_openid}/script/breakpointer +0 -0
- data/examples/{rails_server → rails_openid}/script/console +0 -0
- data/examples/{rails_server → rails_openid}/script/destroy +0 -0
- data/examples/{rails_server → rails_openid}/script/generate +0 -0
- data/examples/{rails_server → rails_openid}/script/performance/benchmarker +0 -0
- data/examples/{rails_server → rails_openid}/script/performance/profiler +0 -0
- data/examples/{rails_server → rails_openid}/script/plugin +0 -0
- data/examples/{rails_server → rails_openid}/script/process/reaper +0 -0
- data/examples/{rails_server → rails_openid}/script/process/spawner +0 -0
- data/examples/{rails_server → rails_openid}/script/process/spinner +0 -0
- data/examples/{rails_server → rails_openid}/script/runner +0 -0
- data/examples/{rails_server → rails_openid}/script/server +0 -0
- data/examples/{rails_server → rails_openid}/test/functional/login_controller_test.rb +0 -0
- data/examples/{rails_server → rails_openid}/test/functional/server_controller_test.rb +0 -0
- data/examples/{rails_server → rails_openid}/test/test_helper.rb +0 -0
- data/lib/{hmac.rb → hmac/hmac.rb} +0 -0
- data/lib/{hmac-sha1.rb → hmac/sha1.rb} +1 -1
- data/lib/{hmac-sha2.rb → hmac/sha2.rb} +1 -1
- data/lib/openid/association.rb +213 -73
- data/lib/openid/consumer/associationmanager.rb +338 -0
- data/lib/openid/consumer/checkid_request.rb +175 -0
- data/lib/openid/consumer/discovery.rb +480 -0
- data/lib/openid/consumer/discovery_manager.rb +123 -0
- data/lib/openid/consumer/html_parse.rb +136 -0
- data/lib/openid/consumer/idres.rb +525 -0
- data/lib/openid/consumer/responses.rb +133 -0
- data/lib/openid/consumer.rb +280 -807
- data/lib/openid/cryptutil.rb +85 -0
- data/lib/openid/dh.rb +60 -23
- data/lib/openid/extension.rb +31 -0
- data/lib/openid/extensions/ax.rb +506 -0
- data/lib/openid/extensions/pape.rb +182 -0
- data/lib/openid/extensions/sreg.rb +275 -0
- data/lib/openid/extras.rb +11 -0
- data/lib/openid/fetchers.rb +132 -93
- data/lib/openid/kvform.rb +133 -0
- data/lib/openid/kvpost.rb +56 -0
- data/lib/openid/message.rb +534 -0
- data/lib/openid/protocolerror.rb +6 -0
- data/lib/openid/server.rb +1215 -666
- data/lib/openid/store/filesystem.rb +271 -0
- data/lib/openid/store/interface.rb +75 -0
- data/lib/openid/store/memory.rb +84 -0
- data/lib/openid/store/nonce.rb +68 -0
- data/lib/openid/trustroot.rb +314 -87
- data/lib/openid/urinorm.rb +37 -34
- data/lib/openid/util.rb +42 -220
- data/lib/openid/yadis/accept.rb +148 -0
- data/lib/openid/yadis/constants.rb +21 -0
- data/lib/openid/yadis/discovery.rb +153 -0
- data/lib/openid/yadis/filters.rb +205 -0
- data/lib/openid/{htmltokenizer.rb → yadis/htmltokenizer.rb} +1 -54
- data/lib/openid/yadis/parsehtml.rb +36 -0
- data/lib/openid/yadis/services.rb +42 -0
- data/lib/openid/yadis/xrds.rb +171 -0
- data/lib/openid/yadis/xri.rb +90 -0
- data/lib/openid/yadis/xrires.rb +106 -0
- data/lib/openid.rb +1 -4
- data/test/data/accept.txt +124 -0
- data/test/data/dh.txt +29 -0
- data/test/data/example-xrds.xml +14 -0
- data/test/data/linkparse.txt +587 -0
- data/test/data/n2b64 +650 -0
- data/test/data/test1-discover.txt +137 -0
- data/test/data/test1-parsehtml.txt +128 -0
- data/test/data/test_discover/openid.html +11 -0
- data/test/data/test_discover/openid2.html +11 -0
- data/test/data/test_discover/openid2_xrds.xml +12 -0
- data/test/data/test_discover/openid2_xrds_no_local_id.xml +11 -0
- data/test/data/test_discover/openid_1_and_2.html +11 -0
- data/test/data/test_discover/openid_1_and_2_xrds.xml +16 -0
- data/test/data/test_discover/openid_1_and_2_xrds_bad_delegate.xml +17 -0
- data/test/data/test_discover/openid_and_yadis.html +12 -0
- data/test/data/test_discover/openid_no_delegate.html +10 -0
- data/test/data/test_discover/yadis_0entries.xml +12 -0
- data/test/data/test_discover/yadis_2_bad_local_id.xml +15 -0
- data/test/data/test_discover/yadis_2entries_delegate.xml +22 -0
- data/test/data/test_discover/yadis_2entries_idp.xml +21 -0
- data/test/data/test_discover/yadis_another_delegate.xml +14 -0
- data/test/data/test_discover/yadis_idp.xml +12 -0
- data/test/data/test_discover/yadis_idp_delegate.xml +13 -0
- data/test/data/test_discover/yadis_no_delegate.xml +11 -0
- data/test/data/test_xrds/=j3h.2007.11.14.xrds +25 -0
- data/test/data/test_xrds/README +12 -0
- data/test/data/test_xrds/delegated-20060809-r1.xrds +34 -0
- data/test/data/test_xrds/delegated-20060809-r2.xrds +34 -0
- data/test/data/test_xrds/delegated-20060809.xrds +34 -0
- data/test/data/test_xrds/no-xrd.xml +7 -0
- data/test/data/test_xrds/not-xrds.xml +2 -0
- data/test/data/test_xrds/prefixsometimes.xrds +34 -0
- data/test/data/test_xrds/ref.xrds +109 -0
- data/test/data/test_xrds/sometimesprefix.xrds +34 -0
- data/test/data/test_xrds/spoof1.xrds +25 -0
- data/test/data/test_xrds/spoof2.xrds +25 -0
- data/test/data/test_xrds/spoof3.xrds +37 -0
- data/test/data/test_xrds/status222.xrds +9 -0
- data/test/data/test_xrds/valid-populated-xrds.xml +39 -0
- data/test/data/trustroot.txt +147 -0
- data/test/discoverdata.rb +131 -0
- data/test/test_accept.rb +170 -0
- data/test/test_association.rb +266 -0
- data/test/test_associationmanager.rb +899 -0
- data/test/test_ax.rb +587 -0
- data/test/test_checkid_request.rb +297 -0
- data/test/test_consumer.rb +257 -0
- data/test/test_cryptutil.rb +117 -0
- data/test/test_dh.rb +86 -0
- data/test/test_discover.rb +772 -0
- data/test/test_discovery_manager.rb +262 -0
- data/test/test_extras.rb +35 -0
- data/test/test_fetchers.rb +472 -0
- data/test/test_filters.rb +270 -0
- data/test/test_idres.rb +816 -0
- data/test/test_kvform.rb +165 -0
- data/test/test_kvpost.rb +65 -0
- data/test/test_linkparse.rb +101 -0
- data/test/test_message.rb +1058 -0
- data/test/test_nonce.rb +89 -0
- data/test/test_openid_yadis.rb +178 -0
- data/test/test_pape.rb +233 -0
- data/test/test_parsehtml.rb +80 -0
- data/test/test_responses.rb +63 -0
- data/test/test_server.rb +2270 -0
- data/test/test_sreg.rb +479 -0
- data/test/test_stores.rb +269 -0
- data/test/test_trustroot.rb +112 -0
- data/test/{urinorm.rb → test_urinorm.rb} +6 -3
- data/test/test_util.rb +144 -0
- data/test/test_xrds.rb +160 -0
- data/test/test_xri.rb +48 -0
- data/test/test_xrires.rb +63 -0
- data/test/test_yadis_discovery.rb +207 -0
- data/test/testutil.rb +116 -0
- data/test/util.rb +47 -50
- metadata +233 -143
- data/examples/consumer.rb +0 -290
- data/examples/rails_openid_login_generator/openid_login_generator-0.1.gem +0 -0
- data/examples/rails_server/app/controllers/server_controller.rb +0 -190
- data/examples/rails_server/app/views/server/decide.rhtml +0 -11
- data/examples/rails_server/public/images/rails.png +0 -0
- data/lib/hmac-md5.rb +0 -11
- data/lib/hmac-rmd160.rb +0 -11
- data/lib/openid/discovery.rb +0 -122
- data/lib/openid/filestore.rb +0 -315
- data/lib/openid/parse.rb +0 -23
- data/lib/openid/service.rb +0 -147
- data/lib/openid/stores.rb +0 -178
- data/test/assoc.rb +0 -38
- data/test/consumer.rb +0 -376
- data/test/data/brian.xrds +0 -16
- data/test/data/brianellin.mylid.xrds +0 -42
- data/test/dh.rb +0 -20
- data/test/extensions.rb +0 -30
- data/test/linkparse.rb +0 -305
- data/test/runtests.rb +0 -22
- data/test/server2.rb +0 -1053
- data/test/service.rb +0 -47
- data/test/storetestcase.rb +0 -172
- data/test/teststore.rb +0 -47
- data/test/trustroot.rb +0 -117
data/lib/openid/consumer.rb
CHANGED
|
@@ -1,120 +1,124 @@
|
|
|
1
|
-
require "
|
|
1
|
+
require "openid/consumer/idres.rb"
|
|
2
|
+
require "openid/consumer/checkid_request.rb"
|
|
3
|
+
require "openid/consumer/associationmanager.rb"
|
|
4
|
+
require "openid/consumer/responses.rb"
|
|
5
|
+
require "openid/consumer/discovery_manager"
|
|
6
|
+
require "openid/consumer/discovery"
|
|
7
|
+
require "openid/message"
|
|
8
|
+
require "openid/yadis/discovery"
|
|
9
|
+
require "openid/store/nonce"
|
|
2
10
|
|
|
3
|
-
require "openid/util"
|
|
4
|
-
require "openid/dh"
|
|
5
|
-
require "openid/fetchers"
|
|
6
|
-
require "openid/association"
|
|
7
|
-
require "openid/discovery"
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
# Everything in this library exists within the OpenID Module. Users of
|
|
11
|
-
# the library should look at OpenID::Consumer and/or OpenID::Server
|
|
12
11
|
module OpenID
|
|
13
|
-
|
|
14
|
-
# ==Overview
|
|
12
|
+
# OpenID support for Relying Parties (aka Consumers).
|
|
15
13
|
#
|
|
16
|
-
#
|
|
14
|
+
# This module documents the main interface with the OpenID consumer
|
|
15
|
+
# library. The only part of the library which has to be used and
|
|
16
|
+
# isn't documented in full here is the store required to create an
|
|
17
|
+
# Consumer instance.
|
|
17
18
|
#
|
|
18
|
-
#
|
|
19
|
-
# The website wanting to verify an OpenID identity URL. Sometimes
|
|
20
|
-
# called a "relying party". If you want people to log into your site
|
|
21
|
-
# using OpenID, then you are the consumer.
|
|
22
|
-
#
|
|
23
|
-
# [+Server+]
|
|
24
|
-
# The website which makes assertions as to whether or not the user
|
|
25
|
-
# at the end of the browser owns the URL they say they do.
|
|
19
|
+
# = OVERVIEW
|
|
26
20
|
#
|
|
27
|
-
#
|
|
28
|
-
#
|
|
29
|
-
# response from the server, the browser changes it's location to the
|
|
30
|
-
# value specified.
|
|
31
|
-
#
|
|
32
|
-
# The OpenID authentication process requires the following steps,
|
|
33
|
-
# as visible to the user of this library:
|
|
21
|
+
# The OpenID identity verification process most commonly uses the
|
|
22
|
+
# following steps, as visible to the user of this library:
|
|
34
23
|
#
|
|
35
24
|
# 1. The user enters their OpenID into a field on the consumer's
|
|
36
25
|
# site, and hits a login button.
|
|
37
26
|
#
|
|
38
|
-
# 2. The consumer site discovers the user's OpenID
|
|
39
|
-
# the Yadis protocol
|
|
40
|
-
# discovery).
|
|
27
|
+
# 2. The consumer site discovers the user's OpenID provider using
|
|
28
|
+
# the Yadis protocol.
|
|
41
29
|
#
|
|
42
|
-
# 3. The consumer site
|
|
43
|
-
#
|
|
44
|
-
#
|
|
30
|
+
# 3. The consumer site sends the browser a redirect to the OpenID
|
|
31
|
+
# provider. This is the authentication request as described in
|
|
32
|
+
# the OpenID specification.
|
|
45
33
|
#
|
|
46
|
-
# 4. The
|
|
47
|
-
#
|
|
48
|
-
#
|
|
49
|
-
# server's response to the authentication request.
|
|
34
|
+
# 4. The OpenID provider's site sends the browser a redirect back to
|
|
35
|
+
# the consumer site. This redirect contains the provider's
|
|
36
|
+
# response to the authentication request.
|
|
50
37
|
#
|
|
51
38
|
# The most important part of the flow to note is the consumer's site
|
|
52
39
|
# must handle two separate HTTP requests in order to perform the
|
|
53
|
-
# full identity check.
|
|
54
|
-
# steps 1 and 4 above, and are handled by Consumer.begin and
|
|
55
|
-
# Consumer.complete respectively.
|
|
56
|
-
#
|
|
40
|
+
# full identity check.
|
|
57
41
|
#
|
|
58
|
-
#
|
|
42
|
+
# = LIBRARY DESIGN
|
|
59
43
|
#
|
|
60
|
-
#
|
|
44
|
+
# This consumer library is designed with that flow in mind. The
|
|
61
45
|
# goal is to make it as easy as possible to perform the above steps
|
|
62
46
|
# securely.
|
|
63
47
|
#
|
|
64
48
|
# At a high level, there are two important parts in the consumer
|
|
65
|
-
# library. The first important part is
|
|
66
|
-
#
|
|
67
|
-
#
|
|
68
|
-
#
|
|
69
|
-
# between requests.
|
|
49
|
+
# library. The first important part is this module, which contains
|
|
50
|
+
# the interface to actually use this library. The second is
|
|
51
|
+
# openid/store/interface.rb, which describes the interface to use if
|
|
52
|
+
# you need to create a custom method for storing the state this
|
|
53
|
+
# library needs to maintain between requests.
|
|
70
54
|
#
|
|
71
55
|
# In general, the second part is less important for users of the
|
|
72
|
-
# library to know about, as several
|
|
73
|
-
#
|
|
74
|
-
#
|
|
75
|
-
#
|
|
76
|
-
#
|
|
77
|
-
#
|
|
78
|
-
#
|
|
79
|
-
#
|
|
80
|
-
#
|
|
81
|
-
#
|
|
82
|
-
#
|
|
83
|
-
#
|
|
84
|
-
#
|
|
85
|
-
#
|
|
86
|
-
#
|
|
87
|
-
#
|
|
88
|
-
#
|
|
89
|
-
#
|
|
90
|
-
#
|
|
56
|
+
# library to know about, as several implementations are provided
|
|
57
|
+
# which cover a wide variety of situations in which consumers may
|
|
58
|
+
# use the library.
|
|
59
|
+
#
|
|
60
|
+
# The Consumer class has methods corresponding to the actions
|
|
61
|
+
# necessary in each of steps 2, 3, and 4 described in the overview.
|
|
62
|
+
# Use of this library should be as easy as creating an Consumer
|
|
63
|
+
# instance and calling the methods appropriate for the action the
|
|
64
|
+
# site wants to take.
|
|
65
|
+
#
|
|
66
|
+
# This library automatically detects which version of the OpenID
|
|
67
|
+
# protocol should be used for a transaction and constructs the
|
|
68
|
+
# proper requests and responses. Users of this library do not need
|
|
69
|
+
# to worry about supporting multiple protocol versions; the library
|
|
70
|
+
# supports them implicitly. Depending on the version of the
|
|
71
|
+
# protocol in use, the OpenID transaction may be more secure. See
|
|
72
|
+
# the OpenID specifications for more information.
|
|
73
|
+
#
|
|
74
|
+
# = SESSIONS, STORES, AND STATELESS MODE
|
|
75
|
+
#
|
|
76
|
+
# The Consumer object keeps track of two types of state:
|
|
77
|
+
#
|
|
78
|
+
# 1. State of the user's current authentication attempt. Things
|
|
79
|
+
# like the identity URL, the list of endpoints discovered for
|
|
80
|
+
# that URL, and in case where some endpoints are unreachable, the
|
|
81
|
+
# list of endpoints already tried. This state needs to be held
|
|
82
|
+
# from Consumer.begin() to Consumer.complete(), but it is only
|
|
83
|
+
# applicable to a single session with a single user agent, and at
|
|
84
|
+
# the end of the authentication process (i.e. when an OP replies
|
|
85
|
+
# with either <tt>id_res</tt>. or <tt>cancel</tt> it may be
|
|
86
|
+
# discarded.
|
|
87
|
+
#
|
|
88
|
+
# 2. State of relationships with servers, i.e. shared secrets
|
|
89
|
+
# (associations) with servers and nonces seen on signed messages.
|
|
90
|
+
# This information should persist from one session to the next
|
|
91
|
+
# and should not be bound to a particular user-agent.
|
|
92
|
+
#
|
|
93
|
+
# These two types of storage are reflected in the first two
|
|
94
|
+
# arguments of Consumer's constructor, <tt>session</tt> and
|
|
95
|
+
# <tt>store</tt>. <tt>session</tt> is a dict-like object and we
|
|
96
|
+
# hope your web framework provides you with one of these bound to
|
|
97
|
+
# the user agent. <tt>store</tt> is an instance of Store.
|
|
98
|
+
#
|
|
99
|
+
# Since the store does hold secrets shared between your application
|
|
100
|
+
# and the OpenID provider, you should be careful about how you use
|
|
101
|
+
# it in a shared hosting environment. If the filesystem or database
|
|
102
|
+
# permissions of your web host allow strangers to read from them, do
|
|
103
|
+
# not store your data there! If you have no safe place to store
|
|
104
|
+
# your data, construct your consumer with nil for the store, and it
|
|
105
|
+
# will operate only in stateless mode. Stateless mode may be
|
|
106
|
+
# slower, put more load on the OpenID provider, and trusts the
|
|
107
|
+
# provider to keep you safe from replay attacks.
|
|
91
108
|
#
|
|
92
109
|
# Several store implementation are provided, and the interface is
|
|
93
|
-
# fully documented so that custom stores can be used as well.
|
|
94
|
-
#
|
|
95
|
-
#
|
|
96
|
-
#
|
|
97
|
-
#
|
|
98
|
-
#
|
|
99
|
-
#
|
|
100
|
-
#
|
|
101
|
-
#
|
|
102
|
-
#
|
|
103
|
-
#
|
|
104
|
-
#
|
|
105
|
-
#
|
|
106
|
-
# If your ennvironment permits, use of the FilesystemStore
|
|
107
|
-
# is recommended.
|
|
108
|
-
#
|
|
109
|
-
#
|
|
110
|
-
# ==Immediate Mode
|
|
111
|
-
#
|
|
112
|
-
# If you are new to OpenID, it is suggested that you skip this section
|
|
113
|
-
# and refer to it later. Immediate mode is an advanced consumer topic.
|
|
114
|
-
#
|
|
115
|
-
# In the flow described in the overview, the user may need to confirm to the
|
|
116
|
-
# identity server that it's ok to authorize his or her identity.
|
|
117
|
-
# The server may draw pages asking for information from the user
|
|
110
|
+
# fully documented so that custom stores can be used as well. See
|
|
111
|
+
# the documentation for the Consumer class for more information on
|
|
112
|
+
# the interface for stores. The implementations that are provided
|
|
113
|
+
# allow the consumer site to store the necessary data in several
|
|
114
|
+
# different ways, including several SQL databases and normal files
|
|
115
|
+
# on disk.
|
|
116
|
+
#
|
|
117
|
+
# = IMMEDIATE MODE
|
|
118
|
+
#
|
|
119
|
+
# In the flow described above, the user may need to confirm to the
|
|
120
|
+
# OpenID provider that it's ok to disclose his or her identity. The
|
|
121
|
+
# provider may draw pages asking for information from the user
|
|
118
122
|
# before it redirects the browser back to the consumer's site. This
|
|
119
123
|
# is generally transparent to the consumer site, so it is typically
|
|
120
124
|
# ignored as an implementation detail.
|
|
@@ -124,794 +128,263 @@ module OpenID
|
|
|
124
128
|
# put the library in immediate mode. In immediate mode, there is an
|
|
125
129
|
# extra response possible from the server, which is essentially the
|
|
126
130
|
# server reporting that it doesn't have enough information to answer
|
|
127
|
-
# the question yet.
|
|
128
|
-
# provides a URL to which the user can be sent to provide the needed
|
|
129
|
-
# information and let the server finish handling the original
|
|
130
|
-
# request.
|
|
131
|
-
#
|
|
132
|
-
# You may invoke immediate mode when building the redirect URL to the
|
|
133
|
-
# OpenID server in the SuccessRequest.redirect_url method. Pass true
|
|
134
|
-
# for the +immediate+ paramter. Read the interface for Consumer.complete
|
|
135
|
-
# for information about handling the additional response.
|
|
131
|
+
# the question yet.
|
|
136
132
|
#
|
|
137
|
-
#
|
|
133
|
+
# = USING THIS LIBRARY
|
|
138
134
|
#
|
|
139
|
-
# Integrating this library into an application is a
|
|
140
|
-
# relatively straightforward process. The process
|
|
135
|
+
# Integrating this library into an application is usually a
|
|
136
|
+
# relatively straightforward process. The process should basically
|
|
137
|
+
# follow this plan:
|
|
141
138
|
#
|
|
142
139
|
# Add an OpenID login field somewhere on your site. When an OpenID
|
|
143
140
|
# is entered in that field and the form is submitted, it should make
|
|
144
|
-
# a request to the site which includes that OpenID URL.
|
|
145
|
-
#
|
|
146
|
-
#
|
|
147
|
-
#
|
|
148
|
-
#
|
|
149
|
-
#
|
|
150
|
-
#
|
|
151
|
-
# the
|
|
152
|
-
#
|
|
153
|
-
#
|
|
154
|
-
#
|
|
155
|
-
#
|
|
156
|
-
#
|
|
157
|
-
#
|
|
158
|
-
#
|
|
159
|
-
#
|
|
141
|
+
# a request to the your site which includes that OpenID URL.
|
|
142
|
+
#
|
|
143
|
+
# First, the application should instantiate a Consumer with a
|
|
144
|
+
# session for per-user state and store for shared state using the
|
|
145
|
+
# store of choice.
|
|
146
|
+
#
|
|
147
|
+
# Next, the application should call the <tt>begin</tt> method of
|
|
148
|
+
# Consumer instance. This method takes the OpenID URL as entered by
|
|
149
|
+
# the user. The <tt>begin</tt> method returns a CheckIDRequest
|
|
150
|
+
# object.
|
|
151
|
+
#
|
|
152
|
+
# Next, the application should call the redirect_url method on the
|
|
153
|
+
# CheckIDRequest object. The parameter <tt>return_to</tt> is the
|
|
154
|
+
# URL that the OpenID server will send the user back to after
|
|
155
|
+
# attempting to verify his or her identity. The <tt>realm</tt>
|
|
156
|
+
# parameter is the URL (or URL pattern) that identifies your web
|
|
157
|
+
# site to the user when he or she is authorizing it. Send a
|
|
158
|
+
# redirect to the resulting URL to the user's browser.
|
|
160
159
|
#
|
|
161
160
|
# That's the first half of the authentication process. The second
|
|
162
|
-
# half of the process is done after the OpenID
|
|
163
|
-
# user's browser a redirect back to your site
|
|
164
|
-
#
|
|
165
|
-
#
|
|
166
|
-
# When that happens, the
|
|
167
|
-
#
|
|
168
|
-
#
|
|
169
|
-
#
|
|
161
|
+
# half of the process is done after the user's OpenID Provider sends
|
|
162
|
+
# the user's browser a redirect back to your site to complete their
|
|
163
|
+
# login.
|
|
164
|
+
#
|
|
165
|
+
# When that happens, the user will contact your site at the URL
|
|
166
|
+
# given as the <tt>return_to</tt> URL to the redirect_url call made
|
|
167
|
+
# above. The request will have several query parameters added to
|
|
168
|
+
# the URL by the OpenID provider as the information necessary to
|
|
170
169
|
# finish the request.
|
|
171
170
|
#
|
|
172
|
-
#
|
|
173
|
-
#
|
|
174
|
-
#
|
|
175
|
-
# return a SuccessResponse object, or a subclass of OpenIDStatus explaining,
|
|
176
|
-
# the failure. See the documentation for Consumer.complete
|
|
177
|
-
# for a full explanation of the possible responses.
|
|
171
|
+
# Get a Consumer instance with the same session and store as before
|
|
172
|
+
# and call its complete() method, passing in all the received query
|
|
173
|
+
# arguments and the <tt>return_to</tt> URL mentioned above.
|
|
178
174
|
#
|
|
179
|
-
#
|
|
180
|
-
#
|
|
175
|
+
# There are multiple possible return types possible from that
|
|
176
|
+
# method. These indicate the whether or not the login was
|
|
177
|
+
# successful, and include any additional information appropriate for
|
|
178
|
+
# their type.
|
|
181
179
|
class Consumer
|
|
182
|
-
|
|
183
|
-
@@service_key = '_openid_consumer_service'
|
|
184
|
-
@@disco_suffix = 'xopenid_services'
|
|
185
|
-
attr_accessor :consumer, :session, :fetcher
|
|
180
|
+
attr_accessor :session_key_prefix
|
|
186
181
|
|
|
187
|
-
#
|
|
188
|
-
#
|
|
189
|
-
#
|
|
190
|
-
#
|
|
182
|
+
# Initialize a Consumer instance.
|
|
183
|
+
#
|
|
184
|
+
# You should create a new instance of the Consumer object with
|
|
185
|
+
# every HTTP request that handles OpenID transactions.
|
|
191
186
|
#
|
|
192
|
-
#
|
|
193
|
-
#
|
|
194
|
-
# used for keeping state of the OpenID transaction when the user is
|
|
195
|
-
# redirected to the server. In a rails application, the controller's
|
|
196
|
-
# @session instance variable should be used.
|
|
187
|
+
# session: the session object to use to store request information.
|
|
188
|
+
# The session should behave like a hash.
|
|
197
189
|
#
|
|
198
|
-
#
|
|
199
|
-
|
|
200
|
-
# Several concrete implementations are provided, to cover
|
|
201
|
-
# most common use cases. We recommend using the simple file based
|
|
202
|
-
# store bundled with the library: OpenID::FilesystemStore.
|
|
203
|
-
#
|
|
204
|
-
# [+fetcher+]
|
|
205
|
-
# Optional. If provided, this must be an instance that implements
|
|
206
|
-
# OpenID::Fetcher interface. If no fetcher is provided,
|
|
207
|
-
# an OpenID::StandardFetcher instance will be created
|
|
208
|
-
# for you automatically. If you need custom fetcher behavior, it
|
|
209
|
-
# is probably best to subclass StandardFetcher, and pass your instance
|
|
210
|
-
# in here.
|
|
211
|
-
#
|
|
212
|
-
# This object keeps an internal instance of OpenID::GenericConsumer
|
|
213
|
-
# for low level OpenID calls, called +consumer+. You may use a custom
|
|
214
|
-
# certificate authority PEM file for veryifying HTTPS server certs
|
|
215
|
-
# by calling the GenericConsumer.ca_path= method of the +consumer+
|
|
216
|
-
# instance variable.
|
|
217
|
-
def initialize(session, store, fetcher=nil)
|
|
190
|
+
# store: an object that implements the interface in Store.
|
|
191
|
+
def initialize(session, store)
|
|
218
192
|
@session = session
|
|
219
|
-
@
|
|
193
|
+
@store = store
|
|
194
|
+
@session_key_prefix = 'OpenID::Consumer::'
|
|
220
195
|
end
|
|
221
196
|
|
|
222
|
-
#
|
|
223
|
-
#
|
|
224
|
-
#
|
|
225
|
-
# ==Parameters
|
|
226
|
-
#
|
|
227
|
-
# [+user_url+]
|
|
228
|
-
# Identity URL given by the user. +begin+ performs a textual
|
|
229
|
-
# transformation of the URL to try and make sure it is "normalized",
|
|
230
|
-
# for example, a user_url of example.com will be normalized to
|
|
231
|
-
# http://example.com/ normalizing and resolving any redirects
|
|
232
|
-
# the server might issue.
|
|
197
|
+
# Start the OpenID authentication process. See steps 1-2 in the
|
|
198
|
+
# overview for the Consumer class.
|
|
233
199
|
#
|
|
234
|
-
#
|
|
200
|
+
# user_url: Identity URL given by the user. This method performs a
|
|
201
|
+
# textual transformation of the URL to try and make sure it is
|
|
202
|
+
# normalized. For example, a user_url of example.com will be
|
|
203
|
+
# normalized to http://example.com/ normalizing and resolving any
|
|
204
|
+
# redirects the server might issue.
|
|
235
205
|
#
|
|
236
|
-
#
|
|
237
|
-
#
|
|
238
|
-
#
|
|
239
|
-
#
|
|
240
|
-
#
|
|
241
|
-
#
|
|
242
|
-
# ===Success
|
|
206
|
+
# anonymous: A boolean value. Whether to make an anonymous
|
|
207
|
+
# request of the OpenID provider. Such a request does not ask for
|
|
208
|
+
# an authorization assertion for an OpenID identifier, but may be
|
|
209
|
+
# used with extensions to pass other data. e.g. "I don't care who
|
|
210
|
+
# you are, but I'd like to know your time zone."
|
|
243
211
|
#
|
|
244
|
-
#
|
|
245
|
-
#
|
|
246
|
-
#
|
|
247
|
-
#
|
|
248
|
-
#
|
|
249
|
-
# in step 3 of the overview.
|
|
212
|
+
# Returns a CheckIDRequest object containing the discovered
|
|
213
|
+
# information, with a method for building a redirect URL to the
|
|
214
|
+
# server, as described in step 3 of the overview. This object may
|
|
215
|
+
# also be used to add extension arguments to the request, using
|
|
216
|
+
# its add_extension_arg method.
|
|
250
217
|
#
|
|
251
|
-
#
|
|
252
|
-
# URL
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
# finishes the operations on the server, she will be redirected back to
|
|
257
|
-
# the return_to URL you passed to redirect_url, which should invoke
|
|
258
|
-
# the Consumer.complete method.
|
|
259
|
-
#
|
|
260
|
-
# ===Failure
|
|
261
|
-
#
|
|
262
|
-
# If the library is unable to fetch the +user_url+, or no server
|
|
263
|
-
# information can be determined, or if the server information is malformed,
|
|
264
|
-
# +begin+ will return a FailureRequest object. The status method of this
|
|
265
|
-
# object will return OpenID::FAILURE. FailureRequest objects have a
|
|
266
|
-
# +msg+ method which provides more detailed information as to why
|
|
267
|
-
# the request failed.
|
|
268
|
-
def begin(user_url)
|
|
269
|
-
discovery = self.get_discovery(user_url)
|
|
270
|
-
|
|
271
|
-
unless discovery
|
|
272
|
-
return FailureRequest.new("Don't know how to find services for that identifier")
|
|
273
|
-
end
|
|
274
|
-
|
|
275
|
-
service = discovery.next_service
|
|
218
|
+
# Raises DiscoveryFailure when no OpenID server can be found for
|
|
219
|
+
# this URL.
|
|
220
|
+
def begin(openid_identifier, anonymous=false)
|
|
221
|
+
manager = discovery_manager(openid_identifier)
|
|
222
|
+
service = manager.get_next_service(&method(:discover))
|
|
276
223
|
|
|
277
|
-
|
|
278
|
-
|
|
224
|
+
if service.nil?
|
|
225
|
+
raise DiscoveryFailure.new("No usable OpenID services were found "\
|
|
226
|
+
"for #{openid_identifier.inspect}", nil)
|
|
227
|
+
else
|
|
228
|
+
begin_without_discovery(service, anonymous)
|
|
279
229
|
end
|
|
280
|
-
|
|
281
|
-
return self.begin_without_discovery(service)
|
|
282
230
|
end
|
|
283
231
|
|
|
284
|
-
# Start
|
|
285
|
-
# discovery. This method is used internally by Consumer.begin
|
|
286
|
-
# discovery is performed, and exists to provide an interface
|
|
287
|
-
# users needing to perform their own discovery.
|
|
288
|
-
#
|
|
289
|
-
# ==Parameters
|
|
232
|
+
# Start OpenID verification without doing OpenID server
|
|
233
|
+
# discovery. This method is used internally by Consumer.begin()
|
|
234
|
+
# after discovery is performed, and exists to provide an interface
|
|
235
|
+
# for library users needing to perform their own discovery.
|
|
290
236
|
#
|
|
291
|
-
#
|
|
292
|
-
#
|
|
293
|
-
#
|
|
237
|
+
# service: an OpenID service endpoint descriptor. This object and
|
|
238
|
+
# factories for it are found in the openid/consumer/discovery.rb
|
|
239
|
+
# module.
|
|
294
240
|
#
|
|
295
|
-
#
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
241
|
+
# Returns an OpenID authentication request object.
|
|
242
|
+
def begin_without_discovery(service, anonymous)
|
|
243
|
+
assoc = association_manager(service).get_association
|
|
244
|
+
checkid_request = CheckIDRequest.new(assoc, service)
|
|
245
|
+
checkid_request.anonymous = anonymous
|
|
246
|
+
|
|
247
|
+
if service.compatibility_mode
|
|
248
|
+
rt_args = checkid_request.return_to_args
|
|
249
|
+
rt_args[Consumer.openid1_return_to_nonce_name] = Nonce.mk_nonce
|
|
250
|
+
rt_args[Consumer.openid1_return_to_claimed_id_name] =
|
|
251
|
+
service.claimed_id
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
self.last_requested_endpoint = service
|
|
255
|
+
return checkid_request
|
|
304
256
|
end
|
|
305
|
-
|
|
306
|
-
# Called to interpret the server's response to an OpenID
|
|
307
|
-
# is called in step 4 of the flow described in the
|
|
308
|
-
#
|
|
309
|
-
# ==Parameters
|
|
310
|
-
# [+query+]
|
|
311
|
-
# A hash of the query paramters for this HTTP request.
|
|
312
|
-
#
|
|
313
|
-
# ==Return Value
|
|
314
|
-
# Return value is a subclass of OpenIDStatus, and may have a status
|
|
315
|
-
# of OpenID::SUCCESS, OpenID::CANCEL, OpenID::FAILURE,
|
|
316
|
-
# or OpenID::SETUP_NEEDED. The status may be accessed through the
|
|
317
|
-
# +status+ method of the response object.
|
|
318
|
-
#
|
|
319
|
-
# When OpenID::SUCCESS is returned, the response object will be of
|
|
320
|
-
# type SuccessResponse, which has several useful attributes including
|
|
321
|
-
# +identity_url+, +service+, and a method +extension_response+ for
|
|
322
|
-
# extracting potential signed extension reponses from the server. See
|
|
323
|
-
# the documentation for OpenID::SuccessResponse for more information
|
|
324
|
-
# about it's interface and methods.
|
|
257
|
+
|
|
258
|
+
# Called to interpret the server's response to an OpenID
|
|
259
|
+
# request. It is called in step 4 of the flow described in the
|
|
260
|
+
# Consumer overview.
|
|
325
261
|
#
|
|
326
|
-
#
|
|
327
|
-
#
|
|
328
|
-
#
|
|
329
|
-
#
|
|
262
|
+
# query: A hash of the query parameters for this HTTP request.
|
|
263
|
+
# Note that in rails, this is <b>not</b> <tt>params</tt> but
|
|
264
|
+
# <tt>params.reject{|k,v|request.path_parameters[k]}</tt>
|
|
265
|
+
# because <tt>controller</tt> and <tt>action</tt> and other
|
|
266
|
+
# "path parameters" are included in params.
|
|
330
267
|
#
|
|
331
|
-
#
|
|
332
|
-
#
|
|
333
|
-
#
|
|
334
|
-
#
|
|
335
|
-
#
|
|
336
|
-
# nil.
|
|
268
|
+
# return_to: The return URL used to invoke the application.
|
|
269
|
+
# Extract the URL from your application's web request framework
|
|
270
|
+
# and specify it here to have it checked against the
|
|
271
|
+
# openid.return_to value in the response. If the return_to URL
|
|
272
|
+
# check fails, the status of the completion will be FAILURE.
|
|
337
273
|
#
|
|
338
|
-
#
|
|
339
|
-
#
|
|
340
|
-
#
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
if service.nil?
|
|
350
|
-
resp = FailureResponse.new(nil, 'No session state found.')
|
|
351
|
-
else
|
|
352
|
-
resp = @consumer.complete(query, service)
|
|
274
|
+
# Returns a subclass of Response. The type of response is
|
|
275
|
+
# indicated by the status attribute, which will be one of
|
|
276
|
+
# SUCCESS, CANCEL, FAILURE, or SETUP_NEEDED.
|
|
277
|
+
def complete(query, return_to)
|
|
278
|
+
message = Message.from_post_args(query)
|
|
279
|
+
mode = message.get_arg(OPENID_NS, 'mode', 'invalid')
|
|
280
|
+
begin
|
|
281
|
+
meth = method('complete_' + mode)
|
|
282
|
+
rescue NameError
|
|
283
|
+
meth = method(:complete_invalid)
|
|
353
284
|
end
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
# servce to nil.
|
|
359
|
-
if resp.identity_url
|
|
360
|
-
disco = self.get_discovery(resp.identity_url)
|
|
361
|
-
if [SUCCESS, CANCEL].member?(resp.status)
|
|
362
|
-
if resp.identity_url
|
|
363
|
-
resp.service = disco.finish
|
|
364
|
-
end
|
|
365
|
-
else
|
|
366
|
-
resp.service = disco.current
|
|
367
|
-
end
|
|
368
|
-
else
|
|
369
|
-
resp.service = nil
|
|
285
|
+
response = meth.call(message, return_to)
|
|
286
|
+
cleanup_last_requested_endpoint
|
|
287
|
+
if [SUCCESS, CANCEL].member?(response.status)
|
|
288
|
+
cleanup_session
|
|
370
289
|
end
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
# want to delete service unless status is SETUP_NEEDED,
|
|
374
|
-
# because we still need the service info when the user returns from
|
|
375
|
-
# the server
|
|
376
|
-
unless resp.status == SETUP_NEEDED
|
|
377
|
-
@session[@@service_key] = nil
|
|
378
|
-
end
|
|
379
|
-
|
|
380
|
-
return resp
|
|
290
|
+
return response
|
|
381
291
|
end
|
|
382
292
|
|
|
383
293
|
protected
|
|
384
|
-
|
|
385
|
-
# Used internally to create an instnace of the OpenIDDiscovery object.
|
|
386
|
-
def get_discovery(url)
|
|
387
|
-
if XRI::identifier_scheme(url) == :xri
|
|
388
|
-
XRIDiscovery.new(@session, url, @@disco_suffix)
|
|
389
|
-
else
|
|
390
|
-
url = OpenID::Util.normalize_url(url)
|
|
391
|
-
if url
|
|
392
|
-
user_url = user_url.to_s
|
|
393
|
-
OpenIDDiscovery.new(@session, url, @consumer.fetcher, @@disco_suffix)
|
|
394
|
-
else
|
|
395
|
-
nil
|
|
396
|
-
end
|
|
397
|
-
end
|
|
398
|
-
end
|
|
399
|
-
end
|
|
400
294
|
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
# users with special needs should ever have to look at this class.
|
|
404
|
-
#
|
|
405
|
-
# The only part of the library which has to be used and isn't
|
|
406
|
-
# documented in full here is the store required to create an
|
|
407
|
-
# OpenID::Consumer instance. More on the abstract store type and
|
|
408
|
-
# concrete implementations of it that are provided in the documentation
|
|
409
|
-
# of OpenID::Consumer.new
|
|
410
|
-
class GenericConsumer
|
|
411
|
-
|
|
412
|
-
# Number of characters to be used in generated nonces
|
|
413
|
-
@@NONCE_LEN = 8
|
|
414
|
-
|
|
415
|
-
# Nonce character set
|
|
416
|
-
@@NONCE_CHRS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
|
417
|
-
@@D_SUFFIX = 'openid_disco'
|
|
418
|
-
|
|
419
|
-
attr_reader :fetcher
|
|
420
|
-
|
|
421
|
-
public
|
|
422
|
-
|
|
423
|
-
# Creates a new Consumer instance. You should create a new
|
|
424
|
-
# instance of the Consumer object with every HTTP request. Do not
|
|
425
|
-
# store the instance of it in a global variable somewhere.
|
|
426
|
-
#
|
|
427
|
-
# [+store+]
|
|
428
|
-
# This must be an object that implements the OpenID::Store interface.
|
|
429
|
-
# Several concrete implementations are provided, to cover
|
|
430
|
-
# most common use cases. We recommend using the simple file based
|
|
431
|
-
# store bundled with the library: OpenID::FilesystemStore.
|
|
432
|
-
#
|
|
433
|
-
# [+fetcher+]
|
|
434
|
-
# Optional. If provided, this must be an instance that implements
|
|
435
|
-
# Fetcher interface. If no fetcher is provided,
|
|
436
|
-
# an instance of OpenID::StandardFetcher will be created for
|
|
437
|
-
# you automatically.
|
|
438
|
-
def initialize(store, fetcher=nil)
|
|
439
|
-
if fetcher.nil?
|
|
440
|
-
fetcher = StandardFetcher.new
|
|
441
|
-
end
|
|
442
|
-
@store = store
|
|
443
|
-
@fetcher = fetcher
|
|
444
|
-
@ca_path = nil
|
|
445
|
-
end
|
|
446
|
-
|
|
447
|
-
# Set the path to a pem certificate authority file for verifying
|
|
448
|
-
# server certificates during HTTPS. If you are interested in verifying
|
|
449
|
-
# certs like the mozilla web browser, have a look at the files here:
|
|
450
|
-
#
|
|
451
|
-
# http://curl.haxx.se/docs/caextract.html
|
|
452
|
-
def ca_path=(ca_path)
|
|
453
|
-
ca_path = ca_path.to_s
|
|
454
|
-
if File.exists?(ca_path)
|
|
455
|
-
@ca_path = ca_path
|
|
456
|
-
@fetcher.ca_path = ca_path
|
|
457
|
-
else
|
|
458
|
-
raise ArgumentError, "#{ca_path} is not a valid file path"
|
|
459
|
-
end
|
|
295
|
+
def session_get(name)
|
|
296
|
+
@session[session_key(name)]
|
|
460
297
|
end
|
|
461
298
|
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
# begin_without_discovery is a light wrapper around this method, and the
|
|
465
|
-
# has the same interface.
|
|
466
|
-
def begin(service)
|
|
467
|
-
nonce = self.create_nonce
|
|
468
|
-
assoc = self.get_association(service.server_url)
|
|
469
|
-
return SuccessRequest.new(assoc, nonce, service)
|
|
299
|
+
def session_set(name, val)
|
|
300
|
+
@session[session_key(name)] = val
|
|
470
301
|
end
|
|
471
302
|
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
# generated in the +begin+ call. The +service+ should be stored somewhere
|
|
475
|
-
# in the user's session or environment and passed into this method
|
|
476
|
-
# along with the full query string Hash. Consumer.complete has the
|
|
477
|
-
# full list of return values for this method.
|
|
478
|
-
def complete(query, service_endpoint)
|
|
479
|
-
return FailureResponse.new(nil, msg='no session state found') unless service_endpoint
|
|
480
|
-
|
|
481
|
-
consumer_id = service_endpoint.consumer_id
|
|
482
|
-
server_id = service_endpoint.server_id
|
|
483
|
-
server_url = service_endpoint.server_url
|
|
484
|
-
|
|
485
|
-
# get the nonce out of the query
|
|
486
|
-
nonce = query['nonce']
|
|
487
|
-
if nonce.nil?
|
|
488
|
-
return FailureResponse.new(consumer_id, 'could not extract nonce')
|
|
489
|
-
end
|
|
490
|
-
|
|
491
|
-
mode = query["openid.mode"]
|
|
492
|
-
|
|
493
|
-
case mode
|
|
494
|
-
when "cancel"
|
|
495
|
-
return CancelResponse.new(consumer_id)
|
|
496
|
-
|
|
497
|
-
when "error"
|
|
498
|
-
error = query["openid.error"]
|
|
499
|
-
unless error.nil?
|
|
500
|
-
OpenID::Util.log('Error: '+error)
|
|
501
|
-
end
|
|
502
|
-
return FailureResponse.new(nil, msg=error)
|
|
503
|
-
|
|
504
|
-
when "id_res"
|
|
505
|
-
return self.do_id_res(nonce, consumer_id, server_id, server_url, query)
|
|
506
|
-
|
|
507
|
-
else
|
|
508
|
-
return FailureResponse.new(nil, msg="unknown mode #{mode}")
|
|
509
|
-
end
|
|
510
|
-
|
|
303
|
+
def session_key(suffix)
|
|
304
|
+
@session_key_prefix + suffix
|
|
511
305
|
end
|
|
512
306
|
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
user_setup_url = query["openid.user_setup_url"]
|
|
516
|
-
if user_setup_url
|
|
517
|
-
return SetupNeededResponse.new(user_setup_url)
|
|
518
|
-
end
|
|
519
|
-
|
|
520
|
-
return_to = query["openid.return_to"]
|
|
521
|
-
server_id2 = query["openid.identity"]
|
|
522
|
-
assoc_handle = query["openid.assoc_handle"]
|
|
523
|
-
|
|
524
|
-
if return_to.nil?
|
|
525
|
-
return FailureResponse.new(consumer_id, msg='openid.return_to was nil')
|
|
526
|
-
elsif server_id2.nil?
|
|
527
|
-
return FailureResponse.new(consumer_id, msg='openid.identity was nil')
|
|
528
|
-
elsif assoc_handle.nil?
|
|
529
|
-
return FailureResponse.new(consumer_id, msg='openid.assoc_handle was nil')
|
|
530
|
-
end
|
|
531
|
-
|
|
532
|
-
if server_id != server_id2
|
|
533
|
-
return FailureResponse.new(consumer_id, msg='server ids do not match')
|
|
534
|
-
end
|
|
535
|
-
|
|
536
|
-
assoc = @store.get_association(server_url, assoc_handle)
|
|
537
|
-
|
|
538
|
-
if assoc.nil?
|
|
539
|
-
# It's not an association we know about. Dumb mode is our
|
|
540
|
-
# only possible path for recovery.
|
|
541
|
-
code, msg = self.check_auth(nonce, query, server_url)
|
|
542
|
-
if code == SUCCESS
|
|
543
|
-
return SuccessResponse.new(consumer_id, query)
|
|
544
|
-
else
|
|
545
|
-
return FailureResponse.new(consumer_id, "check_auth failed: #{msg}")
|
|
546
|
-
end
|
|
547
|
-
end
|
|
548
|
-
|
|
549
|
-
if assoc.expires_in <= 0
|
|
550
|
-
OpenID::Util.log("Association with #{server_url} expired")
|
|
551
|
-
return FailureResponse.new(consumer_id, 'assoc expired')
|
|
552
|
-
end
|
|
553
|
-
|
|
554
|
-
# Check the signature
|
|
555
|
-
sig = query["openid.sig"]
|
|
556
|
-
return FailureResponse.new(consumer_id, 'no sig') if sig.nil?
|
|
557
|
-
signed = query["openid.signed"]
|
|
558
|
-
return FailureResponse.new(consumer_id, 'no signed') if signed.nil?
|
|
559
|
-
|
|
560
|
-
args = OpenID::Util.get_openid_params(query)
|
|
561
|
-
signed_list = signed.split(",")
|
|
562
|
-
_signed, v_sig = OpenID::Util.sign_reply(args, assoc.secret, signed_list)
|
|
563
|
-
|
|
564
|
-
if v_sig != sig
|
|
565
|
-
return FailureResponse.new(consumer_id, 'sig mismatch')
|
|
566
|
-
end
|
|
567
|
-
|
|
568
|
-
unless @store.use_nonce(nonce)
|
|
569
|
-
return FailureResponse.new(consumer_id, 'nonce already used')
|
|
570
|
-
end
|
|
571
|
-
|
|
572
|
-
return SuccessResponse.new(consumer_id, query)
|
|
307
|
+
def last_requested_endpoint
|
|
308
|
+
session_get('last_requested_endpoint')
|
|
573
309
|
end
|
|
574
310
|
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
check_args = OpenID::Util.get_openid_params(query)
|
|
578
|
-
check_args["openid.mode"] = "check_authentication"
|
|
579
|
-
post_data = OpenID::Util.urlencode(check_args)
|
|
580
|
-
|
|
581
|
-
ret = @fetcher.post(server_url, post_data)
|
|
582
|
-
if ret.nil?
|
|
583
|
-
return FAILURE, "unable to post to #{server_url}"
|
|
584
|
-
else
|
|
585
|
-
url, body = ret
|
|
586
|
-
end
|
|
587
|
-
|
|
588
|
-
results = OpenID::Util.parsekv(body)
|
|
589
|
-
is_valid = results.fetch("is_valid", "false")
|
|
590
|
-
|
|
591
|
-
if is_valid == "true"
|
|
592
|
-
|
|
593
|
-
# we started this request with a bad association,
|
|
594
|
-
# falling back to dumb mode, the invalidate_handle tells
|
|
595
|
-
# us to handle of the assoc to remove from our store.
|
|
596
|
-
invalidate_handle = results["invalidate_handle"]
|
|
597
|
-
if invalidate_handle
|
|
598
|
-
@store.remove_association(server_url, invalidate_handle)
|
|
599
|
-
end
|
|
600
|
-
|
|
601
|
-
# make sure response is not getting replayed by checking the nonce
|
|
602
|
-
unless @store.use_nonce(nonce)
|
|
603
|
-
return FAILURE, "#{server_url}, nonce #{nonce} already used"
|
|
604
|
-
end
|
|
605
|
-
|
|
606
|
-
# is_valid = true, and we successfully used the nonce.
|
|
607
|
-
return SUCCESS, nil
|
|
608
|
-
end
|
|
609
|
-
|
|
610
|
-
error = results["error"]
|
|
611
|
-
if error
|
|
612
|
-
msg = "error from server: #{error}"
|
|
613
|
-
else
|
|
614
|
-
msg = "is_valid was false"
|
|
615
|
-
end
|
|
616
|
-
return FAILURE, msg
|
|
311
|
+
def last_requested_endpoint=(endpoint)
|
|
312
|
+
session_set('last_requested_endpoint', endpoint)
|
|
617
313
|
end
|
|
618
314
|
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
# build the nonce and store it
|
|
622
|
-
nonce = OpenID::Util.random_string(@@NONCE_LEN, @@NONCE_CHRS)
|
|
623
|
-
@store.store_nonce(nonce)
|
|
624
|
-
return nonce
|
|
315
|
+
def cleanup_last_requested_endpoint
|
|
316
|
+
@session[session_key('last_requested_endpoint')] = nil
|
|
625
317
|
end
|
|
626
318
|
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
def get_association(server_url)
|
|
630
|
-
return nil if @store.dumb?
|
|
631
|
-
assoc = @store.get_association(server_url)
|
|
632
|
-
return assoc unless assoc.nil?
|
|
633
|
-
return self.associate(server_url)
|
|
319
|
+
def discovery_manager(openid_identifier)
|
|
320
|
+
DiscoveryManager.new(@session, openid_identifier, @session_key_prefix)
|
|
634
321
|
end
|
|
635
|
-
|
|
636
|
-
# Make the openid.associate call to the server.
|
|
637
|
-
def associate(server_url)
|
|
638
|
-
dh = OpenID::DiffieHellman.new
|
|
639
|
-
cpub = OpenID::Util.to_base64(OpenID::Util.num_to_str(dh.public))
|
|
640
|
-
args = {
|
|
641
|
-
'openid.mode' => 'associate',
|
|
642
|
-
'openid.assoc_type' =>'HMAC-SHA1',
|
|
643
|
-
'openid.session_type' =>'DH-SHA1',
|
|
644
|
-
'openid.dh_modulus' => OpenID::Util.to_base64(OpenID::Util.num_to_str(dh.p)),
|
|
645
|
-
'openid.dh_gen' => OpenID::Util.to_base64(OpenID::Util.num_to_str(dh.g)),
|
|
646
|
-
'openid.dh_consumer_public' => cpub
|
|
647
|
-
}
|
|
648
|
-
body = OpenID::Util.urlencode(args)
|
|
649
|
-
|
|
650
|
-
ret = @fetcher.post(server_url, body)
|
|
651
|
-
return nil if ret.nil?
|
|
652
|
-
url, data = ret
|
|
653
|
-
results = OpenID::Util.parsekv(data)
|
|
654
|
-
|
|
655
|
-
assoc_type = results["assoc_type"]
|
|
656
|
-
return nil if assoc_type.nil? or assoc_type != "HMAC-SHA1"
|
|
657
|
-
|
|
658
|
-
assoc_handle = results["assoc_handle"]
|
|
659
|
-
return nil if assoc_handle.nil?
|
|
660
|
-
|
|
661
|
-
expires_in = results.fetch("expires_in", "0").to_i
|
|
662
322
|
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
secret = OpenID::Util.from_base64(results["mac_key"])
|
|
666
|
-
else
|
|
667
|
-
return nil if session_type != "DH-SHA1"
|
|
668
|
-
|
|
669
|
-
dh_server_public = results["dh_server_public"]
|
|
670
|
-
return nil if dh_server_public.nil?
|
|
671
|
-
|
|
672
|
-
spub = OpenID::Util.str_to_num(OpenID::Util.from_base64(dh_server_public))
|
|
673
|
-
dh_shared = dh.get_shared_secret(spub)
|
|
674
|
-
enc_mac_key = results["enc_mac_key"]
|
|
675
|
-
secret = OpenID::Util.strxor(OpenID::Util.from_base64(enc_mac_key),
|
|
676
|
-
OpenID::Util.sha1(OpenID::Util.num_to_str(dh_shared)))
|
|
677
|
-
end
|
|
678
|
-
|
|
679
|
-
assoc = OpenID::Association.from_expires_in(expires_in, assoc_handle,
|
|
680
|
-
secret, 'HMAC-SHA1')
|
|
681
|
-
@store.store_association(server_url, assoc)
|
|
682
|
-
return assoc
|
|
323
|
+
def cleanup_session
|
|
324
|
+
discovery_manager(nil).cleanup(true)
|
|
683
325
|
end
|
|
684
326
|
|
|
685
|
-
end
|
|
686
|
-
|
|
687
|
-
# Base class for objects returned from Consumer.begin and Consumer.complete
|
|
688
|
-
class OpenIDStatus
|
|
689
|
-
|
|
690
|
-
attr_reader :status
|
|
691
327
|
|
|
692
|
-
def
|
|
693
|
-
|
|
328
|
+
def discover(identifier)
|
|
329
|
+
OpenID.discover(identifier)
|
|
694
330
|
end
|
|
695
331
|
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
# Returned by Consumer.begin when server information cannot be determined
|
|
699
|
-
# from the provided identity URL. The +msg+ method may return a useful
|
|
700
|
-
# string for debugging the request.
|
|
701
|
-
class FailureRequest < OpenIDStatus
|
|
702
|
-
|
|
703
|
-
attr_reader :msg
|
|
704
|
-
|
|
705
|
-
def initialize(msg='')
|
|
706
|
-
super(FAILURE)
|
|
707
|
-
@msg = msg
|
|
332
|
+
def negotiator
|
|
333
|
+
DefaultNegotiator
|
|
708
334
|
end
|
|
709
335
|
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
# Consumer.begin.
|
|
714
|
-
class SuccessRequest < OpenIDStatus
|
|
715
|
-
|
|
716
|
-
attr_reader :server_id, :server_url, :nonce, :identity_url, \
|
|
717
|
-
:service, :return_to_args
|
|
718
|
-
|
|
719
|
-
# Creates a new SuccessRequest object. This just stores each
|
|
720
|
-
# argument in an appropriately named field.
|
|
721
|
-
#
|
|
722
|
-
# Users of this library should not create instances of this
|
|
723
|
-
# class. Instances of this class are created by Consumer
|
|
724
|
-
# during begin.
|
|
725
|
-
def initialize(assoc, nonce, service)
|
|
726
|
-
super(SUCCESS)
|
|
727
|
-
@service = service
|
|
728
|
-
@server_id = service.server_id
|
|
729
|
-
@server_url = service.server_url
|
|
730
|
-
@identity_url = service.consumer_id
|
|
731
|
-
@extra_args = {}
|
|
732
|
-
@return_to_args = {'nonce' => nonce}
|
|
733
|
-
|
|
734
|
-
@assoc = assoc
|
|
735
|
-
@nonce = nonce
|
|
736
|
-
end
|
|
737
|
-
|
|
738
|
-
# Called to construct the redirect URL sent to
|
|
739
|
-
# the browser to ask the server to verify its identity. This is
|
|
740
|
-
# called in step 3 of the flow described in the overview.
|
|
741
|
-
# Please note that you don't need to call this method directly
|
|
742
|
-
# unless you need to create a custom redirect, as it is called
|
|
743
|
-
# directly during begin. The generated redirect should be
|
|
744
|
-
# sent to the browser which initiated the authorization request.
|
|
745
|
-
#
|
|
746
|
-
# ==Parameters
|
|
747
|
-
# [+trust_root+]
|
|
748
|
-
# This is a URL that will be sent to the
|
|
749
|
-
# server to identify this site. The OpenID spec (
|
|
750
|
-
# http://www.openid.net/specs.bml#mode-checkid_immediate )
|
|
751
|
-
# has more information on what the trust_root value is for
|
|
752
|
-
# and what its form can be. While the trust root is
|
|
753
|
-
# officially optional in the OpenID specification, this
|
|
754
|
-
# implementation requires that it be set. Nothing is
|
|
755
|
-
# actually gained by leaving out the trust root, as you can
|
|
756
|
-
# get identical behavior by specifying the return_to URL as
|
|
757
|
-
# the trust root.
|
|
758
|
-
#
|
|
759
|
-
# [+return_to+]
|
|
760
|
-
# This is the URL that will be included in the
|
|
761
|
-
# generated redirect as the URL the OpenID server will send
|
|
762
|
-
# its response to. The URL passed in must handle OpenID
|
|
763
|
-
# authentication responses.
|
|
764
|
-
#
|
|
765
|
-
# [+immediate+]
|
|
766
|
-
# Optional. If +immediate+ is true, the request will be made using
|
|
767
|
-
# openid.mode=checkid_immediate instead of the standard
|
|
768
|
-
# openid.mode=checkid_setup.
|
|
769
|
-
#
|
|
770
|
-
# ==Return Value
|
|
771
|
-
# Return a string which is the URL to which you should redirect the user.
|
|
772
|
-
def redirect_url(trust_root, return_to, immediate=false)
|
|
773
|
-
# add the nonce into the return_to url
|
|
774
|
-
return_to = OpenID::Util.append_args(return_to, @return_to_args)
|
|
775
|
-
|
|
776
|
-
redir_args = {
|
|
777
|
-
"openid.identity" => @server_id,
|
|
778
|
-
"openid.return_to" => return_to,
|
|
779
|
-
"openid.trust_root" => trust_root,
|
|
780
|
-
"openid.mode" => immediate ? 'checkid_immediate' : 'checkid_setup'
|
|
781
|
-
}
|
|
782
|
-
|
|
783
|
-
redir_args["openid.assoc_handle"] = @assoc.handle if @assoc
|
|
784
|
-
redir_args.update(@extra_args)
|
|
785
|
-
|
|
786
|
-
return OpenID::Util.append_args(server_url, redir_args).to_s
|
|
336
|
+
def association_manager(service)
|
|
337
|
+
AssociationManager.new(@store, service.server_url,
|
|
338
|
+
service.compatibility_mode, negotiator)
|
|
787
339
|
end
|
|
788
340
|
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
OpenID::Util.append_args(return_to, @return_to_args)
|
|
341
|
+
def handle_idres(message, return_to)
|
|
342
|
+
IdResHandler.new(message, return_to, @store, last_requested_endpoint)
|
|
792
343
|
end
|
|
793
344
|
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
# req.add_extension_arg('sreg','optional','nickname,gender')
|
|
799
|
-
# req.add_extension_arg('sreg','policy_url','http://example.com/policy')
|
|
800
|
-
def add_extension_arg(namespace, key, value)
|
|
801
|
-
@extra_args['openid.'+namespace+'.'+key] = value
|
|
345
|
+
def complete_invalid(message, unused_return_to)
|
|
346
|
+
mode = message.get_arg(OPENID_NS, 'mode', '<No mode set>')
|
|
347
|
+
return FailureResponse.new(last_requested_endpoint,
|
|
348
|
+
"Invalid openid.mode: #{mode}")
|
|
802
349
|
end
|
|
803
350
|
|
|
804
|
-
def
|
|
805
|
-
|
|
351
|
+
def complete_cancel(unused_message, unused_return_to)
|
|
352
|
+
return CancelResponse.new(last_requested_endpoint)
|
|
806
353
|
end
|
|
807
354
|
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
355
|
+
def complete_error(message, unused_return_to)
|
|
356
|
+
error = message.get_arg(OPENID_NS, 'error')
|
|
357
|
+
contact = message.get_arg(OPENID_NS, 'contact')
|
|
358
|
+
reference = message.get_arg(OPENID_NS, 'reference')
|
|
812
359
|
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
return true
|
|
816
|
-
end
|
|
817
|
-
end
|
|
818
|
-
|
|
819
|
-
return false
|
|
360
|
+
return FailureResponse.new(last_requested_endpoint,
|
|
361
|
+
error, contact, reference)
|
|
820
362
|
end
|
|
821
363
|
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
attr_reader :identity_url
|
|
830
|
-
attr_accessor :service
|
|
831
|
-
|
|
832
|
-
# Instances of this object will be created for you automatically
|
|
833
|
-
# by OpenID::Consumer. You should never have to construct this
|
|
834
|
-
# object yourself.
|
|
835
|
-
def initialize(identity_url, query)
|
|
836
|
-
super(SUCCESS)
|
|
837
|
-
@identity_url = identity_url
|
|
838
|
-
@query = query
|
|
839
|
-
@service = nil
|
|
364
|
+
def complete_setup_needed(message, unused_return_to)
|
|
365
|
+
if message.is_openid1
|
|
366
|
+
return complete_invalid(message, nil)
|
|
367
|
+
else
|
|
368
|
+
return SetupNeededResponse.new(last_requested_endpoint, nil)
|
|
369
|
+
end
|
|
840
370
|
end
|
|
841
371
|
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
#
|
|
848
|
-
# {'email' => 'mayor@example.com', 'nickname' => 'MayorMcCheese'}
|
|
849
|
-
#
|
|
850
|
-
# The extension namespace is not included in the keys of the returned
|
|
851
|
-
# hash. Values returned from this method are guaranteed to be signed.
|
|
852
|
-
# Calling this method should be the *only* way you access extension
|
|
853
|
-
# response data!
|
|
854
|
-
def extension_response(extension_name)
|
|
855
|
-
prefix = extension_name
|
|
856
|
-
|
|
857
|
-
signed = @query['openid.signed']
|
|
858
|
-
return nil if signed.nil?
|
|
859
|
-
|
|
860
|
-
signed = signed.split(',')
|
|
861
|
-
extension_args = {}
|
|
862
|
-
extension_prefix = prefix + '.'
|
|
863
|
-
|
|
864
|
-
signed.each do |arg|
|
|
865
|
-
if arg.index(extension_prefix) == 0
|
|
866
|
-
query_key = 'openid.'+arg
|
|
867
|
-
extension_args[arg[(1+prefix.length..-1)]] = @query[query_key]
|
|
372
|
+
def complete_id_res(message, return_to)
|
|
373
|
+
if message.is_openid1
|
|
374
|
+
setup_url = message.get_arg(OPENID1_NS, 'user_setup_url')
|
|
375
|
+
if !setup_url.nil?
|
|
376
|
+
return SetupNeededResponse.new(last_requested_endpoint, setup_url)
|
|
868
377
|
end
|
|
869
378
|
end
|
|
870
|
-
|
|
871
|
-
return extension_args
|
|
872
|
-
end
|
|
873
|
-
|
|
874
|
-
end
|
|
875
379
|
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
def initialize(identity_url=nil, msg=nil)
|
|
885
|
-
super(FAILURE)
|
|
886
|
-
@identity_url = identity_url
|
|
887
|
-
@msg = msg
|
|
888
|
-
end
|
|
889
|
-
|
|
890
|
-
end
|
|
891
|
-
|
|
892
|
-
# Returned by Consumer.begin in immediate mode when the user needs to
|
|
893
|
-
# log into the OpenID server. User should be redirected to the value
|
|
894
|
-
# returned from the +setup_url+ method.
|
|
895
|
-
class SetupNeededResponse < OpenIDStatus
|
|
896
|
-
|
|
897
|
-
attr_reader :setup_url
|
|
898
|
-
attr_accessor :identity_url, :service
|
|
899
|
-
|
|
900
|
-
def initialize(setup_url)
|
|
901
|
-
super(SETUP_NEEDED)
|
|
902
|
-
@setup_url = setup_url
|
|
903
|
-
end
|
|
904
|
-
|
|
905
|
-
end
|
|
906
|
-
|
|
907
|
-
# Response returned from Consumer.complete when the user cancels the
|
|
908
|
-
# OpenID transaction.
|
|
909
|
-
class CancelResponse < OpenIDStatus
|
|
910
|
-
attr_accessor :identity_url, :service
|
|
911
|
-
def initialize(identity_url)
|
|
912
|
-
super(CANCEL)
|
|
913
|
-
@identity_url = identity_url
|
|
380
|
+
begin
|
|
381
|
+
idres = handle_idres(message, return_to)
|
|
382
|
+
rescue DiscoveryFailure, ProtocolError => why
|
|
383
|
+
return FailureResponse.new(last_requested_endpoint, why.message)
|
|
384
|
+
else
|
|
385
|
+
return SuccessResponse.new(idres.endpoint, message,
|
|
386
|
+
idres.signed_fields)
|
|
387
|
+
end
|
|
914
388
|
end
|
|
915
389
|
end
|
|
916
|
-
|
|
917
390
|
end
|