@@ -12,6 +12,8 @@ import (
1212 grant "github.com/conductorone/baton-sdk/pkg/types/grant"
1313 rs "github.com/conductorone/baton-sdk/pkg/types/resource"
1414 "github.com/fastly/go-fastly/v8/fastly"
15+ "github.com/grpc-ecosystem/go-grpc-middleware/logging/zap/ctxzap"
16+ "go.uber.org/zap"
1517)
1618
1719type serviceBuilder struct {
@@ -20,18 +22,31 @@ type serviceBuilder struct {
2022 customerId string
2123}
2224
23- var (
25+ const (
2426 ReadOnlyPermission = "read_only"
2527 PurgeSelectPermission = "purge_select"
2628 PurgeAllPermission = "purge_all"
2729 FullAccessPermission = "full"
30+ )
2831
32+ var (
2933 permissionEntitlementMap = map [string ][]string {
3034 ReadOnlyPermission : {readStatsAndConfigurationEntitlement },
3135 PurgeSelectPermission : {readStatsAndConfigurationEntitlement , purgeSelectedContentEntitlement },
3236 PurgeAllPermission : {readStatsAndConfigurationEntitlement , purgeSelectedContentEntitlement , purgeAllEntitlement },
3337 FullAccessPermission : {readStatsAndConfigurationEntitlement , purgeSelectedContentEntitlement , purgeAllEntitlement , fullAccessEntitlement },
3438 }
39+ entitlementPermissionMap = map [string ]string {
40+ readStatsAndConfigurationEntitlement : ReadOnlyPermission ,
41+ purgeSelectedContentEntitlement : PurgeSelectPermission ,
42+ purgeAllEntitlement : PurgeAllPermission ,
43+ fullAccessEntitlement : FullAccessPermission ,
44+ }
45+ revokeEntitlementMap = map [string ]string {
46+ purgeSelectedContentEntitlement : readStatsAndConfigurationEntitlement ,
47+ purgeAllEntitlement : purgeSelectedContentEntitlement ,
48+ fullAccessEntitlement : purgeAllEntitlement ,
49+ }
3550)
3651
3752func newServiceBuilder (client * fastly.Client , customerId string ) * serviceBuilder {
@@ -307,3 +322,194 @@ func (o *serviceBuilder) grantEngineer(ctx context.Context, service *v2.Resource
307322
308323 return rv , nil
309324}
325+
326+ func (o * serviceBuilder ) Grant (ctx context.Context , principal * v2.Resource , entitlement * v2.Entitlement ) (annotations.Annotations , error ) {
327+ l := ctxzap .Extract (ctx )
328+
329+ permission , exists := entitlementPermissionMap [entitlement .Slug ]
330+ if ! exists {
331+ err := fmt .Errorf ("baton-fastly: unable to grant %s entitlement" , entitlement .Slug )
332+
333+ l .Warn (
334+ err .Error (),
335+ zap .String ("entitlement_id" , entitlement .Slug ),
336+ )
337+
338+ return nil , err
339+ }
340+
341+ err := o .validateGrantOperation (principal , entitlement , l )
342+ if err != nil {
343+ return nil , err
344+ }
345+
346+ _ , err = o .upsertServiceAuthorizationForUser (entitlement .Resource .Id .Resource , principal .Id .Resource , permission , l )
347+ if err != nil {
348+ return nil , err
349+ }
350+
351+ return nil , nil
352+ }
353+
354+ func (o * serviceBuilder ) getServiceAuthorizationForUser (serviceId , userId string ) (* fastly.ServiceAuthorization , error ) {
355+ pageNumber := 1
356+
357+ for {
358+ serviceAuthorizations , err := o .client .ListServiceAuthorizations (& fastly.ListServiceAuthorizationsInput {
359+ PageNumber : pageNumber ,
360+ PageSize : resourcePageSize ,
361+ })
362+ if err != nil {
363+ return nil , err
364+ }
365+
366+ for _ , serviceAuthorization := range serviceAuthorizations .Items {
367+ if serviceAuthorization .Service .ID == serviceId && serviceAuthorization .User .ID == userId {
368+ return serviceAuthorization , nil
369+ }
370+ }
371+
372+ if pageNumber >= serviceAuthorizations .Info .Meta .TotalPages {
373+ break
374+ }
375+ }
376+
377+ return nil , nil
378+ }
379+
380+ // Service authorization for user can already exist with different permission.
381+ // In this case we need to update it.
382+ func (o * serviceBuilder ) upsertServiceAuthorizationForUser (serviceId , userId , permission string , l * zap.Logger ) (* fastly.ServiceAuthorization , error ) {
383+ serviceAuthorization , err := o .getServiceAuthorizationForUser (serviceId , userId )
384+ if err != nil {
385+ return nil , wrapError (err , "failed to get service authorization" )
386+ }
387+
388+ if serviceAuthorization != nil {
389+ if serviceAuthorization .Permission == permission {
390+ return serviceAuthorization , nil
391+ }
392+
393+ serviceAuthorization , err := o .client .UpdateServiceAuthorization (& fastly.UpdateServiceAuthorizationInput {
394+ ID : serviceAuthorization .ID ,
395+ Permission : permission ,
396+ })
397+ if err != nil {
398+ err = wrapError (err , "failed to update permission to user" )
399+
400+ l .Error (
401+ err .Error (),
402+ zap .String ("permission" , permission ),
403+ zap .String ("user_id" , userId ),
404+ zap .String ("service_id" , serviceId ),
405+ )
406+ }
407+
408+ return serviceAuthorization , nil
409+ } else {
410+ serviceAuthorization , err := o .client .CreateServiceAuthorization (& fastly.CreateServiceAuthorizationInput {
411+ Service : & fastly.SAService {
412+ ID : serviceId ,
413+ },
414+ User : & fastly.SAUser {
415+ ID : userId ,
416+ },
417+ Permission : permission ,
418+ })
419+ if err != nil {
420+ err = wrapError (err , "failed to grant permission to user" )
421+
422+ l .Error (
423+ err .Error (),
424+ zap .String ("permission" , permission ),
425+ zap .String ("user_id" , userId ),
426+ zap .String ("service_id" , serviceId ),
427+ )
428+ }
429+
430+ return serviceAuthorization , nil
431+ }
432+ }
433+
434+ func (o * serviceBuilder ) validateGrantOperation (principal * v2.Resource , entitlement * v2.Entitlement , l * zap.Logger ) error {
435+ if principal .Id .ResourceType != userResourceType .Id {
436+ err := fmt .Errorf ("baton-fastly: only users can be granted to service" )
437+
438+ l .Warn (
439+ err .Error (),
440+ zap .String ("principal_id" , principal .Id .Resource ),
441+ zap .String ("principal_type" , principal .Id .ResourceType ),
442+ )
443+
444+ return err
445+ }
446+
447+ user , err := o .client .GetUser (& fastly.GetUserInput {ID : principal .Id .Resource })
448+ if err != nil {
449+ err := wrapError (err , "failed to get user" )
450+
451+ l .Error (
452+ err .Error (),
453+ zap .String ("user_id" , principal .Id .Resource ),
454+ )
455+
456+ return err
457+ }
458+
459+ if user .Role != strings .ToLower (engineerRole ) {
460+ err := fmt .Errorf ("baton-fastly: only users with role %s can be granted to service" , engineerRole )
461+
462+ l .Warn (
463+ err .Error (),
464+ zap .String ("user_id" , principal .Id .Resource ),
465+ zap .String ("user_role" , user .Role ),
466+ )
467+
468+ return err
469+ }
470+
471+ return nil
472+ }
473+
474+ func (o * serviceBuilder ) Revoke (ctx context.Context , grant * v2.Grant ) (annotations.Annotations , error ) {
475+ l := ctxzap .Extract (ctx )
476+
477+ principal := grant .Principal
478+ entitlement := grant .Entitlement
479+
480+ revokedEntitlement , exists := revokeEntitlementMap [entitlement .Slug ]
481+ if ! exists {
482+ err := fmt .Errorf ("baton-fastly: unable to revoke %s entitlement" , entitlement .Slug )
483+
484+ l .Warn (
485+ err .Error (),
486+ zap .String ("entitlement_id" , entitlement .Slug ),
487+ )
488+
489+ return nil , err
490+ }
491+
492+ revokedPermission , exists := entitlementPermissionMap [revokedEntitlement ]
493+ if ! exists {
494+ err := fmt .Errorf ("baton-fastly: unable to map %s entitlement to permission" , revokedEntitlement )
495+
496+ l .Warn (
497+ err .Error (),
498+ zap .String ("entitlement_id" , revokedEntitlement ),
499+ )
500+
501+ return nil , err
502+ }
503+
504+ err := o .validateGrantOperation (principal , entitlement , l )
505+ if err != nil {
506+ return nil , err
507+ }
508+
509+ _ , err = o .upsertServiceAuthorizationForUser (entitlement .Resource .Id .Resource , principal .Id .Resource , revokedPermission , l )
510+ if err != nil {
511+ return nil , err
512+ }
513+
514+ return nil , nil
515+ }
0 commit comments