pelle-ruby-openid 2.1.8

Sign up to get free protection for your applications and to get access to all the features.
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