Skip to content

Commit

Permalink
Opportunistically use system users for admins, otherwise create a use…
Browse files Browse the repository at this point in the history
…r for them.
  • Loading branch information
jirwin committed Sep 26, 2023
1 parent fe58ec9 commit 0606344
Show file tree
Hide file tree
Showing 5 changed files with 135 additions and 24 deletions.
26 changes: 19 additions & 7 deletions pkg/connector/apps.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,10 @@ type graphRequest interface {
Execute() ([]jcapi2.GraphConnection, *http.Response, error)
}

type appAdminPrincipal interface {
GetId() string
}

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 {
Expand All @@ -152,16 +156,24 @@ func (o *appResourceType) adminGrants(ctx context.Context, resource *v2.Resource
ctx, client := o.client1(ctx)

var rv []*v2.Grant
for _, u := range users {
user, err := fetchUserByEmail(ctx, client, u.GetEmail())
if err != nil {
for i := range users {
adminUser := &users[i]
var adminPrincipal appAdminPrincipal = adminUser

// If the user is a system user, we need to fetch the user by email to get the ID
systemUser, err := fetchUserByEmail(ctx, client, adminUser.GetEmail())
if err != nil && !errors.Is(err, errUserNotFoundForEmail) {
return nil, "", nil, err
}
if systemUser != nil {
adminPrincipal = systemUser
}

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

rv = append(rv, &v2.Grant{
Expand Down
2 changes: 1 addition & 1 deletion pkg/connector/connector.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ func (s *Jumpcloud) ResourceSyncers(ctx context.Context) []connectorbuilder.Reso
// NOTE: Jumpcloud has 'two' types of Users, "admin users" and... uh, "normal users".
// So, we put each in their own resource type.
// 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),
newUserBuilder(s.client1, s.client2, s.ext),
newGroupBuilder(s.client1, s.client2),
newRoleBuilder(s.client1, s.ext),
newAppBuilder(s.client1, s.client2, s.ext),
Expand Down
2 changes: 1 addition & 1 deletion pkg/connector/pagination.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ func unmarshalSkipToken(token *pagination.Token) (int32, *pagination.Bag, error)

func marshalSkipToken(newObjects int, lastSkip int32, b *pagination.Bag) (string, error) {
if newObjects == 0 {
return "", nil
return nextToken(b, "")
}
nextSkip := int64(newObjects) + int64(lastSkip)
pageToken, err := nextToken(b, strconv.FormatInt(nextSkip, 10))
Expand Down
17 changes: 14 additions & 3 deletions pkg/connector/role.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package connector

import (
"context"
"errors"
"fmt"
"regexp"
"strings"
Expand Down Expand Up @@ -119,6 +120,10 @@ func (o *roleResourceType) cacheAllUsers(ctx context.Context) ([]jcapi1.Userretu
return rv, nil
}

type rolePrincipal interface {
GetId() string
}

func (o *roleResourceType) Grants(
ctx context.Context,
resource *v2.Resource,
Expand All @@ -139,17 +144,23 @@ func (o *roleResourceType) Grants(
continue
}

var principal rolePrincipal = adminUser

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

rv = append(rv, roleGrant(resource, resourceTypeUser.Id, user))
if user != nil {
principal = user
}

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

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

Expand Down
112 changes: 100 additions & 12 deletions pkg/connector/users.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@ package connector

import (
"context"
"errors"
"fmt"
"strings"

"github.com/conductorone/baton-jumpcloud/pkg/jcapi1"
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"
"github.com/grpc-ecosystem/go-grpc-middleware/logging/zap/ctxzap"
"go.uber.org/zap"
"google.golang.org/protobuf/types/known/structpb"
Expand All @@ -19,18 +21,20 @@ type userResourceType struct {
client1 jc1Func
client2 jc2Func
managers map[string]*jcapi1.Systemuserreturn
ext *ExtensionClient
}

func (o *userResourceType) ResourceType(_ context.Context) *v2.ResourceType {
return o.resourceType
}

func newUserBuilder(jc1 jc1Func, jc2 jc2Func) *userResourceType {
func newUserBuilder(jc1 jc1Func, jc2 jc2Func, ext *ExtensionClient) *userResourceType {
return &userResourceType{
resourceType: resourceTypeUser,
client1: jc1,
client2: jc2,
managers: make(map[string]*jcapi1.Systemuserreturn),
ext: ext,
}
}

Expand All @@ -43,34 +47,118 @@ func (o *userResourceType) Grants(_ context.Context, _ *v2.Resource, _ *paginati
}

func (o *userResourceType) List(ctx context.Context, parentResourceID *v2.ResourceId, pt *pagination.Token) ([]*v2.Resource, string, annotations.Annotations, error) {
l := ctxzap.Extract(ctx)

ctx, client := o.client1(ctx)

skip, b, err := unmarshalSkipToken(pt)
if err != nil {
return nil, "", nil, err
}

list, resp, err := client.SystemusersApi.SystemusersList(ctx).Skip(skip).Execute()
if err != nil {
return nil, "", nil, err
if b.Current() == nil {
// Push onto stack in reverse
b.Push(pagination.PageState{
ResourceTypeID: "list-admin-users",
})
b.Push(pagination.PageState{
ResourceTypeID: "list-users",
})
}
defer resp.Body.Close()

var rv []*v2.Resource
for i := range list.Results {
ur, err := o.userResource(ctx, &list.Results[i])
var pageToken string
switch b.Current().ResourceTypeID {
case "list-users":
list, resp, err := client.SystemusersApi.SystemusersList(ctx).Skip(skip).Execute()
if err != nil {
return nil, "", nil, err
}
defer resp.Body.Close()

for i := range list.Results {
ur, err := o.userResource(ctx, &list.Results[i])
if err != nil {
return nil, "", nil, err
}
rv = append(rv, ur)
}
pageToken, err = marshalSkipToken(len(list.Results), skip, b)
if err != nil {
return nil, "", nil, err
}
case "list-admin-users":
adminUsers, resp, err := o.ext.UserList().Skip(skip).Execute(ctx)
if err != nil {
return nil, "", nil, err
}
rv = append(rv, ur)
defer resp.Body.Close()

for i := range adminUsers {
adminEmail := adminUsers[i].GetEmail()
adminUser, err := o.adminUserResource(ctx, &adminUsers[i])
if err != nil {
return nil, "", nil, err
}

// Check if the admin user is also a system user, if so we'll use that user instead
systemUser, err := fetchUserByEmail(ctx, client, adminEmail)
if err != nil && !errors.Is(err, errUserNotFoundForEmail) {
return nil, "", nil, err
}

if systemUser != nil {
continue
}

l.Debug("admin user not found as system user, creating", zap.String("email", adminEmail))
rv = append(rv, adminUser)
}
pageToken, err = marshalSkipToken(len(adminUsers), skip, b)
if err != nil {
return nil, "", nil, err
}
default:
return nil, "", nil, fmt.Errorf("baton-jumpcloud: unknown page state: %s", b.Current().ResourceTypeID)
}

return rv, pageToken, nil, nil
}

func (o *userResourceType) adminUserResource(ctx context.Context, user *jcapi1.Userreturn) (*v2.Resource, error) {
profile := map[string]interface{}{
"id": user.GetId(),
}

if user.HasOrganization() {
profile["organization"] = user.GetOrganization()
}

userTraitOps := []sdkResources.UserTraitOption{
sdkResources.WithUserProfile(profile),
}

pageToken, err := marshalSkipToken(len(list.Results), skip, b)
status := v2.UserTrait_Status_STATUS_ENABLED
if user.GetSuspended() {
status = v2.UserTrait_Status_STATUS_DISABLED
}
userTraitOps = append(userTraitOps, sdkResources.WithStatus(status))

email := user.GetEmail()
if email != "" {
userTraitOps = append(userTraitOps, sdkResources.WithEmail(email, true))
}

r, err := sdkResources.NewUserResource(
fmt.Sprintf("%s %s", user.GetFirstname(), user.GetLastname()),
o.resourceType,
user.GetId(),
userTraitOps,
)
if err != nil {
return nil, "", nil, err
return nil, err
}

return rv, pageToken, nil, nil
return r, nil
}

func (o *userResourceType) userResource(ctx context.Context, user *jcapi1.Systemuserreturn) (*v2.Resource, error) {
Expand Down

0 comments on commit 0606344

Please sign in to comment.