2021-11-11 15:10:24 +00:00
package database
2022-01-19 09:55:38 +01:00
//nolint:goimports
2021-11-11 15:10:24 +00:00
import (
"context"
2022-03-08 11:07:58 +00:00
"errors"
2021-12-16 14:28:16 +01:00
"fmt"
2022-03-01 08:21:55 +00:00
"strings"
2022-01-19 09:55:38 +01:00
"time"
2021-11-11 15:10:24 +00:00
2022-06-15 15:59:40 +03:00
"github.com/grafana/grafana/pkg/infra/kvstore"
2021-12-16 14:28:16 +01:00
"github.com/grafana/grafana/pkg/infra/log"
2021-11-11 15:10:24 +00:00
"github.com/grafana/grafana/pkg/models"
2022-03-04 12:04:07 +01:00
"github.com/grafana/grafana/pkg/services/accesscontrol"
2021-11-11 15:10:24 +00:00
"github.com/grafana/grafana/pkg/services/serviceaccounts"
"github.com/grafana/grafana/pkg/services/sqlstore"
2022-06-28 14:32:25 +02:00
"github.com/grafana/grafana/pkg/services/user"
2021-11-11 15:10:24 +00:00
)
type ServiceAccountsStoreImpl struct {
sqlStore * sqlstore . SQLStore
2022-06-15 15:59:40 +03:00
kvStore kvstore . KVStore
2021-12-16 14:28:16 +01:00
log log . Logger
2021-11-11 15:10:24 +00:00
}
2022-07-08 10:53:18 +01:00
func ProvideServiceAccountsStore ( store * sqlstore . SQLStore , kvStore kvstore . KVStore ) * ServiceAccountsStoreImpl {
2021-11-11 15:10:24 +00:00
return & ServiceAccountsStoreImpl {
sqlStore : store ,
2022-06-15 15:59:40 +03:00
kvStore : kvStore ,
log : log . New ( "serviceaccounts.store" ) ,
2021-11-11 15:10:24 +00:00
}
}
2022-06-15 15:59:40 +03:00
// CreateServiceAccount creates service account
2022-07-07 17:32:56 +01:00
func ( s * ServiceAccountsStoreImpl ) CreateServiceAccount ( ctx context . Context , orgId int64 , saForm * serviceaccounts . CreateServiceAccountForm ) ( * serviceaccounts . ServiceAccountDTO , error ) {
generatedLogin := "sa-" + strings . ToLower ( saForm . Name )
2022-03-08 11:07:58 +00:00
generatedLogin = strings . ReplaceAll ( generatedLogin , " " , "-" )
2022-07-07 17:32:56 +01:00
isDisabled := false
role := models . ROLE_VIEWER
if saForm . IsDisabled != nil {
isDisabled = * saForm . IsDisabled
}
if saForm . Role != nil {
role = * saForm . Role
}
2022-07-07 12:50:38 +00:00
var newSA * user . User
createErr := s . sqlStore . WithTransactionalDbSession ( ctx , func ( sess * sqlstore . DBSession ) ( err error ) {
var errUser error
newSA , errUser = s . sqlStore . CreateUser ( ctx , user . CreateUserCommand {
Login : generatedLogin ,
OrgID : orgId ,
2022-07-07 17:32:56 +01:00
Name : saForm . Name ,
IsDisabled : isDisabled ,
2022-07-07 12:50:38 +00:00
IsServiceAccount : true ,
SkipOrgSetup : true ,
} )
if errUser != nil {
return errUser
}
errAddOrgUser := s . sqlStore . AddOrgUser ( ctx , & models . AddOrgUserCommand {
2022-07-07 17:32:56 +01:00
Role : role ,
2022-07-07 12:50:38 +00:00
OrgId : orgId ,
UserId : newSA . ID ,
AllowAddingServiceAccount : true ,
} )
if errAddOrgUser != nil {
return errAddOrgUser
}
return nil
} )
if createErr != nil {
if errors . Is ( createErr , models . ErrUserAlreadyExists ) {
2022-06-16 17:02:03 +03:00
return nil , ErrServiceAccountAlreadyExists
2022-03-08 11:07:58 +00:00
}
2022-07-07 12:50:38 +00:00
return nil , fmt . Errorf ( "failed to create service account: %w" , createErr )
2021-12-14 14:39:25 +01:00
}
2022-03-08 11:07:58 +00:00
2022-02-08 14:31:34 +01:00
return & serviceaccounts . ServiceAccountDTO {
2022-07-07 17:32:56 +01:00
Id : newSA . ID ,
Name : newSA . Name ,
Login : newSA . Login ,
OrgId : newSA . OrgID ,
Tokens : 0 ,
Role : string ( role ) ,
IsDisabled : isDisabled ,
2022-02-08 14:31:34 +01:00
} , nil
2021-12-14 14:39:25 +01:00
}
2021-11-11 15:10:24 +00:00
2022-06-15 15:59:40 +03:00
// UpdateServiceAccount updates service account
func ( s * ServiceAccountsStoreImpl ) UpdateServiceAccount ( ctx context . Context ,
orgId , serviceAccountId int64 ,
saForm * serviceaccounts . UpdateServiceAccountForm ) ( * serviceaccounts . ServiceAccountProfileDTO , error ) {
updatedUser := & serviceaccounts . ServiceAccountProfileDTO { }
err := s . sqlStore . WithTransactionalDbSession ( ctx , func ( sess * sqlstore . DBSession ) error {
var err error
updatedUser , err = s . RetrieveServiceAccount ( ctx , orgId , serviceAccountId )
2021-11-11 15:10:24 +00:00
if err != nil {
return err
}
2022-06-15 15:59:40 +03:00
if saForm . Name == nil && saForm . Role == nil && saForm . IsDisabled == nil {
return nil
2022-03-14 17:24:07 +00:00
}
2022-06-15 15:59:40 +03:00
updateTime := time . Now ( )
if saForm . Role != nil {
var orgUser models . OrgUser
orgUser . Role = * saForm . Role
orgUser . Updated = updateTime
if _ , err := sess . Where ( "org_id = ? AND user_id = ?" , orgId , serviceAccountId ) . Update ( & orgUser ) ; err != nil {
2022-03-14 17:24:07 +00:00
return err
}
2022-06-15 15:59:40 +03:00
updatedUser . Role = string ( * saForm . Role )
2022-03-14 17:24:07 +00:00
}
2022-06-15 15:59:40 +03:00
if saForm . Name != nil || saForm . IsDisabled != nil {
2022-06-28 14:32:25 +02:00
user := user . User {
2022-06-15 15:59:40 +03:00
Updated : updateTime ,
}
if saForm . IsDisabled != nil {
user . IsDisabled = * saForm . IsDisabled
updatedUser . IsDisabled = * saForm . IsDisabled
sess . UseBool ( "is_disabled" )
}
if saForm . Name != nil {
user . Name = * saForm . Name
updatedUser . Name = * saForm . Name
}
if _ , err := sess . ID ( serviceAccountId ) . Update ( & user ) ; err != nil {
return err
}
}
2022-03-14 17:24:07 +00:00
return nil
} )
2022-06-15 15:59:40 +03:00
return updatedUser , err
2021-11-11 15:10:24 +00:00
}
2021-12-16 14:28:16 +01:00
2022-06-15 15:59:40 +03:00
func ServiceAccountDeletions ( ) [ ] string {
deletes := [ ] string {
"DELETE FROM api_key WHERE service_account_id = ?" ,
2021-12-16 14:28:16 +01:00
}
2022-06-15 15:59:40 +03:00
deletes = append ( deletes , sqlstore . UserDeletions ( ) ... )
return deletes
2021-12-16 14:28:16 +01:00
}
2022-01-12 13:23:00 +01:00
2022-06-15 15:59:40 +03:00
// DeleteServiceAccount deletes service account and all associated tokens
func ( s * ServiceAccountsStoreImpl ) DeleteServiceAccount ( ctx context . Context , orgId , serviceAccountId int64 ) error {
return s . sqlStore . WithTransactionalDbSession ( ctx , func ( sess * sqlstore . DBSession ) error {
return s . deleteServiceAccount ( sess , orgId , serviceAccountId )
} )
2022-01-20 16:51:18 +01:00
}
2022-06-15 15:59:40 +03:00
func ( s * ServiceAccountsStoreImpl ) deleteServiceAccount ( sess * sqlstore . DBSession , orgId , serviceAccountId int64 ) error {
2022-06-28 14:32:25 +02:00
user := user . User { }
2022-06-15 15:59:40 +03:00
has , err := sess . Where ( ` org_id = ? and id = ? and is_service_account = ? ` ,
orgId , serviceAccountId , s . sqlStore . Dialect . BooleanStr ( true ) ) . Get ( & user )
if err != nil {
return err
2022-01-20 16:51:18 +01:00
}
2022-06-15 15:59:40 +03:00
if ! has {
return serviceaccounts . ErrServiceAccountNotFound
2022-01-20 16:51:18 +01:00
}
2022-06-15 15:59:40 +03:00
for _ , sql := range ServiceAccountDeletions ( ) {
2022-06-28 14:32:25 +02:00
_ , err := sess . Exec ( sql , user . ID )
2022-06-15 15:59:40 +03:00
if err != nil {
return err
}
2022-03-14 17:24:07 +00:00
}
2022-01-20 16:51:18 +01:00
return nil
}
2022-06-15 15:59:40 +03:00
// RetrieveServiceAccount returns a service account by its ID
func ( s * ServiceAccountsStoreImpl ) RetrieveServiceAccount ( ctx context . Context , orgId , serviceAccountId int64 ) ( * serviceaccounts . ServiceAccountProfileDTO , error ) {
2022-03-01 08:21:55 +00:00
serviceAccount := & serviceaccounts . ServiceAccountProfileDTO { }
err := s . sqlStore . WithDbSession ( ctx , func ( dbSession * sqlstore . DBSession ) error {
sess := dbSession . Table ( "org_user" )
sess . Join ( "INNER" , s . sqlStore . Dialect . Quote ( "user" ) ,
fmt . Sprintf ( "org_user.user_id=%s.id" , s . sqlStore . Dialect . Quote ( "user" ) ) )
whereConditions := make ( [ ] string , 0 , 3 )
whereParams := make ( [ ] interface { } , 0 )
whereConditions = append ( whereConditions , "org_user.org_id = ?" )
2022-06-15 15:59:40 +03:00
whereParams = append ( whereParams , orgId )
2022-03-01 08:21:55 +00:00
whereConditions = append ( whereConditions , "org_user.user_id = ?" )
2022-06-15 15:59:40 +03:00
whereParams = append ( whereParams , serviceAccountId )
2022-03-01 08:21:55 +00:00
whereConditions = append ( whereConditions ,
fmt . Sprintf ( "%s.is_service_account = %s" ,
s . sqlStore . Dialect . Quote ( "user" ) ,
s . sqlStore . Dialect . BooleanStr ( true ) ) )
sess . Where ( strings . Join ( whereConditions , " AND " ) , whereParams ... )
sess . Cols (
"org_user.user_id" ,
"org_user.org_id" ,
"org_user.role" ,
"user.email" ,
"user.name" ,
"user.login" ,
"user.created" ,
"user.updated" ,
"user.is_disabled" ,
)
if ok , err := sess . Get ( serviceAccount ) ; err != nil {
return err
} else if ! ok {
return serviceaccounts . ErrServiceAccountNotFound
}
return nil
} )
2022-06-02 13:14:48 +01:00
return serviceAccount , err
2022-01-19 09:23:46 +00:00
}
2022-01-20 16:51:18 +01:00
2022-06-15 15:59:40 +03:00
func ( s * ServiceAccountsStoreImpl ) RetrieveServiceAccountIdByName ( ctx context . Context , orgId int64 , name string ) ( int64 , error ) {
2022-04-12 19:34:04 +02:00
serviceAccount := & struct {
Id int64
} { }
err := s . sqlStore . WithDbSession ( ctx , func ( dbSession * sqlstore . DBSession ) error {
sess := dbSession . Table ( "user" )
whereConditions := [ ] string {
fmt . Sprintf ( "%s.name = ?" ,
s . sqlStore . Dialect . Quote ( "user" ) ) ,
fmt . Sprintf ( "%s.org_id = ?" ,
s . sqlStore . Dialect . Quote ( "user" ) ) ,
fmt . Sprintf ( "%s.is_service_account = %s" ,
s . sqlStore . Dialect . Quote ( "user" ) ,
s . sqlStore . Dialect . BooleanStr ( true ) ) ,
}
2022-06-15 15:59:40 +03:00
whereParams := [ ] interface { } { name , orgId }
2022-04-12 19:34:04 +02:00
sess . Where ( strings . Join ( whereConditions , " AND " ) , whereParams ... )
sess . Cols (
"user.id" ,
)
if ok , err := sess . Get ( serviceAccount ) ; err != nil {
return err
} else if ! ok {
return serviceaccounts . ErrServiceAccountNotFound
}
return nil
} )
if err != nil {
return 0 , err
}
return serviceAccount . Id , nil
}
2022-03-14 17:24:07 +00:00
func ( s * ServiceAccountsStoreImpl ) SearchOrgServiceAccounts (
2022-06-15 15:59:40 +03:00
ctx context . Context , orgId int64 , query string , filter serviceaccounts . ServiceAccountFilter , page int , limit int ,
2022-03-14 17:24:07 +00:00
signedInUser * models . SignedInUser ,
) ( * serviceaccounts . SearchServiceAccountsResult , error ) {
searchResult := & serviceaccounts . SearchServiceAccountsResult {
TotalCount : 0 ,
ServiceAccounts : make ( [ ] * serviceaccounts . ServiceAccountDTO , 0 ) ,
Page : page ,
PerPage : limit ,
}
2022-03-04 12:04:07 +01:00
err := s . sqlStore . WithDbSession ( ctx , func ( dbSession * sqlstore . DBSession ) error {
sess := dbSession . Table ( "org_user" )
sess . Join ( "INNER" , s . sqlStore . Dialect . Quote ( "user" ) , fmt . Sprintf ( "org_user.user_id=%s.id" , s . sqlStore . Dialect . Quote ( "user" ) ) )
whereConditions := make ( [ ] string , 0 )
whereParams := make ( [ ] interface { } , 0 )
whereConditions = append ( whereConditions , "org_user.org_id = ?" )
2022-06-15 15:59:40 +03:00
whereParams = append ( whereParams , orgId )
2022-03-04 12:04:07 +01:00
2022-03-08 13:10:16 +00:00
whereConditions = append ( whereConditions ,
fmt . Sprintf ( "%s.is_service_account = %s" ,
s . sqlStore . Dialect . Quote ( "user" ) ,
s . sqlStore . Dialect . BooleanStr ( true ) ) )
2022-03-04 12:04:07 +01:00
2022-05-05 16:31:14 +01:00
if ! accesscontrol . IsDisabled ( s . sqlStore . Cfg ) {
2022-03-21 17:58:18 +01:00
acFilter , err := accesscontrol . Filter ( signedInUser , "org_user.user_id" , "serviceaccounts:id:" , serviceaccounts . ActionRead )
2022-03-04 12:04:07 +01:00
if err != nil {
return err
}
whereConditions = append ( whereConditions , acFilter . Where )
whereParams = append ( whereParams , acFilter . Args ... )
}
2022-03-14 17:24:07 +00:00
if query != "" {
queryWithWildcards := "%" + query + "%"
2022-03-04 12:04:07 +01:00
whereConditions = append ( whereConditions , "(email " + s . sqlStore . Dialect . LikeStr ( ) + " ? OR name " + s . sqlStore . Dialect . LikeStr ( ) + " ? OR login " + s . sqlStore . Dialect . LikeStr ( ) + " ?)" )
whereParams = append ( whereParams , queryWithWildcards , queryWithWildcards , queryWithWildcards )
}
2022-03-18 15:50:34 +01:00
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 )
2022-06-01 10:35:16 +03:00
case serviceaccounts . FilterOnlyDisabled :
whereConditions = append (
whereConditions ,
"is_disabled = ?" )
whereParams = append ( whereParams , s . sqlStore . Dialect . BooleanStr ( true ) )
2022-03-18 15:50:34 +01:00
default :
s . log . Warn ( "invalid filter user for service account filtering" , "service account search filtering" , filter )
}
2022-03-04 12:04:07 +01:00
if len ( whereConditions ) > 0 {
sess . Where ( strings . Join ( whereConditions , " AND " ) , whereParams ... )
}
2022-03-14 17:24:07 +00:00
if limit > 0 {
offset := limit * ( page - 1 )
sess . Limit ( limit , offset )
2022-03-04 12:04:07 +01:00
}
sess . Cols (
"org_user.user_id" ,
"org_user.org_id" ,
"org_user.role" ,
"user.email" ,
"user.name" ,
"user.login" ,
"user.last_seen_at" ,
2022-03-08 13:10:16 +00:00
"user.is_disabled" ,
2022-03-04 12:04:07 +01:00
)
sess . Asc ( "user.email" , "user.login" )
2022-03-14 17:24:07 +00:00
if err := sess . Find ( & searchResult . ServiceAccounts ) ; err != nil {
2022-03-04 12:04:07 +01:00
return err
}
// get total
serviceaccount := serviceaccounts . ServiceAccountDTO { }
countSess := dbSession . Table ( "org_user" )
sess . Join ( "INNER" , s . sqlStore . Dialect . Quote ( "user" ) , fmt . Sprintf ( "org_user.user_id=%s.id" , s . sqlStore . Dialect . Quote ( "user" ) ) )
if len ( whereConditions ) > 0 {
countSess . Where ( strings . Join ( whereConditions , " AND " ) , whereParams ... )
}
count , err := countSess . Count ( & serviceaccount )
if err != nil {
return err
}
2022-03-14 17:24:07 +00:00
searchResult . TotalCount = count
2022-03-04 12:04:07 +01:00
return nil
} )
if err != nil {
return nil , err
}
2022-03-14 17:24:07 +00:00
return searchResult , nil
2022-03-04 12:04:07 +01:00
}
2022-06-15 15:59:40 +03:00
func ( s * ServiceAccountsStoreImpl ) GetAPIKeysMigrationStatus ( ctx context . Context , orgId int64 ) ( status * serviceaccounts . APIKeysMigrationStatus , err error ) {
migrationStatus , exists , err := s . kvStore . Get ( ctx , orgId , "serviceaccounts" , "migrationStatus" )
if err != nil {
return nil , err
}
if exists && migrationStatus == "1" {
return & serviceaccounts . APIKeysMigrationStatus {
Migrated : true ,
} , nil
} else {
return & serviceaccounts . APIKeysMigrationStatus {
Migrated : false ,
} , nil
}
}
func ( s * ServiceAccountsStoreImpl ) HideApiKeysTab ( ctx context . Context , orgId int64 ) error {
if err := s . kvStore . Set ( ctx , orgId , "serviceaccounts" , "hideApiKeys" , "1" ) ; err != nil {
s . log . Error ( "Failed to hide API keys tab" , err )
}
return nil
}
func ( s * ServiceAccountsStoreImpl ) MigrateApiKeysToServiceAccounts ( ctx context . Context , orgId int64 ) error {
basicKeys := s . sqlStore . GetAllAPIKeys ( ctx , orgId )
if len ( basicKeys ) > 0 {
for _ , key := range basicKeys {
err := s . CreateServiceAccountFromApikey ( ctx , key )
if err != nil {
s . log . Error ( "migating to service accounts failed with error" , err )
return err
}
s . log . Debug ( "API key converted to service account token" , "keyId" , key . Id )
2022-01-20 16:51:18 +01:00
}
}
2022-06-15 15:59:40 +03:00
if err := s . kvStore . Set ( ctx , orgId , "serviceaccounts" , "migrationStatus" , "1" ) ; err != nil {
s . log . Error ( "Failed to write API keys migration status" , err )
}
return nil
}
func ( s * ServiceAccountsStoreImpl ) MigrateApiKey ( ctx context . Context , orgId int64 , keyId int64 ) error {
basicKeys := s . sqlStore . GetAllAPIKeys ( ctx , orgId )
if len ( basicKeys ) == 0 {
return fmt . Errorf ( "no API keys to convert found" )
}
for _ , key := range basicKeys {
if keyId == key . Id {
err := s . CreateServiceAccountFromApikey ( ctx , key )
if err != nil {
s . log . Error ( "converting to service account failed with error" , "keyId" , keyId , "error" , err )
return err
}
}
}
return nil
}
func ( s * ServiceAccountsStoreImpl ) CreateServiceAccountFromApikey ( ctx context . Context , key * models . ApiKey ) error {
prefix := "sa-autogen"
2022-06-28 14:32:25 +02:00
cmd := user . CreateUserCommand {
2022-06-15 15:59:40 +03:00
Login : fmt . Sprintf ( "%v-%v-%v" , prefix , key . OrgId , key . Name ) ,
Name : fmt . Sprintf ( "%v-%v" , prefix , key . Name ) ,
2022-06-28 14:32:25 +02:00
OrgID : key . OrgId ,
2022-06-15 15:59:40 +03:00
DefaultOrgRole : string ( key . Role ) ,
IsServiceAccount : true ,
}
return s . sqlStore . WithTransactionalDbSession ( ctx , func ( sess * sqlstore . DBSession ) error {
newSA , errCreateSA := s . sqlStore . CreateUser ( ctx , cmd )
if errCreateSA != nil {
return fmt . Errorf ( "failed to create service account: %w" , errCreateSA )
}
2022-06-28 14:32:25 +02:00
if err := s . assignApiKeyToServiceAccount ( sess , key . Id , newSA . ID ) ; err != nil {
if err := s . sqlStore . DeleteUser ( ctx , & models . DeleteUserCommand { UserId : newSA . ID } ) ; err != nil {
2022-06-15 15:59:40 +03:00
s . log . Error ( "Error deleting service account" , "error" , err )
}
return fmt . Errorf ( "failed to migrate API key to service account token: %w" , err )
}
return nil
} )
}
// RevertApiKey converts service account token to old API key
func ( s * ServiceAccountsStoreImpl ) RevertApiKey ( ctx context . Context , keyId int64 ) error {
query := models . GetApiKeyByIdQuery { ApiKeyId : keyId }
if err := s . sqlStore . GetApiKeyById ( ctx , & query ) ; err != nil {
return err
}
key := query . Result
if key . ServiceAccountId == nil {
2022-06-16 17:02:03 +03:00
return fmt . Errorf ( "API key is not service account token" )
2022-06-15 15:59:40 +03:00
}
tokens , err := s . ListTokens ( ctx , key . OrgId , * key . ServiceAccountId )
if err != nil {
2022-06-16 17:02:03 +03:00
return fmt . Errorf ( "cannot revert token: %w" , err )
2022-06-15 15:59:40 +03:00
}
if len ( tokens ) > 1 {
2022-06-16 17:02:03 +03:00
return fmt . Errorf ( "cannot revert token: service account contains more than one token" )
2022-06-15 15:59:40 +03:00
}
err = s . sqlStore . WithTransactionalDbSession ( ctx , func ( sess * sqlstore . DBSession ) error {
2022-06-28 14:32:25 +02:00
user := user . User { }
2022-06-15 15:59:40 +03:00
has , err := sess . Where ( ` org_id = ? and id = ? and is_service_account = ? ` ,
key . OrgId , * key . ServiceAccountId , s . sqlStore . Dialect . BooleanStr ( true ) ) . Get ( & user )
if err != nil {
return err
}
if ! has {
return serviceaccounts . ErrServiceAccountNotFound
}
// Detach API key from service account
if err := s . detachApiKeyFromServiceAccount ( sess , key . Id ) ; err != nil {
return err
}
// Delete service account
if err := s . deleteServiceAccount ( sess , key . OrgId , * key . ServiceAccountId ) ; err != nil {
return err
}
return nil
} )
2022-06-16 10:11:22 +00:00
2022-06-15 15:59:40 +03:00
if err != nil {
2022-06-16 17:02:03 +03:00
return fmt . Errorf ( "cannot revert token to API key: %w" , err )
2022-06-15 15:59:40 +03:00
}
return nil
2022-01-20 16:51:18 +01:00
}