ginjo-omniauth-slack 2.4.1 → 2.5.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.
@@ -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