Skip to content

Commit f4dd0ee

Browse files
committed
disallow provisioned users creating organizations
1 parent 2271ad8 commit f4dd0ee

2 files changed

Lines changed: 100 additions & 43 deletions

File tree

integration-tests/tests/api/auth/scim.spec.ts

Lines changed: 92 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -3372,49 +3372,6 @@ test.concurrent('user cannot login via OIDC if SCIM user provisioning is require
33723372
invariant(signInUpResult.type === 'success', 'Expected sign in/up to succeed.');
33733373
});
33743374

3375-
test.concurrent('provisioned user cannot update their profile via GraphQL', async ({ expect }) => {
3376-
const seed = initSeed();
3377-
const owner = await seed.createOwner();
3378-
const org = await owner.createOrg();
3379-
const oidc = await org.createOIDCIntegration();
3380-
const oidcMock = await oidc.createMockServerAndUpdateIntegrationEndpoints();
3381-
const domain = await oidc.registerFakeDomain();
3382-
const accessToken = await org.createOrganizationAccessToken({
3383-
permissions: ['member:describe', 'member:modify'],
3384-
resources: { mode: ResourceAssignmentModeType.Granular },
3385-
});
3386-
const scim = createScimTestkit({
3387-
baseUrl,
3388-
headers: {
3389-
'Content-Type': 'application/scim+json',
3390-
Authorization: `Bearer ${accessToken.privateAccessKey}`,
3391-
},
3392-
});
3393-
const email = `profile@${domain}`;
3394-
const externalId = crypto.randomUUID();
3395-
3396-
await scim.createUser({
3397-
externalId,
3398-
emails: [{ primary: true, type: 'work', value: email }],
3399-
userName: email,
3400-
});
3401-
3402-
oidcMock.setUser({ email, userIdClaim: externalId });
3403-
const auth = await oidcMock.runGetAuthorizationUrl();
3404-
const signInResult = await oidcMock.runSignInUp({ state: auth.state });
3405-
invariant(signInResult.type === 'success', 'Expected sign in to succeed.');
3406-
3407-
const result = await updateMe(
3408-
{
3409-
displayName: 'updated-display-name',
3410-
fullName: 'Updated Full Name',
3411-
},
3412-
signInResult.accessToken,
3413-
).then(r => r.expectNoGraphQLErrors());
3414-
expect(result.updateMe.ok).toEqual(null);
3415-
expect(result.updateMe.error?.message).toEqual('Provisioned users can not be modified.');
3416-
});
3417-
34183375
test.concurrent(
34193376
'organization admin can still sign in via non-oidc method even if login through the identity provider is enforced',
34203377
async () => {
@@ -4122,3 +4079,95 @@ describe('Personal Access Tokens', () => {
41224079
},
41234080
);
41244081
});
4082+
4083+
4084+
test.concurrent('provisioned user cannot update their profile via GraphQL', async ({ expect }) => {
4085+
const seed = initSeed();
4086+
const owner = await seed.createOwner();
4087+
const org = await owner.createOrg();
4088+
const oidc = await org.createOIDCIntegration();
4089+
const oidcMock = await oidc.createMockServerAndUpdateIntegrationEndpoints();
4090+
const domain = await oidc.registerFakeDomain();
4091+
const accessToken = await org.createOrganizationAccessToken({
4092+
permissions: ['member:describe', 'member:modify'],
4093+
resources: { mode: ResourceAssignmentModeType.Granular },
4094+
});
4095+
const scim = createScimTestkit({
4096+
baseUrl,
4097+
headers: {
4098+
'Content-Type': 'application/scim+json',
4099+
Authorization: `Bearer ${accessToken.privateAccessKey}`,
4100+
},
4101+
});
4102+
const email = `profile@${domain}`;
4103+
const externalId = crypto.randomUUID();
4104+
4105+
await scim.createUser({
4106+
externalId,
4107+
emails: [{ primary: true, type: 'work', value: email }],
4108+
userName: email,
4109+
});
4110+
4111+
oidcMock.setUser({ email, userIdClaim: externalId });
4112+
const auth = await oidcMock.runGetAuthorizationUrl();
4113+
const signInResult = await oidcMock.runSignInUp({ state: auth.state });
4114+
invariant(signInResult.type === 'success', 'Expected sign in to succeed.');
4115+
4116+
const result = await updateMe(
4117+
{
4118+
displayName: 'updated-display-name',
4119+
fullName: 'Updated Full Name',
4120+
},
4121+
signInResult.accessToken,
4122+
).then(r => r.expectNoGraphQLErrors());
4123+
expect(result.updateMe.ok).toEqual(null);
4124+
expect(result.updateMe.error?.message).toEqual('Provisioned users can not be modified.');
4125+
});
4126+
4127+
test.concurrent('provisioned user cannot create an organization', async ({ expect }) => {
4128+
const seed = initSeed();
4129+
const owner = await seed.createOwner();
4130+
const org = await owner.createOrg();
4131+
const oidc = await org.createOIDCIntegration();
4132+
const oidcMock = await oidc.createMockServerAndUpdateIntegrationEndpoints();
4133+
const domain = await oidc.registerFakeDomain();
4134+
const accessToken = await org.createOrganizationAccessToken({
4135+
permissions: ['member:describe', 'member:modify'],
4136+
resources: { mode: ResourceAssignmentModeType.Granular },
4137+
});
4138+
const scim = createScimTestkit({
4139+
baseUrl,
4140+
headers: {
4141+
'Content-Type': 'application/scim+json',
4142+
Authorization: `Bearer ${accessToken.privateAccessKey}`,
4143+
},
4144+
});
4145+
const email = `create-organization@${domain}`;
4146+
const externalId = crypto.randomUUID();
4147+
4148+
await scim.createUser({
4149+
externalId,
4150+
emails: [{ primary: true, type: 'work', value: email }],
4151+
userName: email,
4152+
});
4153+
4154+
oidcMock.setUser({ email, userIdClaim: externalId });
4155+
const auth = await oidcMock.runGetAuthorizationUrl();
4156+
const signInResult = await oidcMock.runSignInUp({ state: auth.state });
4157+
invariant(signInResult.type === 'success', 'Expected sign in to succeed.');
4158+
4159+
const slug = `scim-${crypto.randomUUID()}`;
4160+
const result = await createOrganization({ slug }, signInResult.accessToken).then(response =>
4161+
response.expectNoGraphQLErrors(),
4162+
);
4163+
4164+
expect(result.createOrganization).toEqual({
4165+
ok: null,
4166+
error: {
4167+
message: 'Provisioned users can not create organizations.',
4168+
inputErrors: {
4169+
slug: null,
4170+
},
4171+
},
4172+
});
4173+
});

packages/services/api/src/modules/organization/providers/organization-manager.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -320,11 +320,19 @@ export class OrganizationManager {
320320
user: {
321321
id: string;
322322
superTokensUserId: string | null;
323+
provisionedByOrganizationId: string | null;
323324
};
324325
}) {
325326
const { slug, user } = input;
326327
this.logger.info('Creating an organization (input=%o)', input);
327328

329+
if (user.provisionedByOrganizationId !== null) {
330+
return {
331+
ok: false as const,
332+
message: 'Provisioned users can not create organizations.',
333+
};
334+
}
335+
328336
const result = await this.storage.createOrganization({
329337
slug,
330338
userId: user.id,

0 commit comments

Comments
 (0)