Skip to content

Commit eff26ac

Browse files
[TBT-46] Handle new web token with expiry (#1340)
* handle new web token with expiry * handle expired auth token on regenerate fix specs * add audit log for regenerate token * set the auth cli token to expire * fix job/jobs find_specs vm_size * add user_id to find web_token for expiry reset * add puts * set default expiries to 5 mins, more puts * remove puts * remove user_id * return user_id and defaults for api and cli tokens * add unlimited expiry two years if env set * rename env variable for web token * change default expiries for testing * refactor reset_expiry * bring back default expiries
1 parent 813b26f commit eff26ac

File tree

11 files changed

+92
-17
lines changed

11 files changed

+92
-17
lines changed

lib/travis/api/app/access_token.rb

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ def self.find_by_token(token)
1818
return token if token.is_a? self
1919
user_id, app_id, *scopes = redis.lrange(key(token), 0, -1)
2020
extra = decode_json(scopes.pop) if scopes.last && scopes.last =~ /^json:/
21+
22+
reset_expiry(token, user_id, app_id)
23+
2124
new(token: token, scopes: scopes, user_id: user_id, app_id: app_id, extra: extra) if user_id
2225
end
2326

@@ -101,5 +104,30 @@ def reuse_key
101104
keys.join(':')
102105
end
103106
end
107+
108+
def self.reset_expiry(token, user_id, app_id)
109+
web_token = Token.find_by(user_id: user_id, purpose: :web)
110+
111+
if web_token && (token == web_token.token)
112+
redis.expire(key(token), web_token_expires_in)
113+
elsif app_id == '1' # This is the TravisCLI token app_id
114+
redis.expire(key(token), auth_cli_token_expires_in)
115+
redis.expire("r:#{user_id}:#{app_id}", auth_cli_token_expires_in)
116+
else
117+
redis.expire(key(token), auth_token_expires_in)
118+
end
119+
end
120+
121+
def self.web_token_expires_in
122+
Travis.config.tokens&.web_token.expires_in
123+
end
124+
125+
def self.auth_token_expires_in
126+
Travis.config.tokens&.auth_token.expires_in
127+
end
128+
129+
def self.auth_cli_token_expires_in
130+
Travis.config.tokens&.auth_cli_token.expires_in
131+
end
104132
end
105133
end

lib/travis/api/app/endpoint/authorization.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,7 @@ def serialize_user(user)
162162
rendered['user'].merge(
163163
'token' => token,
164164
'rss_token' => user.tokens.rss.first.try(:token) || token,
165+
'web_token' => user.tokens.web.first.try(:token) || token
165166
)
166167
end
167168

lib/travis/api/v3/queries/access_token.rb

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,19 @@
11
module Travis::API::V3
22
class Queries::AccessToken < Query
3-
def regenerate_token(user, token, app_id)
3+
def regenerate_token(user, token, app_id, expires_in: nil)
44
Travis.redis.del("t:#{token}")
5-
Travis::Api::App::AccessToken.create(user: user, app_id: app_id, force: true).token
5+
new_token = Travis::Api::App::AccessToken.create(user: user, app_id: app_id, expires_in:, force: true).token
6+
Travis::API::V3::Models::Audit.create!(
7+
owner: user,
8+
change_source: 'travis-api',
9+
source: user,
10+
source_changes: {
11+
action: 'regenerate_token',
12+
old_api_token: token,
13+
new_api_token: new_token,
14+
}
15+
)
16+
new_token
617
end
718

819
def remove_token(user, token, app_id)
Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
module Travis::API::V3
22
class Services::AccessToken::RegenerateToken < Service
3+
params :token
4+
35
def run!
46
raise LoginRequired unless access_control.logged_in?
5-
app_id = Travis::Api::App::AccessToken.find_by_token(access_control.token)&.app_id
6-
result query.regenerate_token(access_control.user, access_control.token, app_id)
7+
app_id = Travis::Api::App::AccessToken.find_by_token(params['token'])&.app_id || 0
8+
result query.regenerate_token(access_control.user, params['token'], app_id, expires_in: Travis::Api::App::AccessToken.auth_token_expires_in)
79
end
810
end
911
end

lib/travis/config/defaults.rb

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,18 @@ def fallback_logs_api_auth_token
3737
shorten_host: 'trvs.io',
3838
public_mode: !!ENV['PUBLIC_MODE'],
3939
applications: {},
40-
tokens: { internal: 'token' },
40+
tokens: {
41+
internal: 'token',
42+
web_token: {
43+
expires_in: ENV['WEB_TOKEN_EXPIRES_IN_HOURS'] == 'unlimited' ? 2 * 365 * 86_400 : (ENV['WEB_TOKEN_EXPIRES_IN_HOURS'] ? ENV['WEB_TOKEN_EXPIRES_IN_HOURS'].to_i * 3_600 : 1 * 3_600)
44+
},
45+
auth_token: {
46+
expires_in: ENV['AUTH_TOKEN_EXPIRES_IN_DAYS'] == 'unlimited' ? 2 * 365 * 86_400 : (ENV['AUTH_TOKEN_EXPIRES_IN_DAYS'] ? ENV['AUTH_TOKEN_EXPIRES_IN_DAYS'].to_i * 86_400 : 90 * 86_400)
47+
},
48+
auth_cli_token: {
49+
expires_in: ENV['AUTH_CLI_TOKEN_EXPIRES_IN_DAYS'] == 'unlimited' ? 2 * 365 * 86_400 : (ENV['AUTH_CLI_TOKEN_EXPIRES_IN_DAYS'] ? ENV['AUTH_CLI_TOKEN_EXPIRES_IN_DAYS'].to_i * 86_400 : 90 * 86_400)
50+
}
51+
},
4152
auth: { target_origin: nil },
4253
assets: { host: HOSTS[Travis.env.to_sym] },
4354
amqp: { username: 'guest', password: 'guest', host: 'localhost', prefetch: 1 },

lib/travis/model/token.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
# that people cannot throw random repositories at Travis CI.
99
class Token < Travis::Model
1010
self.table_name = 'tokens'
11-
enum purpose: [ :asset, :rss ]
11+
enum purpose: [ :asset, :rss, :web ]
1212

1313
belongs_to :user
1414

lib/travis/model/user.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,8 @@ def inspect
172172

173173
def create_the_tokens
174174
self.tokens.asset.create! unless self.tokens.asset.exists?
175-
self.tokens.rss.create!
175+
self.tokens.rss.create! unless self.tokens.rss.exists?
176+
self.tokens.web.create! unless self.tokens.web.exists?
176177
end
177178

178179
def github?

spec/unit/access_token_spec.rb

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,15 @@
1919
end
2020

2121
it 'expires the token after given period of time' do
22-
token = described_class.new(app_id: 1, user_id: 2, expires_in: 1).tap(&:save)
22+
token = described_class.new(app_id: 1, user_id: 2).tap(&:save)
23+
key = "t:#{token.token}"
24+
Travis.redis.expire(key, 1)
2325

24-
expect(described_class.find_by_token(token.token)).not_to be_nil
26+
expect(Travis.redis.exists(key)).to eq 1
2527

2628
sleep 2
2729

28-
expect(described_class.find_by_token(token.token)).to be_nil
30+
expect(Travis.redis.exists(key)).to eq 0
2931
end
3032

3133
it 'allows to save extra information' do
Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
11
describe Travis::API::V3::Services::AccessToken::RegenerateToken, set_app: true do
22
let(:user) { FactoryBot.create(:user) }
33
let(:token) { Travis::Api::App::AccessToken.create(user: user, app_id: 0) }
4-
let(:headers) {{ 'HTTP_AUTHORIZATION' => "token #{token}" }}
5-
let(:parsed_body) { JSON.load(body) }
4+
let(:headers) {{ 'HTTP_AUTHORIZATION' => "token #{token}", 'CONTENT_TYPE' => 'application/json' }}
5+
let(:params) {{ token: token.token }.to_json }
6+
let(:parsed_body) { JSON.load(last_response.body) }
67

78
describe "regenerating the API access token" do
8-
before { patch('/v3/access_token', {}, headers) }
9-
example { expect(last_response.status).to eq 200 }
10-
example { expect(Travis.redis.exists?("t:#{token}")).to be_falsey }
11-
example { expect(Travis.redis.exists?("t:#{parsed_body['token']}")).to be_truthy }
9+
before do
10+
patch('/v3/access_token', params, headers)
11+
end
12+
13+
example { expect(last_response.status).to eq 200 }
14+
example { expect(Travis.redis.exists?("t:#{token.token}")).to be_falsey }
15+
example { expect(Travis.redis.exists?("t:#{parsed_body['token']}")).to be_truthy }
1216
end
1317
end

spec/v3/services/job/find_spec.rb

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@
6969
"private" => false,
7070
"restarted_at" => nil,
7171
"restarted_by" => nil,
72+
"vm_size" => nil,
7273
"build" => {
7374
"@type" => "build",
7475
"@href" => "/v3/build/#{build.id}",
@@ -158,7 +159,7 @@
158159

159160
describe "fetching job on private repository, private API, authenticated as user with access" do
160161
let(:token) { Travis::Api::App::AccessToken.create(user: repo.owner, app_id: 1) }
161-
let(:headers) {{ 'HTTP_AUTHORIZATION' => "token #{token}" }}
162+
let(:headers) {{ 'HTTP_AUTHORIZATION' => "token #{token}", 'CONTENT_TYPE' => 'application/json' }}
162163
before { Travis::API::V3::Models::Permission.create(repository: repo, user: repo.owner, pull: true) }
163164
before { repo.update_attribute(:private, true) }
164165
before { allow_any_instance_of(Travis::API::V3::Permissions::Job).to receive(:delete_log?).and_return(true) }
@@ -191,6 +192,7 @@
191192
"private" => false,
192193
"restarted_at" => nil,
193194
"restarted_by" => nil,
195+
"vm_size" => nil,
194196
"build" => {
195197
"@type" => "build",
196198
"@href" => "/v3/build/#{build.id}",
@@ -274,6 +276,7 @@
274276
"private" => false,
275277
"restarted_at" => nil,
276278
"restarted_by" => nil,
279+
"vm_size" => nil,
277280
"build" => {
278281
"@type" => "build",
279282
"@href" => "/v3/build/#{build.id}",

0 commit comments

Comments
 (0)