Skip to content

Commit

Permalink
Make a new 'administration' app and give admin users a grant there in…
Browse files Browse the repository at this point in the history
…stead of their own resource type
  • Loading branch information
jirwin committed Aug 31, 2023
1 parent 7865958 commit 302e111
Show file tree
Hide file tree
Showing 8 changed files with 123 additions and 289 deletions.
120 changes: 0 additions & 120 deletions pkg/connector/admin_users.go

This file was deleted.

71 changes: 68 additions & 3 deletions pkg/connector/apps.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,20 @@ import (
v2 "github.com/conductorone/baton-sdk/pb/c1/connector/v2"
"github.com/conductorone/baton-sdk/pkg/annotations"
"github.com/conductorone/baton-sdk/pkg/pagination"
sdkResources "github.com/conductorone/baton-sdk/pkg/types/resource"
)

const (
apiUserType = "user"
apiUserGroupType = "user_group"
adminAppID = "2UjI0eIRo77RGFwi2GKpAa0Til0"
)

type appResourceType struct {
resourceType *v2.ResourceType
client1 jc1Func
client2 jc2Func
ext *ExtensionClient
}

func (o *appResourceType) ResourceType(_ context.Context) *v2.ResourceType {
Expand All @@ -34,6 +37,17 @@ func (o *appResourceType) List(
resourceID *v2.ResourceId,
token *pagination.Token,
) ([]*v2.Resource, string, annotations.Annotations, error) {
var rv []*v2.Resource

// If this is the first call to List, we need to create the JumpCloud Administration app
if token.Token == "" {
adminApp, err := sdkResources.NewAppResource("JumpCloud Administration", resourceTypeApp, adminAppID, nil)
if err != nil {
return nil, "", nil, err
}
rv = append(rv, adminApp)
}

ctx, client := o.client1(ctx)

skip, b, err := unmarshalSkipToken(token)
Expand All @@ -47,7 +61,6 @@ func (o *appResourceType) List(
}
defer resp.Body.Close()

var rv []*v2.Resource
for i := range apps.Results {
ur, err := appResource(ctx, &apps.Results[i])
if err != nil {
Expand Down Expand Up @@ -114,21 +127,72 @@ func appEntitlement(ctx context.Context, resource *v2.Resource) *v2.Entitlement
Description: fmt.Sprintf("Assigned to %s app", resource.DisplayName),
GrantableTo: []*v2.ResourceType{resourceTypeUser},
Purpose: v2.Entitlement_PURPOSE_VALUE_ASSIGNMENT,
Slug: resource.DisplayName,
Slug: "access",
}
}

type graphRequest interface {
Execute() ([]jcapi2.GraphConnection, *http.Response, error)
}

func (o *appResourceType) adminGrants(ctx context.Context, resource *v2.Resource, pt *pagination.Token) ([]*v2.Grant, string, annotations.Annotations, error) {
skip, b, err := unmarshalSkipToken(pt)
if err != nil {
return nil, "", nil, err
}

appID := resource.Id.GetResource()

users, resp, err := o.ext.UserList().Skip(skip).Execute(ctx)
if err != nil {
return nil, "", nil, err
}
defer resp.Body.Close()

ctx, client := o.client1(ctx)

var rv []*v2.Grant
for _, u := range users {
user, err := fetchUserByEmail(ctx, client, u.GetEmail())
if err != nil {
return nil, "", nil, err
}

ur := &v2.Resource{Id: &v2.ResourceId{
ResourceType: resourceTypeUser.Id,
Resource: user.GetId(),
},
}

rv = append(rv, &v2.Grant{
Id: fmtResourceGrant(resource.Id, ur.Id, appID),
Entitlement: &v2.Entitlement{
Id: fmtResource(resource.Id, appID),
Resource: resource,
},
Principal: ur,
})
}

pageToken, err := marshalSkipToken(len(users), skip, b)
if err != nil {
return nil, "", nil, err
}

return rv, pageToken, nil, nil
}

func (o *appResourceType) Grants(
ctx context.Context,
resource *v2.Resource,
token *pagination.Token,
) ([]*v2.Grant, string, annotations.Annotations, error) {
ctx, client := o.client2(ctx)

if resource.Id.Resource == adminAppID {
return o.adminGrants(ctx, resource, token)
}

b := pagination.Bag{}
if token.Token == "" {
b.Push(pagination.PageState{
Expand Down Expand Up @@ -231,10 +295,11 @@ func appGrant(resource *v2.Resource, resoureTypeId string, member *jcapi2.GraphC
}
}

func newAppBuilder(jc1 jc1Func, jc2 jc2Func) *appResourceType {
func newAppBuilder(jc1 jc1Func, jc2 jc2Func, ext *ExtensionClient) *appResourceType {
return &appResourceType{
resourceType: resourceTypeApp,
client1: jc1,
client2: jc2,
ext: ext,
}
}
5 changes: 2 additions & 3 deletions pkg/connector/connector.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,8 +135,7 @@ func (s *Jumpcloud) ResourceSyncers(ctx context.Context) []connectorbuilder.Reso
// https://support.jumpcloud.com/support/s/article/getting-started-jumpcloud-admin-accounts-vs-user-accounts-2019-08-21-10-36-47
newUserBuilder(s.client1, s.client2),
newGroupBuilder(s.client1, s.client2),
newAdministrationAppBuilder(s.client1, s.ext),
newRoleBuilder(s.ext),
newAppBuilder(s.client1, s.client2),
newRoleBuilder(s.client1, s.ext),
newAppBuilder(s.client1, s.client2, s.ext),
}
}
38 changes: 38 additions & 0 deletions pkg/connector/helper.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,21 @@
package connector

import (
"context"
"errors"
"fmt"
"sync"

"github.com/conductorone/baton-jumpcloud/pkg/jcapi1"
v2 "github.com/conductorone/baton-sdk/pb/c1/connector/v2"
)

var (
userCache sync.Map
errUserNotFoundForEmail = errors.New("user not found for email")
errMultipleUsersForEmail = errors.New("multiple users found for email")
)

func fmtResourceId(resourceTypeID string, id string) *v2.ResourceId {
return &v2.ResourceId{
ResourceType: resourceTypeID,
Expand All @@ -31,3 +41,31 @@ func fmtResourceGrant(resourceID *v2.ResourceId, principalId *v2.ResourceId, per
permission,
)
}

func fetchUserByEmail(ctx context.Context, client *jcapi1.APIClient, email string) (*jcapi1.Systemuserreturn, error) {
if email == "" {
return nil, errors.New("email cannot be empty")
}

if u, ok := userCache.Load(email); ok {
return u.(*jcapi1.Systemuserreturn), nil
}

list, resp, err := client.SystemusersApi.SystemusersList(ctx).Filter(fmt.Sprintf("email:$eq:%s", email)).Execute()
if err != nil {
return nil, err
}
defer resp.Body.Close()

if len(list.Results) == 0 {
return nil, errUserNotFoundForEmail
}

if len(list.Results) != 1 {
return nil, errMultipleUsersForEmail
}

userCache.Store(email, &list.Results[0])

return &list.Results[0], nil
}
20 changes: 15 additions & 5 deletions pkg/connector/role.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (

type roleResourceType struct {
resourceType *v2.ResourceType
client jc1Func
ext *ExtensionClient

allUsers []jcapi1.Userreturn
Expand Down Expand Up @@ -128,21 +129,29 @@ func (o *roleResourceType) Grants(
return nil, "", nil, err
}

ctx, client := o.client(ctx)

var rv []*v2.Grant
for i := range users {
user := &users[i]
roleID := fmtRoleNameAsID(user.GetRoleName())
adminUser := &users[i]
roleID := fmtRoleNameAsID(adminUser.GetRoleName())
if resource.Id.Resource != roleID {
continue
}

user, err := fetchUserByEmail(ctx, client, adminUser.GetEmail())
if err != nil {
return nil, "", nil, err
}

rv = append(rv, roleGrant(resource, resourceTypeUser.Id, user))
}
return rv, "", nil, nil
}

func roleGrant(resource *v2.Resource, resoureTypeId string, user *jcapi1.Userreturn) *v2.Grant {
func roleGrant(resource *v2.Resource, resourceTypeID string, user *jcapi1.Systemuserreturn) *v2.Grant {
roleID := resource.Id.GetResource()
ur := &v2.Resource{Id: &v2.ResourceId{ResourceType: resoureTypeId, Resource: user.GetId()}}
ur := &v2.Resource{Id: &v2.ResourceId{ResourceType: resourceTypeID, Resource: user.GetId()}}

var annos annotations.Annotations

Expand All @@ -157,9 +166,10 @@ func roleGrant(resource *v2.Resource, resoureTypeId string, user *jcapi1.Userret
}
}

func newRoleBuilder(ext *ExtensionClient) *roleResourceType {
func newRoleBuilder(client jc1Func, ext *ExtensionClient) *roleResourceType {
return &roleResourceType{
resourceType: resourceTypeRole,
client: client,
ext: ext,
}
}
Loading

0 comments on commit 302e111

Please sign in to comment.