Commit 747d0a9e authored by DeAndre Harris's avatar DeAndre Harris Committed by Jarka Košanová

Record and show last used date of PAT

1. Adds a last_used_at attribute to Personal Access Tokens
2. Update a token's last_used_at whenever it gets used
3. Display how long ago a personal access token was last used
parent b140ed88
# frozen_string_literal: true
module PersonalAccessTokens
class LastUsedService
def initialize(personal_access_token)
@personal_access_token = personal_access_token
end
def execute
# Needed to avoid calling service on Oauth tokens
return unless @personal_access_token.has_attribute?(:last_used_at)
# We _only_ want to update last_used_at and not also updated_at (which
# would be updated when using #touch).
@personal_access_token.update_column(:last_used_at, Time.zone.now) if update?
end
private
def update?
return false if ::Gitlab::Database.read_only?
last_used = @personal_access_token.last_used_at
last_used.nil? || (last_used <= 1.day.ago)
end
end
end
......@@ -16,6 +16,9 @@
%tr
%th= _('Name')
%th= s_('AccessTokens|Created')
%th
= _('Last Used')
= link_to icon('question-circle'), help_page_path('user/profile/personal_access_tokens.md', anchor: 'token-activity'), target: '_blank'
%th= _('Expires')
%th= _('Scopes')
%th
......@@ -24,6 +27,11 @@
%tr
%td= token.name
%td= token.created_at.to_date.to_s(:medium)
%td
- if token.last_used_at?
%span.token-last-used-label= _(time_ago_with_tooltip(token.last_used_at))
- else
%span.token-never-used-label= _('Never')
%td
- if token.expires?
- if token.expires_at.past? || token.expires_at.today?
......
---
title: Track last activity for Personal Access Token
merge_request: 35471
author:
type: changed
# frozen_string_literal: true
class AddLastUsedToPersonalAccessTokens < ActiveRecord::Migration[6.0]
DOWNTIME = false
include Gitlab::Database::MigrationHelpers
def up
with_lock_retries do
add_column :personal_access_tokens, :last_used_at, :datetime_with_timezone
end
end
def down
with_lock_retries do
remove_column :personal_access_tokens, :last_used_at, :datetime_with_timezone
end
end
end
......@@ -13748,7 +13748,8 @@ CREATE TABLE public.personal_access_tokens (
'::character varying NOT NULL,
impersonation boolean DEFAULT false NOT NULL,
token_digest character varying,
expire_notification_delivered boolean DEFAULT false NOT NULL
expire_notification_delivered boolean DEFAULT false NOT NULL,
last_used_at timestamp with time zone
);
CREATE SEQUENCE public.personal_access_tokens_id_seq
......@@ -23584,6 +23585,7 @@ COPY "schema_migrations" (version) FROM STDIN;
20200624222443
20200625045442
20200625082258
20200625113337
20200625190458
20200626060151
20200626130220
......
......@@ -44,6 +44,10 @@ profile.
At any time, you can revoke any personal access token by clicking the
respective **Revoke** button under the **Active Personal Access Token** area.
### Token activity
You can see when a token was last used from the **Personal Access Tokens** page. Updates to the token usage is fixed at once per 24 hours. Requests to [API resources](../../api/api_resources.md) and the [GraphQL API](../../api/graphql/index.md) will update a token's usage.
## Limiting scopes of a personal access token
Personal access tokens can be created with one or more scopes that allow various
......
......@@ -92,6 +92,8 @@ module Gitlab
validate_access_token!(scopes: [:api])
::PersonalAccessTokens::LastUsedService.new(access_token).execute
access_token.user || raise(UnauthorizedError)
end
......@@ -100,6 +102,8 @@ module Gitlab
validate_access_token!
::PersonalAccessTokens::LastUsedService.new(access_token).execute
access_token.user || raise(UnauthorizedError)
end
......
......@@ -13260,6 +13260,9 @@ msgstr ""
msgid "Last Seen"
msgstr ""
msgid "Last Used"
msgstr ""
msgid "Last accessed on"
msgstr ""
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe PersonalAccessTokens::LastUsedService do
describe '#execute' do
subject { described_class.new(personal_access_token).execute }
context 'when the personal access token has not been used recently' do
let_it_be(:personal_access_token) { create(:personal_access_token, last_used_at: 1.year.ago) }
it 'updates the last_used_at timestamp' do
expect { subject }.to change { personal_access_token.last_used_at }
end
it 'does not run on read-only GitLab instances' do
allow(::Gitlab::Database).to receive(:read_only?).and_return(true)
expect { subject }.not_to change { personal_access_token.last_used_at }
end
end
context 'when the personal access token has been used recently' do
let_it_be(:personal_access_token) { create(:personal_access_token, last_used_at: 1.minute.ago) }
it 'does not update the last_used_at timestamp' do
expect { subject }.not_to change { personal_access_token.last_used_at }
end
end
context 'when the last_used_at timestamp is nil' do
let_it_be(:personal_access_token) { create(:personal_access_token, last_used_at: nil) }
it 'updates the last_used_at timestamp' do
expect { subject }.to change { personal_access_token.last_used_at }
end
end
context 'when not a personal access token' do
let_it_be(:personal_access_token) { create(:oauth_access_token) }
it 'does not execute' do
expect(subject).to be_nil
end
end
end
end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment