signet-rails 0.0.8 → 0.0.9

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: a1ad0e819bbfeceb6ab6795c49ca30d37ee15986
4
+ data.tar.gz: 9713281a98476b5ed795506df87b93a0686881dd
5
+ SHA512:
6
+ metadata.gz: 9f5205dcfea2bdf5411d46f371672ce810e81b04406c3e24a5477016332260a782420080ba4443f70a0ef67d5c6192dcf145429c8302132b21e1bfb523eeebd9
7
+ data.tar.gz: 1f078768bc115957fa5410657cfe579e174ab5d96ce03e15f1bf3267ca8f69401fb8467b182f5b5ae14baec7df8f2862db403c90f349d218f24c18c4b07d9e38
data/.gitignore CHANGED
@@ -15,3 +15,5 @@ spec/reports
15
15
  test/tmp
16
16
  test/version_tmp
17
17
  tmp
18
+ tags
19
+ bin
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format documentation
@@ -0,0 +1,5 @@
1
+ LineLength:
2
+ Max: 120
3
+
4
+ Documentation:
5
+ Enabled: false
@@ -3,136 +3,195 @@ require 'active_support/core_ext/string'
3
3
 
4
4
  module Signet
5
5
  module Rails
6
+
7
+ # The overall structure:
8
+ #
9
+ # Builder - used to create OAuth2 providers. These providers are Rack middleware
10
+ # and instances of Handler. A Builder can create multiple providers. See initialize
11
+ # def below
12
+ #
13
+ # Handler - Rack middleware that represents and instance of an OAuth2 provider
14
+ #
15
+ # Factory - a dumb factory that takes an env and returns an instance of
16
+ # a named Signet::OAuth2::Client
6
17
  class Builder < ::Rack::Builder
7
- @@default_options = {}
8
18
 
9
- def self.set_default_options opts = {}
10
- # normalize to symbol hash
11
- n_opts = opts.inject({}) { |memo,(k,v)| memo[k.to_sym] = v; memo }
12
- @@default_options = n_opts
19
+ class << self
20
+ attr_accessor :default_options
13
21
  end
14
22
 
15
- def initialize(app, &block)
16
- super
23
+ # User can set default_options for all providers via this static accessor
24
+ #
25
+ # TODO not very pretty.... can we refactor?
26
+ Builder.default_options = {}
27
+
28
+ # Options accepted by this Builder
29
+ #
30
+ # TODO obviously we can find a better way of packaging defaults e.g. Google setup
31
+ OPTIONS = {
32
+ # What returned OAuth2 values are persisted?
33
+ #
34
+ # Google config is the default. See:
35
+ #
36
+ # https://developers.google.com/accounts/docs/OAuth2WebServer#handlingtheresponse
37
+ #
38
+ # specifically, the section that deals with the 'shape' of the response to a request
39
+ # for an access token
40
+ persist_attrs: [:refresh_token, :access_token, :expires_in],
41
+
42
+ # What is the name of this provider? It will be used in the auth and auth_callback urls:
43
+ #
44
+ # /signet/google/auth - to authorise a user
45
+ # /signet/google/auth_callback - to handle the response from the OAuth2 provider
46
+ name: :google,
47
+
48
+ # What type is this provider? Is it a login-based OAuth2 adapter? If so, the callback
49
+ # will be used to identify a user and create one if necessary, specifically the uid
50
+ # in the response will be used as a secondary key in the user table
51
+ #
52
+ # Options:
53
+ # :login - as described above
54
+ # :webserver - the expectation will be that
55
+ type: :webserver,
56
+
57
+ # TODO need to define this better
58
+ storage_attr: :signet,
59
+
60
+ # TODO: see https://developers.google.com/accounts/docs/OAuth2Login#authenticationuriparameters
61
+ approval_prompt: 'auto',
62
+ authorization_uri: 'https://accounts.google.com/o/oauth2/auth',
63
+ token_credential_uri: 'https://accounts.google.com/o/oauth2/token',
64
+
65
+ # whether we handle the persistence of the auth callback or simply pass-through
66
+ handle_auth_callback: true,
67
+
68
+ # These need to be set either as default_options or per provider
69
+ client_id: nil,
70
+ client_secret: nil,
71
+
72
+ # redirect_uri can be set otherwise it will default based on the Rack env
73
+ redirect_uri: nil,
74
+
75
+ # A method which takes a Rack env and a Signet::OAuth2::Client (minus
76
+ # credentials) and returns a persistence wrapped object TODO finish
77
+ extract_credentials_from_env: nil,
78
+
79
+ # TODO
80
+ extract_credentials_from_env_by_oauth_id: nil,
81
+
82
+ # What connection object to use (Signet::OAuth2::Client will default to a Faraday default if
83
+ # this is not set)
84
+ connection: nil,
85
+ }
86
+
87
+ def self.set_default_options(opts = {})
88
+ Builder.default_options = opts.symbolize_keys
17
89
  end
18
90
 
19
- def provider(opts = {}, &block)
20
- # normalize to symbol hash
21
- n_opts = opts.inject({}) { |memo,(k,v)| memo[k.to_sym] = v; memo }
22
-
23
- # use the default_options as a base... then merge these changes on top
24
- combined_options = @@default_options.merge(n_opts)
25
-
26
- # now set some defaults if they aren't already set
27
-
28
- combined_options[:persist_attrs] ||= [:refresh_token, :access_token, :expires_in, :issued_at]
29
- combined_options[:name] ||= :google
30
-
31
- # is this a login-based OAuth2 adapter? If so, the callback will be used to identify a
32
- # user and create one if necessary
33
- # Options: :login, :webserver
34
- combined_options[:type] ||= :webserver
35
-
36
- # name of hash-behaving attribute on our wrapper that contains credentials
37
- # keyed by :name
38
- # {
39
- # "google": {
40
- # "uid": "012345676789abcde",
41
- # "refresh_token": "my_first_refresh_token",
42
- # "access_token": "my_first_access_token",
43
- # "expires_in": 123
44
- # }
45
- # }
46
- combined_options[:storage_attr] ||= :signet
47
-
48
- # TODO: see https://developers.google.com/accounts/docs/OAuth2Login#authenticationuriparameters
49
- combined_options[:approval_prompt] ||= 'auto'
50
-
51
- # unless specified, we need to set this at request-time because we need the env to get server etc
52
- # combined_options[:redirect_uri] = ??? need env
53
-
54
- # TODO: better way of sourcing these defaults... from signet?
55
- combined_options[:authorization_uri] ||= 'https://accounts.google.com/o/oauth2/auth'
56
- combined_options[:token_credential_uri] ||= 'https://accounts.google.com/o/oauth2/token'
57
-
58
- # whether we handle the persistence of the auth callback or simply pass-through
59
- combined_options[:handle_auth_callback] ||= true
60
-
61
- # The following lambda will be used when creating a new client in a factory
62
- # to get the persistence object
63
- combined_options[:extract_from_env] ||= lambda do |env, client|
64
- oac = nil
65
- session = env['rack.session']
66
- if !!session && !!session[:user_id]
67
- begin
68
- u = User.find(session[:user_id])
69
- oac = u.o_auth2_credentials.where(name: combined_options[:name]).first
70
- rescue ActiveRecord::RecordNotFound => e
71
- end
72
- end
73
- oac
74
- end
75
-
76
- # The following lambda will be used when handling the callback from the oauth server
77
- # In this flow we might not yet have established a session... need to handle two
78
- # flows, one for login, one not
79
- # when on a login auth_callback, how do we get the persistence object from the JWT?
80
- combined_options[:extract_by_oauth_id] ||= lambda do |env, client, id|
81
- oac = nil
82
- begin
83
- u = nil
84
- if combined_options[:type] == :login
85
- u = User.first_or_initialize(uid: combined_options[:name].to_s + "_" + id)
86
- u.save
87
- else
88
- session = env['rack.session']
89
- if !!session && !!session[:user_id]
90
- begin
91
- u = User.find(session[:user_id])
92
- rescue ActiveRecord::RecordNotFound => e
93
- end
94
- else
95
- raise "Expected to be able to find user in session"
96
- end
97
- end
98
-
99
- oac = u.o_auth2_credentials.first_or_initialize(name: combined_options[:name])
100
-
101
- rescue ActiveRecord::RecordNotFound => e
102
- end
103
-
104
- oac
105
- end
106
-
107
- combined_options[:persistence_wrapper] ||= :active_record
108
-
109
- # define a lambda that returns a lambda that wraps our OAC lambda return object
110
- # in a persistance object
111
- persistence_wrapper = lambda do |meth|
112
- lambda do |env, client, *args|
113
- y = meth.call env, client, *args
114
- klass_str = combined_options[:persistence_wrapper].to_s
115
- require "signet/rails/wrappers/#{klass_str}"
116
- w = "Signet::Rails::Wrappers::#{klass_str.camelize}".constantize.new y, client
117
- end
118
- end
119
-
120
- combined_options[:extract_by_oauth_id] = persistence_wrapper.call combined_options[:extract_by_oauth_id]
121
- combined_options[:extract_from_env] = persistence_wrapper.call combined_options[:extract_from_env]
122
-
123
- # TODO: check here we have the basics?
124
-
125
- # TODO: better auth_options split?
126
- auth_option_keys = [:prompt, :redirect_uri, :access_type, :approval_prompt, :client_id]
127
- base_options = combined_options
128
- auth_options = base_options.select { |k,v| auth_option_keys.include? k }
129
-
130
- use Signet::Rails::Handler, base_options, auth_options, &block
91
+ # Standard ::Rack::Builder initialize
92
+ #
93
+ # It is expected that after creating an instance of Signet::Rails::Builder one will call
94
+ # provider at least one in order to add an instance of the Handler Rack middleware to the
95
+ # Rack. e.g.
96
+ #
97
+ # class FakeApp < Sinatra::Base
98
+ # get "/" do
99
+ # "Hello"
100
+ # end
101
+ # end
102
+ #
103
+ # Signet::Rails::Builder.new FakeApp.new do
104
+ # provider name: :google...
105
+ # end
106
+ def initialize(app, &block)
107
+ super
131
108
  end
132
109
 
110
+ # Called when constructing the Rack stack
133
111
  def call(env)
134
- to_app.call(env)
112
+ to_app.call(env)
135
113
  end
114
+
115
+ # add an OAuth2 Handler to the initialize'ed app, defined by options
116
+ def provider(opts = {}, &block)
117
+ combined_options = OPTIONS.merge \
118
+ Builder.default_options.merge \
119
+ opts.symbolize_keys
120
+
121
+ provider_name = combined_options[:name]
122
+
123
+ # the following defaults depend on other defaults... hence are initialised here
124
+ # If customising either, clearly a similar effect can be achieved in the calling
125
+ # code or by passing in a &block which get invoked in Rack space (see the use call
126
+ # below)
127
+
128
+ # minor efficiency gain... only load the ActiveRecord persistance wrapper if the
129
+ # user hasn't set either extract_credentials_from_env or extract_credentials_from_env_by_oauth_id
130
+
131
+ unless combined_options[:extract_credentials_from_env] and combined_options[:extract_credentials_from_env_by_oauth_id]
132
+ require 'active_record'
133
+ require 'signet/rails/wrappers/active_record'
134
+ end
135
+
136
+ # TODO document
137
+ # Deliberately left verbose to give an example (although the code that follows
138
+ # could hardly be desribed as example) of what's required
139
+ combined_options[:extract_credentials_from_env] ||= lambda do |env, client|
140
+ oac = nil
141
+ session = env['rack.session']
142
+ if !!session && !!session[:user_id]
143
+ begin
144
+ u = ::User.find(session[:user_id])
145
+ oac = u.o_auth2_credentials.where(name: combined_options[:name]).first
146
+ rescue ::ActiveRecord::RecordNotFound => e
147
+ end
148
+ end
149
+ Signet::Rails::Wrappers::ActiveRecord.new oac, client
150
+ end
151
+
152
+ combined_options[:extract_credentials_from_env_by_oauth_id] ||= lambda do |env, client, id|
153
+ oac = nil
154
+ begin
155
+ u = nil
156
+ if combined_options[:type] == :login
157
+ u = ::User.where(uid: combined_options[:name].to_s + "_" + id).first_or_create
158
+ else
159
+ session = env['rack.session']
160
+ if !!session && !!session[:user_id]
161
+ begin
162
+ u = ::User.find(session[:user_id])
163
+ rescue ::ActiveRecord::RecordNotFound => e
164
+ end
165
+ else
166
+ raise "Expected to be able to find user in session"
167
+ end
168
+ end
169
+
170
+ oac = u.o_auth2_credentials.where(name: combined_options[:name]).first_or_initialize
171
+
172
+ rescue ::ActiveRecord::RecordNotFound => e
173
+ # TODO
174
+ end
175
+
176
+ Signet::Rails::Wrappers::ActiveRecord.new oac, client
177
+ end
178
+
179
+ # TODO: check here we have the basics?
180
+
181
+ # TODO: better auth_options split?
182
+ auth_option_keys = [:prompt, :redirect_uri, :approval_prompt, :client_id]
183
+ auth_options = combined_options.slice(*auth_option_keys)
184
+
185
+ # verify we have certain required values
186
+ if combined_options[:type] == :login
187
+ raise ArgumentError, 'Client id is required for a type: :login provider' unless auth_options[:client_id] and auth_options[:client_id].is_a? String
188
+ raise ArgumentError, 'Scope is required' unless combined_options[:scope]
189
+ # TODO error handling for scope: must be a string or array of strings
190
+ end
191
+
192
+ use Signet::Rails::Handler, combined_options, auth_options, &block
193
+ end
194
+
136
195
  end
137
196
  end
138
197
  end
@@ -1,32 +1,63 @@
1
- require 'signet/oauth_2'
1
+ require 'signet/oauth_2/client'
2
2
 
3
3
  module Signet
4
4
  module Rails
5
5
 
6
+ # Factory for creating instances of the Signet::OAuth2::Client or extracting
7
+ # them from an existing env
8
+ #
9
+ # There are two use cases:
10
+ #
11
+ # 1. This is called from within the user app (e.g. Rails) - in this case we
12
+ # extract an existing instance from the env provided by the Handler Rack
13
+ # middleware. We then load the persisted token information (if there is any)
14
+ # and set that on the client. The client is then returned.
15
+ #
16
+ # name: the provider name
17
+ # env: the env available to the caller e.g. request.env in rails
18
+ # options: left blank
19
+ #
20
+ # 2. This is called from within the Handler Rack Middleware (as described above)
21
+ # In this case, we have have to construct the client instance from the Handler
22
+ # options (previously configured)
23
+ #
24
+ # name: the provider name
25
+ # env: the env available to the caller e.g. request.env in rails, the env
26
+ # from the previous Rack middleware in the case of a Handler
27
+ # options: load_token: false
28
+ #
29
+ # In either case, we can rely on env["signet.<HANDLER_NAME>"] being set at the
30
+ # point of calling
6
31
  class Factory
7
- def self.create_from_env name, env, opt_hsh = {load_token: true}
32
+
33
+ def self.create_from_env(name, env, options = { load_token: true })
34
+ # TODO: checking of env not pretty...thread safe? best approach?
8
35
 
9
- # TODO: not pretty...thread safe? best approach? Other uses below
10
- handler = env["signet.#{name.to_s}"]
11
- instance = env["signet.#{name.to_s}.instance"]
12
-
13
- #client = instance.obj
14
- client = instance
36
+ # If there is already an instance in this env, return it
37
+ env["signet.#{name}.instance"] ||
38
+ # else create one from the handler
39
+ create_client_from_handler(env["signet.#{name}"], name, env, options)
40
+ end
15
41
 
16
- return client if !!client
42
+ private
17
43
 
18
- if handler.nil?
19
- raise ArgumentError, "Unable to find signet handler named #{name.to_s}"
20
- end
44
+ def self.create_client_from_handler(handler, name, env, options)
45
+ # There should be a handler set at this stage... big problems otherwise
46
+ raise ArgumentError, "Unable to find signet handler named #{name}" unless handler
21
47
 
22
48
  client = Signet::OAuth2::Client.new handler.options
23
49
 
24
- if opt_hsh[:load_token]
25
- obj = handler.options[:extract_from_env].call env, client
50
+ # at this point we have satisfied use case 2
51
+
52
+ # here is use case 1; the client we have created thus far is blank as far
53
+ # as access/refresh tokens are concerned. If we are calling from a user app
54
+ # then we want that loaded (for the current user)
55
+ if options[:load_token]
56
+ obj = handler.options[:extract_credentials_from_env].call env, client
26
57
  handler.load_token_state obj, client
27
58
 
28
- #instance.obj = obj
29
- env["signet.#{name.to_s}.instance"] = obj
59
+ # store the created instance in the env for future use/persistance
60
+ env["signet.#{name}.instance"] = obj
30
61
  end
31
62
 
32
63
  client
@@ -4,122 +4,182 @@ require 'rack/utils'
4
4
 
5
5
  module Signet
6
6
  module Rails
7
- class Handler
7
+
8
+ class Handler # this is the Rack middleware - it responds to call(env)
9
+
10
+ # we are initialized here with the app that is above us in the stack
11
+ # i.e. where we relay the request (unless handled)
8
12
  def initialize(app, opts = {}, auth_opts = {}, &block)
9
- @app = app
10
- @options = opts
11
- @auth_options = auth_opts
13
+ @app = app
14
+ @options = opts
15
+ @auth_options = auth_opts
12
16
  end
13
17
 
14
- def options
15
- # TODO: this is because signet doesn't dup what we pass in....
16
- @options.dup
18
+ # The Rack entry point. This method gets called from the Rack middleware below us
19
+ # in the stack. If we need to handle the request we respond else we pass the request
20
+ # up the stack and then persist the oauth tokens
21
+ def call(env)
22
+
23
+ # Make this Handler available to the env
24
+ # TODO rework this to use singleton?
25
+ env["signet.#{options[:name]}"] = self
26
+
27
+ status, headers, body = nil, nil, nil
28
+
29
+ # TODO: better way than a gross if elsif block?
30
+ #
31
+ # There are two requests which we do something with:
32
+ #
33
+ # 1. on_auth_path? - a request to auth against a particular
34
+ # provider. We respond to this by redirecting to the provider's
35
+ # authorization_uri, translating appropriate options
36
+ #
37
+ # 2. on_auth_callback_path? - the callback from the provider's
38
+ # authorization_uri (ultimately) which will have an authorisation
39
+ # code. We respond to this by extracting the code, getting an
40
+ # access token (and response token), persisting said tokens then
41
+ # forwarding the request to the next Rack app up the stack. When
42
+ # the Rack app above us responds we parse the env to extract
43
+ # the potentially modified TODO finish
44
+ if on_auth_path?(env)
45
+ status, headers, body = create_auth_redirect_response env
46
+ else
47
+ if on_auth_callback_path?(env)
48
+ process_auth_callback env
49
+ end
50
+
51
+ status, headers, body = @app.call(env)
52
+
53
+ # did we create an instance during the process of @app.call?
54
+ # if so persist
55
+ instance = env["signet.#{options[:name]}.instance"]
56
+ if instance
57
+ save_token_state instance, instance.client
58
+ instance.persist
59
+ end
60
+ end
61
+
62
+ # notice here that only on_auth_path?(env) will ensure we respond
63
+ # otherwise status will be nil
64
+ [status, headers, body]
17
65
  end
18
66
 
19
- def auth_options env
20
- # TODO: this is because signet doesn't dup what we pass in....
21
- ret = @auth_options.dup
22
- unless ret.include? :redirect_uri
23
- req = Rack::Request.new env
24
- scheme = req.ssl? ? 'https' : 'http'
25
- ret[:redirect_uri] = "#{scheme}://#{req.host_with_port}/signet/#{options[:name]}/auth_callback"
26
- end
27
- ret
67
+
68
+ # Take a persistence wrapper object and a Signet::OAuth2Client
69
+ # and transfer the :persist_attrs to the persistence wrapper
70
+ def save_token_state wrapper, client
71
+ if not wrapper.credentials.respond_to?(options[:storage_attr])
72
+ raise "Persistence object does not support the storage attribute #{options[:storage_attr]}"
73
+ end
74
+
75
+ if (store_hash = wrapper.credentials.method(options[:storage_attr]).call).nil?
76
+ store_hash = wrapper.credentials.method("#{options[:storage_attr]}=").call({})
77
+ end
78
+
79
+ # not nice... the wrapper.credentials.changed? will only be triggered if we clone the hash
80
+ # Is this a bug? https://github.com/rails/rails/issues/11968
81
+ # TODO: check if there is a better solution
82
+ store_hash = store_hash.clone
83
+
84
+ for i in options[:persist_attrs]
85
+ if client.respond_to?(i)
86
+ # only transfer the value if it is non-nil
87
+ store_hash[i.to_s] = client.method(i).call unless client.method(i).call.nil?
88
+ end
89
+ end
90
+
91
+ wrapper.credentials.method("#{options[:storage_attr]}=").call(store_hash)
28
92
  end
29
93
 
30
- def handle(env)
31
-
32
- # set these to 'handle' the request
33
- status = headers = body = nil
34
-
35
- # TODO: better way than a gross if elsif block?
36
- if "/signet/#{options[:name]}/auth" == env['PATH_INFO'] && 'GET' == env['REQUEST_METHOD']
37
-
38
- # we are looking to auth... so nothing to load
39
- client = Factory.create_from_env options[:name], env, load_token: false
40
-
41
- r = Rack::Response.new
42
- redirect_uri = client.authorization_uri(auth_options(env)).to_s
43
- r.redirect(redirect_uri)
44
- status, headers, body = r.finish
45
- elsif "/signet/#{options[:name]}/auth_callback" == env['PATH_INFO'] && 'GET' == env['REQUEST_METHOD']
46
- client = Factory.create_from_env options[:name], env, load_token: false
47
- query_string_params = Rack::Utils.parse_query(env['QUERY_STRING'])
48
- client.code = query_string_params['code']
49
- client.redirect_uri = auth_options(env)[:redirect_uri]
50
- client.fetch_access_token!
51
-
52
- if options[:handle_auth_callback]
53
- obj = options[:extract_by_oauth_id].call env, client, client.decoded_id_token['sub']
54
- persist_token_state obj, client
55
- obj.persist
56
- env["signet.#{options[:name]}.persistence_obj"] = obj.obj
57
- else
58
- env["signet.#{options[:name]}.auth_client"] = client
59
- end
60
- end
61
-
62
- [status, headers, body]
94
+ # The inverse of save_token_state
95
+ def load_token_state wrapper, client
96
+ if not wrapper.credentials.respond_to?(options[:storage_attr])
97
+ raise "Persistence object does not support the storage attribute #{options[:storage_attr]}"
98
+ end
99
+
100
+ if not (store_hash = wrapper.credentials.method(options[:storage_attr]).call).nil?
101
+ for i in options[:persist_attrs]
102
+ if client.respond_to?(i.to_s+'=')
103
+ client.method(i.to_s+'=').call(store_hash[i.to_s])
104
+ end
105
+ end
106
+ end
63
107
  end
64
108
 
65
- def persist_token_state wrapper, client
66
- if not wrapper.obj.respond_to?(options[:storage_attr])
67
- raise "Persistence object does not support the storage attribute #{options[:storage_attr]}"
68
- end
69
-
70
- if (store_hash = wrapper.obj.method(options[:storage_attr]).call).nil?
71
- store_hash = wrapper.obj.method("#{options[:storage_attr]}=").call({})
72
- end
73
-
74
- # not nice... the wrapper.obj.changed? will only be triggered if we clone the hash
75
- # Is this a bug? https://github.com/rails/rails/issues/11968
76
- # TODO: check if there is a better solution
77
- store_hash = store_hash.clone
78
-
79
- for i in options[:persist_attrs]
80
- if client.respond_to?(i)
81
- # only transfer the value if it is non-nil
82
- store_hash[i.to_s] = client.method(i).call unless client.method(i).call.nil?
83
- end
84
- end
85
-
86
- wrapper.obj.method("#{options[:storage_attr]}=").call(store_hash)
87
-
109
+ # TODO the code separation here is not great. Refactoring so that
110
+ # we don't have to publicly expose our options would be best
111
+ def options
112
+ # TODO: Signet does not dup the options that are passed to it...
113
+ # hence 'our' value would be corrupted were it not dup'ed here
114
+ @options.dup
88
115
  end
89
116
 
90
- def load_token_state wrapper, client
91
- if not wrapper.obj.respond_to?(options[:storage_attr])
92
- raise "Persistence object does not support the storage attribute #{options[:storage_attr]}"
93
- end
94
-
95
- if not (store_hash = wrapper.obj.method(options[:storage_attr]).call).nil?
96
- for i in options[:persist_attrs]
97
- if client.respond_to?(i.to_s+'=')
98
- client.method(i.to_s+'=').call(store_hash[i.to_s])
99
- end
100
- end
101
- end
117
+ def auth_options(env)
118
+ # TODO: Signet does not dup the options that are passed to it...
119
+ # hence 'our' value would be corrupted were it not dup'ed here
120
+ ret = @auth_options.dup
121
+
122
+ # the redirect uri can't be set at config time (when we mount the
123
+ # Rack middleware) - it must be done at request time. TODO really?
124
+ # Build the redirect_uri from the env in which we are called
125
+ unless ret.include? :redirect_uri and not ret[:redirect_uri].nil?
126
+ req = Rack::Request.new env
127
+ scheme = req.ssl? ? 'https' : 'http'
128
+ ret[:redirect_uri] = "#{scheme}://#{req.host_with_port}/signet/#{options[:name]}/auth_callback"
129
+ end
130
+ ret
131
+ end
132
+
133
+ private
134
+
135
+ def create_auth_redirect_response(env)
136
+ # we build the redirect uri from an OAuth2 client
137
+ # clearly we don't need token state loaded... indeed
138
+ # it probably doesn't exist for this user if we're being
139
+ # asked to auth
140
+ client = Factory.create_from_env(options[:name], env, load_token: false)
141
+
142
+ response = Rack::Response.new
143
+
144
+ # if it needs reiterating... we can't set the redirect uri
145
+ # at config time because we don't have certain config
146
+ # available like server_name etc. This is available at request
147
+ # time (i.e. now)
148
+ redirect_uri = client.authorization_uri(auth_options(env)).to_s
149
+ response.redirect(redirect_uri)
150
+
151
+ # will return status, headers, body
152
+ response.finish
102
153
  end
103
154
 
104
- def call(env)
105
- # rework this to use singleton?
106
- env["signet.#{options[:name]}"] = self
155
+ def process_auth_callback(env)
107
156
 
108
- status, headers, body = handle(env)
157
+ client = Factory.create_from_env options[:name], env, load_token: false
158
+ query_string_params = Rack::Utils.parse_query(env['QUERY_STRING'])
159
+ client.code = query_string_params['code']
160
+ client.redirect_uri = auth_options(env)[:redirect_uri]
109
161
 
110
- unless status
162
+ raise ArgumentError, 'Missing authorization code in auth_callback' unless client.code
111
163
 
112
- status, headers, body = @app.call(env)
164
+ # TODO is there a better way of passing in a connection for testing?
165
+ client.fetch_access_token!({connection: options[:connection]})
113
166
 
114
- instance = env["signet.#{options[:name]}.instance"]
115
- if !!instance
116
- persist_token_state instance, instance.client
117
- instance.persist
118
- end
167
+ if options[:handle_auth_callback]
168
+ user_oauth_credentials = options[:extract_credentials_from_env_by_oauth_id].call env, client, client.decoded_id_token['sub']
169
+ save_token_state user_oauth_credentials, client
170
+ user_oauth_credentials.persist
171
+ env["signet.#{options[:name]}.persistence_obj"] = user_oauth_credentials.credentials
172
+ else
173
+ env["signet.#{options[:name]}.auth_client"] = client
174
+ end
175
+ end
119
176
 
120
- end
177
+ def on_auth_path?(env)
178
+ "/signet/#{options[:name]}/auth" == env['PATH_INFO'] && 'GET' == env['REQUEST_METHOD']
179
+ end
121
180
 
122
- [status, headers, body]
181
+ def on_auth_callback_path?(env)
182
+ "/signet/#{options[:name]}/auth_callback" == env['PATH_INFO'] && 'GET' == env['REQUEST_METHOD']
123
183
  end
124
184
  end
125
185
  end