entp-ruby-openid 2.2

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