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
|
@@ -0,0 +1,506 @@
|
|
|
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
|
+
@mode = nil
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
protected
|
|
36
|
+
|
|
37
|
+
# Raise an exception if the mode in the attribute exchange
|
|
38
|
+
# arguments does not match what is expected for this class.
|
|
39
|
+
def check_mode(ax_args)
|
|
40
|
+
actual_mode = ax_args['mode']
|
|
41
|
+
if actual_mode != @mode
|
|
42
|
+
raise Error, "Expected mode #{mode.inspect}, got #{actual_mode.inspect}"
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def new_args
|
|
47
|
+
{'mode' => @mode}
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Represents a single attribute in an attribute exchange
|
|
52
|
+
# request. This should be added to an Request object in order to
|
|
53
|
+
# request the attribute.
|
|
54
|
+
#
|
|
55
|
+
# @ivar required: Whether the attribute will be marked as required
|
|
56
|
+
# when presented to the subject of the attribute exchange
|
|
57
|
+
# request.
|
|
58
|
+
# @type required: bool
|
|
59
|
+
#
|
|
60
|
+
# @ivar count: How many values of this type to request from the
|
|
61
|
+
# subject. Defaults to one.
|
|
62
|
+
# @type count: int
|
|
63
|
+
#
|
|
64
|
+
# @ivar type_uri: The identifier that determines what the attribute
|
|
65
|
+
# represents and how it is serialized. For example, one type URI
|
|
66
|
+
# representing dates could represent a Unix timestamp in base 10
|
|
67
|
+
# and another could represent a human-readable string.
|
|
68
|
+
# @type type_uri: str
|
|
69
|
+
#
|
|
70
|
+
# @ivar ns_alias: The name that should be given to this alias in the
|
|
71
|
+
# request. If it is not supplied, a generic name will be
|
|
72
|
+
# assigned. For example, if you want to call a Unix timestamp
|
|
73
|
+
# value 'tstamp', set its alias to that value. If two attributes
|
|
74
|
+
# in the same message request to use the same alias, the request
|
|
75
|
+
# will fail to be generated.
|
|
76
|
+
# @type alias: str or NoneType
|
|
77
|
+
class AttrInfo < Object
|
|
78
|
+
attr_reader :type_uri, :count, :ns_alias
|
|
79
|
+
attr_accessor :required
|
|
80
|
+
def initialize(type_uri, ns_alias=nil, required=false, count=1)
|
|
81
|
+
@type_uri = type_uri
|
|
82
|
+
@count = count
|
|
83
|
+
@required = required
|
|
84
|
+
@ns_alias = ns_alias
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def wants_unlimited_values?
|
|
88
|
+
@count == UNLIMITED_VALUES
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Given a namespace mapping and a string containing a
|
|
93
|
+
# comma-separated list of namespace aliases, return a list of type
|
|
94
|
+
# URIs that correspond to those aliases.
|
|
95
|
+
# namespace_map: OpenID::NamespaceMap
|
|
96
|
+
def self.to_type_uris(namespace_map, alias_list_s)
|
|
97
|
+
return [] if alias_list_s.nil?
|
|
98
|
+
alias_list_s.split(',').inject([]) {|uris, name|
|
|
99
|
+
type_uri = namespace_map.get_namespace_uri(name)
|
|
100
|
+
raise IndexError, "No type defined for attribute name #{name.inspect}" if type_uri.nil?
|
|
101
|
+
uris << type_uri
|
|
102
|
+
}
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
# An attribute exchange 'fetch_request' message. This message is
|
|
107
|
+
# sent by a relying party when it wishes to obtain attributes about
|
|
108
|
+
# the subject of an OpenID authentication request.
|
|
109
|
+
class FetchRequest < AXMessage
|
|
110
|
+
attr_reader :requested_attributes
|
|
111
|
+
attr_accessor :update_url
|
|
112
|
+
|
|
113
|
+
def initialize(update_url = nil)
|
|
114
|
+
@mode = 'fetch_request'
|
|
115
|
+
@requested_attributes = {}
|
|
116
|
+
@update_url = update_url
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# Add an attribute to this attribute exchange request.
|
|
120
|
+
# attribute: AttrInfo, the attribute being requested
|
|
121
|
+
# Raises IndexError if the requested attribute is already present
|
|
122
|
+
# in this request.
|
|
123
|
+
def add(attribute)
|
|
124
|
+
if @requested_attributes[attribute.type_uri]
|
|
125
|
+
raise IndexError, "The attribute #{attribute.type_uri} has already been requested"
|
|
126
|
+
end
|
|
127
|
+
@requested_attributes[attribute.type_uri] = attribute
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# Get the serialized form of this attribute fetch request.
|
|
131
|
+
# returns a hash of the arguments
|
|
132
|
+
def get_extension_args
|
|
133
|
+
aliases = NamespaceMap.new
|
|
134
|
+
required = []
|
|
135
|
+
if_available = []
|
|
136
|
+
ax_args = new_args
|
|
137
|
+
@requested_attributes.each{|type_uri, attribute|
|
|
138
|
+
if attribute.ns_alias
|
|
139
|
+
name = aliases.add_alias(type_uri, attribute.ns_alias)
|
|
140
|
+
else
|
|
141
|
+
name = aliases.add(type_uri)
|
|
142
|
+
end
|
|
143
|
+
if attribute.required
|
|
144
|
+
required << name
|
|
145
|
+
else
|
|
146
|
+
if_available << name
|
|
147
|
+
end
|
|
148
|
+
if attribute.count != 1
|
|
149
|
+
ax_args["count.#{name}"] = attribute.count.to_s
|
|
150
|
+
end
|
|
151
|
+
ax_args["type.#{name}"] = type_uri
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
unless required.empty?
|
|
155
|
+
ax_args['required'] = required.join(',')
|
|
156
|
+
end
|
|
157
|
+
unless if_available.empty?
|
|
158
|
+
ax_args['if_available'] = if_available.join(',')
|
|
159
|
+
end
|
|
160
|
+
return ax_args
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
# Get the type URIs for all attributes that have been marked
|
|
164
|
+
# as required.
|
|
165
|
+
def get_required_attrs
|
|
166
|
+
@requested_attributes.inject([]) {|required, (type_uri, attribute)|
|
|
167
|
+
if attribute.required
|
|
168
|
+
required << type_uri
|
|
169
|
+
else
|
|
170
|
+
required
|
|
171
|
+
end
|
|
172
|
+
}
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
# Extract a FetchRequest from an OpenID message
|
|
176
|
+
# message: OpenID::Message
|
|
177
|
+
# return a FetchRequest or nil if AX arguments are not present
|
|
178
|
+
def self.from_openid_request(oidreq)
|
|
179
|
+
message = oidreq.message
|
|
180
|
+
ax_args = message.get_args(NS_URI)
|
|
181
|
+
return nil if ax_args == {}
|
|
182
|
+
req = new
|
|
183
|
+
req.parse_extension_args(ax_args)
|
|
184
|
+
|
|
185
|
+
if req.update_url
|
|
186
|
+
realm = message.get_arg(OPENID_NS, 'realm',
|
|
187
|
+
message.get_arg(OPENID_NS, 'return_to'))
|
|
188
|
+
if realm.nil? or realm.empty?
|
|
189
|
+
raise Error, "Cannot validate update_url #{req.update_url.inspect} against absent realm"
|
|
190
|
+
end
|
|
191
|
+
tr = TrustRoot::TrustRoot.parse(realm)
|
|
192
|
+
unless tr.validate_url(req.update_url)
|
|
193
|
+
raise Error, "Update URL #{req.update_url.inspect} failed validation against realm #{realm.inspect}"
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
return req
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
def parse_extension_args(ax_args)
|
|
201
|
+
check_mode(ax_args)
|
|
202
|
+
|
|
203
|
+
aliases = NamespaceMap.new
|
|
204
|
+
|
|
205
|
+
ax_args.each{|k,v|
|
|
206
|
+
if k.index('type.') == 0
|
|
207
|
+
name = k[5..-1]
|
|
208
|
+
type_uri = v
|
|
209
|
+
aliases.add_alias(type_uri, name)
|
|
210
|
+
|
|
211
|
+
count_key = 'count.'+name
|
|
212
|
+
count_s = ax_args[count_key]
|
|
213
|
+
count = 1
|
|
214
|
+
if count_s
|
|
215
|
+
if count_s == UNLIMITED_VALUES
|
|
216
|
+
count = count_s
|
|
217
|
+
else
|
|
218
|
+
count = count_s.to_i
|
|
219
|
+
if count <= 0
|
|
220
|
+
raise Error, "Invalid value for count #{count_key.inspect}: #{count_s.inspect}"
|
|
221
|
+
end
|
|
222
|
+
end
|
|
223
|
+
end
|
|
224
|
+
add(AttrInfo.new(type_uri, name, false, count))
|
|
225
|
+
end
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
required = AX.to_type_uris(aliases, ax_args['required'])
|
|
229
|
+
required.each{|type_uri|
|
|
230
|
+
@requested_attributes[type_uri].required = true
|
|
231
|
+
}
|
|
232
|
+
if_available = AX.to_type_uris(aliases, ax_args['if_available'])
|
|
233
|
+
all_type_uris = required + if_available
|
|
234
|
+
|
|
235
|
+
aliases.namespace_uris.each{|type_uri|
|
|
236
|
+
unless all_type_uris.member? type_uri
|
|
237
|
+
raise Error, "Type URI #{type_uri.inspect} was in the request but not present in 'required' or 'if_available'"
|
|
238
|
+
end
|
|
239
|
+
}
|
|
240
|
+
@update_url = ax_args['update_url']
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
# return the list of AttrInfo objects contained in the FetchRequest
|
|
244
|
+
def attributes
|
|
245
|
+
@requested_attributes.values
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
# return the list of requested attribute type URIs
|
|
249
|
+
def requested_types
|
|
250
|
+
@requested_attributes.keys
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
def member?(type_uri)
|
|
254
|
+
! @requested_attributes[type_uri].nil?
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
# Abstract class that implements a message that has attribute
|
|
260
|
+
# keys and values. It contains the common code between
|
|
261
|
+
# fetch_response and store_request.
|
|
262
|
+
class KeyValueMessage < AXMessage
|
|
263
|
+
attr_reader :data
|
|
264
|
+
def initialize
|
|
265
|
+
@mode = nil
|
|
266
|
+
@data = {}
|
|
267
|
+
@data.default = []
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
# Add a single value for the given attribute type to the
|
|
271
|
+
# message. If there are already values specified for this type,
|
|
272
|
+
# this value will be sent in addition to the values already
|
|
273
|
+
# specified.
|
|
274
|
+
def add_value(type_uri, value)
|
|
275
|
+
@data[type_uri] = @data[type_uri] << value
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
# Set the values for the given attribute type. This replaces
|
|
279
|
+
# any values that have already been set for this attribute.
|
|
280
|
+
def set_values(type_uri, values)
|
|
281
|
+
@data[type_uri] = values
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
# Get the extension arguments for the key/value pairs
|
|
285
|
+
# contained in this message.
|
|
286
|
+
def _get_extension_kv_args(aliases = nil)
|
|
287
|
+
aliases = NamespaceMap.new if aliases.nil?
|
|
288
|
+
|
|
289
|
+
ax_args = new_args
|
|
290
|
+
|
|
291
|
+
@data.each{|type_uri, values|
|
|
292
|
+
name = aliases.add(type_uri)
|
|
293
|
+
ax_args['type.'+name] = type_uri
|
|
294
|
+
ax_args['count.'+name] = values.size.to_s
|
|
295
|
+
|
|
296
|
+
values.each_with_index{|value, i|
|
|
297
|
+
key = "value.#{name}.#{i+1}"
|
|
298
|
+
ax_args[key] = value
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
return ax_args
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
# Parse attribute exchange key/value arguments into this object.
|
|
305
|
+
|
|
306
|
+
def parse_extension_args(ax_args)
|
|
307
|
+
check_mode(ax_args)
|
|
308
|
+
aliases = NamespaceMap.new
|
|
309
|
+
|
|
310
|
+
ax_args.each{|k, v|
|
|
311
|
+
if k.index('type.') == 0
|
|
312
|
+
type_uri = v
|
|
313
|
+
name = k[5..-1]
|
|
314
|
+
|
|
315
|
+
AX.check_alias(name)
|
|
316
|
+
aliases.add_alias(type_uri,name)
|
|
317
|
+
end
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
aliases.each{|type_uri, name|
|
|
321
|
+
count_s = ax_args['count.'+name]
|
|
322
|
+
count = count_s.to_i
|
|
323
|
+
if count_s.nil?
|
|
324
|
+
value = ax_args['value.'+name]
|
|
325
|
+
if value.nil?
|
|
326
|
+
raise IndexError, "Missing #{'value.'+name} in FetchResponse"
|
|
327
|
+
elsif value.empty?
|
|
328
|
+
values = []
|
|
329
|
+
else
|
|
330
|
+
values = [value]
|
|
331
|
+
end
|
|
332
|
+
elsif count_s.to_i == 0
|
|
333
|
+
values = []
|
|
334
|
+
else
|
|
335
|
+
values = (1..count).inject([]){|l,i|
|
|
336
|
+
key = "value.#{name}.#{i}"
|
|
337
|
+
v = ax_args[key]
|
|
338
|
+
raise IndexError, "Missing #{key} in FetchResponse" if v.nil?
|
|
339
|
+
l << v
|
|
340
|
+
}
|
|
341
|
+
end
|
|
342
|
+
@data[type_uri] = values
|
|
343
|
+
}
|
|
344
|
+
end
|
|
345
|
+
|
|
346
|
+
# Get a single value for an attribute. If no value was sent
|
|
347
|
+
# for this attribute, use the supplied default. If there is more
|
|
348
|
+
# than one value for this attribute, this method will fail.
|
|
349
|
+
def get_single(type_uri, default = nil)
|
|
350
|
+
values = @data[type_uri]
|
|
351
|
+
return default if values.empty?
|
|
352
|
+
if values.size != 1
|
|
353
|
+
raise Error, "More than one value present for #{type_uri.inspect}"
|
|
354
|
+
else
|
|
355
|
+
return values[0]
|
|
356
|
+
end
|
|
357
|
+
end
|
|
358
|
+
|
|
359
|
+
# retrieve the list of values for this attribute
|
|
360
|
+
def get(type_uri)
|
|
361
|
+
@data[type_uri]
|
|
362
|
+
end
|
|
363
|
+
|
|
364
|
+
# retrieve the list of values for this attribute
|
|
365
|
+
def [](type_uri)
|
|
366
|
+
@data[type_uri]
|
|
367
|
+
end
|
|
368
|
+
|
|
369
|
+
# get the number of responses for this attribute
|
|
370
|
+
def count(type_uri)
|
|
371
|
+
@data[type_uri].size
|
|
372
|
+
end
|
|
373
|
+
|
|
374
|
+
end
|
|
375
|
+
|
|
376
|
+
# A fetch_response attribute exchange message
|
|
377
|
+
class FetchResponse < KeyValueMessage
|
|
378
|
+
attr_reader :update_url
|
|
379
|
+
|
|
380
|
+
def initialize(update_url = nil)
|
|
381
|
+
super()
|
|
382
|
+
@mode = 'fetch_response'
|
|
383
|
+
@update_url = update_url
|
|
384
|
+
end
|
|
385
|
+
|
|
386
|
+
# Serialize this object into arguments in the attribute
|
|
387
|
+
# exchange namespace
|
|
388
|
+
# Takes an optional FetchRequest. If specified, the response will be
|
|
389
|
+
# validated against this request, and empty responses for requested
|
|
390
|
+
# fields with no data will be sent.
|
|
391
|
+
def get_extension_args(request = nil)
|
|
392
|
+
aliases = NamespaceMap.new
|
|
393
|
+
zero_value_types = []
|
|
394
|
+
|
|
395
|
+
if request
|
|
396
|
+
# Validate the data in the context of the request (the
|
|
397
|
+
# same attributes should be present in each, and the
|
|
398
|
+
# counts in the response must be no more than the counts
|
|
399
|
+
# in the request)
|
|
400
|
+
@data.keys.each{|type_uri|
|
|
401
|
+
unless request.member? type_uri
|
|
402
|
+
raise IndexError, "Response attribute not present in request: #{type_uri.inspect}"
|
|
403
|
+
end
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
request.attributes.each{|attr_info|
|
|
407
|
+
# Copy the aliases from the request so that reading
|
|
408
|
+
# the response in light of the request is easier
|
|
409
|
+
if attr_info.ns_alias.nil?
|
|
410
|
+
aliases.add(attr_info.type_uri)
|
|
411
|
+
else
|
|
412
|
+
aliases.add_alias(attr_info.type_uri, attr_info.ns_alias)
|
|
413
|
+
end
|
|
414
|
+
values = @data[attr_info.type_uri]
|
|
415
|
+
if values.empty? # @data defaults to []
|
|
416
|
+
zero_value_types << attr_info
|
|
417
|
+
end
|
|
418
|
+
if attr_info.count != UNLIMITED_VALUES and attr_info.count < values.size
|
|
419
|
+
raise Error, "More than the number of requested values were specified for #{attr_info.type_uri.inspect}"
|
|
420
|
+
end
|
|
421
|
+
}
|
|
422
|
+
end
|
|
423
|
+
|
|
424
|
+
kv_args = _get_extension_kv_args(aliases)
|
|
425
|
+
|
|
426
|
+
# Add the KV args into the response with the args that are
|
|
427
|
+
# unique to the fetch_response
|
|
428
|
+
ax_args = new_args
|
|
429
|
+
|
|
430
|
+
zero_value_types.each{|attr_info|
|
|
431
|
+
name = aliases.get_alias(attr_info.type_uri)
|
|
432
|
+
kv_args['type.' + name] = attr_info.type_uri
|
|
433
|
+
kv_args['count.' + name] = '0'
|
|
434
|
+
}
|
|
435
|
+
update_url = (request and request.update_url or @update_url)
|
|
436
|
+
ax_args['update_url'] = update_url unless update_url.nil?
|
|
437
|
+
ax_args.update(kv_args)
|
|
438
|
+
return ax_args
|
|
439
|
+
end
|
|
440
|
+
|
|
441
|
+
def parse_extension_args(ax_args)
|
|
442
|
+
super
|
|
443
|
+
@update_url = ax_args['update_url']
|
|
444
|
+
end
|
|
445
|
+
|
|
446
|
+
# Construct a FetchResponse object from an OpenID library
|
|
447
|
+
# SuccessResponse object.
|
|
448
|
+
def self.from_success_response(success_response, signed=true)
|
|
449
|
+
if signed
|
|
450
|
+
ax_args = success_response.get_signed_ns(@ns_uri)
|
|
451
|
+
else
|
|
452
|
+
ax_args = success_response.message.get_args(@ns_uri)
|
|
453
|
+
end
|
|
454
|
+
new.parse_extension_args(ax_args)
|
|
455
|
+
end
|
|
456
|
+
end
|
|
457
|
+
|
|
458
|
+
# A store request attribute exchange message representation
|
|
459
|
+
class StoreRequest < KeyValueMessage
|
|
460
|
+
def initialize
|
|
461
|
+
super
|
|
462
|
+
@mode = 'store_request'
|
|
463
|
+
end
|
|
464
|
+
|
|
465
|
+
def get_extension_args(aliases=nil)
|
|
466
|
+
ax_args = new_args
|
|
467
|
+
kv_args = _get_extension_kv_args(aliases)
|
|
468
|
+
ax_args.update(kv_args)
|
|
469
|
+
return ax_args
|
|
470
|
+
end
|
|
471
|
+
end
|
|
472
|
+
|
|
473
|
+
# An indication that the store request was processed along with
|
|
474
|
+
# this OpenID transaction.
|
|
475
|
+
class StoreResponse < AXMessage
|
|
476
|
+
SUCCESS_MODE = 'store_response_success'
|
|
477
|
+
FAILURE_MODE = 'store_response_failure'
|
|
478
|
+
attr_reader :error_message
|
|
479
|
+
|
|
480
|
+
def initialize(succeeded = true, error_message = nil)
|
|
481
|
+
super()
|
|
482
|
+
if succeeded and error_message
|
|
483
|
+
raise Error, "Error message included in a success response"
|
|
484
|
+
end
|
|
485
|
+
if succeeded
|
|
486
|
+
@mode = SUCCESS_MODE
|
|
487
|
+
else
|
|
488
|
+
@mode = FAILURE_MODE
|
|
489
|
+
end
|
|
490
|
+
@error_message = error_message
|
|
491
|
+
end
|
|
492
|
+
|
|
493
|
+
def succeeded?
|
|
494
|
+
@mode == SUCCESS_MODE
|
|
495
|
+
end
|
|
496
|
+
|
|
497
|
+
def get_extension_args
|
|
498
|
+
ax_args = new_args
|
|
499
|
+
if !succeeded? and error_message
|
|
500
|
+
ax_args['error'] = @error_message
|
|
501
|
+
end
|
|
502
|
+
return ax_args
|
|
503
|
+
end
|
|
504
|
+
end
|
|
505
|
+
end
|
|
506
|
+
end
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
# An implementation of the OpenID Provider Authentication Policy
|
|
2
|
+
# Extension 1.0
|
|
3
|
+
# see: http://openid.net/specs/
|
|
4
|
+
|
|
5
|
+
require 'openid/extension'
|
|
6
|
+
|
|
7
|
+
module OpenID
|
|
8
|
+
|
|
9
|
+
module PAPE
|
|
10
|
+
NS_URI = "http://specs.openid.net/extensions/pape/1.0"
|
|
11
|
+
AUTH_MULTI_FACTOR_PHYSICAL =
|
|
12
|
+
'http://schemas.openid.net/pape/policies/2007/06/multi-factor-physical'
|
|
13
|
+
AUTH_MULTI_FACTOR =
|
|
14
|
+
'http://schemas.openid.net/pape/policies/2007/06/multi-factor'
|
|
15
|
+
AUTH_PHISHING_RESISTANT =
|
|
16
|
+
'http://schemas.openid.net/pape/policies/2007/06/phishing-resistant'
|
|
17
|
+
|
|
18
|
+
# A Provider Authentication Policy request, sent from a relying
|
|
19
|
+
# party to a provider
|
|
20
|
+
class Request < Extension
|
|
21
|
+
attr_accessor :preferred_auth_policies, :max_auth_age, :ns_alias, :ns_uri
|
|
22
|
+
def initialize(preferred_auth_policies=[], max_auth_age=nil)
|
|
23
|
+
@ns_alias = 'pape'
|
|
24
|
+
@ns_uri = NS_URI
|
|
25
|
+
@preferred_auth_policies = preferred_auth_policies
|
|
26
|
+
@max_auth_age = max_auth_age
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Add an acceptable authentication policy URI to this request
|
|
30
|
+
# This method is intended to be used by the relying party to add
|
|
31
|
+
# acceptable authentication types to the request.
|
|
32
|
+
def add_policy_uri(policy_uri)
|
|
33
|
+
unless @preferred_auth_policies.member? policy_uri
|
|
34
|
+
@preferred_auth_policies << policy_uri
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def get_extension_args
|
|
39
|
+
ns_args = {
|
|
40
|
+
'preferred_auth_policies' => @preferred_auth_policies.join(' ')
|
|
41
|
+
}
|
|
42
|
+
ns_args['max_auth_age'] = @max_auth_age.to_s if @max_auth_age
|
|
43
|
+
return ns_args
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Instantiate a Request object from the arguments in a
|
|
47
|
+
# checkid_* OpenID message
|
|
48
|
+
# return nil if the extension was not requested.
|
|
49
|
+
def self.from_openid_request(oid_req)
|
|
50
|
+
pape_req = new
|
|
51
|
+
args = oid_req.message.get_args(NS_URI)
|
|
52
|
+
if args == {}
|
|
53
|
+
return nil
|
|
54
|
+
end
|
|
55
|
+
pape_req.parse_extension_args(args)
|
|
56
|
+
return pape_req
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Set the state of this request to be that expressed in these
|
|
60
|
+
# PAPE arguments
|
|
61
|
+
def parse_extension_args(args)
|
|
62
|
+
@preferred_auth_policies = []
|
|
63
|
+
policies_str = args['preferred_auth_policies']
|
|
64
|
+
if policies_str
|
|
65
|
+
policies_str.split(' ').each{|uri|
|
|
66
|
+
add_policy_uri(uri)
|
|
67
|
+
}
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
max_auth_age_str = args['max_auth_age']
|
|
71
|
+
if max_auth_age_str
|
|
72
|
+
@max_auth_age = max_auth_age_str.to_i
|
|
73
|
+
else
|
|
74
|
+
@max_auth_age = nil
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Given a list of authentication policy URIs that a provider
|
|
79
|
+
# supports, this method returns the subset of those types
|
|
80
|
+
# that are preferred by the relying party.
|
|
81
|
+
def preferred_types(supported_types)
|
|
82
|
+
@preferred_auth_policies.select{|uri| supported_types.member? uri}
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# A Provider Authentication Policy response, sent from a provider
|
|
87
|
+
# to a relying party
|
|
88
|
+
class Response < Extension
|
|
89
|
+
attr_accessor :ns_alias, :auth_policies, :auth_age, :nist_auth_level
|
|
90
|
+
def initialize(auth_policies=[], auth_age=nil, nist_auth_level=nil)
|
|
91
|
+
@ns_alias = 'pape'
|
|
92
|
+
@ns_uri = NS_URI
|
|
93
|
+
@auth_policies = auth_policies
|
|
94
|
+
@auth_age = auth_age
|
|
95
|
+
@nist_auth_level = nist_auth_level
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Add a policy URI to the response
|
|
99
|
+
# see http://openid.net/specs/openid-provider-authentication-policy-extension-1_0-01.html#auth_policies
|
|
100
|
+
def add_policy_uri(policy_uri)
|
|
101
|
+
@auth_policies << policy_uri unless @auth_policies.member?(policy_uri)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# Create a Response object from an OpenID::Consumer::SuccessResponse
|
|
105
|
+
def self.from_success_response(success_response)
|
|
106
|
+
args = success_response.get_signed_ns(NS_URI)
|
|
107
|
+
pape_resp = new
|
|
108
|
+
pape_resp.parse_extension_args(args)
|
|
109
|
+
return pape_resp
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# parse the provider authentication policy arguments into the
|
|
113
|
+
# internal state of this object
|
|
114
|
+
# if strict is specified, raise an exception when bad data is
|
|
115
|
+
# encountered
|
|
116
|
+
def parse_extension_args(args, strict=false)
|
|
117
|
+
policies_str = args['auth_policies']
|
|
118
|
+
if policies_str
|
|
119
|
+
@auth_policies = policies_str.split(' ')
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
nist_level_str = args['nist_auth_level']
|
|
123
|
+
if nist_level_str
|
|
124
|
+
# special handling of zero to handle to_i behavior
|
|
125
|
+
if nist_level_str.strip == '0'
|
|
126
|
+
nist_level = 0
|
|
127
|
+
else
|
|
128
|
+
nist_level = nist_level_str.to_i
|
|
129
|
+
# if it's zero here we have a bad value
|
|
130
|
+
if nist_level == 0
|
|
131
|
+
nist_level = nil
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
if nist_level and nist_level >= 0 and nist_level < 5
|
|
135
|
+
@nist_auth_level = nist_level
|
|
136
|
+
elsif strict:
|
|
137
|
+
raise ArgumentError, "nist_auth_level must be an integer 0 through 4, not #{nist_level_str.inspect}"
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
auth_age_str = args['auth_age']
|
|
142
|
+
if auth_age_str
|
|
143
|
+
# special handling of zero to handle to_i behavior
|
|
144
|
+
if auth_age_str.strip == '0'
|
|
145
|
+
auth_age = 0
|
|
146
|
+
else
|
|
147
|
+
auth_age = auth_age_str.to_i
|
|
148
|
+
# if it's zero here we have a bad value
|
|
149
|
+
if auth_age == 0
|
|
150
|
+
auth_age = nil
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
if auth_age and auth_age >= 0
|
|
154
|
+
@auth_age = auth_age
|
|
155
|
+
elsif strict
|
|
156
|
+
raise ArgumentError, "auth_age must be a non-negative integer, not #{auth_age_str.inspect}"
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def get_extension_args
|
|
162
|
+
ns_args = {'auth_policies' => @auth_policies.join(' ')}
|
|
163
|
+
if @nist_auth_level
|
|
164
|
+
unless (0..4).member? @nist_auth_level
|
|
165
|
+
raise ArgumentError, "nist_auth_level must be an integer 0 through 4, not #{@nist_auth_level.inspect}"
|
|
166
|
+
end
|
|
167
|
+
ns_args['nist_auth_level'] = @nist_auth_level.to_s
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
if @auth_age
|
|
171
|
+
if @auth_age < 0
|
|
172
|
+
raise ArgumentError, "auth_age must be a non-negative integer, not #{@auth_age.inspect}"
|
|
173
|
+
end
|
|
174
|
+
ns_args['auth_age'] = @auth_age.to_s
|
|
175
|
+
end
|
|
176
|
+
return ns_args
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
end
|