mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
ServiceAccounts: Add Service Account Token last used at date (#51446)
* ServiceAccounts Add api key last used at * ServiceAccounts: LastUpdateAt tests
This commit is contained in:
parent
daf0e3cb4e
commit
6d0261263c
@ -20,6 +20,7 @@ type ApiKey struct {
|
||||
Role RoleType
|
||||
Created time.Time
|
||||
Updated time.Time
|
||||
LastUsedAt *time.Time `xorm:"last_used_at"`
|
||||
Expires *int64
|
||||
ServiceAccountId *int64
|
||||
}
|
||||
|
@ -272,6 +272,12 @@ func (h *ContextHandler) initContextWithAPIKey(reqContext *models.ReqContext) bo
|
||||
return true
|
||||
}
|
||||
|
||||
// update api_key last used date
|
||||
if err := h.SQLStore.UpdateAPIKeyLastUsedDate(reqContext.Req.Context(), apikey.Id); err != nil {
|
||||
reqContext.JsonApiErr(http.StatusInternalServerError, InvalidAPIKey, errKey)
|
||||
return true
|
||||
}
|
||||
|
||||
if apikey.ServiceAccountId == nil || *apikey.ServiceAccountId < 1 { //There is no service account attached to the apikey
|
||||
//Use the old APIkey method. This provides backwards compatibility.
|
||||
reqContext.SignedInUser = &models.SignedInUser{}
|
||||
@ -305,6 +311,7 @@ func (h *ContextHandler) initContextWithAPIKey(reqContext *models.ReqContext) bo
|
||||
|
||||
reqContext.IsSignedIn = true
|
||||
reqContext.SignedInUser = querySignedInUser.Result
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
|
@ -24,6 +24,7 @@ type TokenDTO struct {
|
||||
Id int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Created *time.Time `json:"created"`
|
||||
LastUsedAt *time.Time `json:"lastUsedAt"`
|
||||
Expiration *time.Time `json:"expiration"`
|
||||
SecondsUntilExpiration *float64 `json:"secondsUntilExpiration"`
|
||||
HasExpired bool `json:"hasExpired"`
|
||||
@ -72,6 +73,7 @@ func (api *ServiceAccountsAPI) ListTokens(ctx *models.ReqContext) response.Respo
|
||||
Expiration: expiration,
|
||||
SecondsUntilExpiration: &secondsUntilExpiration,
|
||||
HasExpired: isExpired,
|
||||
LastUsedAt: t.LastUsedAt,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -55,6 +55,7 @@ func (s *ServiceAccountsStoreImpl) AddServiceAccountToken(ctx context.Context, s
|
||||
Created: updated,
|
||||
Updated: updated,
|
||||
Expires: expires,
|
||||
LastUsedAt: nil,
|
||||
ServiceAccountId: &serviceAccountId,
|
||||
}
|
||||
|
||||
|
@ -163,3 +163,15 @@ func (ss *SQLStore) GetAPIKeyByHash(ctx context.Context, hash string) (*models.A
|
||||
|
||||
return &apikey, err
|
||||
}
|
||||
|
||||
// UpdateAPIKeyLastUsedDate updates the last used date of the API key to current time.
|
||||
func (ss *SQLStore) UpdateAPIKeyLastUsedDate(ctx context.Context, tokenID int64) error {
|
||||
now := timeNow()
|
||||
return ss.WithDbSession(ctx, func(sess *DBSession) error {
|
||||
if _, err := sess.Table("api_key").ID(tokenID).Cols("last_used_at").Update(&models.ApiKey{LastUsedAt: &now}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
@ -76,6 +76,24 @@ func TestIntegrationApiKeyDataAccess(t *testing.T) {
|
||||
assert.Equal(t, *query.Result.Expires, expected)
|
||||
})
|
||||
|
||||
t.Run("Last Used At datetime update", func(t *testing.T) {
|
||||
// expires in one hour
|
||||
cmd := models.AddApiKeyCommand{OrgId: 1, Name: "last-update-at", Key: "asd3", SecondsToLive: 3600}
|
||||
err := ss.AddAPIKey(context.Background(), &cmd)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Nil(t, cmd.Result.LastUsedAt)
|
||||
|
||||
err = ss.UpdateAPIKeyLastUsedDate(context.Background(), cmd.Result.Id)
|
||||
require.NoError(t, err)
|
||||
|
||||
query := models.GetApiKeyByNameQuery{KeyName: "last-update-at", OrgId: 1}
|
||||
err = ss.GetApiKeyByName(context.Background(), &query)
|
||||
assert.Nil(t, err)
|
||||
|
||||
assert.NotNil(t, query.Result.LastUsedAt)
|
||||
})
|
||||
|
||||
t.Run("Add a key with negative lifespan", func(t *testing.T) {
|
||||
// expires in one day
|
||||
cmd := models.AddApiKeyCommand{OrgId: 1, Name: "key-with-negative-lifespan", Key: "asd3", SecondsToLive: -3600}
|
||||
|
@ -91,4 +91,8 @@ func addApiKeyMigrations(mg *Migrator) {
|
||||
|
||||
mg.AddMigration("set service account foreign key to nil if 0", NewRawSQLMigration(
|
||||
"UPDATE api_key SET service_account_id = NULL WHERE service_account_id = 0;"))
|
||||
|
||||
mg.AddMigration("Add last_used_at to api_key table", NewAddColumnMigration(apiKeyV2, &Column{
|
||||
Name: "last_used_at", Type: DB_DateTime, Nullable: true,
|
||||
}))
|
||||
}
|
||||
|
@ -543,6 +543,10 @@ func (m *SQLStoreMock) GetApiKeyByName(ctx context.Context, query *models.GetApi
|
||||
return m.ExpectedError
|
||||
}
|
||||
|
||||
func (m *SQLStoreMock) UpdateAPIKeyLastUsedDate(ctx context.Context, tokenID int64) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *SQLStoreMock) UpdateTempUserStatus(ctx context.Context, cmd *models.UpdateTempUserStatusCommand) error {
|
||||
return m.ExpectedError
|
||||
}
|
||||
|
@ -121,6 +121,7 @@ type Store interface {
|
||||
GetApiKeyById(ctx context.Context, query *models.GetApiKeyByIdQuery) error
|
||||
GetApiKeyByName(ctx context.Context, query *models.GetApiKeyByNameQuery) error
|
||||
GetAPIKeyByHash(ctx context.Context, hash string) (*models.ApiKey, error)
|
||||
UpdateAPIKeyLastUsedDate(ctx context.Context, tokenID int64) error
|
||||
UpdateTempUserStatus(ctx context.Context, cmd *models.UpdateTempUserStatusCommand) error
|
||||
CreateTempUser(ctx context.Context, cmd *models.CreateTempUserCommand) error
|
||||
UpdateTempUserWithEmailSent(ctx context.Context, cmd *models.UpdateTempUserWithEmailSentCommand) error
|
||||
|
@ -23,6 +23,7 @@ export const ServiceAccountTokensTable = ({ tokens, timeZone, tokenActionsDisabl
|
||||
<th>Name</th>
|
||||
<th>Expires</th>
|
||||
<th>Created</th>
|
||||
<th>Last used at</th>
|
||||
<th />
|
||||
</tr>
|
||||
</thead>
|
||||
@ -35,6 +36,7 @@ export const ServiceAccountTokensTable = ({ tokens, timeZone, tokenActionsDisabl
|
||||
<TokenExpiration timeZone={timeZone} token={key} />
|
||||
</td>
|
||||
<td>{formatDate(timeZone, key.created)}</td>
|
||||
<td>{formatLastUsedAtDate(timeZone, key.lastUsedAt)}</td>
|
||||
<td>
|
||||
<DeleteButton
|
||||
aria-label={`Delete service account token ${key.name}`}
|
||||
@ -51,6 +53,13 @@ export const ServiceAccountTokensTable = ({ tokens, timeZone, tokenActionsDisabl
|
||||
);
|
||||
};
|
||||
|
||||
function formatLastUsedAtDate(timeZone: TimeZone, lastUsedAt?: string): string {
|
||||
if (!lastUsedAt) {
|
||||
return 'Never';
|
||||
}
|
||||
return dateTimeFormat(lastUsedAt, { timeZone });
|
||||
}
|
||||
|
||||
function formatDate(timeZone: TimeZone, expiration?: string): string {
|
||||
if (!expiration) {
|
||||
return 'No expiration date';
|
||||
|
@ -11,6 +11,7 @@ export interface ApiKey extends WithAccessControlMetadata {
|
||||
secondsUntilExpiration?: number;
|
||||
hasExpired?: boolean;
|
||||
created?: string;
|
||||
lastUsedAt?: string;
|
||||
}
|
||||
|
||||
export interface NewApiKey {
|
||||
|
Loading…
Reference in New Issue
Block a user