pelle-ruby-openid 2.1.8

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 (197) hide show
  1. data/CHANGELOG +215 -0
  2. data/CHANGES-2.1.0 +36 -0
  3. data/INSTALL +47 -0
  4. data/LICENSE +210 -0
  5. data/NOTICE +2 -0
  6. data/README +82 -0
  7. data/UPGRADE +127 -0
  8. data/VERSION +1 -0
  9. data/examples/README +32 -0
  10. data/examples/active_record_openid_store/README +58 -0
  11. data/examples/active_record_openid_store/XXX_add_open_id_store_to_db.rb +24 -0
  12. data/examples/active_record_openid_store/XXX_upgrade_open_id_store.rb +26 -0
  13. data/examples/active_record_openid_store/init.rb +8 -0
  14. data/examples/active_record_openid_store/lib/association.rb +10 -0
  15. data/examples/active_record_openid_store/lib/nonce.rb +3 -0
  16. data/examples/active_record_openid_store/lib/open_id_setting.rb +4 -0
  17. data/examples/active_record_openid_store/lib/openid_ar_store.rb +57 -0
  18. data/examples/active_record_openid_store/test/store_test.rb +212 -0
  19. data/examples/discover +49 -0
  20. data/examples/rails_openid/README +153 -0
  21. data/examples/rails_openid/Rakefile +10 -0
  22. data/examples/rails_openid/app/controllers/application.rb +4 -0
  23. data/examples/rails_openid/app/controllers/consumer_controller.rb +122 -0
  24. data/examples/rails_openid/app/controllers/login_controller.rb +45 -0
  25. data/examples/rails_openid/app/controllers/server_controller.rb +265 -0
  26. data/examples/rails_openid/app/helpers/application_helper.rb +3 -0
  27. data/examples/rails_openid/app/helpers/login_helper.rb +2 -0
  28. data/examples/rails_openid/app/helpers/server_helper.rb +9 -0
  29. data/examples/rails_openid/app/views/consumer/index.rhtml +81 -0
  30. data/examples/rails_openid/app/views/layouts/server.rhtml +68 -0
  31. data/examples/rails_openid/app/views/login/index.rhtml +56 -0
  32. data/examples/rails_openid/app/views/server/decide.rhtml +26 -0
  33. data/examples/rails_openid/config/boot.rb +19 -0
  34. data/examples/rails_openid/config/database.yml +74 -0
  35. data/examples/rails_openid/config/environment.rb +54 -0
  36. data/examples/rails_openid/config/environments/development.rb +19 -0
  37. data/examples/rails_openid/config/environments/production.rb +19 -0
  38. data/examples/rails_openid/config/environments/test.rb +19 -0
  39. data/examples/rails_openid/config/routes.rb +24 -0
  40. data/examples/rails_openid/doc/README_FOR_APP +2 -0
  41. data/examples/rails_openid/public/.htaccess +40 -0
  42. data/examples/rails_openid/public/404.html +8 -0
  43. data/examples/rails_openid/public/500.html +8 -0
  44. data/examples/rails_openid/public/dispatch.cgi +12 -0
  45. data/examples/rails_openid/public/dispatch.fcgi +26 -0
  46. data/examples/rails_openid/public/dispatch.rb +12 -0
  47. data/examples/rails_openid/public/favicon.ico +0 -0
  48. data/examples/rails_openid/public/images/openid_login_bg.gif +0 -0
  49. data/examples/rails_openid/public/javascripts/controls.js +750 -0
  50. data/examples/rails_openid/public/javascripts/dragdrop.js +584 -0
  51. data/examples/rails_openid/public/javascripts/effects.js +854 -0
  52. data/examples/rails_openid/public/javascripts/prototype.js +1785 -0
  53. data/examples/rails_openid/public/robots.txt +1 -0
  54. data/examples/rails_openid/script/about +3 -0
  55. data/examples/rails_openid/script/breakpointer +3 -0
  56. data/examples/rails_openid/script/console +3 -0
  57. data/examples/rails_openid/script/destroy +3 -0
  58. data/examples/rails_openid/script/generate +3 -0
  59. data/examples/rails_openid/script/performance/benchmarker +3 -0
  60. data/examples/rails_openid/script/performance/profiler +3 -0
  61. data/examples/rails_openid/script/plugin +3 -0
  62. data/examples/rails_openid/script/process/reaper +3 -0
  63. data/examples/rails_openid/script/process/spawner +3 -0
  64. data/examples/rails_openid/script/process/spinner +3 -0
  65. data/examples/rails_openid/script/runner +3 -0
  66. data/examples/rails_openid/script/server +3 -0
  67. data/examples/rails_openid/test/functional/login_controller_test.rb +18 -0
  68. data/examples/rails_openid/test/functional/server_controller_test.rb +18 -0
  69. data/examples/rails_openid/test/test_helper.rb +28 -0
  70. data/lib/hmac/hmac.rb +112 -0
  71. data/lib/hmac/sha1.rb +11 -0
  72. data/lib/hmac/sha2.rb +25 -0
  73. data/lib/openid/association.rb +249 -0
  74. data/lib/openid/consumer/associationmanager.rb +344 -0
  75. data/lib/openid/consumer/checkid_request.rb +186 -0
  76. data/lib/openid/consumer/discovery.rb +498 -0
  77. data/lib/openid/consumer/discovery_manager.rb +123 -0
  78. data/lib/openid/consumer/html_parse.rb +134 -0
  79. data/lib/openid/consumer/idres.rb +523 -0
  80. data/lib/openid/consumer/responses.rb +148 -0
  81. data/lib/openid/consumer.rb +395 -0
  82. data/lib/openid/cryptutil.rb +97 -0
  83. data/lib/openid/dh.rb +89 -0
  84. data/lib/openid/extension.rb +39 -0
  85. data/lib/openid/extensions/ax.rb +516 -0
  86. data/lib/openid/extensions/oauth.rb +91 -0
  87. data/lib/openid/extensions/pape.rb +179 -0
  88. data/lib/openid/extensions/sreg.rb +277 -0
  89. data/lib/openid/extras.rb +11 -0
  90. data/lib/openid/fetchers.rb +238 -0
  91. data/lib/openid/kvform.rb +136 -0
  92. data/lib/openid/kvpost.rb +58 -0
  93. data/lib/openid/message.rb +553 -0
  94. data/lib/openid/protocolerror.rb +8 -0
  95. data/lib/openid/server.rb +1544 -0
  96. data/lib/openid/store/filesystem.rb +271 -0
  97. data/lib/openid/store/interface.rb +75 -0
  98. data/lib/openid/store/memcache.rb +107 -0
  99. data/lib/openid/store/memory.rb +84 -0
  100. data/lib/openid/store/nonce.rb +68 -0
  101. data/lib/openid/trustroot.rb +349 -0
  102. data/lib/openid/urinorm.rb +75 -0
  103. data/lib/openid/util.rb +110 -0
  104. data/lib/openid/yadis/accept.rb +148 -0
  105. data/lib/openid/yadis/constants.rb +21 -0
  106. data/lib/openid/yadis/discovery.rb +153 -0
  107. data/lib/openid/yadis/filters.rb +205 -0
  108. data/lib/openid/yadis/htmltokenizer.rb +305 -0
  109. data/lib/openid/yadis/parsehtml.rb +45 -0
  110. data/lib/openid/yadis/services.rb +42 -0
  111. data/lib/openid/yadis/xrds.rb +155 -0
  112. data/lib/openid/yadis/xri.rb +90 -0
  113. data/lib/openid/yadis/xrires.rb +106 -0
  114. data/lib/openid.rb +20 -0
  115. data/setup.rb +1551 -0
  116. data/test/data/accept.txt +124 -0
  117. data/test/data/dh.txt +29 -0
  118. data/test/data/example-xrds.xml +14 -0
  119. data/test/data/linkparse.txt +587 -0
  120. data/test/data/n2b64 +650 -0
  121. data/test/data/test1-discover.txt +137 -0
  122. data/test/data/test1-parsehtml.txt +152 -0
  123. data/test/data/test_discover/malformed_meta_tag.html +19 -0
  124. data/test/data/test_discover/openid.html +11 -0
  125. data/test/data/test_discover/openid2.html +11 -0
  126. data/test/data/test_discover/openid2_xrds.xml +12 -0
  127. data/test/data/test_discover/openid2_xrds_no_local_id.xml +11 -0
  128. data/test/data/test_discover/openid_1_and_2.html +11 -0
  129. data/test/data/test_discover/openid_1_and_2_xrds.xml +16 -0
  130. data/test/data/test_discover/openid_1_and_2_xrds_bad_delegate.xml +17 -0
  131. data/test/data/test_discover/openid_and_yadis.html +12 -0
  132. data/test/data/test_discover/openid_no_delegate.html +10 -0
  133. data/test/data/test_discover/yadis_0entries.xml +12 -0
  134. data/test/data/test_discover/yadis_2_bad_local_id.xml +15 -0
  135. data/test/data/test_discover/yadis_2entries_delegate.xml +22 -0
  136. data/test/data/test_discover/yadis_2entries_idp.xml +21 -0
  137. data/test/data/test_discover/yadis_another_delegate.xml +14 -0
  138. data/test/data/test_discover/yadis_idp.xml +12 -0
  139. data/test/data/test_discover/yadis_idp_delegate.xml +13 -0
  140. data/test/data/test_discover/yadis_no_delegate.xml +11 -0
  141. data/test/data/test_xrds/=j3h.2007.11.14.xrds +25 -0
  142. data/test/data/test_xrds/README +12 -0
  143. data/test/data/test_xrds/delegated-20060809-r1.xrds +34 -0
  144. data/test/data/test_xrds/delegated-20060809-r2.xrds +34 -0
  145. data/test/data/test_xrds/delegated-20060809.xrds +34 -0
  146. data/test/data/test_xrds/no-xrd.xml +7 -0
  147. data/test/data/test_xrds/not-xrds.xml +2 -0
  148. data/test/data/test_xrds/prefixsometimes.xrds +34 -0
  149. data/test/data/test_xrds/ref.xrds +109 -0
  150. data/test/data/test_xrds/sometimesprefix.xrds +34 -0
  151. data/test/data/test_xrds/spoof1.xrds +25 -0
  152. data/test/data/test_xrds/spoof2.xrds +25 -0
  153. data/test/data/test_xrds/spoof3.xrds +37 -0
  154. data/test/data/test_xrds/status222.xrds +9 -0
  155. data/test/data/test_xrds/subsegments.xrds +58 -0
  156. data/test/data/test_xrds/valid-populated-xrds.xml +39 -0
  157. data/test/data/trustroot.txt +153 -0
  158. data/test/data/urinorm.txt +79 -0
  159. data/test/discoverdata.rb +131 -0
  160. data/test/test_accept.rb +170 -0
  161. data/test/test_association.rb +266 -0
  162. data/test/test_associationmanager.rb +917 -0
  163. data/test/test_ax.rb +648 -0
  164. data/test/test_checkid_request.rb +294 -0
  165. data/test/test_consumer.rb +257 -0
  166. data/test/test_cryptutil.rb +119 -0
  167. data/test/test_dh.rb +86 -0
  168. data/test/test_discover.rb +838 -0
  169. data/test/test_discovery_manager.rb +262 -0
  170. data/test/test_extension.rb +46 -0
  171. data/test/test_extras.rb +35 -0
  172. data/test/test_fetchers.rb +538 -0
  173. data/test/test_filters.rb +270 -0
  174. data/test/test_idres.rb +963 -0
  175. data/test/test_kvform.rb +165 -0
  176. data/test/test_kvpost.rb +65 -0
  177. data/test/test_linkparse.rb +101 -0
  178. data/test/test_message.rb +1116 -0
  179. data/test/test_nonce.rb +89 -0
  180. data/test/test_oauth.rb +175 -0
  181. data/test/test_openid_yadis.rb +178 -0
  182. data/test/test_pape.rb +247 -0
  183. data/test/test_parsehtml.rb +80 -0
  184. data/test/test_responses.rb +63 -0
  185. data/test/test_server.rb +2457 -0
  186. data/test/test_sreg.rb +479 -0
  187. data/test/test_stores.rb +298 -0
  188. data/test/test_trustroot.rb +113 -0
  189. data/test/test_urinorm.rb +35 -0
  190. data/test/test_util.rb +145 -0
  191. data/test/test_xrds.rb +169 -0
  192. data/test/test_xri.rb +48 -0
  193. data/test/test_xrires.rb +63 -0
  194. data/test/test_yadis_discovery.rb +220 -0
  195. data/test/testutil.rb +127 -0
  196. data/test/util.rb +53 -0
  197. metadata +316 -0
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.zip(t.bytes).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,516 @@
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
+ def initialize(update_url = nil)
115
+ super()
116
+ @mode = 'fetch_request'
117
+ @requested_attributes = {}
118
+ @update_url = update_url
119
+ end
120
+
121
+ # Add an attribute to this attribute exchange request.
122
+ # attribute: AttrInfo, the attribute being requested
123
+ # Raises IndexError if the requested attribute is already present
124
+ # in this request.
125
+ def add(attribute)
126
+ if @requested_attributes[attribute.type_uri]
127
+ raise IndexError, "The attribute #{attribute.type_uri} has already been requested"
128
+ end
129
+ @requested_attributes[attribute.type_uri] = attribute
130
+ end
131
+
132
+ # Get the serialized form of this attribute fetch request.
133
+ # returns a hash of the arguments
134
+ def get_extension_args
135
+ aliases = NamespaceMap.new
136
+ required = []
137
+ if_available = []
138
+ ax_args = new_args
139
+ @requested_attributes.each{|type_uri, attribute|
140
+ if attribute.ns_alias
141
+ name = aliases.add_alias(type_uri, attribute.ns_alias)
142
+ else
143
+ name = aliases.add(type_uri)
144
+ end
145
+ if attribute.required
146
+ required << name
147
+ else
148
+ if_available << name
149
+ end
150
+ if attribute.count != 1
151
+ ax_args["count.#{name}"] = attribute.count.to_s
152
+ end
153
+ ax_args["type.#{name}"] = type_uri
154
+ }
155
+
156
+ unless required.empty?
157
+ ax_args['required'] = required.join(',')
158
+ end
159
+ unless if_available.empty?
160
+ ax_args['if_available'] = if_available.join(',')
161
+ end
162
+ return ax_args
163
+ end
164
+
165
+ # Get the type URIs for all attributes that have been marked
166
+ # as required.
167
+ def get_required_attrs
168
+ @requested_attributes.inject([]) {|required, (type_uri, attribute)|
169
+ if attribute.required
170
+ required << type_uri
171
+ else
172
+ required
173
+ end
174
+ }
175
+ end
176
+
177
+ # Extract a FetchRequest from an OpenID message
178
+ # message: OpenID::Message
179
+ # return a FetchRequest or nil if AX arguments are not present
180
+ def self.from_openid_request(oidreq)
181
+ message = oidreq.message
182
+ ax_args = message.get_args(NS_URI)
183
+ return nil if ax_args == {}
184
+ req = new
185
+ req.parse_extension_args(ax_args)
186
+
187
+ if req.update_url
188
+ realm = message.get_arg(OPENID_NS, 'realm',
189
+ message.get_arg(OPENID_NS, 'return_to'))
190
+ if realm.nil? or realm.empty?
191
+ raise Error, "Cannot validate update_url #{req.update_url.inspect} against absent realm"
192
+ end
193
+ tr = TrustRoot::TrustRoot.parse(realm)
194
+ unless tr.validate_url(req.update_url)
195
+ raise Error, "Update URL #{req.update_url.inspect} failed validation against realm #{realm.inspect}"
196
+ end
197
+ end
198
+
199
+ return req
200
+ end
201
+
202
+ def parse_extension_args(ax_args)
203
+ check_mode(ax_args)
204
+
205
+ aliases = NamespaceMap.new
206
+
207
+ ax_args.each{|k,v|
208
+ if k.index('type.') == 0
209
+ name = k[5..-1]
210
+ type_uri = v
211
+ aliases.add_alias(type_uri, name)
212
+
213
+ count_key = 'count.'+name
214
+ count_s = ax_args[count_key]
215
+ count = 1
216
+ if count_s
217
+ if count_s == UNLIMITED_VALUES
218
+ count = count_s
219
+ else
220
+ count = count_s.to_i
221
+ if count <= 0
222
+ raise Error, "Invalid value for count #{count_key.inspect}: #{count_s.inspect}"
223
+ end
224
+ end
225
+ end
226
+ add(AttrInfo.new(type_uri, name, false, count))
227
+ end
228
+ }
229
+
230
+ required = AX.to_type_uris(aliases, ax_args['required'])
231
+ required.each{|type_uri|
232
+ @requested_attributes[type_uri].required = true
233
+ }
234
+ if_available = AX.to_type_uris(aliases, ax_args['if_available'])
235
+ all_type_uris = required + if_available
236
+
237
+ aliases.namespace_uris.each{|type_uri|
238
+ unless all_type_uris.member? type_uri
239
+ raise Error, "Type URI #{type_uri.inspect} was in the request but not present in 'required' or 'if_available'"
240
+ end
241
+ }
242
+ @update_url = ax_args['update_url']
243
+ end
244
+
245
+ # return the list of AttrInfo objects contained in the FetchRequest
246
+ def attributes
247
+ @requested_attributes.values
248
+ end
249
+
250
+ # return the list of requested attribute type URIs
251
+ def requested_types
252
+ @requested_attributes.keys
253
+ end
254
+
255
+ def member?(type_uri)
256
+ ! @requested_attributes[type_uri].nil?
257
+ end
258
+
259
+ end
260
+
261
+ # Abstract class that implements a message that has attribute
262
+ # keys and values. It contains the common code between
263
+ # fetch_response and store_request.
264
+ class KeyValueMessage < AXMessage
265
+ attr_reader :data
266
+ def initialize
267
+ super()
268
+ @mode = nil
269
+ @data = {}
270
+ @data.default = []
271
+ end
272
+
273
+ # Add a single value for the given attribute type to the
274
+ # message. If there are already values specified for this type,
275
+ # this value will be sent in addition to the values already
276
+ # specified.
277
+ def add_value(type_uri, value)
278
+ @data[type_uri] = @data[type_uri] << value
279
+ end
280
+
281
+ # Set the values for the given attribute type. This replaces
282
+ # any values that have already been set for this attribute.
283
+ def set_values(type_uri, values)
284
+ @data[type_uri] = values
285
+ end
286
+
287
+ # Get the extension arguments for the key/value pairs
288
+ # contained in this message.
289
+ def _get_extension_kv_args(aliases = nil)
290
+ aliases = NamespaceMap.new if aliases.nil?
291
+
292
+ ax_args = new_args
293
+
294
+ @data.each{|type_uri, values|
295
+ name = aliases.add(type_uri)
296
+ ax_args['type.'+name] = type_uri
297
+ ax_args['count.'+name] = values.size.to_s
298
+
299
+ values.each_with_index{|value, i|
300
+ key = "value.#{name}.#{i+1}"
301
+ ax_args[key] = value
302
+ }
303
+ }
304
+ return ax_args
305
+ end
306
+
307
+ # Parse attribute exchange key/value arguments into this object.
308
+
309
+ def parse_extension_args(ax_args)
310
+ check_mode(ax_args)
311
+ aliases = NamespaceMap.new
312
+
313
+ ax_args.each{|k, v|
314
+ if k.index('type.') == 0
315
+ type_uri = v
316
+ name = k[5..-1]
317
+
318
+ AX.check_alias(name)
319
+ aliases.add_alias(type_uri,name)
320
+ end
321
+ }
322
+
323
+ aliases.each{|type_uri, name|
324
+ count_s = ax_args['count.'+name]
325
+ count = count_s.to_i
326
+ if count_s.nil?
327
+ value = ax_args['value.'+name]
328
+ if value.nil?
329
+ raise IndexError, "Missing #{'value.'+name} in FetchResponse"
330
+ elsif value.empty?
331
+ values = []
332
+ else
333
+ values = [value]
334
+ end
335
+ elsif count_s.to_i == 0
336
+ values = []
337
+ else
338
+ values = (1..count).inject([]){|l,i|
339
+ key = "value.#{name}.#{i}"
340
+ v = ax_args[key]
341
+ raise IndexError, "Missing #{key} in FetchResponse" if v.nil?
342
+ l << v
343
+ }
344
+ end
345
+ @data[type_uri] = values
346
+ }
347
+ end
348
+
349
+ # Get a single value for an attribute. If no value was sent
350
+ # for this attribute, use the supplied default. If there is more
351
+ # than one value for this attribute, this method will fail.
352
+ def get_single(type_uri, default = nil)
353
+ values = @data[type_uri]
354
+ return default if values.empty?
355
+ if values.size != 1
356
+ raise Error, "More than one value present for #{type_uri.inspect}"
357
+ else
358
+ return values[0]
359
+ end
360
+ end
361
+
362
+ # retrieve the list of values for this attribute
363
+ def get(type_uri)
364
+ @data[type_uri]
365
+ end
366
+
367
+ # retrieve the list of values for this attribute
368
+ def [](type_uri)
369
+ @data[type_uri]
370
+ end
371
+
372
+ # get the number of responses for this attribute
373
+ def count(type_uri)
374
+ @data[type_uri].size
375
+ end
376
+
377
+ end
378
+
379
+ # A fetch_response attribute exchange message
380
+ class FetchResponse < KeyValueMessage
381
+ attr_reader :update_url
382
+
383
+ def initialize(update_url = nil)
384
+ super()
385
+ @mode = 'fetch_response'
386
+ @update_url = update_url
387
+ end
388
+
389
+ # Serialize this object into arguments in the attribute
390
+ # exchange namespace
391
+ # Takes an optional FetchRequest. If specified, the response will be
392
+ # validated against this request, and empty responses for requested
393
+ # fields with no data will be sent.
394
+ def get_extension_args(request = nil)
395
+ aliases = NamespaceMap.new
396
+ zero_value_types = []
397
+
398
+ if request
399
+ # Validate the data in the context of the request (the
400
+ # same attributes should be present in each, and the
401
+ # counts in the response must be no more than the counts
402
+ # in the request)
403
+ @data.keys.each{|type_uri|
404
+ unless request.member? type_uri
405
+ raise IndexError, "Response attribute not present in request: #{type_uri.inspect}"
406
+ end
407
+ }
408
+
409
+ request.attributes.each{|attr_info|
410
+ # Copy the aliases from the request so that reading
411
+ # the response in light of the request is easier
412
+ if attr_info.ns_alias.nil?
413
+ aliases.add(attr_info.type_uri)
414
+ else
415
+ aliases.add_alias(attr_info.type_uri, attr_info.ns_alias)
416
+ end
417
+ values = @data[attr_info.type_uri]
418
+ if values.empty? # @data defaults to []
419
+ zero_value_types << attr_info
420
+ end
421
+ if attr_info.count != UNLIMITED_VALUES and attr_info.count < values.size
422
+ raise Error, "More than the number of requested values were specified for #{attr_info.type_uri.inspect}"
423
+ end
424
+ }
425
+ end
426
+
427
+ kv_args = _get_extension_kv_args(aliases)
428
+
429
+ # Add the KV args into the response with the args that are
430
+ # unique to the fetch_response
431
+ ax_args = new_args
432
+
433
+ zero_value_types.each{|attr_info|
434
+ name = aliases.get_alias(attr_info.type_uri)
435
+ kv_args['type.' + name] = attr_info.type_uri
436
+ kv_args['count.' + name] = '0'
437
+ }
438
+ update_url = (request and request.update_url or @update_url)
439
+ ax_args['update_url'] = update_url unless update_url.nil?
440
+ ax_args.update(kv_args)
441
+ return ax_args
442
+ end
443
+
444
+ def parse_extension_args(ax_args)
445
+ super
446
+ @update_url = ax_args['update_url']
447
+ end
448
+
449
+ # Construct a FetchResponse object from an OpenID library
450
+ # SuccessResponse object.
451
+ def self.from_success_response(success_response, signed=true)
452
+ obj = self.new
453
+ if signed
454
+ ax_args = success_response.get_signed_ns(obj.ns_uri)
455
+ else
456
+ ax_args = success_response.message.get_args(obj.ns_uri)
457
+ end
458
+
459
+ begin
460
+ obj.parse_extension_args(ax_args)
461
+ return obj
462
+ rescue Error => e
463
+ return nil
464
+ end
465
+ end
466
+ end
467
+
468
+ # A store request attribute exchange message representation
469
+ class StoreRequest < KeyValueMessage
470
+ def initialize
471
+ super
472
+ @mode = 'store_request'
473
+ end
474
+
475
+ def get_extension_args(aliases=nil)
476
+ ax_args = new_args
477
+ kv_args = _get_extension_kv_args(aliases)
478
+ ax_args.update(kv_args)
479
+ return ax_args
480
+ end
481
+ end
482
+
483
+ # An indication that the store request was processed along with
484
+ # this OpenID transaction.
485
+ class StoreResponse < AXMessage
486
+ SUCCESS_MODE = 'store_response_success'
487
+ FAILURE_MODE = 'store_response_failure'
488
+ attr_reader :error_message
489
+
490
+ def initialize(succeeded = true, error_message = nil)
491
+ super()
492
+ if succeeded and error_message
493
+ raise Error, "Error message included in a success response"
494
+ end
495
+ if succeeded
496
+ @mode = SUCCESS_MODE
497
+ else
498
+ @mode = FAILURE_MODE
499
+ end
500
+ @error_message = error_message
501
+ end
502
+
503
+ def succeeded?
504
+ @mode == SUCCESS_MODE
505
+ end
506
+
507
+ def get_extension_args
508
+ ax_args = new_args
509
+ if !succeeded? and error_message
510
+ ax_args['error'] = @error_message
511
+ end
512
+ return ax_args
513
+ end
514
+ end
515
+ end
516
+ end