FEATURE: Automatically expire keys if not used for a configurable amount of time. (#6264)

This commit is contained in:
Bianca Nenciu
2018-08-20 17:36:14 +02:00
committed by Régis Hanol
parent 1d913d3204
commit 860c1c3dcd
11 changed files with 70 additions and 1 deletions

View File

@ -18,6 +18,7 @@
</ul> </ul>
</p> </p>
<p><span>{{i18n "user.api_approved"}}</span> {{bound-date key.created_at}}</p> <p><span>{{i18n "user.api_approved"}}</span> {{bound-date key.created_at}}</p>
<p><span>{{i18n "user.api_last_used_at"}}</span> {{bound-date key.last_used_at}}</p>
</div> </div>
{{/each}} {{/each}}
</div> </div>

View File

@ -0,0 +1,15 @@
module Jobs
class AutoExpireUserApiKeys < Jobs::Scheduled
every 1.day
def execute(args)
if SiteSetting.expire_user_api_keys_days > 0
expire_user_api_keys_days = SiteSetting.expire_user_api_keys_days.days.ago
UserApiKey.where("last_used_at < ?", expire_user_api_keys_days).update_all(revoked_at: Time.zone.now)
end
end
end
end

View File

@ -74,6 +74,7 @@ end
# updated_at :datetime not null # updated_at :datetime not null
# revoked_at :datetime # revoked_at :datetime
# scopes :text default([]), not null, is an Array # scopes :text default([]), not null, is an Array
# last_used_at :datetime not null
# #
# Indexes # Indexes
# #

View File

@ -184,10 +184,12 @@ class UserSerializer < BasicUserSerializer
id: k.id, id: k.id,
application_name: k.application_name, application_name: k.application_name,
scopes: k.scopes.map { |s| I18n.t("user_api_key.scopes.#{s}") }, scopes: k.scopes.map { |s| I18n.t("user_api_key.scopes.#{s}") },
created_at: k.created_at created_at: k.created_at,
last_used_at: k.last_used_at,
} }
end end
keys.sort! { |a, b| a.last_used_at.to_time > b.last_used_at.to_time }
keys.length > 0 ? keys : nil keys.length > 0 ? keys : nil
end end

View File

@ -704,6 +704,7 @@ en:
revoke_access: "Revoke Access" revoke_access: "Revoke Access"
undo_revoke_access: "Undo Revoke Access" undo_revoke_access: "Undo Revoke Access"
api_approved: "Approved:" api_approved: "Approved:"
api_last_used_at: "Last used at:"
theme: "Theme" theme: "Theme"
home: "Default Home Page" home: "Default Home Page"
staged: "Staged" staged: "Staged"

View File

@ -1770,6 +1770,7 @@ en:
min_trust_level_for_user_api_key: "Trust level required for generation of user API keys" min_trust_level_for_user_api_key: "Trust level required for generation of user API keys"
allowed_user_api_auth_redirects: "Allowed URL for authentication redirect for user API keys" allowed_user_api_auth_redirects: "Allowed URL for authentication redirect for user API keys"
allowed_user_api_push_urls: "Allowed URLs for server push to user API" allowed_user_api_push_urls: "Allowed URLs for server push to user API"
expire_user_api_keys_days: "Number of days before an user API key automatically expires (0 for never)"
tagging_enabled: "Enable tags on topics?" tagging_enabled: "Enable tags on topics?"
min_trust_to_create_tag: "The minimum trust level required to create a tag." min_trust_to_create_tag: "The minimum trust level required to create a tag."

View File

@ -1681,6 +1681,8 @@ user_api:
allowed_user_api_auth_redirects: allowed_user_api_auth_redirects:
default: 'https://api.discourse.org/api/auth_redirect|discourse://auth_redirect' default: 'https://api.discourse.org/api/auth_redirect|discourse://auth_redirect'
type: list type: list
expire_user_api_keys_days:
default: 180
tags: tags:
tagging_enabled: tagging_enabled:

View File

@ -0,0 +1,5 @@
class AddUserApiKeysLastUsedAt < ActiveRecord::Migration[5.2]
def change
add_column :user_api_keys, :last_used_at, :datetime, null: false, default: -> { 'CURRENT_TIMESTAMP' }
end
end

View File

@ -258,6 +258,7 @@ class Auth::DefaultCurrentUserProvider
raise Discourse::InvalidAccess raise Discourse::InvalidAccess
end end
api_key.update_columns(last_used_at: Time.zone.now)
if client_id.present? && client_id != api_key.client_id if client_id.present? && client_id != api_key.client_id
api_key.update_columns(client_id: client_id) api_key.update_columns(client_id: client_id)
end end

View File

@ -0,0 +1,16 @@
require 'rails_helper'
describe 'user api keys integration' do
let(:user_api_key) { Fabricate(:readonly_user_api_key) }
it 'updates last used time on use' do
user_api_key.update_columns(last_used_at: 7.days.ago)
freeze_time
get '/session/current.json', headers: {
HTTP_USER_API_KEY: user_api_key.key,
}
expect(user_api_key.reload.last_used_at).to eq(Time.zone.now)
end
end

View File

@ -0,0 +1,24 @@
require 'rails_helper'
RSpec.describe Jobs::AutoExpireUserApiKeys do
let(:key1) { Fabricate(:readonly_user_api_key) }
let(:key2) { Fabricate(:readonly_user_api_key) }
context 'when user api key is unused in last 1 days' do
before do
SiteSetting.expire_user_api_keys_days = true
end
it 'should revoke the key' do
freeze_time
key1.update!(last_used_at: 2.days.ago)
described_class.new.execute({})
expect(key1.reload.revoked_at).to eq(Time.zone.now)
expect(key2.reload.revoked_at).to eq(nil)
end
end
end