entp-ruby-openid 2.2
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/CHANGELOG +215 -0
- data/INSTALL +47 -0
- data/LICENSE +210 -0
- data/NOTICE +2 -0
- data/README +85 -0
- data/UPGRADE +127 -0
- data/admin/runtests.rb +45 -0
- data/examples/README +32 -0
- data/examples/active_record_openid_store/README +58 -0
- data/examples/active_record_openid_store/XXX_add_open_id_store_to_db.rb +24 -0
- data/examples/active_record_openid_store/XXX_upgrade_open_id_store.rb +26 -0
- data/examples/active_record_openid_store/init.rb +8 -0
- data/examples/active_record_openid_store/lib/association.rb +10 -0
- data/examples/active_record_openid_store/lib/nonce.rb +3 -0
- data/examples/active_record_openid_store/lib/open_id_setting.rb +4 -0
- data/examples/active_record_openid_store/lib/openid_ar_store.rb +57 -0
- data/examples/active_record_openid_store/test/store_test.rb +212 -0
- data/examples/discover +49 -0
- data/examples/rails_openid/README +153 -0
- data/examples/rails_openid/Rakefile +10 -0
- data/examples/rails_openid/app/controllers/application.rb +4 -0
- data/examples/rails_openid/app/controllers/consumer_controller.rb +125 -0
- data/examples/rails_openid/app/controllers/login_controller.rb +45 -0
- data/examples/rails_openid/app/controllers/server_controller.rb +265 -0
- data/examples/rails_openid/app/helpers/application_helper.rb +3 -0
- data/examples/rails_openid/app/helpers/login_helper.rb +2 -0
- data/examples/rails_openid/app/helpers/server_helper.rb +9 -0
- data/examples/rails_openid/app/views/consumer/index.rhtml +81 -0
- data/examples/rails_openid/app/views/layouts/server.rhtml +68 -0
- data/examples/rails_openid/app/views/login/index.rhtml +56 -0
- data/examples/rails_openid/app/views/server/decide.rhtml +26 -0
- data/examples/rails_openid/config/boot.rb +19 -0
- data/examples/rails_openid/config/database.yml +74 -0
- data/examples/rails_openid/config/environment.rb +54 -0
- data/examples/rails_openid/config/environments/development.rb +19 -0
- data/examples/rails_openid/config/environments/production.rb +19 -0
- data/examples/rails_openid/config/environments/test.rb +19 -0
- data/examples/rails_openid/config/routes.rb +24 -0
- data/examples/rails_openid/doc/README_FOR_APP +2 -0
- data/examples/rails_openid/public/404.html +8 -0
- data/examples/rails_openid/public/500.html +8 -0
- data/examples/rails_openid/public/dispatch.cgi +12 -0
- data/examples/rails_openid/public/dispatch.fcgi +26 -0
- data/examples/rails_openid/public/dispatch.rb +12 -0
- data/examples/rails_openid/public/favicon.ico +0 -0
- data/examples/rails_openid/public/images/openid_login_bg.gif +0 -0
- data/examples/rails_openid/public/javascripts/controls.js +750 -0
- data/examples/rails_openid/public/javascripts/dragdrop.js +584 -0
- data/examples/rails_openid/public/javascripts/effects.js +854 -0
- data/examples/rails_openid/public/javascripts/prototype.js +1785 -0
- data/examples/rails_openid/public/robots.txt +1 -0
- data/examples/rails_openid/script/about +3 -0
- data/examples/rails_openid/script/breakpointer +3 -0
- data/examples/rails_openid/script/console +3 -0
- data/examples/rails_openid/script/destroy +3 -0
- data/examples/rails_openid/script/generate +3 -0
- data/examples/rails_openid/script/performance/benchmarker +3 -0
- data/examples/rails_openid/script/performance/profiler +3 -0
- data/examples/rails_openid/script/plugin +3 -0
- data/examples/rails_openid/script/process/reaper +3 -0
- data/examples/rails_openid/script/process/spawner +3 -0
- data/examples/rails_openid/script/process/spinner +3 -0
- data/examples/rails_openid/script/runner +3 -0
- data/examples/rails_openid/script/server +3 -0
- data/examples/rails_openid/test/functional/login_controller_test.rb +18 -0
- data/examples/rails_openid/test/functional/server_controller_test.rb +18 -0
- data/examples/rails_openid/test/test_helper.rb +28 -0
- data/lib/hmac/hmac.rb +112 -0
- data/lib/hmac/sha1.rb +11 -0
- data/lib/hmac/sha2.rb +25 -0
- data/lib/openid.rb +22 -0
- data/lib/openid/association.rb +249 -0
- data/lib/openid/consumer.rb +395 -0
- data/lib/openid/consumer/associationmanager.rb +344 -0
- data/lib/openid/consumer/checkid_request.rb +186 -0
- data/lib/openid/consumer/discovery.rb +497 -0
- data/lib/openid/consumer/discovery_manager.rb +123 -0
- data/lib/openid/consumer/html_parse.rb +134 -0
- data/lib/openid/consumer/idres.rb +523 -0
- data/lib/openid/consumer/responses.rb +150 -0
- data/lib/openid/cryptutil.rb +115 -0
- data/lib/openid/dh.rb +89 -0
- data/lib/openid/extension.rb +39 -0
- data/lib/openid/extensions/ax.rb +539 -0
- data/lib/openid/extensions/oauth.rb +91 -0
- data/lib/openid/extensions/pape.rb +179 -0
- data/lib/openid/extensions/sreg.rb +277 -0
- data/lib/openid/extras.rb +11 -0
- data/lib/openid/fetchers.rb +258 -0
- data/lib/openid/kvform.rb +136 -0
- data/lib/openid/kvpost.rb +58 -0
- data/lib/openid/message.rb +553 -0
- data/lib/openid/protocolerror.rb +12 -0
- data/lib/openid/server.rb +1544 -0
- data/lib/openid/store.rb +10 -0
- data/lib/openid/store/filesystem.rb +272 -0
- data/lib/openid/store/interface.rb +75 -0
- data/lib/openid/store/memcache.rb +109 -0
- data/lib/openid/store/memory.rb +84 -0
- data/lib/openid/store/nonce.rb +68 -0
- data/lib/openid/trustroot.rb +349 -0
- data/lib/openid/urinorm.rb +75 -0
- data/lib/openid/util.rb +119 -0
- data/lib/openid/version.rb +3 -0
- data/lib/openid/yadis.rb +15 -0
- 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/yadis/htmltokenizer.rb +305 -0
- data/lib/openid/yadis/parsehtml.rb +45 -0
- data/lib/openid/yadis/services.rb +42 -0
- data/lib/openid/yadis/xrds.rb +155 -0
- data/lib/openid/yadis/xri.rb +90 -0
- data/lib/openid/yadis/xrires.rb +91 -0
- data/test/data/test_discover/openid_utf8.html +11 -0
- data/test/support/test_data_mixin.rb +127 -0
- data/test/support/test_util.rb +53 -0
- data/test/support/yadis_data.rb +131 -0
- data/test/support/yadis_data/accept.txt +124 -0
- data/test/support/yadis_data/dh.txt +29 -0
- data/test/support/yadis_data/example-xrds.xml +14 -0
- data/test/support/yadis_data/linkparse.txt +587 -0
- data/test/support/yadis_data/n2b64 +650 -0
- data/test/support/yadis_data/test1-discover.txt +137 -0
- data/test/support/yadis_data/test1-parsehtml.txt +152 -0
- data/test/support/yadis_data/test_discover/malformed_meta_tag.html +19 -0
- data/test/support/yadis_data/test_discover/openid.html +11 -0
- data/test/support/yadis_data/test_discover/openid2.html +11 -0
- data/test/support/yadis_data/test_discover/openid2_xrds.xml +12 -0
- data/test/support/yadis_data/test_discover/openid2_xrds_no_local_id.xml +11 -0
- data/test/support/yadis_data/test_discover/openid_1_and_2.html +11 -0
- data/test/support/yadis_data/test_discover/openid_1_and_2_xrds.xml +16 -0
- data/test/support/yadis_data/test_discover/openid_1_and_2_xrds_bad_delegate.xml +17 -0
- data/test/support/yadis_data/test_discover/openid_and_yadis.html +12 -0
- data/test/support/yadis_data/test_discover/openid_no_delegate.html +10 -0
- data/test/support/yadis_data/test_discover/openid_utf8.html +11 -0
- data/test/support/yadis_data/test_discover/yadis_0entries.xml +12 -0
- data/test/support/yadis_data/test_discover/yadis_2_bad_local_id.xml +15 -0
- data/test/support/yadis_data/test_discover/yadis_2entries_delegate.xml +22 -0
- data/test/support/yadis_data/test_discover/yadis_2entries_idp.xml +21 -0
- data/test/support/yadis_data/test_discover/yadis_another_delegate.xml +14 -0
- data/test/support/yadis_data/test_discover/yadis_idp.xml +12 -0
- data/test/support/yadis_data/test_discover/yadis_idp_delegate.xml +13 -0
- data/test/support/yadis_data/test_discover/yadis_no_delegate.xml +11 -0
- data/test/support/yadis_data/test_xrds/=j3h.2007.11.14.xrds +25 -0
- data/test/support/yadis_data/test_xrds/README +12 -0
- data/test/support/yadis_data/test_xrds/delegated-20060809-r1.xrds +34 -0
- data/test/support/yadis_data/test_xrds/delegated-20060809-r2.xrds +34 -0
- data/test/support/yadis_data/test_xrds/delegated-20060809.xrds +34 -0
- data/test/support/yadis_data/test_xrds/no-xrd.xml +7 -0
- data/test/support/yadis_data/test_xrds/not-xrds.xml +2 -0
- data/test/support/yadis_data/test_xrds/prefixsometimes.xrds +34 -0
- data/test/support/yadis_data/test_xrds/ref.xrds +109 -0
- data/test/support/yadis_data/test_xrds/sometimesprefix.xrds +34 -0
- data/test/support/yadis_data/test_xrds/spoof1.xrds +25 -0
- data/test/support/yadis_data/test_xrds/spoof2.xrds +25 -0
- data/test/support/yadis_data/test_xrds/spoof3.xrds +37 -0
- data/test/support/yadis_data/test_xrds/status222.xrds +9 -0
- data/test/support/yadis_data/test_xrds/subsegments.xrds +58 -0
- data/test/support/yadis_data/test_xrds/valid-populated-xrds.xml +39 -0
- data/test/support/yadis_data/trustroot.txt +153 -0
- data/test/support/yadis_data/urinorm.txt +79 -0
- data/test/test_accept.rb +170 -0
- data/test/test_association.rb +268 -0
- data/test/test_associationmanager.rb +918 -0
- data/test/test_ax.rb +690 -0
- data/test/test_checkid_request.rb +293 -0
- data/test/test_consumer.rb +260 -0
- data/test/test_cryptutil.rb +119 -0
- data/test/test_dh.rb +85 -0
- data/test/test_discover.rb +848 -0
- data/test/test_discovery_manager.rb +259 -0
- data/test/test_extension.rb +46 -0
- data/test/test_extras.rb +35 -0
- data/test/test_fetchers.rb +554 -0
- data/test/test_filters.rb +269 -0
- data/test/test_helper.rb +4 -0
- data/test/test_idres.rb +961 -0
- data/test/test_kvform.rb +164 -0
- data/test/test_kvpost.rb +64 -0
- data/test/test_linkparse.rb +100 -0
- data/test/test_message.rb +1115 -0
- data/test/test_nonce.rb +89 -0
- data/test/test_oauth.rb +176 -0
- data/test/test_openid_yadis.rb +177 -0
- data/test/test_pape.rb +248 -0
- data/test/test_parsehtml.rb +79 -0
- data/test/test_responses.rb +63 -0
- data/test/test_server.rb +2455 -0
- data/test/test_sreg.rb +479 -0
- data/test/test_stores.rb +292 -0
- data/test/test_trustroot.rb +111 -0
- data/test/test_urinorm.rb +34 -0
- data/test/test_util.rb +145 -0
- data/test/test_xrds.rb +167 -0
- data/test/test_xri.rb +48 -0
- data/test/test_xrires.rb +67 -0
- data/test/test_yadis_discovery.rb +218 -0
- metadata +268 -0
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
module OpenID
|
|
2
|
+
class Consumer
|
|
3
|
+
# Code returned when either the of the
|
|
4
|
+
# OpenID::OpenIDConsumer.begin_auth or OpenID::OpenIDConsumer.complete_auth
|
|
5
|
+
# methods return successfully.
|
|
6
|
+
SUCCESS = :success
|
|
7
|
+
|
|
8
|
+
# Code OpenID::OpenIDConsumer.complete_auth
|
|
9
|
+
# returns when the value it received indicated an invalid login.
|
|
10
|
+
FAILURE = :failure
|
|
11
|
+
|
|
12
|
+
# Code returned by OpenIDConsumer.complete_auth when the user
|
|
13
|
+
# cancels the operation from the server.
|
|
14
|
+
CANCEL = :cancel
|
|
15
|
+
|
|
16
|
+
# Code returned by OpenID::OpenIDConsumer.complete_auth when the
|
|
17
|
+
# OpenIDConsumer instance is in immediate mode and ther server sends back a
|
|
18
|
+
# URL for the user to login with.
|
|
19
|
+
SETUP_NEEDED = :setup_needed
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
module Response
|
|
23
|
+
attr_reader :endpoint
|
|
24
|
+
|
|
25
|
+
def status
|
|
26
|
+
self.class::STATUS
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# The identity URL that has been authenticated; the Claimed Identifier.
|
|
30
|
+
# See also display_identifier.
|
|
31
|
+
def identity_url
|
|
32
|
+
@endpoint ? @endpoint.claimed_id : nil
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# The display identifier is related to the Claimed Identifier, but the
|
|
36
|
+
# two are not always identical. The display identifier is something the
|
|
37
|
+
# user should recognize as what they entered, whereas the response's
|
|
38
|
+
# claimed identifier (in the identity_url attribute) may have extra
|
|
39
|
+
# information for better persistence.
|
|
40
|
+
#
|
|
41
|
+
# URLs will be stripped of their fragments for display. XRIs will
|
|
42
|
+
# display the human-readable identifier (i-name) instead of the
|
|
43
|
+
# persistent identifier (i-number).
|
|
44
|
+
#
|
|
45
|
+
# Use the display identifier in your user interface. Use identity_url
|
|
46
|
+
# for querying your database or authorization server, or other
|
|
47
|
+
# identifier equality comparisons.
|
|
48
|
+
def display_identifier
|
|
49
|
+
@endpoint ? @endpoint.display_identifier : nil
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# A successful acknowledgement from the OpenID server that the
|
|
54
|
+
# supplied URL is, indeed controlled by the requesting agent.
|
|
55
|
+
class SuccessResponse
|
|
56
|
+
include Response
|
|
57
|
+
|
|
58
|
+
STATUS = SUCCESS
|
|
59
|
+
|
|
60
|
+
attr_reader :message, :signed_fields
|
|
61
|
+
|
|
62
|
+
def initialize(endpoint, message, signed_fields)
|
|
63
|
+
# Don't use :endpoint=, because endpoint should never be nil
|
|
64
|
+
# for a successfull transaction.
|
|
65
|
+
@endpoint = endpoint
|
|
66
|
+
@identity_url = endpoint.claimed_id
|
|
67
|
+
@message = message
|
|
68
|
+
@signed_fields = signed_fields
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Was this authentication response an OpenID 1 authentication
|
|
72
|
+
# response?
|
|
73
|
+
def is_openid1
|
|
74
|
+
@message.is_openid1
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Return whether a particular key is signed, regardless of its
|
|
78
|
+
# namespace alias
|
|
79
|
+
def signed?(ns_uri, ns_key)
|
|
80
|
+
@signed_fields.member?(@message.get_key(ns_uri, ns_key))
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Return the specified signed field if available, otherwise
|
|
84
|
+
# return default
|
|
85
|
+
def get_signed(ns_uri, ns_key, default=nil)
|
|
86
|
+
if signed?(ns_uri, ns_key)
|
|
87
|
+
return @message.get_arg(ns_uri, ns_key, default)
|
|
88
|
+
else
|
|
89
|
+
return default
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Get signed arguments from the response message. Return a dict
|
|
94
|
+
# of all arguments in the specified namespace. If any of the
|
|
95
|
+
# arguments are not signed, return nil.
|
|
96
|
+
def get_signed_ns(ns_uri)
|
|
97
|
+
msg_args = @message.get_args(ns_uri)
|
|
98
|
+
msg_args.each_key do |key|
|
|
99
|
+
if !signed?(ns_uri, key)
|
|
100
|
+
return nil
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
return msg_args
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# Return response arguments in the specified namespace.
|
|
107
|
+
# If require_signed is true and the arguments are not signed,
|
|
108
|
+
# return nil.
|
|
109
|
+
def extension_response(namespace_uri, require_signed)
|
|
110
|
+
if require_signed
|
|
111
|
+
get_signed_ns(namespace_uri)
|
|
112
|
+
else
|
|
113
|
+
@message.get_args(namespace_uri)
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
class FailureResponse
|
|
119
|
+
include Response
|
|
120
|
+
STATUS = FAILURE
|
|
121
|
+
|
|
122
|
+
attr_reader :message, :contact, :reference
|
|
123
|
+
def initialize(endpoint, message, contact=nil, reference=nil)
|
|
124
|
+
@endpoint = endpoint
|
|
125
|
+
@message = message
|
|
126
|
+
@contact = contact
|
|
127
|
+
@reference = reference
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
class CancelResponse
|
|
132
|
+
include Response
|
|
133
|
+
STATUS = CANCEL
|
|
134
|
+
def initialize(endpoint)
|
|
135
|
+
@endpoint = endpoint
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
class SetupNeededResponse
|
|
140
|
+
include Response
|
|
141
|
+
STATUS = SETUP_NEEDED
|
|
142
|
+
|
|
143
|
+
attr_reader :setup_url
|
|
144
|
+
def initialize(endpoint, setup_url)
|
|
145
|
+
@endpoint = endpoint
|
|
146
|
+
@setup_url = setup_url
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
end
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
require "openid/util"
|
|
2
|
+
require "digest/sha1"
|
|
3
|
+
require "digest/sha2"
|
|
4
|
+
begin
|
|
5
|
+
require "digest/hmac"
|
|
6
|
+
rescue LoadError
|
|
7
|
+
begin
|
|
8
|
+
# Try loading the ruby-hmac files if they exist
|
|
9
|
+
require "hmac-sha1"
|
|
10
|
+
require "hmac-sha2"
|
|
11
|
+
rescue LoadError
|
|
12
|
+
# Nothing exists use included hmac files
|
|
13
|
+
require "hmac/sha1"
|
|
14
|
+
require "hmac/sha2"
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
module OpenID
|
|
19
|
+
# This module contains everything needed to perform low-level
|
|
20
|
+
# cryptograph and data manipulation tasks.
|
|
21
|
+
module CryptUtil
|
|
22
|
+
|
|
23
|
+
# Generate a random number, doing a little extra work to make it
|
|
24
|
+
# more likely that it's suitable for cryptography. If your system
|
|
25
|
+
# doesn't have /dev/urandom then this number is not
|
|
26
|
+
# cryptographically safe. See
|
|
27
|
+
# <http://www.cosine.org/2007/08/07/security-ruby-kernel-rand/>
|
|
28
|
+
# for more information. max is the largest possible value of such
|
|
29
|
+
# a random number, where the result will be less than max.
|
|
30
|
+
def CryptUtil.rand(max)
|
|
31
|
+
Kernel.srand()
|
|
32
|
+
return Kernel.rand(max)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def CryptUtil.sha1(text)
|
|
36
|
+
return Digest::SHA1.digest(text)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def CryptUtil.hmac_sha1(key, text)
|
|
40
|
+
if Digest.const_defined? :HMAC
|
|
41
|
+
Digest::HMAC.new(key,Digest::SHA1).update(text).digest
|
|
42
|
+
else
|
|
43
|
+
return HMAC::SHA1.digest(key, text)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def CryptUtil.sha256(text)
|
|
48
|
+
return Digest::SHA256.digest(text)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def CryptUtil.hmac_sha256(key, text)
|
|
52
|
+
if Digest.const_defined? :HMAC
|
|
53
|
+
Digest::HMAC.new(key,Digest::SHA256).update(text).digest
|
|
54
|
+
else
|
|
55
|
+
return HMAC::SHA256.digest(key, text)
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Generate a random string of the given length, composed of the
|
|
60
|
+
# specified characters. If chars is nil, generate a string
|
|
61
|
+
# composed of characters in the range 0..255.
|
|
62
|
+
def CryptUtil.random_string(length, chars=nil)
|
|
63
|
+
s = ""
|
|
64
|
+
|
|
65
|
+
unless chars.nil?
|
|
66
|
+
length.times { s << chars[rand(chars.length)] }
|
|
67
|
+
else
|
|
68
|
+
length.times { s << rand(256).chr }
|
|
69
|
+
end
|
|
70
|
+
return s
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Convert a number to its binary representation; return a string
|
|
74
|
+
# of bytes.
|
|
75
|
+
def CryptUtil.num_to_binary(n)
|
|
76
|
+
bits = n.to_s(2)
|
|
77
|
+
prepend = (8 - bits.length % 8)
|
|
78
|
+
bits = ('0' * prepend) + bits
|
|
79
|
+
return [bits].pack('B*')
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Convert a string of bytes into a number.
|
|
83
|
+
def CryptUtil.binary_to_num(s)
|
|
84
|
+
# taken from openid-ruby 0.0.1
|
|
85
|
+
s = "\000" * (4 - (s.length % 4)) + s
|
|
86
|
+
num = 0
|
|
87
|
+
s.unpack('N*').each do |x|
|
|
88
|
+
num <<= 32
|
|
89
|
+
num |= x
|
|
90
|
+
end
|
|
91
|
+
return num
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Encode a number as a base64-encoded byte string.
|
|
95
|
+
def CryptUtil.num_to_base64(l)
|
|
96
|
+
return OpenID::Util.to_base64(num_to_binary(l))
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# Decode a base64 byte string to a number.
|
|
100
|
+
def CryptUtil.base64_to_num(s)
|
|
101
|
+
return binary_to_num(OpenID::Util.from_base64(s))
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def CryptUtil.const_eq(s1, s2)
|
|
105
|
+
if s1.length != s2.length
|
|
106
|
+
return false
|
|
107
|
+
end
|
|
108
|
+
result = true
|
|
109
|
+
s1.length.times do |i|
|
|
110
|
+
result &= (s1[i] == s2[i])
|
|
111
|
+
end
|
|
112
|
+
return result
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
data/lib/openid/dh.rb
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
require "openid/util"
|
|
2
|
+
require "openid/cryptutil"
|
|
3
|
+
|
|
4
|
+
module OpenID
|
|
5
|
+
|
|
6
|
+
# Encapsulates a Diffie-Hellman key exchange. This class is used
|
|
7
|
+
# internally by both the consumer and server objects.
|
|
8
|
+
#
|
|
9
|
+
# Read more about Diffie-Hellman on wikipedia:
|
|
10
|
+
# http://en.wikipedia.org/wiki/Diffie-Hellman
|
|
11
|
+
|
|
12
|
+
class DiffieHellman
|
|
13
|
+
|
|
14
|
+
# From the OpenID specification
|
|
15
|
+
@@default_mod = 155172898181473697471232257763715539915724801966915404479707795314057629378541917580651227423698188993727816152646631438561595825688188889951272158842675419950341258706556549803580104870537681476726513255747040765857479291291572334510643245094715007229621094194349783925984760375594985848253359305585439638443
|
|
16
|
+
@@default_gen = 2
|
|
17
|
+
|
|
18
|
+
attr_reader :modulus, :generator, :public
|
|
19
|
+
|
|
20
|
+
# A new DiffieHellman object, using the modulus and generator from
|
|
21
|
+
# the OpenID specification
|
|
22
|
+
def DiffieHellman.from_defaults
|
|
23
|
+
DiffieHellman.new(@@default_mod, @@default_gen)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def initialize(modulus=nil, generator=nil, priv=nil)
|
|
27
|
+
@modulus = modulus.nil? ? @@default_mod : modulus
|
|
28
|
+
@generator = generator.nil? ? @@default_gen : generator
|
|
29
|
+
set_private(priv.nil? ? OpenID::CryptUtil.rand(@modulus-2) + 1 : priv)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def get_shared_secret(composite)
|
|
33
|
+
DiffieHellman.powermod(composite, @private, @modulus)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def xor_secret(algorithm, composite, secret)
|
|
37
|
+
dh_shared = get_shared_secret(composite)
|
|
38
|
+
packed_dh_shared = OpenID::CryptUtil.num_to_binary(dh_shared)
|
|
39
|
+
hashed_dh_shared = algorithm.call(packed_dh_shared)
|
|
40
|
+
return DiffieHellman.strxor(secret, hashed_dh_shared)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def using_default_values?
|
|
44
|
+
@generator == @@default_gen && @modulus == @@default_mod
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
private
|
|
48
|
+
def set_private(priv)
|
|
49
|
+
@private = priv
|
|
50
|
+
@public = DiffieHellman.powermod(@generator, @private, @modulus)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def DiffieHellman.strxor(s, t)
|
|
54
|
+
if s.length != t.length
|
|
55
|
+
raise ArgumentError, "strxor: lengths don't match. " +
|
|
56
|
+
"Inputs were #{s.inspect} and #{t.inspect}"
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
if String.method_defined? :bytes
|
|
60
|
+
s.bytes.to_a.zip(t.bytes.to_a).map{|sb,tb| sb^tb}.pack('C*')
|
|
61
|
+
else
|
|
62
|
+
indices = 0...(s.length)
|
|
63
|
+
chrs = indices.collect {|i| (s[i]^t[i]).chr}
|
|
64
|
+
chrs.join("")
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# This code is taken from this post:
|
|
69
|
+
# <http://blade.nagaokaut.ac.jp/cgi-bin/scat.\rb/ruby/ruby-talk/19098>
|
|
70
|
+
# by Eric Lee Green.
|
|
71
|
+
def DiffieHellman.powermod(x, n, q)
|
|
72
|
+
counter=0
|
|
73
|
+
n_p=n
|
|
74
|
+
y_p=1
|
|
75
|
+
z_p=x
|
|
76
|
+
while n_p != 0
|
|
77
|
+
if n_p[0]==1
|
|
78
|
+
y_p=(y_p*z_p) % q
|
|
79
|
+
end
|
|
80
|
+
n_p = n_p >> 1
|
|
81
|
+
z_p = (z_p * z_p) % q
|
|
82
|
+
counter += 1
|
|
83
|
+
end
|
|
84
|
+
return y_p
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
require 'openid/message'
|
|
2
|
+
|
|
3
|
+
module OpenID
|
|
4
|
+
# An interface for OpenID extensions.
|
|
5
|
+
class Extension < Object
|
|
6
|
+
|
|
7
|
+
def initialize
|
|
8
|
+
@ns_uri = nil
|
|
9
|
+
@ns_alias = nil
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# Get the string arguments that should be added to an OpenID
|
|
13
|
+
# message for this extension.
|
|
14
|
+
def get_extension_args
|
|
15
|
+
raise NotImplementedError
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Add the arguments from this extension to the provided
|
|
19
|
+
# message, or create a new message containing only those
|
|
20
|
+
# arguments. Returns the message with added extension args.
|
|
21
|
+
def to_message(message = nil)
|
|
22
|
+
if message.nil?
|
|
23
|
+
# warnings.warn('Passing None to Extension.toMessage is deprecated. '
|
|
24
|
+
# 'Creating a message assuming you want OpenID 2.',
|
|
25
|
+
# DeprecationWarning, stacklevel=2)
|
|
26
|
+
Message.new(OPENID2_NS)
|
|
27
|
+
end
|
|
28
|
+
message = Message.new if message.nil?
|
|
29
|
+
|
|
30
|
+
implicit = message.is_openid1()
|
|
31
|
+
|
|
32
|
+
message.namespaces.add_alias(@ns_uri, @ns_alias, implicit)
|
|
33
|
+
# XXX python ignores keyerror if m.ns.getAlias(uri) == alias
|
|
34
|
+
|
|
35
|
+
message.update_args(@ns_uri, get_extension_args)
|
|
36
|
+
return message
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,539 @@
|
|
|
1
|
+
# Implements the OpenID attribute exchange specification, version 1.0
|
|
2
|
+
|
|
3
|
+
require 'openid/extension'
|
|
4
|
+
require 'openid/trustroot'
|
|
5
|
+
require 'openid/message'
|
|
6
|
+
|
|
7
|
+
module OpenID
|
|
8
|
+
module AX
|
|
9
|
+
|
|
10
|
+
UNLIMITED_VALUES = "unlimited"
|
|
11
|
+
MINIMUM_SUPPORTED_ALIAS_LENGTH = 32
|
|
12
|
+
|
|
13
|
+
# check alias for invalid characters, raise AXError if found
|
|
14
|
+
def self.check_alias(name)
|
|
15
|
+
if name.match(/(,|\.)/)
|
|
16
|
+
raise Error, ("Alias #{name.inspect} must not contain a "\
|
|
17
|
+
"comma or period.")
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Raised when data does not comply with AX 1.0 specification
|
|
22
|
+
class Error < ArgumentError
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Abstract class containing common code for attribute exchange messages
|
|
26
|
+
class AXMessage < Extension
|
|
27
|
+
attr_accessor :ns_alias, :mode, :ns_uri
|
|
28
|
+
|
|
29
|
+
NS_URI = 'http://openid.net/srv/ax/1.0'
|
|
30
|
+
def initialize
|
|
31
|
+
@ns_alias = 'ax'
|
|
32
|
+
@ns_uri = NS_URI
|
|
33
|
+
@mode = nil
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
protected
|
|
37
|
+
|
|
38
|
+
# Raise an exception if the mode in the attribute exchange
|
|
39
|
+
# arguments does not match what is expected for this class.
|
|
40
|
+
def check_mode(ax_args)
|
|
41
|
+
actual_mode = ax_args['mode']
|
|
42
|
+
if actual_mode != @mode
|
|
43
|
+
raise Error, "Expected mode #{mode.inspect}, got #{actual_mode.inspect}"
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def new_args
|
|
48
|
+
{'mode' => @mode}
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Represents a single attribute in an attribute exchange
|
|
53
|
+
# request. This should be added to an Request object in order to
|
|
54
|
+
# request the attribute.
|
|
55
|
+
#
|
|
56
|
+
# @ivar required: Whether the attribute will be marked as required
|
|
57
|
+
# when presented to the subject of the attribute exchange
|
|
58
|
+
# request.
|
|
59
|
+
# @type required: bool
|
|
60
|
+
#
|
|
61
|
+
# @ivar count: How many values of this type to request from the
|
|
62
|
+
# subject. Defaults to one.
|
|
63
|
+
# @type count: int
|
|
64
|
+
#
|
|
65
|
+
# @ivar type_uri: The identifier that determines what the attribute
|
|
66
|
+
# represents and how it is serialized. For example, one type URI
|
|
67
|
+
# representing dates could represent a Unix timestamp in base 10
|
|
68
|
+
# and another could represent a human-readable string.
|
|
69
|
+
# @type type_uri: str
|
|
70
|
+
#
|
|
71
|
+
# @ivar ns_alias: The name that should be given to this alias in the
|
|
72
|
+
# request. If it is not supplied, a generic name will be
|
|
73
|
+
# assigned. For example, if you want to call a Unix timestamp
|
|
74
|
+
# value 'tstamp', set its alias to that value. If two attributes
|
|
75
|
+
# in the same message request to use the same alias, the request
|
|
76
|
+
# will fail to be generated.
|
|
77
|
+
# @type alias: str or NoneType
|
|
78
|
+
class AttrInfo < Object
|
|
79
|
+
attr_reader :type_uri, :count, :ns_alias
|
|
80
|
+
attr_accessor :required
|
|
81
|
+
def initialize(type_uri, ns_alias=nil, required=false, count=1)
|
|
82
|
+
@type_uri = type_uri
|
|
83
|
+
@count = count
|
|
84
|
+
@required = required
|
|
85
|
+
@ns_alias = ns_alias
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def wants_unlimited_values?
|
|
89
|
+
@count == UNLIMITED_VALUES
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Given a namespace mapping and a string containing a
|
|
94
|
+
# comma-separated list of namespace aliases, return a list of type
|
|
95
|
+
# URIs that correspond to those aliases.
|
|
96
|
+
# namespace_map: OpenID::NamespaceMap
|
|
97
|
+
def self.to_type_uris(namespace_map, alias_list_s)
|
|
98
|
+
return [] if alias_list_s.nil?
|
|
99
|
+
alias_list_s.split(',').inject([]) {|uris, name|
|
|
100
|
+
type_uri = namespace_map.get_namespace_uri(name)
|
|
101
|
+
raise IndexError, "No type defined for attribute name #{name.inspect}" if type_uri.nil?
|
|
102
|
+
uris << type_uri
|
|
103
|
+
}
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
# An attribute exchange 'fetch_request' message. This message is
|
|
108
|
+
# sent by a relying party when it wishes to obtain attributes about
|
|
109
|
+
# the subject of an OpenID authentication request.
|
|
110
|
+
class FetchRequest < AXMessage
|
|
111
|
+
attr_reader :requested_attributes
|
|
112
|
+
attr_accessor :update_url
|
|
113
|
+
|
|
114
|
+
MODE = 'fetch_request'
|
|
115
|
+
|
|
116
|
+
def initialize(update_url = nil)
|
|
117
|
+
super()
|
|
118
|
+
@mode = MODE
|
|
119
|
+
@requested_attributes = {}
|
|
120
|
+
@update_url = update_url
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# Add an attribute to this attribute exchange request.
|
|
124
|
+
# attribute: AttrInfo, the attribute being requested
|
|
125
|
+
# Raises IndexError if the requested attribute is already present
|
|
126
|
+
# in this request.
|
|
127
|
+
def add(attribute)
|
|
128
|
+
if @requested_attributes[attribute.type_uri]
|
|
129
|
+
raise IndexError, "The attribute #{attribute.type_uri} has already been requested"
|
|
130
|
+
end
|
|
131
|
+
@requested_attributes[attribute.type_uri] = attribute
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# Get the serialized form of this attribute fetch request.
|
|
135
|
+
# returns a hash of the arguments
|
|
136
|
+
def get_extension_args
|
|
137
|
+
aliases = NamespaceMap.new
|
|
138
|
+
required = []
|
|
139
|
+
if_available = []
|
|
140
|
+
ax_args = new_args
|
|
141
|
+
@requested_attributes.each{|type_uri, attribute|
|
|
142
|
+
if attribute.ns_alias
|
|
143
|
+
name = aliases.add_alias(type_uri, attribute.ns_alias)
|
|
144
|
+
else
|
|
145
|
+
name = aliases.add(type_uri)
|
|
146
|
+
end
|
|
147
|
+
if attribute.required
|
|
148
|
+
required << name
|
|
149
|
+
else
|
|
150
|
+
if_available << name
|
|
151
|
+
end
|
|
152
|
+
if attribute.count != 1
|
|
153
|
+
ax_args["count.#{name}"] = attribute.count.to_s
|
|
154
|
+
end
|
|
155
|
+
ax_args["type.#{name}"] = type_uri
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
unless required.empty?
|
|
159
|
+
ax_args['required'] = required.join(',')
|
|
160
|
+
end
|
|
161
|
+
unless if_available.empty?
|
|
162
|
+
ax_args['if_available'] = if_available.join(',')
|
|
163
|
+
end
|
|
164
|
+
return ax_args
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
# Get the type URIs for all attributes that have been marked
|
|
168
|
+
# as required.
|
|
169
|
+
def get_required_attrs
|
|
170
|
+
@requested_attributes.inject([]) {|required, (type_uri, attribute)|
|
|
171
|
+
if attribute.required
|
|
172
|
+
required << type_uri
|
|
173
|
+
else
|
|
174
|
+
required
|
|
175
|
+
end
|
|
176
|
+
}
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
# Extract a FetchRequest from an OpenID message
|
|
180
|
+
# message: OpenID::Message
|
|
181
|
+
# return a FetchRequest or nil if AX arguments are not present
|
|
182
|
+
def self.from_openid_request(oidreq)
|
|
183
|
+
message = oidreq.message
|
|
184
|
+
ax_args = message.get_args(NS_URI)
|
|
185
|
+
return nil if ax_args == {} or ax_args['mode'] != MODE
|
|
186
|
+
req = new
|
|
187
|
+
req.parse_extension_args(ax_args)
|
|
188
|
+
|
|
189
|
+
if req.update_url
|
|
190
|
+
realm = message.get_arg(OPENID_NS, 'realm',
|
|
191
|
+
message.get_arg(OPENID_NS, 'return_to'))
|
|
192
|
+
if realm.nil? or realm.empty?
|
|
193
|
+
raise Error, "Cannot validate update_url #{req.update_url.inspect} against absent realm"
|
|
194
|
+
end
|
|
195
|
+
tr = TrustRoot::TrustRoot.parse(realm)
|
|
196
|
+
unless tr.validate_url(req.update_url)
|
|
197
|
+
raise Error, "Update URL #{req.update_url.inspect} failed validation against realm #{realm.inspect}"
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
return req
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
def parse_extension_args(ax_args)
|
|
205
|
+
check_mode(ax_args)
|
|
206
|
+
|
|
207
|
+
aliases = NamespaceMap.new
|
|
208
|
+
|
|
209
|
+
ax_args.each{|k,v|
|
|
210
|
+
if k.index('type.') == 0
|
|
211
|
+
name = k[5..-1]
|
|
212
|
+
type_uri = v
|
|
213
|
+
aliases.add_alias(type_uri, name)
|
|
214
|
+
|
|
215
|
+
count_key = 'count.'+name
|
|
216
|
+
count_s = ax_args[count_key]
|
|
217
|
+
count = 1
|
|
218
|
+
if count_s
|
|
219
|
+
if count_s == UNLIMITED_VALUES
|
|
220
|
+
count = count_s
|
|
221
|
+
else
|
|
222
|
+
count = count_s.to_i
|
|
223
|
+
if count <= 0
|
|
224
|
+
raise Error, "Invalid value for count #{count_key.inspect}: #{count_s.inspect}"
|
|
225
|
+
end
|
|
226
|
+
end
|
|
227
|
+
end
|
|
228
|
+
add(AttrInfo.new(type_uri, name, false, count))
|
|
229
|
+
end
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
required = AX.to_type_uris(aliases, ax_args['required'])
|
|
233
|
+
required.each{|type_uri|
|
|
234
|
+
@requested_attributes[type_uri].required = true
|
|
235
|
+
}
|
|
236
|
+
if_available = AX.to_type_uris(aliases, ax_args['if_available'])
|
|
237
|
+
all_type_uris = required + if_available
|
|
238
|
+
|
|
239
|
+
aliases.namespace_uris.each{|type_uri|
|
|
240
|
+
unless all_type_uris.member? type_uri
|
|
241
|
+
raise Error, "Type URI #{type_uri.inspect} was in the request but not present in 'required' or 'if_available'"
|
|
242
|
+
end
|
|
243
|
+
}
|
|
244
|
+
@update_url = ax_args['update_url']
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
# return the list of AttrInfo objects contained in the FetchRequest
|
|
248
|
+
def attributes
|
|
249
|
+
@requested_attributes.values
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
# return the list of requested attribute type URIs
|
|
253
|
+
def requested_types
|
|
254
|
+
@requested_attributes.keys
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
def member?(type_uri)
|
|
258
|
+
! @requested_attributes[type_uri].nil?
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
# Abstract class that implements a message that has attribute
|
|
264
|
+
# keys and values. It contains the common code between
|
|
265
|
+
# fetch_response and store_request.
|
|
266
|
+
class KeyValueMessage < AXMessage
|
|
267
|
+
attr_reader :data
|
|
268
|
+
def initialize
|
|
269
|
+
super()
|
|
270
|
+
@mode = nil
|
|
271
|
+
@data = {}
|
|
272
|
+
@data.default = []
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
# Add a single value for the given attribute type to the
|
|
276
|
+
# message. If there are already values specified for this type,
|
|
277
|
+
# this value will be sent in addition to the values already
|
|
278
|
+
# specified.
|
|
279
|
+
def add_value(type_uri, value)
|
|
280
|
+
@data[type_uri] = @data[type_uri] << value
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
# Set the values for the given attribute type. This replaces
|
|
284
|
+
# any values that have already been set for this attribute.
|
|
285
|
+
def set_values(type_uri, values)
|
|
286
|
+
@data[type_uri] = values
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
# Get the extension arguments for the key/value pairs
|
|
290
|
+
# contained in this message.
|
|
291
|
+
def _get_extension_kv_args(aliases = nil)
|
|
292
|
+
aliases = NamespaceMap.new if aliases.nil?
|
|
293
|
+
|
|
294
|
+
ax_args = new_args
|
|
295
|
+
|
|
296
|
+
@data.each{|type_uri, values|
|
|
297
|
+
name = aliases.add(type_uri)
|
|
298
|
+
ax_args['type.'+name] = type_uri
|
|
299
|
+
ax_args['count.'+name] = values.size.to_s
|
|
300
|
+
|
|
301
|
+
values.each_with_index{|value, i|
|
|
302
|
+
key = "value.#{name}.#{i+1}"
|
|
303
|
+
ax_args[key] = value
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
return ax_args
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
# Parse attribute exchange key/value arguments into this object.
|
|
310
|
+
|
|
311
|
+
def parse_extension_args(ax_args)
|
|
312
|
+
check_mode(ax_args)
|
|
313
|
+
aliases = NamespaceMap.new
|
|
314
|
+
|
|
315
|
+
ax_args.each{|k, v|
|
|
316
|
+
if k.index('type.') == 0
|
|
317
|
+
type_uri = v
|
|
318
|
+
name = k[5..-1]
|
|
319
|
+
|
|
320
|
+
AX.check_alias(name)
|
|
321
|
+
aliases.add_alias(type_uri,name)
|
|
322
|
+
end
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
aliases.each{|type_uri, name|
|
|
326
|
+
count_s = ax_args['count.'+name]
|
|
327
|
+
count = count_s.to_i
|
|
328
|
+
if count_s.nil?
|
|
329
|
+
value = ax_args['value.'+name]
|
|
330
|
+
if value.nil?
|
|
331
|
+
raise IndexError, "Missing #{'value.'+name} in FetchResponse"
|
|
332
|
+
elsif value.empty?
|
|
333
|
+
values = []
|
|
334
|
+
else
|
|
335
|
+
values = [value]
|
|
336
|
+
end
|
|
337
|
+
elsif count_s.to_i == 0
|
|
338
|
+
values = []
|
|
339
|
+
else
|
|
340
|
+
values = (1..count).inject([]){|l,i|
|
|
341
|
+
key = "value.#{name}.#{i}"
|
|
342
|
+
v = ax_args[key]
|
|
343
|
+
raise IndexError, "Missing #{key} in FetchResponse" if v.nil?
|
|
344
|
+
l << v
|
|
345
|
+
}
|
|
346
|
+
end
|
|
347
|
+
@data[type_uri] = values
|
|
348
|
+
}
|
|
349
|
+
end
|
|
350
|
+
|
|
351
|
+
# Get a single value for an attribute. If no value was sent
|
|
352
|
+
# for this attribute, use the supplied default. If there is more
|
|
353
|
+
# than one value for this attribute, this method will fail.
|
|
354
|
+
def get_single(type_uri, default = nil)
|
|
355
|
+
values = @data[type_uri]
|
|
356
|
+
return default if values.empty?
|
|
357
|
+
if values.size != 1
|
|
358
|
+
raise Error, "More than one value present for #{type_uri.inspect}"
|
|
359
|
+
else
|
|
360
|
+
return values[0]
|
|
361
|
+
end
|
|
362
|
+
end
|
|
363
|
+
|
|
364
|
+
# retrieve the list of values for this attribute
|
|
365
|
+
def get(type_uri)
|
|
366
|
+
@data[type_uri]
|
|
367
|
+
end
|
|
368
|
+
|
|
369
|
+
# retrieve the list of values for this attribute
|
|
370
|
+
def [](type_uri)
|
|
371
|
+
@data[type_uri]
|
|
372
|
+
end
|
|
373
|
+
|
|
374
|
+
# get the number of responses for this attribute
|
|
375
|
+
def count(type_uri)
|
|
376
|
+
@data[type_uri].size
|
|
377
|
+
end
|
|
378
|
+
|
|
379
|
+
end
|
|
380
|
+
|
|
381
|
+
# A fetch_response attribute exchange message
|
|
382
|
+
class FetchResponse < KeyValueMessage
|
|
383
|
+
attr_reader :update_url
|
|
384
|
+
|
|
385
|
+
def initialize(update_url = nil)
|
|
386
|
+
super()
|
|
387
|
+
@mode = 'fetch_response'
|
|
388
|
+
@update_url = update_url
|
|
389
|
+
end
|
|
390
|
+
|
|
391
|
+
# Serialize this object into arguments in the attribute
|
|
392
|
+
# exchange namespace
|
|
393
|
+
# Takes an optional FetchRequest. If specified, the response will be
|
|
394
|
+
# validated against this request, and empty responses for requested
|
|
395
|
+
# fields with no data will be sent.
|
|
396
|
+
def get_extension_args(request = nil)
|
|
397
|
+
aliases = NamespaceMap.new
|
|
398
|
+
zero_value_types = []
|
|
399
|
+
|
|
400
|
+
if request
|
|
401
|
+
# Validate the data in the context of the request (the
|
|
402
|
+
# same attributes should be present in each, and the
|
|
403
|
+
# counts in the response must be no more than the counts
|
|
404
|
+
# in the request)
|
|
405
|
+
@data.keys.each{|type_uri|
|
|
406
|
+
unless request.member? type_uri
|
|
407
|
+
raise IndexError, "Response attribute not present in request: #{type_uri.inspect}"
|
|
408
|
+
end
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
request.attributes.each{|attr_info|
|
|
412
|
+
# Copy the aliases from the request so that reading
|
|
413
|
+
# the response in light of the request is easier
|
|
414
|
+
if attr_info.ns_alias.nil?
|
|
415
|
+
aliases.add(attr_info.type_uri)
|
|
416
|
+
else
|
|
417
|
+
aliases.add_alias(attr_info.type_uri, attr_info.ns_alias)
|
|
418
|
+
end
|
|
419
|
+
values = @data[attr_info.type_uri]
|
|
420
|
+
if values.empty? # @data defaults to []
|
|
421
|
+
zero_value_types << attr_info
|
|
422
|
+
end
|
|
423
|
+
if attr_info.count != UNLIMITED_VALUES and attr_info.count < values.size
|
|
424
|
+
raise Error, "More than the number of requested values were specified for #{attr_info.type_uri.inspect}"
|
|
425
|
+
end
|
|
426
|
+
}
|
|
427
|
+
end
|
|
428
|
+
|
|
429
|
+
kv_args = _get_extension_kv_args(aliases)
|
|
430
|
+
|
|
431
|
+
# Add the KV args into the response with the args that are
|
|
432
|
+
# unique to the fetch_response
|
|
433
|
+
ax_args = new_args
|
|
434
|
+
|
|
435
|
+
zero_value_types.each{|attr_info|
|
|
436
|
+
name = aliases.get_alias(attr_info.type_uri)
|
|
437
|
+
kv_args['type.' + name] = attr_info.type_uri
|
|
438
|
+
kv_args['count.' + name] = '0'
|
|
439
|
+
}
|
|
440
|
+
update_url = (request and request.update_url or @update_url)
|
|
441
|
+
ax_args['update_url'] = update_url unless update_url.nil?
|
|
442
|
+
ax_args.update(kv_args)
|
|
443
|
+
return ax_args
|
|
444
|
+
end
|
|
445
|
+
|
|
446
|
+
def parse_extension_args(ax_args)
|
|
447
|
+
super
|
|
448
|
+
@update_url = ax_args['update_url']
|
|
449
|
+
end
|
|
450
|
+
|
|
451
|
+
# Construct a FetchResponse object from an OpenID library
|
|
452
|
+
# SuccessResponse object.
|
|
453
|
+
def self.from_success_response(success_response, signed=true)
|
|
454
|
+
obj = self.new
|
|
455
|
+
if signed
|
|
456
|
+
ax_args = success_response.get_signed_ns(obj.ns_uri)
|
|
457
|
+
else
|
|
458
|
+
ax_args = success_response.message.get_args(obj.ns_uri)
|
|
459
|
+
end
|
|
460
|
+
|
|
461
|
+
begin
|
|
462
|
+
obj.parse_extension_args(ax_args)
|
|
463
|
+
return obj
|
|
464
|
+
rescue Error => e
|
|
465
|
+
return nil
|
|
466
|
+
end
|
|
467
|
+
end
|
|
468
|
+
end
|
|
469
|
+
|
|
470
|
+
# A store request attribute exchange message representation
|
|
471
|
+
class StoreRequest < KeyValueMessage
|
|
472
|
+
|
|
473
|
+
MODE = 'store_request'
|
|
474
|
+
|
|
475
|
+
def initialize
|
|
476
|
+
super
|
|
477
|
+
@mode = MODE
|
|
478
|
+
end
|
|
479
|
+
|
|
480
|
+
# Extract a StoreRequest from an OpenID message
|
|
481
|
+
# message: OpenID::Message
|
|
482
|
+
# return a StoreRequest or nil if AX arguments are not present
|
|
483
|
+
def self.from_openid_request(oidreq)
|
|
484
|
+
message = oidreq.message
|
|
485
|
+
ax_args = message.get_args(NS_URI)
|
|
486
|
+
return nil if ax_args.empty? or ax_args['mode'] != MODE
|
|
487
|
+
req = new
|
|
488
|
+
req.parse_extension_args(ax_args)
|
|
489
|
+
req
|
|
490
|
+
end
|
|
491
|
+
|
|
492
|
+
def get_extension_args(aliases=nil)
|
|
493
|
+
ax_args = new_args
|
|
494
|
+
kv_args = _get_extension_kv_args(aliases)
|
|
495
|
+
ax_args.update(kv_args)
|
|
496
|
+
return ax_args
|
|
497
|
+
end
|
|
498
|
+
end
|
|
499
|
+
|
|
500
|
+
# An indication that the store request was processed along with
|
|
501
|
+
# this OpenID transaction.
|
|
502
|
+
class StoreResponse < AXMessage
|
|
503
|
+
SUCCESS_MODE = 'store_response_success'
|
|
504
|
+
FAILURE_MODE = 'store_response_failure'
|
|
505
|
+
attr_reader :error_message
|
|
506
|
+
|
|
507
|
+
def initialize(succeeded = true, error_message = nil)
|
|
508
|
+
super()
|
|
509
|
+
if succeeded and error_message
|
|
510
|
+
raise Error, "Error message included in a success response"
|
|
511
|
+
end
|
|
512
|
+
if succeeded
|
|
513
|
+
@mode = SUCCESS_MODE
|
|
514
|
+
else
|
|
515
|
+
@mode = FAILURE_MODE
|
|
516
|
+
end
|
|
517
|
+
@error_message = error_message
|
|
518
|
+
end
|
|
519
|
+
|
|
520
|
+
def self.from_success_response(success_response)
|
|
521
|
+
resp = nil
|
|
522
|
+
ax_args = success_response.message.get_args(NS_URI)
|
|
523
|
+
resp = ax_args.key?('error') ? new(false, ax_args['error']) : new
|
|
524
|
+
end
|
|
525
|
+
|
|
526
|
+
def succeeded?
|
|
527
|
+
@mode == SUCCESS_MODE
|
|
528
|
+
end
|
|
529
|
+
|
|
530
|
+
def get_extension_args
|
|
531
|
+
ax_args = new_args
|
|
532
|
+
if !succeeded? and error_message
|
|
533
|
+
ax_args['error'] = @error_message
|
|
534
|
+
end
|
|
535
|
+
return ax_args
|
|
536
|
+
end
|
|
537
|
+
end
|
|
538
|
+
end
|
|
539
|
+
end
|