ruby-openid 1.0

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 (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