ruby-openid2 3.0.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 (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