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.
Files changed (207) hide show
  1. data/INSTALL +0 -9
  2. data/README +21 -22
  3. data/UPGRADE +117 -0
  4. data/admin/runtests.rb +36 -0
  5. data/examples/README +13 -21
  6. data/examples/active_record_openid_store/README +8 -3
  7. data/examples/active_record_openid_store/XXX_add_open_id_store_to_db.rb +4 -8
  8. data/examples/active_record_openid_store/XXX_upgrade_open_id_store.rb +26 -0
  9. data/examples/active_record_openid_store/lib/association.rb +2 -0
  10. data/examples/active_record_openid_store/lib/openid_ar_store.rb +22 -47
  11. data/examples/active_record_openid_store/test/store_test.rb +78 -48
  12. data/examples/discover +46 -0
  13. data/examples/{rails_server → rails_openid}/README +0 -0
  14. data/examples/{rails_server → rails_openid}/Rakefile +0 -0
  15. data/examples/{rails_server → rails_openid}/app/controllers/application.rb +0 -0
  16. data/examples/rails_openid/app/controllers/consumer_controller.rb +115 -0
  17. data/examples/{rails_server → rails_openid}/app/controllers/login_controller.rb +10 -2
  18. data/examples/rails_openid/app/controllers/server_controller.rb +265 -0
  19. data/examples/{rails_server → rails_openid}/app/helpers/application_helper.rb +0 -0
  20. data/examples/{rails_server → rails_openid}/app/helpers/login_helper.rb +0 -0
  21. data/examples/{rails_server → rails_openid}/app/helpers/server_helper.rb +0 -0
  22. data/examples/rails_openid/app/views/consumer/index.rhtml +81 -0
  23. data/examples/rails_openid/app/views/consumer/start.rhtml +8 -0
  24. data/examples/{rails_server → rails_openid}/app/views/layouts/server.rhtml +0 -0
  25. data/examples/{rails_server → rails_openid}/app/views/login/index.rhtml +1 -1
  26. data/examples/rails_openid/app/views/server/decide.rhtml +26 -0
  27. data/examples/{rails_server → rails_openid}/config/boot.rb +0 -0
  28. data/examples/{rails_server → rails_openid}/config/database.yml +0 -0
  29. data/examples/{rails_server → rails_openid}/config/environment.rb +0 -0
  30. data/examples/{rails_server → rails_openid}/config/environments/development.rb +0 -0
  31. data/examples/{rails_server → rails_openid}/config/environments/production.rb +0 -0
  32. data/examples/{rails_server → rails_openid}/config/environments/test.rb +0 -0
  33. data/examples/{rails_server → rails_openid}/config/routes.rb +2 -1
  34. data/examples/{rails_server → rails_openid}/doc/README_FOR_APP +0 -0
  35. data/examples/{rails_server → rails_openid}/public/404.html +0 -0
  36. data/examples/{rails_server → rails_openid}/public/500.html +0 -0
  37. data/examples/{rails_server → rails_openid}/public/dispatch.cgi +0 -0
  38. data/examples/{rails_server → rails_openid}/public/dispatch.fcgi +0 -0
  39. data/examples/{rails_server → rails_openid}/public/dispatch.rb +0 -0
  40. data/examples/{rails_server → rails_openid}/public/favicon.ico +0 -0
  41. data/examples/rails_openid/public/images/openid_login_bg.gif +0 -0
  42. data/examples/{rails_server → rails_openid}/public/javascripts/controls.js +0 -0
  43. data/examples/{rails_server → rails_openid}/public/javascripts/dragdrop.js +0 -0
  44. data/examples/{rails_server → rails_openid}/public/javascripts/effects.js +0 -0
  45. data/examples/{rails_server → rails_openid}/public/javascripts/prototype.js +0 -0
  46. data/examples/{rails_server → rails_openid}/public/robots.txt +0 -0
  47. data/examples/{rails_server → rails_openid}/script/about +0 -0
  48. data/examples/{rails_server → rails_openid}/script/breakpointer +0 -0
  49. data/examples/{rails_server → rails_openid}/script/console +0 -0
  50. data/examples/{rails_server → rails_openid}/script/destroy +0 -0
  51. data/examples/{rails_server → rails_openid}/script/generate +0 -0
  52. data/examples/{rails_server → rails_openid}/script/performance/benchmarker +0 -0
  53. data/examples/{rails_server → rails_openid}/script/performance/profiler +0 -0
  54. data/examples/{rails_server → rails_openid}/script/plugin +0 -0
  55. data/examples/{rails_server → rails_openid}/script/process/reaper +0 -0
  56. data/examples/{rails_server → rails_openid}/script/process/spawner +0 -0
  57. data/examples/{rails_server → rails_openid}/script/process/spinner +0 -0
  58. data/examples/{rails_server → rails_openid}/script/runner +0 -0
  59. data/examples/{rails_server → rails_openid}/script/server +0 -0
  60. data/examples/{rails_server → rails_openid}/test/functional/login_controller_test.rb +0 -0
  61. data/examples/{rails_server → rails_openid}/test/functional/server_controller_test.rb +0 -0
  62. data/examples/{rails_server → rails_openid}/test/test_helper.rb +0 -0
  63. data/lib/{hmac.rb → hmac/hmac.rb} +0 -0
  64. data/lib/{hmac-sha1.rb → hmac/sha1.rb} +1 -1
  65. data/lib/{hmac-sha2.rb → hmac/sha2.rb} +1 -1
  66. data/lib/openid/association.rb +213 -73
  67. data/lib/openid/consumer/associationmanager.rb +338 -0
  68. data/lib/openid/consumer/checkid_request.rb +175 -0
  69. data/lib/openid/consumer/discovery.rb +480 -0
  70. data/lib/openid/consumer/discovery_manager.rb +123 -0
  71. data/lib/openid/consumer/html_parse.rb +136 -0
  72. data/lib/openid/consumer/idres.rb +525 -0
  73. data/lib/openid/consumer/responses.rb +133 -0
  74. data/lib/openid/consumer.rb +280 -807
  75. data/lib/openid/cryptutil.rb +85 -0
  76. data/lib/openid/dh.rb +60 -23
  77. data/lib/openid/extension.rb +31 -0
  78. data/lib/openid/extensions/ax.rb +506 -0
  79. data/lib/openid/extensions/pape.rb +182 -0
  80. data/lib/openid/extensions/sreg.rb +275 -0
  81. data/lib/openid/extras.rb +11 -0
  82. data/lib/openid/fetchers.rb +132 -93
  83. data/lib/openid/kvform.rb +133 -0
  84. data/lib/openid/kvpost.rb +56 -0
  85. data/lib/openid/message.rb +534 -0
  86. data/lib/openid/protocolerror.rb +6 -0
  87. data/lib/openid/server.rb +1215 -666
  88. data/lib/openid/store/filesystem.rb +271 -0
  89. data/lib/openid/store/interface.rb +75 -0
  90. data/lib/openid/store/memory.rb +84 -0
  91. data/lib/openid/store/nonce.rb +68 -0
  92. data/lib/openid/trustroot.rb +314 -87
  93. data/lib/openid/urinorm.rb +37 -34
  94. data/lib/openid/util.rb +42 -220
  95. data/lib/openid/yadis/accept.rb +148 -0
  96. data/lib/openid/yadis/constants.rb +21 -0
  97. data/lib/openid/yadis/discovery.rb +153 -0
  98. data/lib/openid/yadis/filters.rb +205 -0
  99. data/lib/openid/{htmltokenizer.rb → yadis/htmltokenizer.rb} +1 -54
  100. data/lib/openid/yadis/parsehtml.rb +36 -0
  101. data/lib/openid/yadis/services.rb +42 -0
  102. data/lib/openid/yadis/xrds.rb +171 -0
  103. data/lib/openid/yadis/xri.rb +90 -0
  104. data/lib/openid/yadis/xrires.rb +106 -0
  105. data/lib/openid.rb +1 -4
  106. data/test/data/accept.txt +124 -0
  107. data/test/data/dh.txt +29 -0
  108. data/test/data/example-xrds.xml +14 -0
  109. data/test/data/linkparse.txt +587 -0
  110. data/test/data/n2b64 +650 -0
  111. data/test/data/test1-discover.txt +137 -0
  112. data/test/data/test1-parsehtml.txt +128 -0
  113. data/test/data/test_discover/openid.html +11 -0
  114. data/test/data/test_discover/openid2.html +11 -0
  115. data/test/data/test_discover/openid2_xrds.xml +12 -0
  116. data/test/data/test_discover/openid2_xrds_no_local_id.xml +11 -0
  117. data/test/data/test_discover/openid_1_and_2.html +11 -0
  118. data/test/data/test_discover/openid_1_and_2_xrds.xml +16 -0
  119. data/test/data/test_discover/openid_1_and_2_xrds_bad_delegate.xml +17 -0
  120. data/test/data/test_discover/openid_and_yadis.html +12 -0
  121. data/test/data/test_discover/openid_no_delegate.html +10 -0
  122. data/test/data/test_discover/yadis_0entries.xml +12 -0
  123. data/test/data/test_discover/yadis_2_bad_local_id.xml +15 -0
  124. data/test/data/test_discover/yadis_2entries_delegate.xml +22 -0
  125. data/test/data/test_discover/yadis_2entries_idp.xml +21 -0
  126. data/test/data/test_discover/yadis_another_delegate.xml +14 -0
  127. data/test/data/test_discover/yadis_idp.xml +12 -0
  128. data/test/data/test_discover/yadis_idp_delegate.xml +13 -0
  129. data/test/data/test_discover/yadis_no_delegate.xml +11 -0
  130. data/test/data/test_xrds/=j3h.2007.11.14.xrds +25 -0
  131. data/test/data/test_xrds/README +12 -0
  132. data/test/data/test_xrds/delegated-20060809-r1.xrds +34 -0
  133. data/test/data/test_xrds/delegated-20060809-r2.xrds +34 -0
  134. data/test/data/test_xrds/delegated-20060809.xrds +34 -0
  135. data/test/data/test_xrds/no-xrd.xml +7 -0
  136. data/test/data/test_xrds/not-xrds.xml +2 -0
  137. data/test/data/test_xrds/prefixsometimes.xrds +34 -0
  138. data/test/data/test_xrds/ref.xrds +109 -0
  139. data/test/data/test_xrds/sometimesprefix.xrds +34 -0
  140. data/test/data/test_xrds/spoof1.xrds +25 -0
  141. data/test/data/test_xrds/spoof2.xrds +25 -0
  142. data/test/data/test_xrds/spoof3.xrds +37 -0
  143. data/test/data/test_xrds/status222.xrds +9 -0
  144. data/test/data/test_xrds/valid-populated-xrds.xml +39 -0
  145. data/test/data/trustroot.txt +147 -0
  146. data/test/discoverdata.rb +131 -0
  147. data/test/test_accept.rb +170 -0
  148. data/test/test_association.rb +266 -0
  149. data/test/test_associationmanager.rb +899 -0
  150. data/test/test_ax.rb +587 -0
  151. data/test/test_checkid_request.rb +297 -0
  152. data/test/test_consumer.rb +257 -0
  153. data/test/test_cryptutil.rb +117 -0
  154. data/test/test_dh.rb +86 -0
  155. data/test/test_discover.rb +772 -0
  156. data/test/test_discovery_manager.rb +262 -0
  157. data/test/test_extras.rb +35 -0
  158. data/test/test_fetchers.rb +472 -0
  159. data/test/test_filters.rb +270 -0
  160. data/test/test_idres.rb +816 -0
  161. data/test/test_kvform.rb +165 -0
  162. data/test/test_kvpost.rb +65 -0
  163. data/test/test_linkparse.rb +101 -0
  164. data/test/test_message.rb +1058 -0
  165. data/test/test_nonce.rb +89 -0
  166. data/test/test_openid_yadis.rb +178 -0
  167. data/test/test_pape.rb +233 -0
  168. data/test/test_parsehtml.rb +80 -0
  169. data/test/test_responses.rb +63 -0
  170. data/test/test_server.rb +2270 -0
  171. data/test/test_sreg.rb +479 -0
  172. data/test/test_stores.rb +269 -0
  173. data/test/test_trustroot.rb +112 -0
  174. data/test/{urinorm.rb → test_urinorm.rb} +6 -3
  175. data/test/test_util.rb +144 -0
  176. data/test/test_xrds.rb +160 -0
  177. data/test/test_xri.rb +48 -0
  178. data/test/test_xrires.rb +63 -0
  179. data/test/test_yadis_discovery.rb +207 -0
  180. data/test/testutil.rb +116 -0
  181. data/test/util.rb +47 -50
  182. metadata +233 -143
  183. data/examples/consumer.rb +0 -290
  184. data/examples/rails_openid_login_generator/openid_login_generator-0.1.gem +0 -0
  185. data/examples/rails_server/app/controllers/server_controller.rb +0 -190
  186. data/examples/rails_server/app/views/server/decide.rhtml +0 -11
  187. data/examples/rails_server/public/images/rails.png +0 -0
  188. data/lib/hmac-md5.rb +0 -11
  189. data/lib/hmac-rmd160.rb +0 -11
  190. data/lib/openid/discovery.rb +0 -122
  191. data/lib/openid/filestore.rb +0 -315
  192. data/lib/openid/parse.rb +0 -23
  193. data/lib/openid/service.rb +0 -147
  194. data/lib/openid/stores.rb +0 -178
  195. data/test/assoc.rb +0 -38
  196. data/test/consumer.rb +0 -376
  197. data/test/data/brian.xrds +0 -16
  198. data/test/data/brianellin.mylid.xrds +0 -42
  199. data/test/dh.rb +0 -20
  200. data/test/extensions.rb +0 -30
  201. data/test/linkparse.rb +0 -305
  202. data/test/runtests.rb +0 -22
  203. data/test/server2.rb +0 -1053
  204. data/test/service.rb +0 -47
  205. data/test/storetestcase.rb +0 -172
  206. data/test/teststore.rb +0 -47
  207. 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