ginjo-omniauth-slack 2.4.1 → 2.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,10 @@
1
+ require 'hashie'
2
+ require 'omniauth/auth_hash'
3
+
4
+ module OmniAuth
5
+ module Slack
6
+ class AuthHash < OmniAuth::AuthHash
7
+ include Hashie::Extensions::DeepFind
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,68 @@
1
+ require 'hashie'
2
+ require 'omniauth/strategies/oauth2'
3
+ require 'omniauth/auth_hash'
4
+ require 'oauth2'
5
+ require 'omniauth-slack/omniauth/auth_hash'
6
+
7
+
8
+ # Refinements will work as long as the call to the refined method is lexically scoped with the 'using'.
9
+
10
+ module OmniAuth
11
+ module Slack
12
+
13
+ module OAuth2Refinements
14
+ refine ::OAuth2::Response do
15
+ def to_auth_hash
16
+ #Module.const_get('::OmniAuth::Slack::AuthHash').new(parsed)
17
+ ::OmniAuth::Slack::AuthHash.new(parsed)
18
+ end
19
+ end
20
+ end
21
+
22
+ module ArrayRefinements
23
+ refine ::Array do
24
+ # Sort this array according to other-array's current order.
25
+ # See https://stackoverflow.com/questions/44536537/sort-the-array-with-reference-to-another-array
26
+ # This also handles items not in the reference_array.
27
+ # Pass :beginning or :ending as the 2nd arg to specify where to put unmatched source items.
28
+ # Pass a block to specify exactly which part of source value is being used for sort.
29
+ # Example: sources.sort_with(dependencies){|v| v.name.to_s}
30
+ def sort_with(reference_array, unmatched = :beginning)
31
+ ref_index = reference_array.to_a.each_with_index.to_h
32
+ unmatched_destination = case unmatched
33
+ when /begin/; -1
34
+ when /end/; 1
35
+ when Integer; unmatched
36
+ else -1
37
+ end
38
+ #puts "Sorting array #{self} with unmatched_destination '#{unmatched_destination}' and reference index #{ref_index}"
39
+ sort_by do |v|
40
+ val = block_given? ? yield(v) : v
41
+ [ref_index[val] || (unmatched_destination * reference_array.size), val]
42
+ end
43
+ end
44
+ end
45
+ end
46
+
47
+ module StringRefinements
48
+ refine String do
49
+ def words
50
+ split(/[,\s]+/)
51
+ end
52
+ end
53
+ end
54
+
55
+ module CallerMethodName
56
+ def caller_method_name
57
+ #caller[0][/`([^']*)'/, 1] # This gets the method name only 1 level up.
58
+ caller[1][/`([^']*)'/, 1] # This gets the method name 2 levels up.
59
+ end
60
+
61
+ def self.included(other)
62
+ other.send(:extend, CallerMethodName)
63
+ end
64
+ end
65
+
66
+ end # Slack
67
+ end # OmniAuth
68
+
@@ -0,0 +1,121 @@
1
+ require 'uri'
2
+ require 'omniauth-slack/refinements'
3
+ require 'omniauth-slack/oauth2/client'
4
+ require 'omniauth-slack/oauth2/access_token'
5
+
6
+ require 'rack'
7
+ require 'json'
8
+
9
+ module OmniAuth
10
+ module Slack
11
+
12
+ # Build an access token from access-token-hash or from token-string.
13
+ def self.build_access_token(client_id, client_key, token_string_or_hash)
14
+ client = OmniAuth::Slack::OAuth2::Client.new(
15
+ client_id,
16
+ client_key,
17
+ OmniAuth::Strategies::Slack.default_options.client_options.to_h.map{|k,v| [k.to_sym, v]}.to_h
18
+ )
19
+
20
+ access_token = case
21
+ when token_string_or_hash.is_a?(String)
22
+ OmniAuth::Slack::OAuth2::AccessToken.new(client, token_string_or_hash)
23
+ when token_string_or_hash.is_a?(Hash)
24
+ OmniAuth::Slack::OAuth2::AccessToken.from_hash(client, token_string_or_hash)
25
+ end
26
+
27
+ access_token
28
+ end
29
+
30
+
31
+ # Rack middleware to verify incoming slack request signature.
32
+ #
33
+ # use OmniAuth::Slack::VerifySlackSignature
34
+ #
35
+ # ENV ... TODO: Complete this section of required env variables.
36
+ # or consider having accepting a config block in the 'use' call.
37
+ #
38
+ class VerifySlackSignature
39
+ include OmniAuth::Slack::Debug
40
+
41
+ attr_accessor :app_id, :signing_secret
42
+
43
+ def initialize(app)
44
+ @app = app
45
+ @app_id = nil
46
+ @signing_secret = nil
47
+
48
+ middleware_instance = self
49
+
50
+ if block_given?
51
+ # Can set app_id and signing_secret from here.
52
+ yield(middleware_instance)
53
+ end
54
+ end
55
+
56
+ def call(env)
57
+ @env = env
58
+ @logger = logger = OmniAuth.logger
59
+
60
+ debug{"calling middleware"}
61
+
62
+ env['rack.input'].rewind
63
+ body_string = env['rack.input'].read
64
+ env['rack.input'].rewind
65
+
66
+ debug{"VerifySlackSignature body_string: #{body_string}"}
67
+
68
+ body_hash =
69
+ begin
70
+ body_string && JSON.load(body_string)
71
+ rescue
72
+ {}
73
+ end
74
+
75
+ if body_hash.to_a.size == 0
76
+ debug{"not detecting JSON body"}
77
+ pass
78
+
79
+ else
80
+ api_app_id = body_hash['api_app_id']
81
+ slack_signature = env['HTTP_X_SLACK_SIGNATURE']
82
+ slack_timestamp = env['HTTP_X_SLACK_REQUEST_TIMESTAMP']
83
+
84
+ if ! [api_app_id, slack_signature, slack_timestamp].all?
85
+ logger.debug("(slack) VerifySlackSignature not detecting incoming Slack request")
86
+ pass
87
+
88
+ elsif signing_secret.to_s.empty?
89
+ logger.info("(slack) VerifySlackSignature missing signing_secret")
90
+ pass
91
+
92
+ elsif app_id && app_id.to_s != api_app_id.to_s
93
+ logger.info("(slack) VerifySlackSignature app_id mismatch")
94
+ pass
95
+
96
+ else
97
+ computed_signature = 'v0=' + OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new('sha256'), signing_secret, "v0:#{slack_timestamp}:#{body_string}").to_s
98
+ rslt = (slack_signature == computed_signature)
99
+
100
+ if rslt
101
+ logger.info("(slack) VerifySlackSignature: #{rslt}")
102
+ else
103
+ logger.info("(slack) VerifySlackSignature: #{rslt} (slack: #{slack_signature}, computed: #{computed_signature})")
104
+ end
105
+
106
+ pass rslt
107
+ end
108
+ end
109
+ end
110
+
111
+ def pass(result = nil)
112
+ @env['omniauth.slack.verification'] = result
113
+ debug{"set env omniauth.slack.verification to: #{result}"}
114
+ @app.call(@env)
115
+ end
116
+
117
+ end # VerifySlackSignature
118
+
119
+ end
120
+ end
121
+
@@ -1,5 +1,5 @@
1
1
  module OmniAuth
2
2
  module Slack
3
- VERSION = "2.4.1"
3
+ VERSION = "2.5.0"
4
4
  end
5
5
  end
@@ -1,411 +1,280 @@
1
+ # :markup: tomdoc
2
+
1
3
  require 'omniauth/strategies/oauth2'
4
+ require 'omniauth-slack/refinements'
5
+ require 'omniauth-slack/slack'
6
+ require 'omniauth-slack/omniauth/auth_hash'
2
7
  require 'thread'
3
8
  require 'uri'
4
9
 
5
10
  module OmniAuth
11
+ using Slack::OAuth2Refinements
12
+
6
13
  module Strategies
7
-
14
+
15
+ # This is the OmniAuth strategy for Slack.
16
+ # It is used as Rack middleware.
17
+ #
18
+ # use OmniAuth::Builder do
19
+ # provider :slack, OAUTH_KEY, OAUTH_SECRET, options...
20
+ # end
21
+ #
8
22
  class Slack < OmniAuth::Strategies::OAuth2
9
- option :name, 'slack'
10
-
11
- option :authorize_options, [:scope, :team, :team_domain, :redirect_uri]
23
+ include OmniAuth::Slack::Debug
24
+
25
+
26
+ ### Options ###
12
27
 
28
+ # Master list of authorization options handled by omniauth-slack.
29
+ # See below for redirect_uri.
30
+ #
31
+ AUTH_OPTIONS = %i(scope user_scope team team_domain)
32
+
33
+ debug{"#{self} setting up default options"}
34
+
35
+ # Default strategy name
36
+ option :name, 'slack'
37
+
38
+ # Options that can be passed with provider authorization URL.
39
+ option :authorize_options, AUTH_OPTIONS - %i(team_domain)
40
+
41
+ # OAuth2::Client options.
13
42
  option :client_options, {
14
43
  site: 'https://slack.com',
15
- token_url: '/api/oauth.access',
16
- auth_scheme: :basic_auth
44
+ authorize_url: '/oauth/v2/authorize',
45
+ token_url: '/api/oauth.v2.access',
46
+ auth_scheme: :basic_auth,
47
+ raise_errors: false, # MUST be false to allow Slack's get-token response from v2 API.
48
+ history: Array.new,
17
49
  }
18
50
 
51
+ # Authorization token-exchange API call options.
19
52
  option :auth_token_params, {
20
53
  mode: :query,
21
54
  param_name: 'token'
22
55
  }
56
+
57
+
58
+ ### Omniauth Slack custom options ###
23
59
 
24
- option :preload_data_with_threads, 0
25
-
26
- option :include_data, []
27
-
28
- option :exclude_data, []
60
+ # redirect_uri does not need to be in authorize_options,
61
+ # since it inserted anyway by omniauth-oauth2 during both
62
+ # the request (authorization) phase and the callback (get-token) phase.
63
+ # The magic of redirect_uri actually happens in the callback_url method.
64
+ option :redirect_uri
29
65
 
30
- option :additional_data, {}
66
+ # Options allowed to pass from omniauth /auth/<provider> URL
67
+ # to provider authorization URL.
68
+ option :pass_through_params, %i(team)
69
+
70
+
71
+ ### Data ###
31
72
 
32
73
  # User ID is not guaranteed to be globally unique across all Slack users.
33
74
  # The combination of user ID and team ID, on the other hand, is guaranteed
34
75
  # to be globally unique.
35
- uid { "#{user_id}-#{team_id}" }
36
-
76
+ #
77
+ uid { access_token&.uid }
78
+
79
+
80
+ # Gathers access_token and awarded scopes for :credentials section of AuthHash.
81
+ #
37
82
  credentials do
38
83
  {
39
- token: auth['token'],
40
- scope: (is_app_token? ? all_scopes : auth['scope']),
41
- expires: false
84
+ token_type: access_or_user_token&.token_type,
85
+ scope: access_or_user_token&.scope,
86
+ scopes: access_or_user_token&.all_scopes,
87
+ token: access_or_user_token&.token
42
88
  }
43
89
  end
44
90
 
91
+ # Gathers a myriad of possible data returned from omniauth-slack /api/oauth.access call,
92
+ # for `:info` section of AuthHash.
93
+ # :markup: markdown
94
+ #
95
+ # You an modify the info hash from your application.
96
+ # This example adds a users_info API request and response.
97
+ # Note that this will automatically store Client request history,
98
+ # if enabled. You do not need to link the auth-hash raw-info to
99
+ # the Client history array (See notes in OmniAuth::Slack::OAuth2::Client).
100
+ #
101
+ # Example:
102
+ #
103
+ # class OmniAuth::Strategies::Slack
104
+ # original_info = info.dup
105
+ # info do
106
+ # {
107
+ # access_token: instance_exec(&original_info),
108
+ # users_info: access_token.get('/api/users.info', params: {user: access_token.user_id}, headers: {'X-Slack-User' => (access_token.user_id)}).parsed
109
+ # }
110
+ # end
111
+ # end
112
+ #
45
113
  info do
46
-
47
- unless skip_info?
48
- define_additional_data
49
- semaphore
50
- end
51
-
52
- num_threads = options.preload_data_with_threads.to_i
53
-
54
- if num_threads > 0 && !skip_info?
55
- preload_data_with_threads(num_threads)
56
- end
57
-
58
- # Start with only what we can glean from the authorization response.
59
- hash = {
60
- name: auth['user'].to_h['name'],
61
- email: auth['user'].to_h['email'],
62
- user_id: user_id,
63
- team_name: auth['team_name'] || auth['team'].to_h['name'],
64
- team_id: team_id,
65
- image: auth['team'].to_h['image_48']
114
+ {
115
+ name: access_token.user_name
66
116
  }
67
-
68
- # Now add everything else, using further calls to the api, if necessary.
69
- unless skip_info?
70
- %w(first_name last_name phone skype avatar_hash real_name real_name_normalized).each do |key|
71
- hash[key.to_sym] = (
72
- user_info['user'].to_h['profile'] ||
73
- user_profile['profile']
74
- ).to_h[key]
75
- end
76
-
77
- %w(deleted status color tz tz_label tz_offset is_admin is_owner is_primary_owner is_restricted is_ultra_restricted is_bot has_2fa).each do |key|
78
- hash[key.to_sym] = user_info['user'].to_h[key]
79
- end
80
-
81
- more_info = {
82
- image: (
83
- hash[:image] ||
84
- user_identity.to_h['image_48'] ||
85
- user_info['user'].to_h['profile'].to_h['image_48'] ||
86
- user_profile['profile'].to_h['image_48']
87
- ),
88
- name:(
89
- hash[:name] ||
90
- user_identity['name'] ||
91
- user_info['user'].to_h['real_name'] ||
92
- user_profile['profile'].to_h['real_name']
93
- ),
94
- email:(
95
- hash[:email] ||
96
- user_identity.to_h['email'] ||
97
- user_info['user'].to_h['profile'].to_h['email'] ||
98
- user_profile['profile'].to_h['email']
99
- ),
100
- team_name:(
101
- hash[:team_name] ||
102
- team_identity.to_h['name'] ||
103
- team_info['team'].to_h['name']
104
- ),
105
- team_domain:(
106
- auth['team'].to_h['domain'] ||
107
- team_identity.to_h['domain'] ||
108
- team_info['team'].to_h['domain']
109
- ),
110
- team_image:(
111
- auth['team'].to_h['image_44'] ||
112
- team_identity.to_h['image_44'] ||
113
- team_info['team'].to_h['icon'].to_h['image_44']
114
- ),
115
- team_email_domain:(
116
- team_info['team'].to_h['email_domain']
117
- ),
118
- nickname:(
119
- user_info.to_h['user'].to_h['name'] ||
120
- auth['user'].to_h['name'] ||
121
- user_identity.to_h['name']
122
- ),
123
- }
124
-
125
- hash.merge!(more_info)
126
- end
127
- hash
117
+ .merge access_token.to_hash
128
118
  end
129
119
 
120
+ # Defines a section for all additional data to be
121
+ # included with the AuthHash instance.
122
+ #
123
+ # for :extra section of AuthHash.
124
+ #
130
125
  extra do
131
126
  {
132
- scopes_requested: (env['omniauth.params'] && env['omniauth.params']['scope']) || \
133
- (env['omniauth.strategy'] && env['omniauth.strategy'].options && env['omniauth.strategy'].options.scope),
134
- web_hook_info: web_hook_info,
135
- bot_info: auth['bot'] || bot_info['bot'],
136
- auth: auth,
137
- identity: identity,
138
- user_info: user_info,
139
- user_profile: user_profile,
140
- team_info: team_info,
141
- apps_permissions_users_list: apps_permissions_users_list,
142
- additional_data: get_additional_data,
143
- raw_info: @raw_info
127
+ scopes_requested: scopes_requested,
128
+ raw_info: raw_info
144
129
  }
145
130
  end
146
131
 
147
-
148
- # Pass on certain authorize_params to the Slack authorization GET request.
132
+
133
+ ### Instance Methods ###
134
+
135
+ # Wraps OmniAuth::Oauth2#authorize_params so that specified params
136
+ # can be passed on to Slack authorization GET request.
149
137
  # See https://github.com/omniauth/omniauth/issues/390
138
+ #
150
139
  def authorize_params
151
- super.tap do |params|
152
- %w(scope team redirect_uri).each do |v|
153
- if !request.params[v].to_s.empty?
154
- params[v.to_sym] = request.params[v]
155
- end
156
- end
157
- log(:debug, "Authorize_params #{params.to_h}")
140
+ super.tap do |prms|
141
+ params_digest = prms.hash
142
+ debug{"Using omniauth authorize_params #{prms}"}
143
+ debug{"Considering request.params #{request.params}"}
144
+ debug{"Considering pass_through_params #{pass_through_params}"}
145
+ filtered_ptp = pass_through_params.reject{|o| o.to_s == 'team_domain'}
146
+ filtered_rp = request.params.reject{|k,v| !filtered_ptp.any?{|ptp| ptp.to_s == k.to_s}}
147
+ debug{"Filtered request params #{filtered_rp}"}
148
+ prms.merge! filtered_rp
149
+ log(:debug, "Using modified authorize_params #{prms}") if prms.hash != params_digest
150
+ session['omniauth.authorize_params'] = prms
158
151
  end
159
152
  end
160
153
 
161
- # Get a new OAuth2::Client and define custom behavior.
162
- # * overrides previous omniauth-strategies-oauth2 :client definition.
154
+ # Pre-sets env vars for super.
163
155
  #
164
- # * Log API requests with OmniAuth.logger
165
- # * Add API responses to @raw_info hash
166
- # * Set auth site uri with custom subdomain (if provided).
156
+ # OmniAuth callback phase to extract session var for
157
+ # omniauth.authorize_params into env (this is how omniauth does this).
167
158
  #
168
- def client
169
- new_client = super
170
-
171
- team_domain = request.params['team_domain'] || options[:team_domain]
172
- if !team_domain.to_s.empty?
173
- site_uri = URI.parse(options[:client_options]['site'])
174
- site_uri.host = "#{team_domain}.slack.com"
175
- new_client.site = site_uri.to_s
176
- log(:debug, "Oauth site uri with custom team_domain #{site_uri}")
177
- end
178
-
179
- st_raw_info = raw_info
180
- new_client.define_singleton_method(:request) do |*args|
181
- OmniAuth.logger.send(:debug, "(slack) API request #{args[0..1]}; in thread #{Thread.current.object_id}.")
182
- request_output = super(*args)
183
- uri = args[1].to_s.gsub(/^.*\/([^\/]+)/, '\1') # use single-quote or double-back-slash for replacement.
184
- st_raw_info[uri.to_s]= request_output
185
- request_output
186
- end
187
-
188
- new_client
189
- end
190
-
191
- # Dropping query_string from callback_url prevents some errors in call to /api/oauth.access.
192
- def callback_url
193
- full_host + script_name + callback_path
194
- end
195
-
196
- def identity
197
- return {} unless !skip_info? && has_scope?(identity: ['identity.basic','identity:read:user']) && is_not_excluded?
198
- semaphore.synchronize {
199
- @identity_raw ||= access_token.get('/api/users.identity', headers: {'X-Slack-User' => user_id})
200
- @identity ||= @identity_raw.parsed
201
- }
202
- end
203
-
204
-
205
- private
206
-
207
- def initialize(*args)
159
+ def callback_phase #(*args)
160
+ # This technique copied from OmniAuth::Strategy,
161
+ # (this is how they do it for other omniauth objects).
162
+ env['omniauth.authorize_params'] = session.delete('omniauth.authorize_params')
208
163
  super
209
- @main_semaphore = Mutex.new
210
- @semaphores = {}
211
164
  end
212
165
 
213
- # Get a mutex specific to the calling method.
214
- # This operation is synchronized with its own mutex.
215
- def semaphore(method_name = caller[0][/`([^']*)'/, 1])
216
- @main_semaphore.synchronize {
217
- @semaphores[method_name] ||= Mutex.new
218
- }
166
+ # Returns OmniAuth::Slack::AuthHash
167
+ #
168
+ # Super result is converted to plain hash first,
169
+ # so AuthHash can do its recursive build magic.
170
+ #
171
+ def auth_hash
172
+ OmniAuth::Slack::AuthHash.new super.to_hash
219
173
  end
220
174
 
221
- def active_methods
222
- @active_methods ||= (
223
- includes = [options.include_data].flatten.compact
224
- excludes = [options.exclude_data].flatten.compact unless includes.size > 0
225
- method_list = %w(apps_permissions_users_list identity user_info user_profile team_info bot_info) #.concat(options[:additional_data].keys)
226
- if includes.size > 0
227
- method_list.keep_if {|m| includes.include?(m.to_s) || includes.include?(m.to_s.to_sym)}
228
- elsif excludes.size > 0
229
- method_list.delete_if {|m| excludes.include?(m.to_s) || excludes.include?(m.to_s.to_sym)}
230
- end
231
- log :debug, "Activated API calls: #{method_list}."
232
- log :debug, "Activated additional_data calls: #{options.additional_data.keys}."
233
- method_list
175
+ # Uses `OmniAuth::Slack::OAuth2::Client` to handle Slack-specific features.
176
+ #
177
+ # * Logs API requests with OmniAuth.logger.
178
+ # * Allows passthrough of Slack team_domain.
179
+ # * Enables/disables Client instance history.
180
+ # * Allows use of OmniAuth::Slack::OAuth2::AccessToken.
181
+ #
182
+ # Returns instance of OmniAuth::Slack::OAuth2::Client.
183
+ #
184
+ def client
185
+ @client ||= (
186
+ team_domain = (pass_through_params.include?('team_domain') && request.params['team_domain']) ? request.params['team_domain'] : options.team_domain
187
+ new_client = OmniAuth::Slack::OAuth2::Client.new(options.client_id, options.client_secret, deep_symbolize(options.client_options.merge({subdomain:team_domain})))
188
+
189
+ debug{"Strategy #{self} using Client #{new_client} with callback_url #{callback_url}"}
190
+
191
+ new_client
234
192
  )
235
193
  end
236
-
237
- def is_not_excluded?(method_name = caller[0][/`([^']*)'/, 1])
238
- active_methods.include?(method_name.to_s) || active_methods.include?(method_name.to_s.to_sym)
239
- end
240
-
241
- # Preload additional api calls with a pool of threads.
242
- def preload_data_with_threads(num_threads)
243
- return unless num_threads > 0
244
- preload_methods = active_methods.concat(options[:additional_data].keys)
245
- log :info, "Preloading (#{preload_methods.size}) data requests using (#{num_threads}) threads."
246
- work_q = Queue.new
247
- preload_methods.each{|x| work_q.push x }
248
- workers = num_threads.to_i.times.map do
249
- Thread.new do
250
- begin
251
- while x = work_q.pop(true)
252
- log :debug, "Preloading #{x}."
253
- send x
254
- end
255
- rescue ThreadError
256
- end
257
- end
258
- end
259
- workers.map(&:join); "ok"
260
- end
261
-
262
- # Define methods for addional data from :additional_data option
263
- def define_additional_data
264
- hash = options[:additional_data]
265
- if !hash.to_h.empty?
266
- hash.each do |k,v|
267
- define_singleton_method(k) do
268
- instance_variable_get(:"@#{k}") ||
269
- instance_variable_set(:"@#{k}", v.respond_to?(:call) ? v.call(env) : v)
270
- end
271
- end
272
- end
273
- end
274
-
275
- def get_additional_data
276
- if skip_info?
277
- {}
278
- else
279
- options[:additional_data].inject({}) do |hash,tupple|
280
- hash[tupple[0].to_s] = send(tupple[0].to_s)
281
- hash
282
- end
283
- end
284
- end
285
-
286
- # Parsed data returned from /slack/oauth.access api call.
287
- def auth
288
- @auth ||= access_token.params.to_h.merge({'token' => access_token.token})
289
- end
290
194
 
291
- def user_identity
292
- @user_identity ||= identity['user'].to_h
293
- end
294
-
295
- def team_identity
296
- @team_identity ||= identity['team'].to_h
297
- end
298
-
299
- def user_info
300
- return {} unless !skip_info? && has_scope?(identity: 'users:read', team: 'users:read') && is_not_excluded?
301
- semaphore.synchronize {
302
- @user_info_raw ||= access_token.get('/api/users.info', params: {user: user_id}, headers: {'X-Slack-User' => user_id})
303
- @user_info ||= @user_info_raw.parsed
304
- }
195
+ # Dropping query_string from the default OmniAuth callback_url prevents
196
+ # some errors in call to /api/oauth.[v2.]access.
197
+ #
198
+ def callback_url
199
+ options.redirect_uri || full_host + script_name + callback_path
305
200
  end
306
201
 
307
- def user_profile
308
- return {} unless !skip_info? && has_scope?(identity: 'users.profile:read', team: 'users.profile:read') && is_not_excluded?
309
- semaphore.synchronize {
310
- @user_profile_raw ||= access_token.get('/api/users.profile.get', params: {user: user_id}, headers: {'X-Slack-User' => user_id})
311
- @user_profile ||= @user_profile_raw.parsed
312
- }
202
+ ### Possibly obsolete
203
+ #
204
+ # def user_id
205
+ # # access_token['user_id'] || access_token['user'].to_h['id'] || access_token['authorizing_user'].to_h['user_id']
206
+ # access_or_user_token&.user_id
207
+ # end
208
+ #
209
+ # def team_id
210
+ # access_token&.team_id
211
+ # end
212
+
213
+ # Gets and decodes :pass_through_params option.
214
+ #
215
+ def pass_through_params
216
+ ptp = [options.pass_through_params].flatten.compact
217
+ case
218
+ when ptp[0].to_s == 'all'
219
+ options.pass_through_params = AUTH_OPTIONS
220
+ when ptp[0].to_s == 'none'
221
+ []
222
+ else
223
+ ptp
224
+ end
313
225
  end
314
226
 
315
- def team_info
316
- return {} unless !skip_info? && has_scope?(identity: 'team:read', team: 'team:read') && is_not_excluded?
317
- semaphore.synchronize {
318
- @team_info_raw ||= access_token.get('/api/team.info')
319
- @team_info ||= @team_info_raw.parsed
320
- }
321
- end
227
+ # Parsed data returned from /slack/oauth.[v2.]access api call.
228
+ #
229
+ # Where does this actually go? Where is it used?
230
+ #
231
+ # Simplifying this to just 'access_token.to_hash' does not appear to
232
+ # have any noticeable negative effect.
233
+ #
234
+ # Possibly obsolete
235
+ #
236
+ # def auth
237
+ # @auth ||= access_token.to_hash
238
+ # end
322
239
 
323
- def web_hook_info
324
- return {} unless auth.key? 'incoming_webhook'
325
- auth['incoming_webhook']
326
- end
327
-
328
- def bot_info
329
- return {} unless !skip_info? && has_scope?(identity: 'users:read') && is_not_excluded?
330
- semaphore.synchronize {
331
- @bot_info_raw ||= access_token.get('/api/bots.info')
332
- @bot_info ||= @bot_info_raw.parsed
333
- }
334
- end
335
-
336
- def user_id
337
- auth['user_id'] || auth['user'].to_h['id'] || auth['authorizing_user'].to_h['user_id']
338
- end
339
-
340
- def team_id
341
- auth['team_id'] || auth['team'].to_h['id']
342
- end
343
-
344
- # API call to get user permissions for workspace token.
345
- # This is needed because workspace token 'sign-in-with-slack' is missing scopes
346
- # in the :scope field (acknowledged issue in developer preview).
240
+ # Points to client @history, which is filled with API response objects.
347
241
  #
348
- # Returns [<id>: <resource>]
349
- def apps_permissions_users_list
350
- return {} unless !skip_info? && is_app_token? && is_not_excluded?
351
- semaphore.synchronize {
352
- @apps_permissions_users_list_raw ||= access_token.get('/api/apps.permissions.users.list')
353
- @apps_permissions_users_list ||= @apps_permissions_users_list_raw.parsed['resources'].inject({}){|h,i| h[i['id']] = i; h}
354
- }
355
- end
356
-
357
242
  def raw_info
358
- @raw_info ||= {}
359
- end
360
-
361
- # Is this a workspace app token?
362
- def is_app_token?
363
- auth['token_type'].to_s == 'app'
243
+ @raw_info ||= access_token.client.history
244
+ debug{"Retrieved raw_info (size #{@raw_info.size}) (object_id #{@raw_info.object_id})"}
245
+ @raw_info
364
246
  end
365
247
 
366
- # Scopes come from at least 3 different places now.
367
- # * The classic :scope field (string)
368
- # * New workshop token :scopes field (hash)
369
- # * Separate call to apps.permissions.users.list (array)
248
+ # Gets 'authed_user' sub-token from main access token.
370
249
  #
371
- # This returns hash of workspace scopes, with classic & new identity scopes in :identity.
372
- # Lists of scopes are in array form.
373
- def all_scopes
374
- @all_scopes ||=
375
- {'identity' => (auth['scope'] || apps_permissions_users_list[user_id].to_h['scopes'].to_a.join(',')).to_s.split(',')}
376
- .merge(auth['scopes'].to_h)
250
+ def user_token
251
+ access_token&.user_token
377
252
  end
378
253
 
379
- # Determine if given scopes exist in current authorization.
380
- # Scopes is hash where
381
- # key == scope type <identity|app_hope|team|channel|group|mpim|im>
382
- # val == array or string of individual scopes.
383
- def has_scope?(**scopes_hash)
384
- scopes_hash.detect do |section, scopes|
385
- test_scopes = case
386
- when scopes.is_a?(String); scopes.split(',')
387
- when scopes.is_a?(Array); scopes
388
- else raise "Scope must be a string or array"
389
- end
390
- test_scopes.detect do |scope|
391
- all_scopes[section.to_s].to_a.include?(scope.to_s)
392
- end
254
+ # Gets main access_token, if valid, otherwise gets user_token, if valid.
255
+ # Handles Slack v1 and v2 API (v2 is non-conformant with OAUTH2 spec).
256
+ def access_or_user_token
257
+ if access_token&.token
258
+ access_token
259
+ elsif user_token
260
+ user_token
261
+ else
262
+ access_token
393
263
  end
394
264
  end
395
265
 
396
- def self.ad_hoc_client(client_id, client_key, token)
397
- puts default_options['client_options'].to_yaml
398
- ::OAuth2::AccessToken.new(
399
- ::OAuth2::Client.new(
400
- client_id,
401
- client_key,
402
- default_options['client_options'].map{|k,v| [k.to_sym, v]}.to_h
403
- ),
404
- token
405
- )
266
+ def scopes_requested
267
+ # omniauth.authorize_params is an enhancement to omniauth functionality for omniauth-slack.
268
+ out = {
269
+ scope: env['omniauth.authorize_params'].to_h['scope'],
270
+ user_scope: env['omniauth.authorize_params'].to_h['user_scope']
271
+ }
272
+
273
+ debug{"scopes_requested: #{out}"}
274
+ return out
406
275
  end
407
-
408
- end
409
- end
410
- end
276
+
277
+ end # Slack
278
+ end # Strategies
279
+ end # OmniAuth
411
280