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 {
|
||||
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 {
|
||||
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(
|
||||
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,
|
||||
) (*serviceaccounts.SearchServiceAccountsResult, error) {
|
||||
searchResult := &serviceaccounts.SearchServiceAccountsResult{
|
||||
@ -328,6 +328,20 @@ func (s *ServiceAccountsStoreImpl) SearchOrgServiceAccounts(
|
||||
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 {
|
||||
sess.Where(strings.Join(whereConditions, " AND "), whereParams...)
|
||||
}
|
||||
|
@ -60,3 +60,10 @@ type ServiceAccountProfileDTO struct {
|
||||
Teams []string `json:"teams" 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 {
|
||||
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)
|
||||
UpdateServiceAccount(ctx context.Context, orgID, serviceAccountID int64,
|
||||
saForm *UpdateServiceAccountForm) (*ServiceAccountProfileDTO, error)
|
||||
|
@ -123,7 +123,13 @@ func (s *ServiceAccountsStoreMock) UpdateServiceAccount(ctx context.Context,
|
||||
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) {
|
||||
s.Calls.SearchOrgServiceAccounts = append(s.Calls.SearchOrgServiceAccounts, []interface{}{ctx, orgID, query, page, limit, user})
|
||||
return nil, nil
|
||||
|
@ -21,6 +21,7 @@ import { contextSrv } from 'app/core/core';
|
||||
import { UserRolePicker } from 'app/core/components/RolePicker/UserRolePicker';
|
||||
import { OrgRolePicker } from '../admin/OrgRolePicker';
|
||||
import pluralize from 'pluralize';
|
||||
import EmptyListCTA from 'app/core/components/EmptyListCTA/EmptyListCTA';
|
||||
|
||||
interface OwnProps {}
|
||||
|
||||
@ -91,20 +92,34 @@ const ServiceAccountsListPage = ({
|
||||
{ label: 'All service accounts', value: false },
|
||||
{ label: 'Expired tokens', value: true },
|
||||
]}
|
||||
onChange={(value) => changeFilter({ name: 'Expired', value })}
|
||||
value={filters.find((f) => f.name === 'Expired')?.value}
|
||||
onChange={(value) => changeFilter({ name: 'expiredTokens', value })}
|
||||
value={filters.find((f) => f.name === 'expiredTokens')?.value}
|
||||
className={styles.filter}
|
||||
/>
|
||||
</div>
|
||||
{contextSrv.hasPermission(AccessControlAction.ServiceAccountsCreate) && (
|
||||
<LinkButton href="org/serviceaccounts/create" variant="primary">
|
||||
New service account
|
||||
</LinkButton>
|
||||
)}
|
||||
{isLoading ? (
|
||||
<PageLoader />
|
||||
) : (
|
||||
{isLoading && <PageLoader />}
|
||||
{!isLoading && serviceAccounts.length === 0 && (
|
||||
<>
|
||||
<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')}>
|
||||
<table className="filter-table form-inline filter-table--hover">
|
||||
<thead>
|
||||
|
@ -44,7 +44,7 @@ export const initialStateList: ServiceAccountsState = {
|
||||
perPage: 50,
|
||||
totalPages: 1,
|
||||
showPaging: false,
|
||||
filters: [{ name: 'Expired', value: true }],
|
||||
filters: [{ name: 'expiredTokens', value: false }],
|
||||
};
|
||||
|
||||
interface ServiceAccountsFetched {
|
||||
|
Loading…
Reference in New Issue
Block a user