mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Filtering of service accounts by expired tokens (#46251)
* Refactor to ServiceAccounts Query * filtering expiredtokens on backend * WIP * WIP * WIP * fix: missing that we do not cover for no service accounts * fix: wrong link * feat: filter able to get only service accounts with expired tokens * refactor: naming * Update pkg/services/serviceaccounts/models.go Co-authored-by: Gabriel MABILLE <gamab@users.noreply.github.com> * goimported * Update pkg/services/serviceaccounts/api/api.go Co-authored-by: Gabriel MABILLE <gamab@users.noreply.github.com> Co-authored-by: Gabriel MABILLE <gamab@users.noreply.github.com>
This commit is contained in:
parent
c3ee98a9b6
commit
c592e6606c
@ -218,7 +218,13 @@ func (api *ServiceAccountsAPI) SearchOrgServiceAccountsWithPaging(c *models.ReqC
|
|||||||
if page < 1 {
|
if page < 1 {
|
||||||
page = 1
|
page = 1
|
||||||
}
|
}
|
||||||
serviceAccountSearch, err := api.store.SearchOrgServiceAccounts(ctx, c.OrgId, c.Query("query"), page, perPage, c.SignedInUser)
|
// its okay that it fails, it is only filtering that might be weird, but to safe quard against any weird incoming query param
|
||||||
|
onlyWithExpiredTokens := c.QueryBool("expiredTokens")
|
||||||
|
filter := serviceaccounts.FilterIncludeAll
|
||||||
|
if onlyWithExpiredTokens {
|
||||||
|
filter = serviceaccounts.FilterOnlyExpiredTokens
|
||||||
|
}
|
||||||
|
serviceAccountSearch, err := api.store.SearchOrgServiceAccounts(ctx, c.OrgId, c.Query("query"), filter, page, perPage, c.SignedInUser)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return response.Error(http.StatusInternalServerError, "Failed to get service accounts for current organization", err)
|
return response.Error(http.StatusInternalServerError, "Failed to get service accounts for current organization", err)
|
||||||
}
|
}
|
||||||
|
@ -288,7 +288,7 @@ func (s *ServiceAccountsStoreImpl) UpdateServiceAccount(ctx context.Context,
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *ServiceAccountsStoreImpl) SearchOrgServiceAccounts(
|
func (s *ServiceAccountsStoreImpl) SearchOrgServiceAccounts(
|
||||||
ctx context.Context, orgID int64, query string, page int, limit int,
|
ctx context.Context, orgID int64, query string, filter serviceaccounts.ServiceAccountFilter, page int, limit int,
|
||||||
signedInUser *models.SignedInUser,
|
signedInUser *models.SignedInUser,
|
||||||
) (*serviceaccounts.SearchServiceAccountsResult, error) {
|
) (*serviceaccounts.SearchServiceAccountsResult, error) {
|
||||||
searchResult := &serviceaccounts.SearchServiceAccountsResult{
|
searchResult := &serviceaccounts.SearchServiceAccountsResult{
|
||||||
@ -328,6 +328,20 @@ func (s *ServiceAccountsStoreImpl) SearchOrgServiceAccounts(
|
|||||||
whereParams = append(whereParams, queryWithWildcards, queryWithWildcards, queryWithWildcards)
|
whereParams = append(whereParams, queryWithWildcards, queryWithWildcards, queryWithWildcards)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
switch filter {
|
||||||
|
case serviceaccounts.FilterIncludeAll:
|
||||||
|
// pass
|
||||||
|
case serviceaccounts.FilterOnlyExpiredTokens:
|
||||||
|
now := time.Now().Unix()
|
||||||
|
// we do a subquery to remove duplicates coming from joining in api_keys, if we find more than one api key that has expired
|
||||||
|
whereConditions = append(
|
||||||
|
whereConditions,
|
||||||
|
"(SELECT count(*) FROM api_key WHERE api_key.service_account_id = org_user.user_id AND api_key.expires < ?) > 0")
|
||||||
|
whereParams = append(whereParams, now)
|
||||||
|
default:
|
||||||
|
s.log.Warn("invalid filter user for service account filtering", "service account search filtering", filter)
|
||||||
|
}
|
||||||
|
|
||||||
if len(whereConditions) > 0 {
|
if len(whereConditions) > 0 {
|
||||||
sess.Where(strings.Join(whereConditions, " AND "), whereParams...)
|
sess.Where(strings.Join(whereConditions, " AND "), whereParams...)
|
||||||
}
|
}
|
||||||
|
@ -60,3 +60,10 @@ type ServiceAccountProfileDTO struct {
|
|||||||
Teams []string `json:"teams" xorm:"-"`
|
Teams []string `json:"teams" xorm:"-"`
|
||||||
AccessControl map[string]bool `json:"accessControl,omitempty" xorm:"-"`
|
AccessControl map[string]bool `json:"accessControl,omitempty" xorm:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ServiceAccountFilter string // used for filtering
|
||||||
|
|
||||||
|
const (
|
||||||
|
FilterOnlyExpiredTokens ServiceAccountFilter = "expiredTokens"
|
||||||
|
FilterIncludeAll ServiceAccountFilter = "all"
|
||||||
|
)
|
||||||
|
@ -14,7 +14,7 @@ type Service interface {
|
|||||||
|
|
||||||
type Store interface {
|
type Store interface {
|
||||||
CreateServiceAccount(ctx context.Context, orgID int64, name string) (*ServiceAccountDTO, error)
|
CreateServiceAccount(ctx context.Context, orgID int64, name string) (*ServiceAccountDTO, error)
|
||||||
SearchOrgServiceAccounts(ctx context.Context, orgID int64, query string, page int, limit int,
|
SearchOrgServiceAccounts(ctx context.Context, orgID int64, query string, filter ServiceAccountFilter, page int, limit int,
|
||||||
signedInUser *models.SignedInUser) (*SearchServiceAccountsResult, error)
|
signedInUser *models.SignedInUser) (*SearchServiceAccountsResult, error)
|
||||||
UpdateServiceAccount(ctx context.Context, orgID, serviceAccountID int64,
|
UpdateServiceAccount(ctx context.Context, orgID, serviceAccountID int64,
|
||||||
saForm *UpdateServiceAccountForm) (*ServiceAccountProfileDTO, error)
|
saForm *UpdateServiceAccountForm) (*ServiceAccountProfileDTO, error)
|
||||||
|
@ -123,7 +123,13 @@ func (s *ServiceAccountsStoreMock) UpdateServiceAccount(ctx context.Context,
|
|||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ServiceAccountsStoreMock) SearchOrgServiceAccounts(ctx context.Context, orgID int64, query string, page int, limit int,
|
func (s *ServiceAccountsStoreMock) SearchOrgServiceAccounts(
|
||||||
|
ctx context.Context,
|
||||||
|
orgID int64,
|
||||||
|
query string,
|
||||||
|
filter serviceaccounts.ServiceAccountFilter,
|
||||||
|
page int,
|
||||||
|
limit int,
|
||||||
user *models.SignedInUser) (*serviceaccounts.SearchServiceAccountsResult, error) {
|
user *models.SignedInUser) (*serviceaccounts.SearchServiceAccountsResult, error) {
|
||||||
s.Calls.SearchOrgServiceAccounts = append(s.Calls.SearchOrgServiceAccounts, []interface{}{ctx, orgID, query, page, limit, user})
|
s.Calls.SearchOrgServiceAccounts = append(s.Calls.SearchOrgServiceAccounts, []interface{}{ctx, orgID, query, page, limit, user})
|
||||||
return nil, nil
|
return nil, nil
|
||||||
|
@ -21,6 +21,7 @@ import { contextSrv } from 'app/core/core';
|
|||||||
import { UserRolePicker } from 'app/core/components/RolePicker/UserRolePicker';
|
import { UserRolePicker } from 'app/core/components/RolePicker/UserRolePicker';
|
||||||
import { OrgRolePicker } from '../admin/OrgRolePicker';
|
import { OrgRolePicker } from '../admin/OrgRolePicker';
|
||||||
import pluralize from 'pluralize';
|
import pluralize from 'pluralize';
|
||||||
|
import EmptyListCTA from 'app/core/components/EmptyListCTA/EmptyListCTA';
|
||||||
|
|
||||||
interface OwnProps {}
|
interface OwnProps {}
|
||||||
|
|
||||||
@ -91,20 +92,34 @@ const ServiceAccountsListPage = ({
|
|||||||
{ label: 'All service accounts', value: false },
|
{ label: 'All service accounts', value: false },
|
||||||
{ label: 'Expired tokens', value: true },
|
{ label: 'Expired tokens', value: true },
|
||||||
]}
|
]}
|
||||||
onChange={(value) => changeFilter({ name: 'Expired', value })}
|
onChange={(value) => changeFilter({ name: 'expiredTokens', value })}
|
||||||
value={filters.find((f) => f.name === 'Expired')?.value}
|
value={filters.find((f) => f.name === 'expiredTokens')?.value}
|
||||||
className={styles.filter}
|
className={styles.filter}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{contextSrv.hasPermission(AccessControlAction.ServiceAccountsCreate) && (
|
{isLoading && <PageLoader />}
|
||||||
<LinkButton href="org/serviceaccounts/create" variant="primary">
|
{!isLoading && serviceAccounts.length === 0 && (
|
||||||
New service account
|
|
||||||
</LinkButton>
|
|
||||||
)}
|
|
||||||
{isLoading ? (
|
|
||||||
<PageLoader />
|
|
||||||
) : (
|
|
||||||
<>
|
<>
|
||||||
|
<EmptyListCTA
|
||||||
|
title="You haven't created any service accounts yet."
|
||||||
|
buttonIcon="key-skeleton-alt"
|
||||||
|
buttonLink="org/serviceaccounts/create"
|
||||||
|
buttonTitle=" New service account"
|
||||||
|
buttonDisabled={!contextSrv.hasPermission(AccessControlAction.ServiceAccountsCreate)}
|
||||||
|
proTip="Remember, you can provide specific permissions for API access to other applications."
|
||||||
|
proTipLink=""
|
||||||
|
proTipLinkTitle=""
|
||||||
|
proTipTarget="_blank"
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{!isLoading && serviceAccounts.length !== 0 && (
|
||||||
|
<>
|
||||||
|
{contextSrv.hasPermission(AccessControlAction.ServiceAccountsCreate) && (
|
||||||
|
<LinkButton href="org/serviceaccounts/create" variant="primary">
|
||||||
|
New service account
|
||||||
|
</LinkButton>
|
||||||
|
)}
|
||||||
<div className={cx(styles.table, 'admin-list-table')}>
|
<div className={cx(styles.table, 'admin-list-table')}>
|
||||||
<table className="filter-table form-inline filter-table--hover">
|
<table className="filter-table form-inline filter-table--hover">
|
||||||
<thead>
|
<thead>
|
||||||
|
@ -44,7 +44,7 @@ export const initialStateList: ServiceAccountsState = {
|
|||||||
perPage: 50,
|
perPage: 50,
|
||||||
totalPages: 1,
|
totalPages: 1,
|
||||||
showPaging: false,
|
showPaging: false,
|
||||||
filters: [{ name: 'Expired', value: true }],
|
filters: [{ name: 'expiredTokens', value: false }],
|
||||||
};
|
};
|
||||||
|
|
||||||
interface ServiceAccountsFetched {
|
interface ServiceAccountsFetched {
|
||||||
|
Loading…
Reference in New Issue
Block a user