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.
- data/COPYING +21 -0
- data/INSTALL +34 -0
- data/README +67 -0
- data/TODO +9 -0
- data/examples/README +54 -0
- data/examples/cacert.pem +7815 -0
- data/examples/consumer.rb +285 -0
- data/examples/openid-store/associations/http-localhost_3A3000_2Fserver-EMQbAy3NnHVzA.s0u5KAcplKGzo +6 -0
- data/examples/openid-store/auth_key +1 -0
- data/examples/rails_active_record_store/README +59 -0
- data/examples/rails_active_record_store/XX_add_openidstore.rb +30 -0
- data/examples/rails_active_record_store/models/openid_association.rb +12 -0
- data/examples/rails_active_record_store/models/openid_nonce.rb +3 -0
- data/examples/rails_active_record_store/models/openid_setting.rb +2 -0
- data/examples/rails_active_record_store/openid_helper.rb +91 -0
- data/examples/rails_active_record_store/openidstore_test.rb +15 -0
- data/examples/rails_active_record_store/schema.mysql.sql +22 -0
- data/examples/rails_active_record_store/schema.postgresql.sql +21 -0
- data/examples/rails_active_record_store/schema.sqlite.sql +21 -0
- data/examples/rails_openid_login_generator/USAGE +23 -0
- data/examples/rails_openid_login_generator/openid_login_generator.rb +36 -0
- data/examples/rails_openid_login_generator/templates/README +116 -0
- data/examples/rails_openid_login_generator/templates/controller.rb +116 -0
- data/examples/rails_openid_login_generator/templates/controller_test.rb +0 -0
- data/examples/rails_openid_login_generator/templates/helper.rb +2 -0
- data/examples/rails_openid_login_generator/templates/openid_login_system.rb +87 -0
- data/examples/rails_openid_login_generator/templates/user.rb +14 -0
- data/examples/rails_openid_login_generator/templates/user_test.rb +0 -0
- data/examples/rails_openid_login_generator/templates/users.yml +0 -0
- data/examples/rails_openid_login_generator/templates/view_login.rhtml +15 -0
- data/examples/rails_openid_login_generator/templates/view_logout.rhtml +10 -0
- data/examples/rails_openid_login_generator/templates/view_welcome.rhtml +9 -0
- data/examples/rails_server/README +153 -0
- data/examples/rails_server/Rakefile +10 -0
- data/examples/rails_server/app/controllers/application.rb +4 -0
- data/examples/rails_server/app/controllers/login_controller.rb +35 -0
- data/examples/rails_server/app/controllers/server_controller.rb +185 -0
- data/examples/rails_server/app/helpers/application_helper.rb +3 -0
- data/examples/rails_server/app/helpers/login_helper.rb +2 -0
- data/examples/rails_server/app/helpers/server_helper.rb +9 -0
- data/examples/rails_server/app/views/layouts/server.rhtml +61 -0
- data/examples/rails_server/app/views/login/index.rhtml +32 -0
- data/examples/rails_server/app/views/server/decide.rhtml +11 -0
- data/examples/rails_server/config/boot.rb +19 -0
- data/examples/rails_server/config/database.yml +85 -0
- data/examples/rails_server/config/environment.rb +53 -0
- data/examples/rails_server/config/environments/development.rb +19 -0
- data/examples/rails_server/config/environments/production.rb +19 -0
- data/examples/rails_server/config/environments/test.rb +19 -0
- data/examples/rails_server/config/routes.rb +23 -0
- data/examples/rails_server/db/openid-store/associations/http-localhost_2F_7Cnormal-YU.tkND1J4fEZhnuAoT5Zc0yCA0 +6 -0
- data/examples/rails_server/doc/README_FOR_APP +2 -0
- data/examples/rails_server/log/development.log +6059 -0
- data/examples/rails_server/log/production.log +0 -0
- data/examples/rails_server/log/server.log +0 -0
- data/examples/rails_server/log/test.log +0 -0
- data/examples/rails_server/public/404.html +8 -0
- data/examples/rails_server/public/500.html +8 -0
- data/examples/rails_server/public/dispatch.cgi +12 -0
- data/examples/rails_server/public/dispatch.fcgi +26 -0
- data/examples/rails_server/public/dispatch.rb +12 -0
- data/examples/rails_server/public/favicon.ico +0 -0
- data/examples/rails_server/public/images/rails.png +0 -0
- data/examples/rails_server/public/javascripts/controls.js +750 -0
- data/examples/rails_server/public/javascripts/dragdrop.js +584 -0
- data/examples/rails_server/public/javascripts/effects.js +854 -0
- data/examples/rails_server/public/javascripts/prototype.js +1785 -0
- data/examples/rails_server/public/robots.txt +1 -0
- data/examples/rails_server/script/about +3 -0
- data/examples/rails_server/script/breakpointer +3 -0
- data/examples/rails_server/script/console +3 -0
- data/examples/rails_server/script/destroy +3 -0
- data/examples/rails_server/script/generate +3 -0
- data/examples/rails_server/script/performance/benchmarker +3 -0
- data/examples/rails_server/script/performance/profiler +3 -0
- data/examples/rails_server/script/plugin +3 -0
- data/examples/rails_server/script/process/reaper +3 -0
- data/examples/rails_server/script/process/spawner +3 -0
- data/examples/rails_server/script/process/spinner +3 -0
- data/examples/rails_server/script/runner +3 -0
- data/examples/rails_server/script/server +3 -0
- data/examples/rails_server/test/functional/login_controller_test.rb +18 -0
- data/examples/rails_server/test/functional/server_controller_test.rb +18 -0
- data/examples/rails_server/test/test_helper.rb +28 -0
- data/lib/hmac-md5.rb +11 -0
- data/lib/hmac-rmd160.rb +11 -0
- data/lib/hmac-sha1.rb +11 -0
- data/lib/hmac-sha2.rb +25 -0
- data/lib/hmac.rb +112 -0
- data/lib/openid/association.rb +109 -0
- data/lib/openid/consumer.rb +928 -0
- data/lib/openid/dh.rb +48 -0
- data/lib/openid/discovery.rb +89 -0
- data/lib/openid/fetchers.rb +119 -0
- data/lib/openid/filestore.rb +315 -0
- data/lib/openid/htmltokenizer.rb +355 -0
- data/lib/openid/parse.rb +23 -0
- data/lib/openid/server.rb +951 -0
- data/lib/openid/service.rb +135 -0
- data/lib/openid/stores.rb +178 -0
- data/lib/openid/trustroot.rb +100 -0
- data/lib/openid/util.rb +273 -0
- data/test/assoc.rb +38 -0
- data/test/consumer.rb +384 -0
- data/test/dh.rb +20 -0
- data/test/extensions.rb +30 -0
- data/test/linkparse.rb +305 -0
- data/test/runtests.rb +11 -0
- data/test/server2.rb +1053 -0
- data/test/storetestcase.rb +172 -0
- data/test/teststore.rb +23 -0
- data/test/trustroot.rb +113 -0
- data/test/util.rb +56 -0
- 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
|
+
|
data/lib/openid/util.rb
ADDED
|
@@ -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
|