@@ -30,6 +30,7 @@ import (
3030 "github.com/jonboulle/clockwork"
3131 "github.com/prometheus/client_golang/prometheus"
3232 "golang.org/x/crypto/chacha20"
33+ "gorm.io/gorm"
3334 "gorm.io/gorm/clause"
3435
3536 cappb "github.com/buildbuddy-io/buildbuddy/proto/capability"
@@ -199,6 +200,8 @@ func (d *AuthDB) backfillUnencryptedKeys() error {
199200 return nil
200201}
201202
203+ // apiKeyGroup is a compact representation of a valid API key and selected
204+ // group metadata, suitable for caching.
202205type apiKeyGroup struct {
203206 APIKeyID string
204207 UserID string
@@ -258,6 +261,36 @@ func (g *apiKeyGroup) GetGroupStatus() grpb.Group_GroupStatus {
258261 return grpb .Group_GroupStatus (g .Status )
259262}
260263
264+ // apiKeyGroupFullData contains full API key data and data for the owning group.
265+ type apiKeyGroupFullData struct {
266+ tables.APIKey
267+
268+ UseGroupOwnedExecutors bool
269+ CacheEncryptionEnabled bool
270+ EnforceIPRules bool
271+ IsParent bool
272+ GroupStatus int32 `gorm:"column:group_status"`
273+ }
274+
275+ func (r * apiKeyGroupFullData ) toAPIKeyGroup () * apiKeyGroup {
276+ return & apiKeyGroup {
277+ APIKeyID : r .APIKeyID ,
278+ UserID : r .UserID ,
279+ GroupID : r .GroupID ,
280+ IsParent : r .IsParent ,
281+ Capabilities : r .Capabilities ,
282+ UseGroupOwnedExecutors : r .UseGroupOwnedExecutors ,
283+ CacheEncryptionEnabled : r .CacheEncryptionEnabled ,
284+ EnforceIPRules : r .EnforceIPRules ,
285+ Status : r .GroupStatus ,
286+ }
287+ }
288+
289+ func (r * apiKeyGroupFullData ) toAPIKey () * tables.APIKey {
290+ key := r .APIKey
291+ return & key
292+ }
293+
261294func (d * AuthDB ) InsertOrUpdateUserSession (ctx context.Context , sessionID string , session * tables.Session ) error {
262295 session .SessionID = sessionID
263296 // Note: this could be one query, but it's likely too complicated to be worth
@@ -408,22 +441,7 @@ func (d *AuthDB) GetAPIKeyGroupFromAPIKey(ctx context.Context, apiKey string) (i
408441 }
409442
410443 akg , _ , err := d .apiKeyFetchGroup .Do (ctx , cacheKey , func (ctx context.Context ) (* apiKeyGroup , error ) {
411- akg , err := d .lookupAPIKeyGroup (ctx , "authdb_get_api_key_group_by_key" , sd , func (qb * query_builder.Query ) error {
412- keyClauses := query_builder.OrClauses {}
413- if ! * encryptOldKeys {
414- keyClauses .AddOr ("ak.value = ?" , apiKey )
415- }
416- if d .apiKeyEncryptionKey != nil {
417- encryptedAPIKey , err := d .encryptAPIKey (apiKey )
418- if err != nil {
419- return err
420- }
421- keyClauses .AddOr ("ak.encrypted_value = ?" , encryptedAPIKey )
422- }
423- keyQuery , keyArgs := keyClauses .Build ()
424- qb .AddWhereClause (keyQuery , keyArgs ... )
425- return nil
426- })
444+ akg , err := d .lookupAPIKeyGroupByValue (ctx , sd , apiKey )
427445 if err != nil {
428446 if db .IsRecordNotFound (err ) {
429447 if d .apiKeyGroupCache != nil {
@@ -454,10 +472,7 @@ func (d *AuthDB) GetAPIKeyGroupFromAPIKeyID(ctx context.Context, apiKeyID string
454472 return d , nil
455473 }
456474 }
457- akg , err := d .lookupAPIKeyGroup (ctx , "authdb_get_api_key_group_by_id" , sd , func (qb * query_builder.Query ) error {
458- qb .AddWhereClause (`ak.api_key_id = ?` , apiKeyID )
459- return nil
460- })
475+ akg , err := d .lookupAPIKeyGroupByID (ctx , sd , apiKeyID )
461476 if err != nil {
462477 if db .IsRecordNotFound (err ) {
463478 return nil , status .UnauthenticatedErrorf ("Invalid API key ID %q" , redactInvalidAPIKey (apiKeyID ))
@@ -471,41 +486,86 @@ func (d *AuthDB) GetAPIKeyGroupFromAPIKeyID(ctx context.Context, apiKeyID string
471486}
472487
473488func (d * AuthDB ) lookupAPIKeyGroup (ctx context.Context , queryName , subDomain string , addConds func (* query_builder.Query ) error ) (* apiKeyGroup , error ) {
474- qb := d .newAPIKeyGroupQuery (subDomain )
475- if err := addConds (qb ); err != nil {
476- return nil , err
477- }
478- q , args := qb .Build ()
479- akg := & apiKeyGroup {}
480- err := d .h .NewQueryWithOpts (
481- ctx ,
482- queryName ,
483- db .Opts ().WithStaleReads (),
484- ).Raw (
485- q ,
486- args ... ,
487- ).Take (akg )
489+ row , err := d .fetchAPIKey (ctx , queryName , subDomain , db .Opts ().WithStaleReads (), addConds )
488490 if err != nil {
489491 return nil , err
490492 }
493+ akg := row .toAPIKeyGroup ()
491494 if err := d .fillChildGroupIDs (ctx , akg ); err != nil {
492495 return nil , err
493496 }
494497 return akg , nil
495498}
496499
497- func (d * AuthDB ) newAPIKeyGroupQuery (subDomain string ) * query_builder.Query {
500+ func (d * AuthDB ) lookupAPIKeyGroupByValue (ctx context.Context , subDomain string , apiKey string ) (* apiKeyGroup , error ) {
501+ return d .lookupAPIKeyGroup (ctx , "authdb_get_api_key_group_by_key" , subDomain , func (qb * query_builder.Query ) error {
502+ keyClauses := query_builder.OrClauses {}
503+ if ! * encryptOldKeys {
504+ keyClauses .AddOr ("ak.value = ?" , apiKey )
505+ }
506+ if d .apiKeyEncryptionKey != nil {
507+ encryptedAPIKey , err := d .encryptAPIKey (apiKey )
508+ if err != nil {
509+ return err
510+ }
511+ keyClauses .AddOr ("ak.encrypted_value = ?" , encryptedAPIKey )
512+ }
513+ keyQuery , keyArgs := keyClauses .Build ()
514+ qb .AddWhereClause (keyQuery , keyArgs ... )
515+ return nil
516+ })
517+ }
518+
519+ func (d * AuthDB ) lookupAPIKeyGroupByID (ctx context.Context , subDomain string , apiKeyID string ) (* apiKeyGroup , error ) {
520+ return d .lookupAPIKeyGroup (ctx , "authdb_get_api_key_group_by_id" , subDomain , func (qb * query_builder.Query ) error {
521+ qb .AddWhereClause (`ak.api_key_id = ?` , apiKeyID )
522+ return nil
523+ })
524+ }
525+
526+ // fetchAPIKey retrieves complete data for a single valid API key.
527+ // The returned data contains both APIKey fields and Group fields for the
528+ // owning group.
529+ // The caller must use the addConds func to add restrictions to limit the
530+ // matching API keys to a single API key (i.e. by API key value or ID).
531+ func (d * AuthDB ) fetchAPIKey (ctx context.Context , queryName , subDomain string , dbOpts interfaces.DBOptions , addConds func (* query_builder.Query ) error ) (* apiKeyGroupFullData , error ) {
532+ rows , err := d .fetchAPIKeys (ctx , queryName , subDomain , dbOpts , addConds )
533+ if err != nil {
534+ return nil , err
535+ }
536+ if len (rows ) == 0 {
537+ return nil , gorm .ErrRecordNotFound
538+ }
539+ if len (rows ) > 1 {
540+ return nil , status .InternalErrorf ("multiple rows found for query %q" , queryName )
541+ }
542+ return rows [0 ], nil
543+ }
544+
545+ // fetchAPIKeys retrieves complete data for valid API keys.
546+ // The returned data contains both APIKey fields and Group fields for the
547+ // owning group.
548+ // The caller must use the addConds func to add restrictions to limit the
549+ // matching API keys to a single group or to a single API key.
550+ func (d * AuthDB ) fetchAPIKeys (ctx context.Context , queryName , subDomain string , dbOpts interfaces.DBOptions , addConds func (* query_builder.Query ) error ) ([]* apiKeyGroupFullData , error ) {
551+ qb := d .newAPIKeyLookupQuery (subDomain )
552+ if err := addConds (qb ); err != nil {
553+ return nil , err
554+ }
555+ q , args := qb .Build ()
556+ rq := d .h .NewQueryWithOpts (ctx , queryName , dbOpts ).Raw (q , args ... )
557+ return db .ScanAll (rq , & apiKeyGroupFullData {})
558+ }
559+
560+ func (d * AuthDB ) newAPIKeyLookupQuery (subDomain string ) * query_builder.Query {
498561 qb := query_builder .NewQuery (`
499562 SELECT
500- ak.capabilities,
501- ak.api_key_id,
502- ak.user_id,
503- g.group_id,
563+ ak.*,
504564 g.use_group_owned_executors,
505565 g.cache_encryption_enabled,
506566 g.enforce_ip_rules,
507567 g.is_parent,
508- g.status
568+ g.status AS group_status
509569 FROM "Groups" AS g,
510570 "APIKeys" AS ak
511571 ` )
@@ -773,7 +833,7 @@ func (d *AuthDB) CreateUserAPIKey(ctx context.Context, groupID, userID, label st
773833
774834 ak := tables.APIKey {
775835 UserID : userID ,
776- GroupID : u . GetGroupID () ,
836+ GroupID : groupID ,
777837 Label : label ,
778838 Capabilities : capabilities .ToInt (caps ),
779839 }
@@ -796,20 +856,21 @@ func (d *AuthDB) isGroupMember(ctx context.Context, groupID, userID string) (boo
796856 return false , nil
797857}
798858
799- func (d * AuthDB ) getAPIKey (ctx context.Context , h interfaces. DB , apiKeyID string ) (* tables.APIKey , error ) {
859+ func (d * AuthDB ) getAPIKey (ctx context.Context , apiKeyID string ) (* tables.APIKey , error ) {
800860 if apiKeyID == "" {
801861 return nil , status .InvalidArgumentError ("API key ID cannot be empty." )
802862 }
803- rq := h .NewQuery (ctx , "authdb_get_api_key_by_id" ).Raw (
804- `SELECT * FROM "APIKeys" WHERE api_key_id = ? AND (expiry_usec = 0 OR expiry_usec > ?)` , apiKeyID , d .clock .Now ().UnixMicro ())
805- key := & tables.APIKey {}
806- if err := rq .Take (key ); err != nil {
863+ row , err := d .fetchAPIKey (ctx , "authdb_get_api_key_by_id" , "" , db .Opts (), func (qb * query_builder.Query ) error {
864+ qb .AddWhereClause (`ak.api_key_id = ?` , apiKeyID )
865+ return nil
866+ })
867+ if err != nil {
807868 if db .IsRecordNotFound (err ) {
808869 return nil , status .NotFoundError ("The requested API key was not found." )
809870 }
810871 return nil , err
811872 }
812- return key , nil
873+ return row . toAPIKey () , nil
813874}
814875
815876func (d * AuthDB ) GetAPIKey (ctx context.Context , apiKeyID string ) (* tables.APIKey , error ) {
@@ -818,7 +879,7 @@ func (d *AuthDB) GetAPIKey(ctx context.Context, apiKeyID string) (*tables.APIKey
818879 return nil , err
819880 }
820881
821- key , err := d .getAPIKey (ctx , d . h , apiKeyID )
882+ key , err := d .getAPIKey (ctx , apiKeyID )
822883 if err != nil {
823884 return nil , err
824885 }
@@ -898,31 +959,32 @@ func (d *AuthDB) GetAPIKeys(ctx context.Context, groupID string) ([]*tables.APIK
898959 if err := authutil .AuthorizeGroupAccess (ctx , d .env , groupID ); err != nil {
899960 return nil , err
900961 }
901- q := query_builder .NewQuery (`SELECT * FROM "APIKeys"` )
902- // Select group-owned keys only
903- q .AddWhereClause (`user_id IS NULL OR user_id = ''` )
904- q .AddWhereClause (`group_id = ?` , groupID )
905- if err := authutil .AuthorizeOrgAdmin (u , groupID ); err != nil {
906- // If we're not an admin, restrict to keys that have only been made
907- // visible to non-admins. Note: the visible_to_developers field means "visible to
908- // non-admins" now that we have reader/writer roles.
909- q .AddWhereClause ("visible_to_developers = ?" , true )
910- }
911- q .AddWhereClause (`impersonation = false` )
912- q .AddWhereClause (`expiry_usec = 0 OR expiry_usec > ?` , d .clock .Now ().UnixMicro ())
913- q .SetOrderBy ("label" , true /*ascending*/ )
914- queryStr , args := q .Build ()
915- rq := d .h .NewQuery (ctx , "authdb_get_api_keys" ).Raw (queryStr , args ... )
916-
917- keys := make ([]* tables.APIKey , 0 )
918- err = db .ScanEach (rq , func (ctx context.Context , k * tables.APIKey ) error {
919- if err := d .fillDecryptedAPIKey (k ); err != nil {
920- return err
962+ rows , err := d .fetchAPIKeys (ctx , "authdb_get_api_keys" , "" , db .Opts (), func (qb * query_builder.Query ) error {
963+ // Select group-owned keys only.
964+ qb .AddWhereClause (`ak.user_id IS NULL OR ak.user_id = ''` )
965+ qb .AddWhereClause (`ak.group_id = ?` , groupID )
966+ if err := authutil .AuthorizeOrgAdmin (u , groupID ); err != nil {
967+ // If we're not an admin, restrict to keys that have only been made
968+ // visible to non-admins. Note: the visible_to_developers field means "visible to
969+ // non-admins" now that we have reader/writer roles.
970+ qb .AddWhereClause ("ak.visible_to_developers = ?" , true )
921971 }
922- keys = append (keys , k )
972+ qb .AddWhereClause (`ak.impersonation = false` )
973+ qb .SetOrderBy ("ak.label" , true /*ascending*/ )
923974 return nil
924975 })
925- return keys , err
976+ if err != nil {
977+ return nil , err
978+ }
979+ keys := make ([]* tables.APIKey , 0 , len (rows ))
980+ for _ , row := range rows {
981+ k := row .toAPIKey ()
982+ if err := d .fillDecryptedAPIKey (k ); err != nil {
983+ return nil , err
984+ }
985+ keys = append (keys , k )
986+ }
987+ return keys , nil
926988}
927989
928990func (d * AuthDB ) authorizeAPIKeyWrite (ctx context.Context , h interfaces.DB , apiKeyID string ) (* tables.APIKey , error ) {
@@ -933,7 +995,7 @@ func (d *AuthDB) authorizeAPIKeyWrite(ctx context.Context, h interfaces.DB, apiK
933995 if err != nil {
934996 return nil , err
935997 }
936- key , err := d .getAPIKey (ctx , h , apiKeyID )
998+ key , err := d .getAPIKey (ctx , apiKeyID )
937999 if err != nil {
9381000 return nil , err
9391001 }
@@ -1052,24 +1114,25 @@ func (d *AuthDB) GetUserAPIKeys(ctx context.Context, userID, groupID string) ([]
10521114 return nil , status .PermissionDeniedError ("user-owned keys are not enabled for this group" )
10531115 }
10541116
1055- q := query_builder .NewQuery (`SELECT * FROM "APIKeys"` )
1056- q .AddWhereClause (`user_id = ?` , userID )
1057- q .AddWhereClause (`group_id = ?` , groupID )
1058- q .AddWhereClause (`impersonation = false` )
1059- q .AddWhereClause (`expiry_usec = 0 OR expiry_usec > ?` , d .clock .Now ().UnixMicro ())
1060- q .SetOrderBy ("label" , true /*=ascending*/ )
1061- queryStr , args := q .Build ()
1062-
1063- rq := d .h .NewQuery (ctx , "authdb_get_user_api_keys" ).Raw (queryStr , args ... )
1064- var keys []* tables.APIKey
1065- err = db .ScanEach (rq , func (ctx context.Context , k * tables.APIKey ) error {
1117+ rows , err := d .fetchAPIKeys (ctx , "authdb_get_user_api_keys" , "" /*=subDomain*/ , db .Opts (), func (qb * query_builder.Query ) error {
1118+ qb .AddWhereClause (`ak.user_id = ?` , userID )
1119+ qb .AddWhereClause (`ak.group_id = ?` , groupID )
1120+ qb .AddWhereClause (`ak.impersonation = false` )
1121+ qb .SetOrderBy ("ak.label" , true /*=ascending*/ )
1122+ return nil
1123+ })
1124+ if err != nil {
1125+ return nil , err
1126+ }
1127+ keys := make ([]* tables.APIKey , 0 , len (rows ))
1128+ for _ , row := range rows {
1129+ k := row .toAPIKey ()
10661130 if err := d .fillDecryptedAPIKey (k ); err != nil {
1067- return err
1131+ return nil , err
10681132 }
10691133 keys = append (keys , k )
1070- return nil
1071- })
1072- return keys , err
1134+ }
1135+ return keys , nil
10731136}
10741137
10751138func (d * AuthDB ) GetUserOwnedKeysEnabled () bool {
0 commit comments