ruby-openid2 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) hide show
  1. checksums.yaml +7 -0
  2. checksums.yaml.gz.sig +0 -0
  3. data/CHANGELOG.md +136 -0
  4. data/CODE_OF_CONDUCT.md +84 -0
  5. data/CONTRIBUTING.md +54 -0
  6. data/LICENSE.txt +210 -0
  7. data/README.md +81 -0
  8. data/SECURITY.md +15 -0
  9. data/lib/hmac/hmac.rb +110 -0
  10. data/lib/hmac/sha1.rb +11 -0
  11. data/lib/hmac/sha2.rb +25 -0
  12. data/lib/openid/association.rb +246 -0
  13. data/lib/openid/consumer/associationmanager.rb +354 -0
  14. data/lib/openid/consumer/checkid_request.rb +179 -0
  15. data/lib/openid/consumer/discovery.rb +516 -0
  16. data/lib/openid/consumer/discovery_manager.rb +144 -0
  17. data/lib/openid/consumer/html_parse.rb +142 -0
  18. data/lib/openid/consumer/idres.rb +513 -0
  19. data/lib/openid/consumer/responses.rb +147 -0
  20. data/lib/openid/consumer/session.rb +36 -0
  21. data/lib/openid/consumer.rb +406 -0
  22. data/lib/openid/cryptutil.rb +112 -0
  23. data/lib/openid/dh.rb +84 -0
  24. data/lib/openid/extension.rb +38 -0
  25. data/lib/openid/extensions/ax.rb +552 -0
  26. data/lib/openid/extensions/oauth.rb +88 -0
  27. data/lib/openid/extensions/pape.rb +170 -0
  28. data/lib/openid/extensions/sreg.rb +268 -0
  29. data/lib/openid/extensions/ui.rb +49 -0
  30. data/lib/openid/fetchers.rb +277 -0
  31. data/lib/openid/kvform.rb +113 -0
  32. data/lib/openid/kvpost.rb +62 -0
  33. data/lib/openid/message.rb +555 -0
  34. data/lib/openid/protocolerror.rb +7 -0
  35. data/lib/openid/server.rb +1571 -0
  36. data/lib/openid/store/filesystem.rb +260 -0
  37. data/lib/openid/store/interface.rb +73 -0
  38. data/lib/openid/store/memcache.rb +109 -0
  39. data/lib/openid/store/memory.rb +79 -0
  40. data/lib/openid/store/nonce.rb +72 -0
  41. data/lib/openid/trustroot.rb +597 -0
  42. data/lib/openid/urinorm.rb +72 -0
  43. data/lib/openid/util.rb +119 -0
  44. data/lib/openid/version.rb +5 -0
  45. data/lib/openid/yadis/accept.rb +141 -0
  46. data/lib/openid/yadis/constants.rb +16 -0
  47. data/lib/openid/yadis/discovery.rb +151 -0
  48. data/lib/openid/yadis/filters.rb +192 -0
  49. data/lib/openid/yadis/htmltokenizer.rb +290 -0
  50. data/lib/openid/yadis/parsehtml.rb +50 -0
  51. data/lib/openid/yadis/services.rb +44 -0
  52. data/lib/openid/yadis/xrds.rb +160 -0
  53. data/lib/openid/yadis/xri.rb +86 -0
  54. data/lib/openid/yadis/xrires.rb +87 -0
  55. data/lib/openid.rb +27 -0
  56. data/lib/ruby-openid.rb +1 -0
  57. data.tar.gz.sig +0 -0
  58. metadata +331 -0
  59. metadata.gz.sig +0 -0
@@ -0,0 +1,88 @@
1
+ # An implementation of the OpenID OAuth Extension
2
+ # Extension 1.0
3
+ # see: http://openid.net/specs/
4
+
5
+ require_relative "../extension"
6
+
7
+ module OpenID
8
+ module OAuth
9
+ NS_URI = "http://specs.openid.net/extensions/oauth/1.0"
10
+ # An OAuth token request, sent from a relying
11
+ # party to a provider
12
+ class Request < Extension
13
+ attr_accessor :consumer, :scope, :ns_alias, :ns_uri
14
+
15
+ def initialize(consumer = nil, scope = nil)
16
+ @ns_alias = "oauth"
17
+ @ns_uri = NS_URI
18
+ @consumer = consumer
19
+ @scope = scope
20
+ end
21
+
22
+ def get_extension_args
23
+ ns_args = {}
24
+ ns_args["consumer"] = @consumer if @consumer
25
+ ns_args["scope"] = @scope if @scope
26
+ ns_args
27
+ end
28
+
29
+ # Instantiate a Request object from the arguments in a
30
+ # checkid_* OpenID message
31
+ # return nil if the extension was not requested.
32
+ def self.from_openid_request(oid_req)
33
+ oauth_req = new
34
+ args = oid_req.message.get_args(NS_URI)
35
+ return if args == {}
36
+
37
+ oauth_req.parse_extension_args(args)
38
+ oauth_req
39
+ end
40
+
41
+ # Set the state of this request to be that expressed in these
42
+ # OAuth arguments
43
+ def parse_extension_args(args)
44
+ @consumer = args["consumer"]
45
+ @scope = args["scope"]
46
+ end
47
+ end
48
+
49
+ # A OAuth request token response, sent from a provider
50
+ # to a relying party
51
+ class Response < Extension
52
+ attr_accessor :request_token, :scope
53
+
54
+ def initialize(request_token = nil, scope = nil)
55
+ @ns_alias = "oauth"
56
+ @ns_uri = NS_URI
57
+ @request_token = request_token
58
+ @scope = scope
59
+ end
60
+
61
+ # Create a Response object from an OpenID::Consumer::SuccessResponse
62
+ def self.from_success_response(success_response)
63
+ args = success_response.get_signed_ns(NS_URI)
64
+ return if args.nil?
65
+
66
+ oauth_resp = new
67
+ oauth_resp.parse_extension_args(args)
68
+ oauth_resp
69
+ end
70
+
71
+ # parse the oauth request arguments into the
72
+ # internal state of this object
73
+ # if strict is specified, raise an exception when bad data is
74
+ # encountered
75
+ def parse_extension_args(args, _strict = false)
76
+ @request_token = args["request_token"]
77
+ @scope = args["scope"]
78
+ end
79
+
80
+ def get_extension_args
81
+ ns_args = {}
82
+ ns_args["request_token"] = @request_token if @request_token
83
+ ns_args["scope"] = @scope if @scope
84
+ ns_args
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,170 @@
1
+ # An implementation of the OpenID Provider Authentication Policy
2
+ # Extension 1.0
3
+ # see: http://openid.net/specs/
4
+
5
+ require_relative "../extension"
6
+
7
+ module OpenID
8
+ module PAPE
9
+ NS_URI = "http://specs.openid.net/extensions/pape/1.0"
10
+ AUTH_MULTI_FACTOR_PHYSICAL =
11
+ "http://schemas.openid.net/pape/policies/2007/06/multi-factor-physical"
12
+ AUTH_MULTI_FACTOR =
13
+ "http://schemas.openid.net/pape/policies/2007/06/multi-factor"
14
+ AUTH_PHISHING_RESISTANT =
15
+ "http://schemas.openid.net/pape/policies/2007/06/phishing-resistant"
16
+ TIME_VALIDATOR = /\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\dZ/
17
+ # A Provider Authentication Policy request, sent from a relying
18
+ # party to a provider
19
+ class Request < Extension
20
+ attr_accessor :preferred_auth_policies, :max_auth_age, :ns_alias, :ns_uri
21
+
22
+ def initialize(preferred_auth_policies = [], max_auth_age = nil)
23
+ @ns_alias = "pape"
24
+ @ns_uri = NS_URI
25
+ @preferred_auth_policies = preferred_auth_policies
26
+ @max_auth_age = max_auth_age
27
+ end
28
+
29
+ # Add an acceptable authentication policy URI to this request
30
+ # This method is intended to be used by the relying party to add
31
+ # acceptable authentication types to the request.
32
+ def add_policy_uri(policy_uri)
33
+ return if @preferred_auth_policies.member?(policy_uri)
34
+
35
+ @preferred_auth_policies << policy_uri
36
+ end
37
+
38
+ def get_extension_args
39
+ ns_args = {
40
+ "preferred_auth_policies" => @preferred_auth_policies.join(" "),
41
+ }
42
+ ns_args["max_auth_age"] = @max_auth_age.to_s if @max_auth_age
43
+ ns_args
44
+ end
45
+
46
+ # Instantiate a Request object from the arguments in a
47
+ # checkid_* OpenID message
48
+ # return nil if the extension was not requested.
49
+ def self.from_openid_request(oid_req)
50
+ pape_req = new
51
+ args = oid_req.message.get_args(NS_URI)
52
+ return if args == {}
53
+
54
+ pape_req.parse_extension_args(args)
55
+ pape_req
56
+ end
57
+
58
+ # Set the state of this request to be that expressed in these
59
+ # PAPE arguments
60
+ def parse_extension_args(args)
61
+ @preferred_auth_policies = []
62
+ policies_str = args["preferred_auth_policies"]
63
+ if policies_str
64
+ policies_str.split(" ").each do |uri|
65
+ add_policy_uri(uri)
66
+ end
67
+ end
68
+
69
+ max_auth_age_str = args["max_auth_age"]
70
+ @max_auth_age = (max_auth_age_str.to_i if max_auth_age_str)
71
+ end
72
+
73
+ # Given a list of authentication policy URIs that a provider
74
+ # supports, this method returns the subset of those types
75
+ # that are preferred by the relying party.
76
+ def preferred_types(supported_types)
77
+ @preferred_auth_policies.select { |uri| supported_types.member?(uri) }
78
+ end
79
+ end
80
+
81
+ # A Provider Authentication Policy response, sent from a provider
82
+ # to a relying party
83
+ class Response < Extension
84
+ attr_accessor :ns_alias, :auth_policies, :auth_time, :nist_auth_level
85
+
86
+ def initialize(auth_policies = [], auth_time = nil, nist_auth_level = nil)
87
+ @ns_alias = "pape"
88
+ @ns_uri = NS_URI
89
+ @auth_policies = auth_policies
90
+ @auth_time = auth_time
91
+ @nist_auth_level = nist_auth_level
92
+ end
93
+
94
+ # Add a policy URI to the response
95
+ # see http://openid.net/specs/openid-provider-authentication-policy-extension-1_0-01.html#auth_policies
96
+ def add_policy_uri(policy_uri)
97
+ @auth_policies << policy_uri unless @auth_policies.member?(policy_uri)
98
+ end
99
+
100
+ # Create a Response object from an OpenID::Consumer::SuccessResponse
101
+ def self.from_success_response(success_response)
102
+ args = success_response.get_signed_ns(NS_URI)
103
+ return if args.nil?
104
+
105
+ pape_resp = new
106
+ pape_resp.parse_extension_args(args)
107
+ pape_resp
108
+ end
109
+
110
+ # parse the provider authentication policy arguments into the
111
+ # internal state of this object
112
+ # if strict is specified, raise an exception when bad data is
113
+ # encountered
114
+ def parse_extension_args(args, strict = false)
115
+ policies_str = args["auth_policies"]
116
+ @auth_policies = policies_str.split(" ") if policies_str and policies_str != "none"
117
+
118
+ nist_level_str = args["nist_auth_level"]
119
+ if nist_level_str
120
+ # special handling of zero to handle to_i behavior
121
+ if nist_level_str.strip == "0"
122
+ nist_level = 0
123
+ else
124
+ nist_level = nist_level_str.to_i
125
+ # if it's zero here we have a bad value
126
+ nist_level = nil if nist_level == 0
127
+ end
128
+ if nist_level and nist_level >= 0 and nist_level < 5
129
+ @nist_auth_level = nist_level
130
+ elsif strict
131
+ raise ArgumentError, "nist_auth_level must be an integer 0 through 4, not #{nist_level_str.inspect}"
132
+ end
133
+ end
134
+
135
+ auth_time_str = args["auth_time"]
136
+ return unless auth_time_str
137
+
138
+ # validate time string
139
+ if TIME_VALIDATOR.match?(auth_time_str)
140
+ @auth_time = auth_time_str
141
+ elsif strict
142
+ raise ArgumentError, "auth_time must be in RFC3339 format"
143
+ end
144
+ end
145
+
146
+ def get_extension_args
147
+ ns_args = {}
148
+ ns_args["auth_policies"] = if @auth_policies.empty?
149
+ "none"
150
+ else
151
+ @auth_policies.join(" ")
152
+ end
153
+ if @nist_auth_level
154
+ unless (0..4).member?(@nist_auth_level)
155
+ raise ArgumentError, "nist_auth_level must be an integer 0 through 4, not #{@nist_auth_level.inspect}"
156
+ end
157
+
158
+ ns_args["nist_auth_level"] = @nist_auth_level.to_s
159
+ end
160
+
161
+ if @auth_time
162
+ raise ArgumentError, "auth_time must be in RFC3339 format" unless TIME_VALIDATOR.match?(@auth_time)
163
+
164
+ ns_args["auth_time"] = @auth_time
165
+ end
166
+ ns_args
167
+ end
168
+ end
169
+ end
170
+ end
@@ -0,0 +1,268 @@
1
+ require_relative "../extension"
2
+ require_relative "../util"
3
+ require_relative "../message"
4
+
5
+ module OpenID
6
+ module SReg
7
+ DATA_FIELDS = {
8
+ "fullname" => "Full Name",
9
+ "nickname" => "Nickname",
10
+ "dob" => "Date of Birth",
11
+ "email" => "E-mail Address",
12
+ "gender" => "Gender",
13
+ "postcode" => "Postal Code",
14
+ "country" => "Country",
15
+ "language" => "Language",
16
+ "timezone" => "Time Zone",
17
+ }
18
+
19
+ NS_URI_1_0 = "http://openid.net/sreg/1.0"
20
+ NS_URI_1_1 = "http://openid.net/extensions/sreg/1.1"
21
+ NS_URI = NS_URI_1_1
22
+
23
+ begin
24
+ Message.register_namespace_alias(NS_URI_1_1, "sreg")
25
+ rescue NamespaceAliasRegistrationError => e
26
+ Util.log(e)
27
+ end
28
+
29
+ # raise ArgumentError if fieldname is not in the defined sreg fields
30
+ def OpenID.check_sreg_field_name(fieldname)
31
+ return if DATA_FIELDS.member?(fieldname)
32
+
33
+ raise ArgumentError, "#{fieldname} is not a defined simple registration field"
34
+ end
35
+
36
+ # Does the given endpoint advertise support for simple registration?
37
+ def OpenID.supports_sreg?(endpoint)
38
+ endpoint.uses_extension(NS_URI_1_1) || endpoint.uses_extension(NS_URI_1_0)
39
+ end
40
+
41
+ # Extract the simple registration namespace URI from the given
42
+ # OpenID message. Handles OpenID 1 and 2, as well as both sreg
43
+ # namespace URIs found in the wild, as well as missing namespace
44
+ # definitions (for OpenID 1)
45
+ def OpenID.get_sreg_ns(message)
46
+ [NS_URI_1_1, NS_URI_1_0].each do |ns|
47
+ return ns if message.namespaces.get_alias(ns)
48
+ end
49
+ # try to add an alias, since we didn't find one
50
+ ns = NS_URI_1_1
51
+ begin
52
+ message.namespaces.add_alias(ns, "sreg")
53
+ rescue IndexError
54
+ raise NamespaceError
55
+ end
56
+ ns
57
+ end
58
+
59
+ # The simple registration namespace was not found and could not
60
+ # be created using the expected name (there's another extension
61
+ # using the name 'sreg')
62
+ #
63
+ # This is not <em>illegal</em>, for OpenID 2, although it probably
64
+ # indicates a problem, since it's not expected that other extensions
65
+ # will re-use the alias that is in use for OpenID 1.
66
+ #
67
+ # If this is an OpenID 1 request, then there is no recourse. This
68
+ # should not happen unless some code has modified the namespaces for
69
+ # the message that is being processed.
70
+ class NamespaceError < ArgumentError
71
+ end
72
+
73
+ # An object to hold the state of a simple registration request.
74
+ class Request < Extension
75
+ attr_reader :optional, :required, :ns_uri
76
+ attr_accessor :policy_url
77
+
78
+ def initialize(required = nil, optional = nil, policy_url = nil, ns_uri = NS_URI)
79
+ super()
80
+
81
+ @policy_url = policy_url
82
+ @ns_uri = ns_uri
83
+ @ns_alias = "sreg"
84
+ @required = []
85
+ @optional = []
86
+
87
+ request_fields(required, true, true) if required
88
+ return unless optional
89
+
90
+ request_fields(optional, false, true)
91
+ end
92
+
93
+ # Create a simple registration request that contains the
94
+ # fields that were requested in the OpenID request with the
95
+ # given arguments
96
+ # Takes an OpenID::CheckIDRequest, returns an OpenID::Sreg::Request
97
+ # return nil if the extension was not requested.
98
+ def self.from_openid_request(request)
99
+ # Since we're going to mess with namespace URI mapping, don't
100
+ # mutate the object that was passed in.
101
+ message = request.message.copy
102
+ ns_uri = OpenID.get_sreg_ns(message)
103
+ args = message.get_args(ns_uri)
104
+ return if args == {}
105
+
106
+ req = new(nil, nil, nil, ns_uri)
107
+ req.parse_extension_args(args)
108
+ req
109
+ end
110
+
111
+ # Parse the unqualified simple registration request
112
+ # parameters and add them to this object.
113
+ #
114
+ # This method is essentially the inverse of
115
+ # getExtensionArgs. This method restores the serialized simple
116
+ # registration request fields.
117
+ #
118
+ # If you are extracting arguments from a standard OpenID
119
+ # checkid_* request, you probably want to use fromOpenIDRequest,
120
+ # which will extract the sreg namespace and arguments from the
121
+ # OpenID request. This method is intended for cases where the
122
+ # OpenID server needs more control over how the arguments are
123
+ # parsed than that method provides.
124
+ def parse_extension_args(args, strict = false)
125
+ required_items = args["required"]
126
+ unless required_items.nil? or required_items.empty?
127
+ required_items.split(",").each do |field_name|
128
+ request_field(field_name, true, strict)
129
+ rescue ArgumentError
130
+ raise if strict
131
+ end
132
+ end
133
+
134
+ optional_items = args["optional"]
135
+ unless optional_items.nil? or optional_items.empty?
136
+ optional_items.split(",").each do |field_name|
137
+ request_field(field_name, false, strict)
138
+ rescue ArgumentError
139
+ raise if strict
140
+ end
141
+ end
142
+ @policy_url = args["policy_url"]
143
+ end
144
+
145
+ # A list of all of the simple registration fields that were
146
+ # requested, whether they were required or optional.
147
+ def all_requested_fields
148
+ @required + @optional
149
+ end
150
+
151
+ # Have any simple registration fields been requested?
152
+ def were_fields_requested?
153
+ !all_requested_fields.empty?
154
+ end
155
+
156
+ # Request the specified field from the OpenID user
157
+ # field_name: the unqualified simple registration field name
158
+ # required: whether the given field should be presented
159
+ # to the user as being a required to successfully complete
160
+ # the request
161
+ # strict: whether to raise an exception when a field is
162
+ # added to a request more than once
163
+ # Raises ArgumentError if the field_name is not a simple registration
164
+ # field, or if strict is set and a field is added more than once
165
+ def request_field(field_name, required = false, strict = false)
166
+ OpenID.check_sreg_field_name(field_name)
167
+
168
+ if strict
169
+ raise ArgumentError, "That field has already been requested" if (@required + @optional).member?(field_name)
170
+ else
171
+ return if @required.member?(field_name)
172
+
173
+ if @optional.member?(field_name)
174
+ return unless required
175
+
176
+ @optional.delete(field_name)
177
+
178
+ end
179
+ end
180
+ if required
181
+ @required << field_name
182
+ else
183
+ @optional << field_name
184
+ end
185
+ end
186
+
187
+ # Add the given list of fields to the request.
188
+ def request_fields(field_names, required = false, strict = false)
189
+ raise ArgumentError unless field_names.respond_to?(:each) and
190
+ field_names[0].is_a?(String)
191
+
192
+ field_names.each { |fn| request_field(fn, required, strict) }
193
+ end
194
+
195
+ # Get a hash of unqualified simple registration arguments
196
+ # representing this request.
197
+ # This method is essentially the inverse of parse_extension_args.
198
+ # This method serializes the simple registration request fields.
199
+ def get_extension_args
200
+ args = {}
201
+ args["required"] = @required.join(",") unless @required.empty?
202
+ args["optional"] = @optional.join(",") unless @optional.empty?
203
+ args["policy_url"] = @policy_url unless @policy_url.nil?
204
+ args
205
+ end
206
+
207
+ def member?(field_name)
208
+ all_requested_fields.member?(field_name)
209
+ end
210
+ end
211
+
212
+ # Represents the data returned in a simple registration response
213
+ # inside of an OpenID id_res response. This object will be
214
+ # created by the OpenID server, added to the id_res response
215
+ # object, and then extracted from the id_res message by the Consumer.
216
+ class Response < Extension
217
+ attr_reader :ns_uri, :data
218
+
219
+ def initialize(data = {}, ns_uri = NS_URI)
220
+ @ns_alias = "sreg"
221
+ @data = data
222
+ @ns_uri = ns_uri
223
+ end
224
+
225
+ # Take a Request and a hash of simple registration
226
+ # values and create a Response object containing that data.
227
+ def self.extract_response(request, data)
228
+ arf = request.all_requested_fields
229
+ resp_data = data.reject { |k, v| !arf.member?(k) || v.nil? }
230
+ new(resp_data, request.ns_uri)
231
+ end
232
+
233
+ # Create an Response object from an
234
+ # OpenID::Consumer::SuccessResponse from consumer.complete
235
+ # If you set the signed_only parameter to false, unsigned data from
236
+ # the id_res message from the server will be processed.
237
+ def self.from_success_response(success_response, signed_only = true)
238
+ ns_uri = OpenID.get_sreg_ns(success_response.message)
239
+ if signed_only
240
+ args = success_response.get_signed_ns(ns_uri)
241
+ return if args.nil? # No signed args, so fail
242
+ else
243
+ args = success_response.message.get_args(ns_uri)
244
+ end
245
+ args.reject! { |k, _v| !DATA_FIELDS.member?(k) }
246
+ new(args, ns_uri)
247
+ end
248
+
249
+ # Get the fields to put in the simple registration namespace
250
+ # when adding them to an id_res message.
251
+ def get_extension_args
252
+ @data
253
+ end
254
+
255
+ # Read-only hashlike interface.
256
+ # Raises an exception if the field name is bad
257
+ def [](field_name)
258
+ OpenID.check_sreg_field_name(field_name)
259
+ data[field_name]
260
+ end
261
+
262
+ def empty?
263
+ @data.empty?
264
+ end
265
+ # XXX is there more to a hashlike interface I should add?
266
+ end
267
+ end
268
+ end
@@ -0,0 +1,49 @@
1
+ # An implementation of the OpenID User Interface Extension 1.0 - DRAFT 0.5
2
+ # see: http://svn.openid.net/repos/specifications/user_interface/1.0/trunk/openid-user-interface-extension-1_0.html
3
+
4
+ require_relative "../extension"
5
+
6
+ module OpenID
7
+ module UI
8
+ NS_URI = "http://specs.openid.net/extensions/ui/1.0"
9
+
10
+ class Request < Extension
11
+ attr_accessor :lang, :icon, :mode, :ns_alias, :ns_uri
12
+
13
+ def initialize(mode = nil, icon = nil, lang = nil)
14
+ @ns_alias = "ui"
15
+ @ns_uri = NS_URI
16
+ @lang = lang
17
+ @icon = icon
18
+ @mode = mode
19
+ end
20
+
21
+ def get_extension_args
22
+ ns_args = {}
23
+ ns_args["lang"] = @lang if @lang
24
+ ns_args["icon"] = @icon if @icon
25
+ ns_args["mode"] = @mode if @mode
26
+ ns_args
27
+ end
28
+
29
+ # Instantiate a Request object from the arguments in a
30
+ # checkid_* OpenID message
31
+ # return nil if the extension was not requested.
32
+ def self.from_openid_request(oid_req)
33
+ ui_req = new
34
+ args = oid_req.message.get_args(NS_URI)
35
+ return if args == {}
36
+
37
+ ui_req.parse_extension_args(args)
38
+ ui_req
39
+ end
40
+
41
+ # Set UI extension parameters
42
+ def parse_extension_args(args)
43
+ @lang = args["lang"]
44
+ @icon = args["icon"]
45
+ @mode = args["mode"]
46
+ end
47
+ end
48
+ end
49
+ end