Skip to content

Commit 1e8ac1f

Browse files
use session storage as cache to get system users
1 parent 8b79177 commit 1e8ac1f

File tree

6 files changed

+104
-57
lines changed

6 files changed

+104
-57
lines changed

cmd/baton-jumpcloud/main.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,17 @@ import (
66
cfg "github.com/conductorone/baton-jumpcloud/pkg/config"
77
"github.com/conductorone/baton-jumpcloud/pkg/connector"
88
"github.com/conductorone/baton-sdk/pkg/config"
9+
"github.com/conductorone/baton-sdk/pkg/connectorrunner"
910
)
1011

1112
var version = "dev"
1213

1314
func main() {
1415
ctx := context.Background()
15-
config.RunConnector(ctx, "baton-jumpcloud", version, cfg.ConfigurationSchema, connector.NewLambdaConnector)
16+
config.RunConnector(ctx,
17+
"baton-jumpcloud",
18+
version,
19+
cfg.ConfigurationSchema,
20+
connector.NewLambdaConnector,
21+
connectorrunner.WithSessionStoreEnabled())
1622
}

pkg/client/client.go

Lines changed: 0 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -160,38 +160,6 @@ func (jc *Client) ListAdminUsers(ctx context.Context, opts *Options) ([]jcapi1.U
160160
return users, resp, pageToken, nil
161161
}
162162

163-
func (jc *Client) FetchUserByEmail(ctx context.Context, email string) (*jcapi1.Systemuserreturn, error) {
164-
ctx, client := jc.client1(ctx)
165-
166-
if email == "" {
167-
return nil, fmt.Errorf("email parameter cannot be empty when fetching user by email")
168-
}
169-
170-
users, resp, err := client.SystemusersApi.SystemusersList(ctx).Filter(fmt.Sprintf("email:$eq:%s", email)).Execute()
171-
if err != nil {
172-
return nil, wrapSDKError(err, resp, "failed to list users")
173-
}
174-
defer resp.Body.Close()
175-
176-
if len(users.Results) == 0 {
177-
return nil, uhttp.WrapErrors(
178-
codes.NotFound,
179-
fmt.Sprintf("user not found for email: %s", email),
180-
nil,
181-
)
182-
}
183-
184-
if len(users.Results) != 1 {
185-
return nil, uhttp.WrapErrors(
186-
codes.InvalidArgument,
187-
fmt.Sprintf("multiple users found for email: %s", email),
188-
nil,
189-
)
190-
}
191-
192-
return &users.Results[0], nil
193-
}
194-
195163
func (jc *Client) GetSystemUserByID(ctx context.Context, userID string) (*jcapi1.Systemuserreturn, error) {
196164
ctx, client := jc.client1(ctx)
197165

pkg/connector/apps.go

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import (
44
"context"
55
"fmt"
66
"strconv"
7-
"strings"
87

98
"github.com/conductorone/baton-jumpcloud/pkg/client"
109
"github.com/conductorone/baton-jumpcloud/pkg/client/jcapi1"
@@ -13,7 +12,6 @@ import (
1312
"github.com/conductorone/baton-sdk/pkg/annotations"
1413
"github.com/conductorone/baton-sdk/pkg/pagination"
1514
sdkResources "github.com/conductorone/baton-sdk/pkg/types/resource"
16-
"google.golang.org/grpc/codes"
1715
)
1816

1917
const (
@@ -25,6 +23,7 @@ const (
2523
type appResourceType struct {
2624
resourceType *v2.ResourceType
2725
client *client.Client
26+
usersCache *usersCache
2827
}
2928

3029
func (o *appResourceType) ResourceType(_ context.Context) *v2.ResourceType {
@@ -148,17 +147,25 @@ func (o *appResourceType) adminGrants(ctx context.Context, resource *v2.Resource
148147
defer resp.Body.Close()
149148

150149
var rv []*v2.Grant
150+
151+
adminEmails := make([]string, len(users))
152+
for i := range users {
153+
adminEmails[i] = users[i].GetEmail()
154+
}
155+
systemUsersMap, err := o.usersCache.GetSystemUsersByEmailList(ctx, opts.Session, adminEmails)
156+
if err != nil {
157+
return nil, nil, err
158+
}
159+
151160
for i := range users {
152161
adminUser := &users[i]
153162
var adminPrincipal appAdminPrincipal = adminUser
154163

155164
// If the user is a system user, we need to fetch the user by email to get the ID
156-
systemUser, err := o.client.FetchUserByEmail(ctx, adminUser.GetEmail())
157-
if err != nil && !strings.Contains(err.Error(), codes.NotFound.String()) {
158-
return nil, nil, fmt.Errorf("failed to fetch system user by email during admin grants processing: %w", err)
159-
}
160-
if systemUser != nil {
161-
adminPrincipal = systemUser
165+
if systemUser, ok := systemUsersMap[adminUser.GetEmail()]; ok {
166+
if systemUser != nil {
167+
adminPrincipal = systemUser
168+
}
162169
}
163170

164171
ur := &v2.Resource{
@@ -303,5 +310,6 @@ func newAppBuilder(c *client.Client) *appResourceType {
303310
return &appResourceType{
304311
resourceType: resourceTypeApp,
305312
client: c,
313+
usersCache: newUsersCache(c),
306314
}
307315
}

pkg/connector/helper.go

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,11 @@
11
package connector
22

33
import (
4-
"errors"
54
"fmt"
6-
"sync"
75

86
v2 "github.com/conductorone/baton-sdk/pb/c1/connector/v2"
97
)
108

11-
var (
12-
userCache sync.Map
13-
errUserNotFoundForEmail = errors.New("user not found for email")
14-
errMultipleUsersForEmail = errors.New("multiple users found for email")
15-
)
16-
179
func fmtResourceId(resourceTypeID string, id string) *v2.ResourceId {
1810
return &v2.ResourceId{
1911
ResourceType: resourceTypeID,

pkg/connector/users.go

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
type userResourceType struct {
2121
resourceType *v2.ResourceType
2222
client *client.Client
23+
usersCache *usersCache
2324
managers map[string]*jcapi1.Systemuserreturn
2425
}
2526

@@ -32,6 +33,7 @@ func newUserBuilder(client *client.Client) *userResourceType {
3233
resourceType: resourceTypeUser,
3334
client: client,
3435
managers: make(map[string]*jcapi1.Systemuserreturn),
36+
usersCache: newUsersCache(client),
3537
}
3638
}
3739

@@ -103,6 +105,12 @@ func (o *userResourceType) List(ctx context.Context, parentResourceID *v2.Resour
103105
return nil, nil, fmt.Errorf("failed to list system users: %w", err)
104106
}
105107

108+
// populate the users cache with the system users
109+
err = o.usersCache.SetSystemUsers(ctx, opts.Session, systemUsers)
110+
if err != nil {
111+
return nil, nil, err
112+
}
113+
106114
for i := range systemUsers {
107115
ur, err := o.userResource(ctx, &systemUsers[i])
108116
if err != nil {
@@ -121,6 +129,14 @@ func (o *userResourceType) List(ctx context.Context, parentResourceID *v2.Resour
121129
return nil, nil, fmt.Errorf("failed to list admin users: %w", err)
122130
}
123131

132+
adminEmails := make([]string, len(adminUsers))
133+
for i := range adminUsers {
134+
adminEmails[i] = adminUsers[i].GetEmail()
135+
}
136+
systemUsersMap, err := o.usersCache.GetSystemUsersByEmailList(ctx, opts.Session, adminEmails)
137+
if err != nil {
138+
return nil, nil, err
139+
}
124140
for i := range adminUsers {
125141
adminEmail := adminUsers[i].GetEmail()
126142
adminUser, err := o.adminUserResource(&adminUsers[i])
@@ -129,14 +145,12 @@ func (o *userResourceType) List(ctx context.Context, parentResourceID *v2.Resour
129145
}
130146

131147
// Check if the admin user is also a system user, if so we'll use that user instead
132-
// TODO (luisina) > use a cache to store user by email, to avoid making multiple requests to the API
133-
systemUser, err := o.client.FetchUserByEmail(ctx, adminEmail)
134-
if err != nil && !strings.Contains(err.Error(), codes.NotFound.String()) {
135-
return nil, nil, err
136-
}
137-
138-
if systemUser != nil {
139-
continue
148+
if systemUser, ok := systemUsersMap[adminEmail]; ok {
149+
if systemUser != nil {
150+
continue
151+
} else {
152+
return nil, nil, err
153+
}
140154
}
141155

142156
l.Debug("admin user not found as system user, creating", zap.String("email", adminEmail))

pkg/connector/users_cache.go

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package connector
2+
3+
import (
4+
"context"
5+
6+
"github.com/conductorone/baton-jumpcloud/pkg/client"
7+
"github.com/conductorone/baton-jumpcloud/pkg/client/jcapi1"
8+
"github.com/conductorone/baton-sdk/pkg/session"
9+
"github.com/conductorone/baton-sdk/pkg/types/sessions"
10+
)
11+
12+
type usersCache struct {
13+
client *client.Client
14+
}
15+
16+
func newUsersCache(jumpcloudClient *client.Client) *usersCache {
17+
return &usersCache{
18+
client: jumpcloudClient,
19+
}
20+
}
21+
22+
func (uc *usersCache) GetSystemUsersByEmailList(ctx context.Context, sessionStorage sessions.SessionStore, emails []string) (map[string]*jcapi1.Systemuserreturn, error) {
23+
systemUsers, err := session.GetManyJSON[*jcapi1.Systemuserreturn](ctx, sessionStorage, emails)
24+
if err != nil {
25+
return nil, err
26+
}
27+
return systemUsers, nil
28+
}
29+
30+
func (uc *usersCache) SetSystemUsers(ctx context.Context, sessionStorage sessions.SessionStore, systemUsers []jcapi1.Systemuserreturn) error {
31+
if len(systemUsers) == 0 {
32+
return nil
33+
}
34+
35+
const batchSize = 100
36+
37+
// Process in batches of 100
38+
for i := 0; i < len(systemUsers); i += batchSize {
39+
end := i + batchSize
40+
if end > len(systemUsers) {
41+
end = len(systemUsers)
42+
}
43+
44+
batch := systemUsers[i:end]
45+
systemUsersMap := make(map[string]*jcapi1.Systemuserreturn, len(batch))
46+
47+
// Create map with email as key and pointer to user
48+
// Note: we need to take address of the slice element, not the loop variable
49+
for j := range batch {
50+
systemUsersMap[batch[j].GetEmail()] = &batch[j]
51+
}
52+
53+
if err := session.SetManyJSON(ctx, sessionStorage, systemUsersMap); err != nil {
54+
return err
55+
}
56+
}
57+
58+
return nil
59+
}

0 commit comments

Comments
 (0)