Skip to content

Commit 0b56772

Browse files
committed
Add support for OAuth v2.
1 parent 13f330c commit 0b56772

File tree

10 files changed

+265
-57
lines changed

10 files changed

+265
-57
lines changed

.rubocop_todo.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# This configuration was generated by
22
# `rubocop --auto-gen-config`
3-
# on 2020-11-15 11:38:22 -0500 using RuboCop version 0.81.0.
3+
# on 2020-11-16 09:56:54 -0500 using RuboCop version 0.81.0.
44
# The point is for the user to remove these configuration records
55
# one by one as the offenses are removed from the code base.
66
# Note that changes in the inspected code, or installation of new

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
### Changelog
22

3-
#### 1.0.1 (Next)
3+
#### 1.1.0 (Next)
44

5+
* [#132](https://github.com/slack-ruby/slack-ruby-bot-server/pull/132): Add support for OAuth v2 - [@dblock](https://github.com/dblock).
56
* Your contribution here.
67

78
#### 1.0.0 (2020/11/15)

README.md

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ Build a complete Slack bot service with Slack button integration, in Ruby.
1616
- [Storage](#storage)
1717
- [MongoDB](#mongodb)
1818
- [ActiveRecord](#activerecord)
19-
- [OAuth Scopes](#oauth-scopes)
19+
- [OAuth Version and Scopes](#oauth-version-and-scopes)
2020
- [Slack App](#slack-app)
2121
- [API](#api)
2222
- [App](#app)
@@ -73,13 +73,14 @@ gem 'otr-activerecord'
7373
gem 'cursor_pagination'
7474
```
7575

76-
### OAuth Scopes
76+
### OAuth Version and Scopes
7777

78-
Configure your app's [OAuth scopes](https://api.slack.com/legacy/oauth-scopes) as needed by your application.
78+
Configure your app's [OAuth version](https://api.slack.com/authentication/oauth-v2) and [scopes](https://api.slack.com/legacy/oauth-scopes) as needed by your application.
7979

8080
```ruby
8181
SlackRubyBotServer.configure do |config|
82-
config.oauth_scope = ['channels:read', 'chat:write:user']
82+
config.oauth_version = :v2
83+
config.oauth_scope = ['channels:read', 'chat:write']
8384
end
8485
```
8586

@@ -91,11 +92,26 @@ Create a new Slack App [here](https://api.slack.com/applications/new).
9192

9293
![](images/create-app.png)
9394

94-
Follow Slack's instructions, note the app client ID and secret, give the bot a default name, etc. The redirect URL should be the location of your app. For local testing purposes use a public tunneling service such as [ngrok](https://ngrok.com/) to expose local port 9292.
95+
Follow Slack's instructions, note the app client ID and secret, give the bot a default name, etc.
9596

9697
Within your application, edit your `.env` file and add `SLACK_CLIENT_ID=...` and `SLACK_CLIENT_SECRET=...` in it.
9798

98-
Run `bundle install` and `foreman start` to boot the app. Navigate to [localhost:9292](http://localhost:9292). You should see an "Add to Slack" button. Use it to install the app into your own Slack team.
99+
Run `bundle install` and `foreman start` to boot the app.
100+
101+
```
102+
$ foreman start
103+
07:44:47 web.1 | started with pid 59258
104+
07:44:50 web.1 | * Listening on tcp://0.0.0.0:5000
105+
```
106+
107+
Set the redirect URL in "OAuth & Permissions" be the location of your app. Since you cannot receive notifications on localhost from Slack use a public tunneling service such as [ngrok](https://ngrok.com/) to expose local port 9292 for testing.
108+
109+
```
110+
$ ngrok http 5000
111+
Forwarding https://ddfd97f80615.ngrok.io -> http://localhost:5000
112+
```
113+
114+
Navigate to either [localhost:9292](http://localhost:9292) or the ngrok URL above. You should see an "Add to Slack" button. Use it to install the app into your own Slack team.
99115

100116
### API
101117

@@ -174,7 +190,7 @@ The [Add to Slack button](https://api.slack.com/docs/slack-button) also allows f
174190
auth = OpenSSL::HMAC.hexdigest("SHA256", "key", "data")
175191
```
176192
```html
177-
<a href="https://slack.com/oauth/authorize?scope=<%= SlackRubyBotServer::Config.oauth_scope_s %>&client_id=<%= ENV['SLACK_CLIENT_ID'] %>&state=#{auth)"> ... </a>
193+
<a href="<%= SlackRubyBotServer::Config.oauth_authorize_url %>?scope=<%= SlackRubyBotServer::Config.oauth_scope_s %>&client_id=<%= ENV['SLACK_CLIENT_ID'] %>&state=#{auth)"> ... </a>
178194
```
179195
```ruby
180196
instance = SlackRubyBotServer::Service.instance

lib/slack-ruby-bot-server/api/endpoints/teams_endpoint.rb

Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -40,22 +40,41 @@ class TeamsEndpoint < Grape::API
4040

4141
raise 'Missing SLACK_CLIENT_ID or SLACK_CLIENT_SECRET.' unless ENV.key?('SLACK_CLIENT_ID') && ENV.key?('SLACK_CLIENT_SECRET')
4242

43-
rc = client.oauth_access(
43+
options = {
4444
client_id: ENV['SLACK_CLIENT_ID'],
4545
client_secret: ENV['SLACK_CLIENT_SECRET'],
4646
code: params[:code]
47-
)
47+
}
4848

49-
access_token = rc['access_token']
50-
user_id = rc['user_id']
49+
rc = client.send(SlackRubyBotServer.config.oauth_access_method, options)
5150

52-
bot = rc['bot']
51+
token = nil
52+
access_token = nil
53+
user_id = nil
54+
bot_user_id = nil
55+
team_id = nil
56+
team_name = nil
5357

54-
token = bot ? bot['bot_access_token'] : access_token
55-
bot_user_id = bot['bot_user_id'] if bot
58+
case SlackRubyBotServer::Config.oauth_version
59+
when :v2
60+
access_token = rc.access_token
61+
token = rc.access_token
62+
user_id = rc.authed_user&.id
63+
bot_user_id = rc.bot_user_id
64+
team_id = rc.team&.id
65+
team_name = rc.team&.name
66+
when :v1
67+
access_token = rc.access_token
68+
bot = rc.bot if rc.key?(:bot)
69+
token = bot ? bot.bot_access_token : access_token
70+
user_id = rc.user_id
71+
bot_user_id = bot ? bot.bot_user_id : nil
72+
team_id = rc.team_id
73+
team_name = rc.team_name
74+
end
5675

5776
team = Team.where(token: token).first
58-
team ||= Team.where(team_id: rc['team_id']).first
77+
team ||= Team.where(team_id: team_id).first
5978

6079
if team
6180
team.ping_if_active!
@@ -72,8 +91,8 @@ class TeamsEndpoint < Grape::API
7291
else
7392
team = Team.create!(
7493
token: token,
75-
team_id: rc['team_id'],
76-
name: rc['team_name'],
94+
team_id: team_id,
95+
name: team_name,
7796
activated_user_id: user_id,
7897
activated_user_access_token: access_token,
7998
bot_user_id: bot_user_id

lib/slack-ruby-bot-server/config.rb

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,13 @@ module Config
77
attr_accessor :database_adapter
88
attr_accessor :view_paths
99
attr_accessor :oauth_scope
10+
attr_accessor :oauth_version
1011

1112
def reset!
1213
self.logger = nil
1314
self.service_class = SlackRubyBotServer::Service
1415
self.oauth_scope = nil
16+
self.oauth_version = :v2
1517

1618
self.view_paths = [
1719
'views',
@@ -28,6 +30,28 @@ def reset!
2830
end
2931
end
3032

33+
def oauth_authorize_url
34+
case oauth_version
35+
when :v2
36+
'https://slack.com/oauth/v2/authorize'
37+
when :v1
38+
'https://slack.com/oauth/authorize'
39+
else
40+
raise ArgumentError, 'Invalid oauth_version, must be one of :v1 or v2.'
41+
end
42+
end
43+
44+
def oauth_access_method
45+
case oauth_version
46+
when :v2
47+
:oauth_v2_access
48+
when :v1
49+
:oauth_access
50+
else
51+
raise ArgumentError, 'Invalid oauth_version, must be one of :v1 or v2.'
52+
end
53+
end
54+
3155
def oauth_scope_s
3256
oauth_scope&.join('+')
3357
end

lib/slack-ruby-bot-server/models/team/methods.rb

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,18 @@ def to_s
3232

3333
def ping!
3434
client = Slack::Web::Client.new(token: token)
35+
3536
auth = client.auth_test
37+
38+
presence = begin
39+
client.users_getPresence(user: auth['user_id'])
40+
rescue Slack::Web::Api::Errors::MissingScope
41+
nil
42+
end
43+
3644
{
3745
auth: auth,
38-
presence: client.users_getPresence(user: auth['user_id'])
46+
presence: presence
3947
}
4048
end
4149

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
module SlackRubyBotServer
2-
VERSION = '1.0.1'.freeze
2+
VERSION = '1.1.0'.freeze
33
end

public/index.html.erb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
</p>
1717
<p id='messages' />
1818
<p id='register'>
19-
<a href="https://slack.com/oauth/authorize?scope=<%= SlackRubyBotServer::Config.oauth_scope_s %>&client_id=<%= ENV['SLACK_CLIENT_ID'] %>"><img alt="Add to Slack" height="40" width="139" src="https://platform.slack-edge.com/img/add_to_slack.png" srcset="https://platform.slack-edge.com/img/add_to_slack.png 1x, https://platform.slack-edge.com/img/[email protected] 2x"></a>
19+
<a href="<%= SlackRubyBotServer::Config.oauth_authorize_url %>?scope=<%= SlackRubyBotServer::Config.oauth_scope_s %>&client_id=<%= ENV['SLACK_CLIENT_ID'] %>"><img alt="Add to Slack" height="40" width="139" src="https://platform.slack-edge.com/img/add_to_slack.png" srcset="https://platform.slack-edge.com/img/add_to_slack.png 1x, https://platform.slack-edge.com/img/[email protected] 2x"></a>
2020
</p>
2121
<p id='active_teams_count'>&nbsp;</p>
2222
<p>

spec/api/endpoints/teams_endpoint_spec.rb

Lines changed: 118 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -45,14 +45,114 @@
4545
end
4646
end
4747

48-
context 'register a bot' do
48+
context 'register a bot via oauth v2' do
4949
before do
50-
oauth_access = {
51-
'access_token' => 'access_token',
52-
'user_id' => 'user_id',
53-
'team_id' => 'team_id',
54-
'team_name' => 'team_name'
55-
}
50+
SlackRubyBotServer.config.oauth_version = :v2
51+
oauth_access = Slack::Messages::Message.new({
52+
'access_token' => 'access_token',
53+
'authed_user' => { 'id' => 'user_id' },
54+
'team' => { 'id' => 'team_id', 'name' => 'team_name' }
55+
})
56+
ENV['SLACK_CLIENT_ID'] = 'client_id'
57+
ENV['SLACK_CLIENT_SECRET'] = 'client_secret'
58+
allow_any_instance_of(Slack::Web::Client).to receive(:oauth_v2_access).with(
59+
hash_including(
60+
code: 'code',
61+
client_id: 'client_id',
62+
client_secret: 'client_secret'
63+
)
64+
).and_return(oauth_access)
65+
end
66+
after do
67+
ENV.delete('SLACK_CLIENT_ID')
68+
ENV.delete('SLACK_CLIENT_SECRET')
69+
end
70+
it 'creates a team' do
71+
expect(SlackRubyBotServer::Service.instance).to receive(:start!)
72+
expect do
73+
team = client.teams._post(code: 'code')
74+
expect(team.team_id).to eq 'team_id'
75+
expect(team.name).to eq 'team_name'
76+
team = Team.find(team.id)
77+
expect(team.token).to eq 'access_token'
78+
expect(team.activated_user_access_token).to eq 'access_token'
79+
expect(team.activated_user_id).to eq 'user_id'
80+
expect(team.bot_user_id).to be nil
81+
end.to change(Team, :count).by(1)
82+
end
83+
84+
it 'includes optional state parameter' do
85+
expect(SlackRubyBotServer::Service.instance).to receive(:create!).with(instance_of(Team), state: 'property')
86+
client.teams._post(code: 'code', state: 'property')
87+
end
88+
89+
it 'reactivates a deactivated team' do
90+
expect(SlackRubyBotServer::Service.instance).to receive(:start!)
91+
existing_team = Fabricate(:team, token: 'access_token', active: false)
92+
expect do
93+
team = client.teams._post(code: 'code')
94+
expect(team.team_id).to eq existing_team.team_id
95+
expect(team.name).to eq existing_team.name
96+
expect(team.active).to be true
97+
team = Team.find(team.id)
98+
expect(team.token).to eq 'access_token'
99+
expect(team.active).to be true
100+
expect(team.activated_user_access_token).to eq 'access_token'
101+
expect(team.activated_user_id).to eq 'user_id'
102+
expect(team.bot_user_id).to be nil
103+
end.to_not change(Team, :count)
104+
end
105+
it 'reactivates a team deactivated on slack' do
106+
expect(SlackRubyBotServer::Service.instance).to receive(:start!)
107+
existing_team = Fabricate(:team, token: 'access_token')
108+
expect do
109+
expect_any_instance_of(Team).to receive(:ping!) { raise Slack::Web::Api::Errors::SlackError, 'invalid_auth' }
110+
team = client.teams._post(code: 'code')
111+
expect(team.team_id).to eq existing_team.team_id
112+
expect(team.name).to eq existing_team.name
113+
expect(team.active).to be true
114+
team = Team.find(team.id)
115+
expect(team.token).to eq 'access_token'
116+
expect(team.active).to be true
117+
expect(team.bot_user_id).to be nil
118+
expect(team.activated_user_id).to eq 'user_id'
119+
end.to_not change(Team, :count)
120+
end
121+
it 'returns a useful error when team already exists' do
122+
expect_any_instance_of(Team).to receive(:ping_if_active!)
123+
existing_team = Fabricate(:team, token: 'access_token')
124+
expect { client.teams._post(code: 'code') }.to raise_error Faraday::ClientError do |e|
125+
json = JSON.parse(e.response[:body])
126+
expect(json['message']).to eq "Team #{existing_team.name} is already registered."
127+
end
128+
end
129+
it 'reactivates a deactivated team with a different code' do
130+
expect(SlackRubyBotServer::Service.instance).to receive(:start!)
131+
existing_team = Fabricate(:team, token: 'old', team_id: 'team_id', active: false)
132+
expect do
133+
team = client.teams._post(code: 'code')
134+
expect(team.team_id).to eq existing_team.team_id
135+
expect(team.name).to eq existing_team.name
136+
expect(team.active).to be true
137+
team = Team.find(team.id)
138+
expect(team.token).to eq 'access_token'
139+
expect(team.active).to be true
140+
expect(team.activated_user_access_token).to eq 'access_token'
141+
expect(team.activated_user_id).to eq 'user_id'
142+
expect(team.bot_user_id).to be nil
143+
end.to_not change(Team, :count)
144+
end
145+
end
146+
147+
context 'register a bot via oauth v1' do
148+
before do
149+
SlackRubyBotServer.config.oauth_version = :v1
150+
oauth_access = Slack::Messages::Message.new({
151+
'access_token' => 'access_token',
152+
'user_id' => 'user_id',
153+
'team_id' => 'team_id',
154+
'team_name' => 'team_name'
155+
})
56156
ENV['SLACK_CLIENT_ID'] = 'client_id'
57157
ENV['SLACK_CLIENT_SECRET'] = 'client_secret'
58158
allow_any_instance_of(Slack::Web::Client).to receive(:oauth_access).with(
@@ -146,16 +246,17 @@
146246

147247
context 'register a legacy bot' do
148248
before do
149-
oauth_access = {
150-
'bot' => {
151-
'bot_access_token' => 'token',
152-
'bot_user_id' => 'bot_user_id'
153-
},
154-
'access_token' => 'access_token',
155-
'user_id' => 'user_id',
156-
'team_id' => 'team_id',
157-
'team_name' => 'team_name'
158-
}
249+
SlackRubyBotServer.config.oauth_version = :v1
250+
oauth_access = Slack::Messages::Message.new({
251+
'bot' => {
252+
'bot_access_token' => 'token',
253+
'bot_user_id' => 'bot_user_id'
254+
},
255+
'access_token' => 'access_token',
256+
'user_id' => 'user_id',
257+
'team_id' => 'team_id',
258+
'team_name' => 'team_name'
259+
})
159260
ENV['SLACK_CLIENT_ID'] = 'client_id'
160261
ENV['SLACK_CLIENT_SECRET'] = 'client_secret'
161262
allow_any_instance_of(Slack::Web::Client).to receive(:oauth_access).with(

0 commit comments

Comments
 (0)