Skip to content

Commit e4c3c02

Browse files
authored
Merge pull request #3 from ConductorOne/feat/provisioning
Feat/provisioning
2 parents e83af81 + cae1328 commit e4c3c02

File tree

4 files changed

+290
-10
lines changed

4 files changed

+290
-10
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,5 @@
1515
# Dependency directories (remove the comment below to include it)
1616
# vendor/
1717
dist/
18+
19+
.env

pkg/connector/entitlements.go

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@ package connector
22

33
const (
44
assignedEntitlement = "assigned"
5-
readStatsAndAnalyticsEntitlement = "read stats and analytics"
6-
accessBillingEntitlement = "access billing"
7-
manageUsersAndAccountsEntitlement = "manage users and accounts"
8-
readStatsAndConfigurationEntitlement = "read stats and configuration"
9-
purgeSelectedContentEntitlement = "purge selected content"
10-
purgeAllEntitlement = "purge all"
11-
fullAccessEntitlement = "full access"
5+
readStatsAndAnalyticsEntitlement = "read-stats-and-analytics"
6+
accessBillingEntitlement = "access-billing"
7+
manageUsersAndAccountsEntitlement = "manage-users-and-accounts"
8+
readStatsAndConfigurationEntitlement = "read-stats-and-configuration"
9+
purgeSelectedContentEntitlement = "purge-selected-content"
10+
purgeAllEntitlement = "purge-all"
11+
fullAccessEntitlement = "full-access"
1212
accessEntitlement = "access"
1313
)

pkg/connector/roles.go

Lines changed: 74 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -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

1719
const (
@@ -22,9 +24,9 @@ const (
2224
)
2325

2426
var (
25-
roles = []string{superUserRole, userRole, billingRole, engineerRole}
26-
27+
roles = []string{superUserRole, userRole, billingRole, engineerRole}
2728
rolesWithAccessToAllServices = []string{superUserRole, userRole, billingRole}
29+
revokedRole = userRole
2830
)
2931

3032
type roleBuilder struct {
@@ -111,3 +113,73 @@ func (o *roleBuilder) Grants(ctx context.Context, resource *v2.Resource, _ *pagi
111113

112114
return rv, "", nil, nil
113115
}
116+
117+
func (o *roleBuilder) Grant(ctx context.Context, principal *v2.Resource, entitlement *v2.Entitlement) (annotations.Annotations, error) {
118+
l := ctxzap.Extract(ctx)
119+
120+
if principal.Id.ResourceType != userResourceType.Id {
121+
err := fmt.Errorf("baton-fastly: only users can be granted to roles")
122+
123+
l.Warn(
124+
err.Error(),
125+
zap.String("principal_id", principal.Id.Resource),
126+
zap.String("principal_type", principal.Id.ResourceType),
127+
)
128+
129+
return nil, err
130+
}
131+
132+
role := strings.ToLower(entitlement.Resource.Id.Resource)
133+
134+
_, err := o.client.UpdateUser(&fastly.UpdateUserInput{
135+
ID: principal.Id.Resource,
136+
Role: &role,
137+
})
138+
if err != nil {
139+
err = wrapError(err, "failed to grant role to user")
140+
141+
l.Error(
142+
err.Error(),
143+
zap.String("role_id", entitlement.Resource.Id.Resource),
144+
zap.String("user_id", principal.Id.Resource),
145+
)
146+
}
147+
148+
return nil, nil
149+
}
150+
151+
func (o *roleBuilder) Revoke(ctx context.Context, grant *v2.Grant) (annotations.Annotations, error) {
152+
l := ctxzap.Extract(ctx)
153+
154+
principal := grant.Principal
155+
156+
if principal.Id.ResourceType != userResourceType.Id {
157+
err := fmt.Errorf("baton-fastly: only users can be granted to roles")
158+
159+
l.Warn(
160+
err.Error(),
161+
zap.String("principal_id", principal.Id.Resource),
162+
zap.String("principal_type", principal.Id.ResourceType),
163+
)
164+
165+
return nil, err
166+
}
167+
168+
role := strings.ToLower(revokedRole)
169+
170+
_, err := o.client.UpdateUser(&fastly.UpdateUserInput{
171+
ID: principal.Id.Resource,
172+
Role: &role,
173+
})
174+
if err != nil {
175+
err = wrapError(err, "failed to grant role to user")
176+
177+
l.Error(
178+
err.Error(),
179+
zap.String("role_id", revokedRole),
180+
zap.String("user_id", principal.Id.Resource),
181+
)
182+
}
183+
184+
return nil, nil
185+
}

pkg/connector/services.go

Lines changed: 207 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -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

1719
type 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

3752
func 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

Comments
 (0)