ruby-openid 1.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of ruby-openid might be problematic. Click here for more details.

Files changed (114) hide show
  1. data/COPYING +21 -0
  2. data/INSTALL +34 -0
  3. data/README +67 -0
  4. data/TODO +9 -0
  5. data/examples/README +54 -0
  6. data/examples/cacert.pem +7815 -0
  7. data/examples/consumer.rb +285 -0
  8. data/examples/openid-store/associations/http-localhost_3A3000_2Fserver-EMQbAy3NnHVzA.s0u5KAcplKGzo +6 -0
  9. data/examples/openid-store/auth_key +1 -0
  10. data/examples/rails_active_record_store/README +59 -0
  11. data/examples/rails_active_record_store/XX_add_openidstore.rb +30 -0
  12. data/examples/rails_active_record_store/models/openid_association.rb +12 -0
  13. data/examples/rails_active_record_store/models/openid_nonce.rb +3 -0
  14. data/examples/rails_active_record_store/models/openid_setting.rb +2 -0
  15. data/examples/rails_active_record_store/openid_helper.rb +91 -0
  16. data/examples/rails_active_record_store/openidstore_test.rb +15 -0
  17. data/examples/rails_active_record_store/schema.mysql.sql +22 -0
  18. data/examples/rails_active_record_store/schema.postgresql.sql +21 -0
  19. data/examples/rails_active_record_store/schema.sqlite.sql +21 -0
  20. data/examples/rails_openid_login_generator/USAGE +23 -0
  21. data/examples/rails_openid_login_generator/openid_login_generator.rb +36 -0
  22. data/examples/rails_openid_login_generator/templates/README +116 -0
  23. data/examples/rails_openid_login_generator/templates/controller.rb +116 -0
  24. data/examples/rails_openid_login_generator/templates/controller_test.rb +0 -0
  25. data/examples/rails_openid_login_generator/templates/helper.rb +2 -0
  26. data/examples/rails_openid_login_generator/templates/openid_login_system.rb +87 -0
  27. data/examples/rails_openid_login_generator/templates/user.rb +14 -0
  28. data/examples/rails_openid_login_generator/templates/user_test.rb +0 -0
  29. data/examples/rails_openid_login_generator/templates/users.yml +0 -0
  30. data/examples/rails_openid_login_generator/templates/view_login.rhtml +15 -0
  31. data/examples/rails_openid_login_generator/templates/view_logout.rhtml +10 -0
  32. data/examples/rails_openid_login_generator/templates/view_welcome.rhtml +9 -0
  33. data/examples/rails_server/README +153 -0
  34. data/examples/rails_server/Rakefile +10 -0
  35. data/examples/rails_server/app/controllers/application.rb +4 -0
  36. data/examples/rails_server/app/controllers/login_controller.rb +35 -0
  37. data/examples/rails_server/app/controllers/server_controller.rb +185 -0
  38. data/examples/rails_server/app/helpers/application_helper.rb +3 -0
  39. data/examples/rails_server/app/helpers/login_helper.rb +2 -0
  40. data/examples/rails_server/app/helpers/server_helper.rb +9 -0
  41. data/examples/rails_server/app/views/layouts/server.rhtml +61 -0
  42. data/examples/rails_server/app/views/login/index.rhtml +32 -0
  43. data/examples/rails_server/app/views/server/decide.rhtml +11 -0
  44. data/examples/rails_server/config/boot.rb +19 -0
  45. data/examples/rails_server/config/database.yml +85 -0
  46. data/examples/rails_server/config/environment.rb +53 -0
  47. data/examples/rails_server/config/environments/development.rb +19 -0
  48. data/examples/rails_server/config/environments/production.rb +19 -0
  49. data/examples/rails_server/config/environments/test.rb +19 -0
  50. data/examples/rails_server/config/routes.rb +23 -0
  51. data/examples/rails_server/db/openid-store/associations/http-localhost_2F_7Cnormal-YU.tkND1J4fEZhnuAoT5Zc0yCA0 +6 -0
  52. data/examples/rails_server/doc/README_FOR_APP +2 -0
  53. data/examples/rails_server/log/development.log +6059 -0
  54. data/examples/rails_server/log/production.log +0 -0
  55. data/examples/rails_server/log/server.log +0 -0
  56. data/examples/rails_server/log/test.log +0 -0
  57. data/examples/rails_server/public/404.html +8 -0
  58. data/examples/rails_server/public/500.html +8 -0
  59. data/examples/rails_server/public/dispatch.cgi +12 -0
  60. data/examples/rails_server/public/dispatch.fcgi +26 -0
  61. data/examples/rails_server/public/dispatch.rb +12 -0
  62. data/examples/rails_server/public/favicon.ico +0 -0
  63. data/examples/rails_server/public/images/rails.png +0 -0
  64. data/examples/rails_server/public/javascripts/controls.js +750 -0
  65. data/examples/rails_server/public/javascripts/dragdrop.js +584 -0
  66. data/examples/rails_server/public/javascripts/effects.js +854 -0
  67. data/examples/rails_server/public/javascripts/prototype.js +1785 -0
  68. data/examples/rails_server/public/robots.txt +1 -0
  69. data/examples/rails_server/script/about +3 -0
  70. data/examples/rails_server/script/breakpointer +3 -0
  71. data/examples/rails_server/script/console +3 -0
  72. data/examples/rails_server/script/destroy +3 -0
  73. data/examples/rails_server/script/generate +3 -0
  74. data/examples/rails_server/script/performance/benchmarker +3 -0
  75. data/examples/rails_server/script/performance/profiler +3 -0
  76. data/examples/rails_server/script/plugin +3 -0
  77. data/examples/rails_server/script/process/reaper +3 -0
  78. data/examples/rails_server/script/process/spawner +3 -0
  79. data/examples/rails_server/script/process/spinner +3 -0
  80. data/examples/rails_server/script/runner +3 -0
  81. data/examples/rails_server/script/server +3 -0
  82. data/examples/rails_server/test/functional/login_controller_test.rb +18 -0
  83. data/examples/rails_server/test/functional/server_controller_test.rb +18 -0
  84. data/examples/rails_server/test/test_helper.rb +28 -0
  85. data/lib/hmac-md5.rb +11 -0
  86. data/lib/hmac-rmd160.rb +11 -0
  87. data/lib/hmac-sha1.rb +11 -0
  88. data/lib/hmac-sha2.rb +25 -0
  89. data/lib/hmac.rb +112 -0
  90. data/lib/openid/association.rb +109 -0
  91. data/lib/openid/consumer.rb +928 -0
  92. data/lib/openid/dh.rb +48 -0
  93. data/lib/openid/discovery.rb +89 -0
  94. data/lib/openid/fetchers.rb +119 -0
  95. data/lib/openid/filestore.rb +315 -0
  96. data/lib/openid/htmltokenizer.rb +355 -0
  97. data/lib/openid/parse.rb +23 -0
  98. data/lib/openid/server.rb +951 -0
  99. data/lib/openid/service.rb +135 -0
  100. data/lib/openid/stores.rb +178 -0
  101. data/lib/openid/trustroot.rb +100 -0
  102. data/lib/openid/util.rb +273 -0
  103. data/test/assoc.rb +38 -0
  104. data/test/consumer.rb +384 -0
  105. data/test/dh.rb +20 -0
  106. data/test/extensions.rb +30 -0
  107. data/test/linkparse.rb +305 -0
  108. data/test/runtests.rb +11 -0
  109. data/test/server2.rb +1053 -0
  110. data/test/storetestcase.rb +172 -0
  111. data/test/teststore.rb +23 -0
  112. data/test/trustroot.rb +113 -0
  113. data/test/util.rb +56 -0
  114. metadata +218 -0
@@ -0,0 +1,135 @@
1
+ require 'rexml/document'
2
+
3
+ begin
4
+ require 'rubygems'
5
+ require_gem 'ruby-yadis', '>=0.2.3'
6
+ rescue LoadError
7
+ require 'yadis/service'
8
+ end
9
+
10
+ module OpenID
11
+
12
+ # OpenIDService is an object representation of an OpenID server,
13
+ # and the services it provides. It contains a useful information such
14
+ # as the server URL, and information about the OpenID identity bound
15
+ # to the server. OpenIDService object should be produced using the
16
+ # OpenIDService.from_service class method with a Yadis Service object.
17
+ # See the ruby Yadis library for more information:
18
+ #
19
+ # http://www.openidenabled.com/yadis/libraries/ruby
20
+ #
21
+ # Unless you choose to do your own discovery and interface with
22
+ # OpenIDConsumer through the OpenIDConsumer.begin_without_discovery
23
+ # method, you won't need to ever use this object directly. It is used
24
+ # internally by the OpenIDConsumer object.
25
+ class OpenIDServiceEndpoint < ServiceEndpoint
26
+
27
+ @@namespace = {'openid' => 'http://openid.net/xmlns/1.0'}
28
+ attr_accessor :service_types, :uri, :yadis_url, :delegate_url
29
+
30
+ # Class method to produce OpenIDService objects. Call with a Yadis Service
31
+ # object. Will return nil if the Service object does not represent an
32
+ # an OpenID server.
33
+ def OpenIDServiceEndpoint.from_endpoint(service, versions=nil)
34
+ return nil unless OpenIDServiceEndpoint.is_type?(service, versions)
35
+
36
+ s = new
37
+ s.service_types = service.service_types
38
+ s.uri = service.uri
39
+ s.yadis_url = service.yadis.uri
40
+
41
+ s.delegate_url = nil
42
+ REXML::XPath.each(service.element, 'openid:Delegate', @@namespace) do |e|
43
+ s.delegate_url = e.text.strip
44
+ end
45
+
46
+ return s
47
+ end
48
+
49
+ # Class method to determine if a Yadis service object is an OpenID server.
50
+ # +versions+ is a list of Strings representing the versions of the OpenID
51
+ # protocol you support. Only service that match one of the versions will
52
+ # return a value that evaluates to true. If no +versions+ list is
53
+ # specified, all versions will be accepted.
54
+ def OpenIDServiceEndpoint.is_type?(service, versions=nil)
55
+ # escape the period in the version numbers
56
+ versions.collect! {|v| v.gsub('.', '\.')} if versions
57
+
58
+ base_url = 'http://openid\.net/signon/'
59
+ base_url += '(' + versions.join('|') + '){1}' if versions
60
+
61
+ service.service_types.each do |st|
62
+ return true if st.match(base_url)
63
+ end
64
+
65
+ return false
66
+ end
67
+
68
+ # Alias for +supports?+
69
+ def uses_extension?(extension_url)
70
+ return self.supports?(extension_url)
71
+ end
72
+
73
+ # Same as uses_extension? Checks to see if the provided URL is
74
+ # in the list of service types. Example that checks for support
75
+ # of the simple registratino protocol:
76
+ #
77
+ # service.supports?('http://openid.net/sreg/1.0')
78
+ #
79
+ def supports?(url)
80
+ return @service_types.member?(extension_url)
81
+ end
82
+
83
+ # Returns the OpenID delegate URL. This is the URL on the OpenID server,
84
+ # For example if example.com delegates to example-server.com/user, then
85
+ # this will return example-server.com/user
86
+ def delegate
87
+ @delegate_url or self.consumer_id
88
+ end
89
+
90
+ # Returns the OpenID server endpoint URL.
91
+ def server_url
92
+ @uri
93
+ end
94
+
95
+ # Returns user's URL which resides on the OpenID server. For
96
+ # example if http://example.com/ delegates to http://example.myopenid.com/,
97
+ # then http://example.myopenid.com/ will be returned by this method.
98
+ def server_id
99
+ self.delegate
100
+ end
101
+
102
+ # The URL the user entered to authenticate. For example, if
103
+ # http://example.com/ delegates to http://example.myopenid.com/, this
104
+ # method will return http://example.com/
105
+ def consumer_id
106
+ @yadis_url
107
+ end
108
+ end
109
+
110
+
111
+ # Used for providing an OpenIDService like object
112
+ # to the OpenID library for 1.X link rel discovery.
113
+ # See the documentation for OpenID::OpenIDService for more information
114
+ # on what this object does.
115
+ class FakeOpenIDServiceEndpoint < OpenIDServiceEndpoint
116
+
117
+ def initialize(consumer_id, server_id, server_url)
118
+ @uri = server_url
119
+ @delegate = server_id
120
+ @yadis_url = consumer_id
121
+ @service_types = ['http://openid.net/signon/1.0']
122
+ @yadis = nil
123
+ end
124
+
125
+ def delegate
126
+ @delegate
127
+ end
128
+
129
+ def supports?(url)
130
+ false
131
+ end
132
+
133
+ end
134
+
135
+ end
@@ -0,0 +1,178 @@
1
+ require "openid/util"
2
+
3
+ module OpenID
4
+
5
+ # Interface for the abstract Store
6
+ class Store
7
+
8
+ @@AUTH_KEY_LEN = 20
9
+
10
+ # Put a Association object into storace
11
+ def store_association(association)
12
+ raise NotImplementedError
13
+ end
14
+
15
+ # Returns a Association object from storage that matches
16
+ # the server_url. Returns nil if no such association is found or if
17
+ # the one matching association is expired. (Is allowed to GC expired
18
+ # associations when found.)
19
+ def get_association(server_url)
20
+ raise NotImplementedError
21
+ end
22
+
23
+ # If there is a matching association, remove it from the store and
24
+ # return true, otherwise return false.
25
+ def removeAssociation(server_url, handle)
26
+ raise NotImplementedError
27
+ end
28
+
29
+ # Stores a nonce (which is passed in as a string).
30
+ def store_nonce(nonce)
31
+ raise NotImplementedError
32
+ end
33
+
34
+ # If the nonce is in the store, remove it and return true. Otherwise
35
+ # return false.
36
+ def use_nonce(nonce)
37
+ raise NotImplementedError
38
+ end
39
+
40
+ # Returns a 20-byte auth key used to sign the tokens, to ensure
41
+ # that they haven't been tampered with in transit. It must return
42
+ # the same key every time it is called.
43
+ def get_auth_key
44
+ raise NotImplementedError
45
+ end
46
+
47
+ # Method return true if the store is dumb-mode-style store.
48
+ def dumb?
49
+ false
50
+ end
51
+
52
+ end
53
+
54
+
55
+ class DumbStore < Store
56
+
57
+ def initialize(secret_phrase)
58
+ require "digest/sha1"
59
+ @auth_key = Digest::SHA1.hexdigest(secret_phrase)
60
+ end
61
+
62
+ def store_association(assoc)
63
+ nil
64
+ end
65
+
66
+ def get_association(server_url)
67
+ nil
68
+ end
69
+
70
+ def remove_association(server_url, handle)
71
+ false
72
+ end
73
+
74
+ def store_nonce(nonce)
75
+ nil
76
+ end
77
+
78
+ def use_nonce(nonce)
79
+ true
80
+ end
81
+
82
+ def get_auth_key
83
+ @auth_key
84
+ end
85
+
86
+ def dumb?
87
+ true
88
+ end
89
+
90
+ end
91
+
92
+ class ServerAssocs
93
+ def initialize
94
+ @assocs = {}
95
+ end
96
+
97
+ def set(assoc)
98
+ @assocs[assoc.handle] = assoc
99
+ end
100
+
101
+ def get(handle)
102
+ @assocs[handle]
103
+ end
104
+
105
+ def remove(handle)
106
+ return @assocs.delete(handle)
107
+ end
108
+
109
+ def best
110
+ best = nil
111
+ @assocs.each do |k, assoc|
112
+ if best.nil? or best.issued < assoc.issued
113
+ best = assoc
114
+ end
115
+ end
116
+ return best
117
+ end
118
+ end
119
+
120
+ # An in-memory implementation of Store. This class is mainly used
121
+ # for testing, though it may be useful for long-running single process apps.
122
+ #
123
+ # You should probably be looking at OpenID::FilesystemStore
124
+ class MemoryStore < Store
125
+
126
+ def initialize
127
+ @server_assocs = {}
128
+ @nonces = {}
129
+ @auth_key = OpenID::Util.random_string(@@AUTH_KEY_LEN)
130
+ end
131
+
132
+ def dumb?
133
+ false
134
+ end
135
+
136
+ def store_association(server_url, assoc)
137
+ assocs = _get_server_assocs(server_url)
138
+ assocs.set(self.deepcopy(assoc))
139
+ end
140
+
141
+ def get_association(server_url, handle=nil)
142
+ assocs = _get_server_assocs(server_url)
143
+ return assocs.best if handle.nil?
144
+ return assocs.get(handle)
145
+ end
146
+
147
+ def remove_association(server_url, handle)
148
+ assocs = _get_server_assocs(server_url)
149
+ return assocs.remove(handle)
150
+ end
151
+
152
+ def use_nonce(nonce)
153
+ return true if @nonces.delete(nonce)
154
+ return false
155
+ end
156
+
157
+ def store_nonce(nonce)
158
+ @nonces[nonce] = true
159
+ end
160
+
161
+ def get_auth_key
162
+ @auth_key
163
+ end
164
+
165
+ def _get_server_assocs(server_url)
166
+ unless @server_assocs.has_key?(server_url)
167
+ @server_assocs[server_url] = ServerAssocs.new
168
+ end
169
+ return @server_assocs[server_url]
170
+ end
171
+
172
+ def deepcopy(o)
173
+ Marshal.load(Marshal.dump(o))
174
+ end
175
+
176
+ end
177
+
178
+ end
@@ -0,0 +1,100 @@
1
+ require 'uri'
2
+
3
+ TOP_LEVEL_DOMAINS = 'com|edu|gov|int|mil|net|org|biz|info|name|museum|coop|aero|ac|ad|ae|af|ag|ai|al|am|an|ao|aq|ar|as|at|au|aw|az|ba|bb|bd|be|bf|bg|bh|bi|bj|bm|bn|bo|br|bs|bt|bv|bw|by|bz|ca|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|co|cr|cu|cv|cx|cy|cz|de|dj|dk|dm|do|dz|ec|ee|eg|eh|er|es|et|fi|fj|fk|fm|fo|fr|ga|gd|ge|gf|gg|gh|gi|gl|gm|gn|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie|il|im|in|io|iq|ir|is|it|je|jm|jo|jp|ke|kg|kh|ki|km|kn|kp|kr|kw|ky|kz|la|lb|lc|li|lk|lr|ls|lt|lu|lv|ly|ma|mc|md|mg|mh|mk|ml|mm|mn|mo|mp|mq|mr|ms|mt|mu|mv|mw|mx|my|mz|na|nc|ne|nf|ng|ni|nl|no|np|nr|nu|nz|om|pa|pe|pf|pg|ph|pk|pl|pm|pn|pr|ps|pt|pw|py|qa|re|ro|ru|rw|sa|sb|sc|sd|se|sg|sh|si|sj|sk|sl|sm|sn|so|sr|st|sv|sy|sz|tc|td|tf|tg|th|tj|tk|tm|tn|to|tp|tr|tt|tv|tw|tz|ua|ug|uk|um|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|ye|yt|yu|za|zm|zw'.split('|')
4
+
5
+ module OpenID
6
+
7
+ class TrustRoot
8
+
9
+ @@empty_re = Regexp.new('^http[s]*:\/\/\*\/$')
10
+
11
+ def TrustRoot._parse_url(url)
12
+ begin
13
+ parsed = URI::parse(url)
14
+ rescue
15
+ return nil
16
+ end
17
+
18
+ return [parsed.scheme, parsed.host, parsed.port, parsed.path]
19
+ end
20
+
21
+ def TrustRoot.parse(trust_root)
22
+ return nil unless trust_root.instance_of?(String)
23
+
24
+ unparsed = trust_root.dup
25
+
26
+ # look for wildcard
27
+ wildcard = (not trust_root.index('://*.').nil?)
28
+ trust_root.sub!('*.', '') if wildcard
29
+
30
+ # handle http://*/ case
31
+ if not wildcard and @@empty_re.match(trust_root)
32
+ proto = trust_root.split(':')[0]
33
+ port = proto == 'http' ? 80 : 443
34
+ return new(unparsed, proto, true, '', port, '/')
35
+ end
36
+
37
+ parts = TrustRoot._parse_url(trust_root)
38
+ return nil if parts.nil?
39
+
40
+ proto, host, port, path = parts
41
+
42
+ return nil unless ['http', 'https'].member?(proto)
43
+ return new(unparsed, proto, wildcard, host, port, path)
44
+ end
45
+
46
+ def TrustRoot.check_sanity(trust_root)
47
+ tr = TrustRoot.parse(trust_root)
48
+ return false if tr.nil?
49
+ return tr.sane?
50
+ end
51
+
52
+
53
+ def initialize(unparsed, proto, wildcard, host, port, path)
54
+ @unparsed = unparsed
55
+ @proto = proto
56
+ @wildcard = wildcard
57
+ @host = host
58
+ @port = port
59
+ @path = path
60
+ end
61
+
62
+ def sane?
63
+ return true if @host == 'localhost'
64
+
65
+ host_parts = @host.split('.')
66
+ return false unless TOP_LEVEL_DOMAINS.member?(host_parts[-1])
67
+
68
+ # wacky heurestic for extracting a sane tld
69
+ host = []
70
+ if host_parts[-1].length == 2 and host_parts.length > 1
71
+ if host_parts[-2].length <= 3
72
+ host = host_parts[0...-2]
73
+ end
74
+ elsif host_parts[-1].length == 3
75
+ host = host_parts[0...-1]
76
+ end
77
+
78
+ return (host.length > 0)
79
+ end
80
+
81
+ def validate_url(url)
82
+ parts = TrustRoot._parse_url(url)
83
+ return false if parts.nil?
84
+
85
+ proto, host, port, path = parts
86
+
87
+ return false unless proto == @proto
88
+ return false unless port == @port
89
+ return false unless path.index(@path) == 0
90
+
91
+ if @wildcard
92
+ return (not host.rindex(@host).nil?)
93
+ else
94
+ return (host == @host)
95
+ end
96
+ end
97
+
98
+ end
99
+ end
100
+
@@ -0,0 +1,273 @@
1
+ require "base64"
2
+ require "cgi"
3
+ require "digest/sha1"
4
+ require "hmac-sha1"
5
+ require "uri"
6
+
7
+ srand(Time.now.to_f)
8
+
9
+ class Object
10
+
11
+ def instance_variable_hash
12
+ h = {}
13
+ self.instance_variables.each { |k| h[k] = self.instance_variable_get(k) }
14
+ return h
15
+ end
16
+
17
+ end
18
+
19
+ class String
20
+
21
+ def starts_with?(other)
22
+ other = other.to_str
23
+ head = self[0, other.length]
24
+ head == other
25
+ end
26
+
27
+ end
28
+
29
+
30
+ module OpenID
31
+
32
+ # Code returned when either the of the
33
+ # OpenID::OpenIDConsumer.begin_auth or OpenID::OpenIDConsumer.complete_auth
34
+ # methods return successfully.
35
+ SUCCESS = 'success'
36
+
37
+ # Code OpenID::OpenIDConsumer.complete_auth
38
+ # returns when the value it received indicated an invalid login.
39
+ FAILURE = 'failure'
40
+
41
+ # Code returned by OpenIDConsumer.complete_auth when the user
42
+ # cancels the operation from the server.
43
+ CANCEL = 'cancel'
44
+
45
+ # Code returned by OpenID::OpenIDConsumer.complete_auth when the
46
+ # OpenIDConsumer instance is in immediate mode and ther server sends back a
47
+ # URL for the user to login with.
48
+ SETUP_NEEDED = 'setup needed'
49
+
50
+ # Code returned by OpenID::OpenIDConsumer.begin_auth when it is unable
51
+ # to fetch the URL given by the user.
52
+ HTTP_FAILURE = 'http failure'
53
+
54
+ # Code returned by OpenID::OpenIDConsumer.begin_auth when the page fetched
55
+ # from the OpenID URL doesn't contain the necessary link tags to function
56
+ # as an identity page.
57
+ PARSE_ERROR = 'parse error'
58
+
59
+ module Util
60
+
61
+ HAS_URANDOM = File.chardev? '/dev/urandom'
62
+
63
+ def Util.get_openid_params(query)
64
+ params = {}
65
+ query.each do |k,v|
66
+ params[k] = v if k.index("openid.") == 0
67
+ end
68
+ params
69
+ end
70
+
71
+ def Util.hmac_sha1(key, text)
72
+ HMAC::SHA1.digest(key, text)
73
+ end
74
+
75
+ def Util.sha1(s)
76
+ Digest::SHA1.digest(s)
77
+ end
78
+
79
+ def Util.to_base64(s)
80
+ Base64.encode64(s).gsub("\n", "")
81
+ end
82
+
83
+ def Util.from_base64(s)
84
+ Base64.decode64(s)
85
+ end
86
+
87
+ def Util.kvform(hash)
88
+ form = ""
89
+ hash.each do |k,v|
90
+ form << "#{k}:#{v}\n"
91
+ end
92
+ form
93
+ end
94
+
95
+ def Util.parsekv(s)
96
+ s.strip!
97
+ form = {}
98
+ s.split("\n").each do |line|
99
+ pair = line.split(":", 2)
100
+ if pair.length == 2
101
+ k, v = pair
102
+ form[k.strip] = v.strip
103
+ end
104
+ end
105
+ form
106
+ end
107
+
108
+ def Util.num_to_str(n)
109
+ bits = n.to_s(2)
110
+ prepend = (8 - bits.length % 8)
111
+ bits = ('0' * prepend) + bits
112
+ [bits].pack('B*')
113
+ end
114
+
115
+ def Util.str_to_num(s)
116
+ # taken from openid-ruby 0.0.1
117
+ s = "\000" * (4 - (s.length % 4)) + s
118
+ num = 0
119
+ s.unpack('N*').each do |x|
120
+ num <<= 32
121
+ num |= x
122
+ end
123
+ num
124
+ end
125
+
126
+ def Util.num_to_base64(l)
127
+ return to_base64(num_to_str(l))
128
+ end
129
+
130
+ def Util.base64_to_num(s)
131
+ return str_to_num(from_base64(s))
132
+ end
133
+
134
+ def Util.random_string(length, chars=nil)
135
+ s = ""
136
+
137
+ unless chars.nil?
138
+ length.times { s << chars[Util.rand(chars.length)] }
139
+ else
140
+ length.times { s << Util.rand(256).chr }
141
+ end
142
+ s
143
+ end
144
+
145
+ def Util.urlencode(args)
146
+ a = []
147
+ args.each do |key, val|
148
+ a << (CGI::escape(key) + "=" + CGI::escape(val))
149
+ end
150
+ a.join("&")
151
+ end
152
+
153
+ def Util.parse_query(qs)
154
+ query = {}
155
+ CGI::parse(qs).each {|k,v| query[k] = v[0]}
156
+ return query
157
+ end
158
+
159
+ def Util.append_args(url, args)
160
+ url = url.dup
161
+ url if args.length == 0
162
+ url << (url.include?("?") ? "&" : "?")
163
+ url << Util.urlencode(args)
164
+ end
165
+
166
+ def Util.strxor(s1, s2)
167
+ raise ArgumentError if s1.length != s2.length
168
+ length = [s1.length, s2.length].min - 1
169
+ a = (0..length).collect {|i| (s1[i]^s2[i]).chr}
170
+ a.join("")
171
+ end
172
+
173
+ # Sign the given fields from the reply with the specified key.
174
+ # Return [signed, sig]
175
+ def Util.sign_reply(reply, key, signed_fields, prefix="openid.")
176
+ token = []
177
+ signed_fields.each do |sf|
178
+ token << [sf+":"+reply[prefix+sf].to_s+"\n"]
179
+ end
180
+ text = token.join("")
181
+ signed = Util.to_base64(Util.hmac_sha1(key, text))
182
+ return [signed_fields.join(","), signed]
183
+ end
184
+
185
+ # This code is taken from this post[http://blade.nagaokaut.ac.jp/cgi-bin/scat.\rb/ruby/ruby-talk/19098]
186
+ # by Eric Lee Green.
187
+ # This implementation is much faster than x ** n % q
188
+ def Util.powermod(x, n, q)
189
+ counter=0
190
+ n_p=n
191
+ y_p=1
192
+ z_p=x
193
+ while n_p != 0
194
+ if n_p[0]==1
195
+ y_p=(y_p*z_p) % q
196
+ end
197
+ n_p = n_p >> 1
198
+ z_p = (z_p * z_p) % q
199
+ counter += 1
200
+ end
201
+ return y_p
202
+ end
203
+
204
+ # Generate a random number less than max. Uses urandom if available.
205
+ def Util.rand(max)
206
+ unless Util::HAS_URANDOM
207
+ return Kernel::rand(max)
208
+ end
209
+
210
+ start = 0
211
+ stop = max
212
+ step = 1
213
+ r = ((stop-start)/step).to_i
214
+
215
+ # figure out how many bytes we need
216
+ rbytes = Util::num_to_str(r)
217
+ nbytes = rbytes.length
218
+ nbytes -= 1 if rbytes[0].chr == "\000"
219
+
220
+ bytes = "\000" + Util::get_random_bytes(nbytes)
221
+ n = Util::str_to_num(bytes)
222
+
223
+ return start + (n % r) * step
224
+ end
225
+
226
+ # change the message below to do whatever you like for logging
227
+ def Util.log(message)
228
+ STDERR.puts('OpenID Log: ' + message)
229
+ end
230
+
231
+
232
+ def Util.get_random_bytes(n)
233
+ bytes = ""
234
+
235
+ if Util::HAS_URANDOM
236
+ f = File.open("/dev/urandom")
237
+ while n != 0
238
+ _bytes = f.read(n)
239
+ n -= _bytes.length
240
+ bytes << _bytes
241
+ end
242
+ else
243
+ bytes = Util.random_string(n)
244
+ end
245
+
246
+ return bytes
247
+ end
248
+
249
+ def Util.normalize_url(url)
250
+ url = url.strip
251
+
252
+ unless url.starts_with?('http://') or url.starts_with?('https://')
253
+ url = 'http://' + url
254
+ end
255
+
256
+ begin
257
+ parsed = URI.parse(url)
258
+ rescue URI::InvalidURIError
259
+ return nil
260
+ else
261
+ return parsed.normalize.to_s
262
+ end
263
+ end
264
+
265
+ def Util.urls_equal?(url1, url2)
266
+ url1 = Util.normalize_url(url1)
267
+ return false if url1.nil?
268
+ return url1 == Util.normalize_url(url2)
269
+ end
270
+
271
+ end
272
+
273
+ end