Commit df90c6e4 authored by Jarka Košanová's avatar Jarka Košanová

Merge branch '33162-add-last-activity-for-personal-access-tokens' into 'master'

Resolve "Add Last Activity for Personal Access Tokens"

Closes #33162

See merge request gitlab-org/gitlab!35471
parents ef9473c3 747d0a9e
# 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 @@ ...@@ -16,6 +16,9 @@
%tr %tr
%th= _('Name') %th= _('Name')
%th= s_('AccessTokens|Created') %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= _('Expires')
%th= _('Scopes') %th= _('Scopes')
%th %th
...@@ -24,6 +27,11 @@ ...@@ -24,6 +27,11 @@
%tr %tr
%td= token.name %td= token.name
%td= token.created_at.to_date.to_s(:medium) %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 %td
- if token.expires? - if token.expires?
- if token.expires_at.past? || token.expires_at.today? - 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 ( ...@@ -13748,7 +13748,8 @@ CREATE TABLE public.personal_access_tokens (
'::character varying NOT NULL, '::character varying NOT NULL,
impersonation boolean DEFAULT false NOT NULL, impersonation boolean DEFAULT false NOT NULL,
token_digest character varying, 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 CREATE SEQUENCE public.personal_access_tokens_id_seq
...@@ -23584,6 +23585,7 @@ COPY "schema_migrations" (version) FROM STDIN; ...@@ -23584,6 +23585,7 @@ COPY "schema_migrations" (version) FROM STDIN;
20200624222443 20200624222443
20200625045442 20200625045442
20200625082258 20200625082258
20200625113337
20200625190458 20200625190458
20200626060151 20200626060151
20200626130220 20200626130220
......
...@@ -44,6 +44,10 @@ profile. ...@@ -44,6 +44,10 @@ profile.
At any time, you can revoke any personal access token by clicking the At any time, you can revoke any personal access token by clicking the
respective **Revoke** button under the **Active Personal Access Token** area. 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 ## Limiting scopes of a personal access token
Personal access tokens can be created with one or more scopes that allow various Personal access tokens can be created with one or more scopes that allow various
......
...@@ -92,6 +92,8 @@ module Gitlab ...@@ -92,6 +92,8 @@ module Gitlab
validate_access_token!(scopes: [:api]) validate_access_token!(scopes: [:api])
::PersonalAccessTokens::LastUsedService.new(access_token).execute
access_token.user || raise(UnauthorizedError) access_token.user || raise(UnauthorizedError)
end end
...@@ -100,6 +102,8 @@ module Gitlab ...@@ -100,6 +102,8 @@ module Gitlab
validate_access_token! validate_access_token!
::PersonalAccessTokens::LastUsedService.new(access_token).execute
access_token.user || raise(UnauthorizedError) access_token.user || raise(UnauthorizedError)
end end
......
...@@ -13260,6 +13260,9 @@ msgstr "" ...@@ -13260,6 +13260,9 @@ msgstr ""
msgid "Last Seen" msgid "Last Seen"
msgstr "" msgstr ""
msgid "Last Used"
msgstr ""
msgid "Last accessed on" msgid "Last accessed on"
msgstr "" 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