Skip to content

Commit 83d64ca

Browse files
committed
scim opt-in via feature flag
1 parent 905f413 commit 83d64ca

16 files changed

Lines changed: 265 additions & 112 deletions

File tree

e2e/fixtures.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ async function createSeedHelper(): Promise<SeedHelper> {
4141
async seedOrg() {
4242
const owner = await seed.createOwner();
4343
const org = await owner.createOrg();
44+
await org.setFeatureFlag('scim', true);
4445

4546
return {
4647
slug: org.organization.slug,

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

Lines changed: 103 additions & 0 deletions
Large diffs are not rendered by default.

integration-tests/tests/api/oidc-integrations/crud.spec.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -656,6 +656,28 @@ describe('restrictions', () => {
656656
return result.createOIDCIntegration.ok!.createdOIDCIntegration.id;
657657
}
658658

659+
test.concurrent(
660+
'SCIM provisioning requires the organization feature flag',
661+
async ({ expect }) => {
662+
const { ownerToken, createOrg } = await initSeed().createOwner();
663+
const { organization } = await createOrg();
664+
const oidcIntegrationId = await configureOIDC({
665+
ownerToken,
666+
organizationId: organization.id,
667+
});
668+
669+
const result = await updateOIDCRestrictions(
670+
{ oidcIntegrationId, userProvisioningRequired: true },
671+
ownerToken,
672+
).then(r => r.expectNoGraphQLErrors());
673+
674+
expect(result.updateOIDCRestrictions).toEqual({
675+
ok: null,
676+
error: { message: 'SCIM provisioning is disabled.' },
677+
});
678+
},
679+
);
680+
659681
test.concurrent(
660682
'users authorized with non-OIDC method cannot join an organization (default)',
661683
async ({ expect }) => {

packages/services/api/src/create.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ import {
2828
} from './modules/integrations/providers/github-integration-manager';
2929
import { labModule } from './modules/lab';
3030
import { oidcIntegrationsModule } from './modules/oidc-integrations';
31-
import { OIDC_INTEGRATIONS_ENABLED } from './modules/oidc-integrations/providers/tokens';
31+
import { OIDCIntegrationConfig } from './modules/oidc-integrations/providers/oidc-integration-config';
3232
import { operationsModule } from './modules/operations';
3333
import { CLICKHOUSE_CONFIG, ClickHouseConfig } from './modules/operations/providers/tokens';
3434
import { OTEL_TRACING_ENABLED } from './modules/operations/providers/traces';
@@ -114,7 +114,7 @@ export function createRegistry({
114114
encryptionSecret,
115115
schemaConfig,
116116
supportConfig,
117-
organizationOIDC,
117+
oidcIntegrationConfig,
118118
pubSub,
119119
appDeploymentsEnabled,
120120
schemaProposalsEnabled,
@@ -157,7 +157,7 @@ export function createRegistry({
157157
} | null;
158158
schemaConfig: SchemaModuleConfig;
159159
supportConfig: SupportConfig | null;
160-
organizationOIDC: boolean;
160+
oidcIntegrationConfig: OIDCIntegrationConfig;
161161
pubSub: HivePubSub;
162162
appDeploymentsEnabled: boolean;
163163
schemaProposalsEnabled: boolean;
@@ -265,8 +265,8 @@ export function createRegistry({
265265
scope: Scope.Singleton,
266266
},
267267
{
268-
provide: OIDC_INTEGRATIONS_ENABLED,
269-
useValue: organizationOIDC,
268+
provide: OIDCIntegrationConfig,
269+
useValue: oidcIntegrationConfig,
270270
scope: Scope.Singleton,
271271
},
272272
{

packages/services/api/src/modules/oidc-integrations/module.graphql.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { gql } from 'graphql-modules';
33
export default gql`
44
extend type Organization {
55
viewerCanManageOIDCIntegration: Boolean!
6+
viewerCanManageSCIM: Boolean!
67
oidcIntegration: OIDCIntegration
78
}
89
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { Injectable } from 'graphql-modules';
2+
3+
@Injectable()
4+
export class OIDCIntegrationConfig {
5+
constructor(
6+
/** Whether OIDC integrations are enabled. */
7+
public readonly isEnabled: boolean,
8+
/** Whether SCIM provisioning is globally enabled. */
9+
public readonly isSCIMEnabled: boolean,
10+
) {}
11+
}

packages/services/api/src/modules/oidc-integrations/providers/oidc-integrations.provider.ts

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { Inject, Injectable, Scope } from 'graphql-modules';
33
import zod from 'zod';
44
import { maskToken } from '@hive/service-common';
55
import * as GraphQLSchema from '../../../__generated__/types';
6-
import { OIDCIntegration } from '../../../shared/entities';
6+
import { OIDCIntegration, Organization } from '../../../shared/entities';
77
import { HiveError } from '../../../shared/errors';
88
import { AuditLogRecorder } from '../../audit-logs/providers/audit-log-recorder';
99
import { Session } from '../../auth/lib/authz';
@@ -13,8 +13,8 @@ import { CryptoProvider } from '../../shared/providers/crypto';
1313
import { Logger } from '../../shared/providers/logger';
1414
import { PUB_SUB_CONFIG, type HivePubSub } from '../../shared/providers/pub-sub';
1515
import { Storage } from '../../shared/providers/storage';
16+
import { OIDCIntegrationConfig } from './oidc-integration-config';
1617
import { OIDCIntegrationDomain, OIDCIntegrationStore } from './oidc-integration.store';
17-
import { OIDC_INTEGRATIONS_ENABLED } from './tokens';
1818

1919
const dnsList = [
2020
// Google
@@ -38,7 +38,7 @@ export class OIDCIntegrationsProvider {
3838
private crypto: CryptoProvider,
3939
private auditLog: AuditLogRecorder,
4040
@Inject(PUB_SUB_CONFIG) private pubSub: HivePubSub,
41-
@Inject(OIDC_INTEGRATIONS_ENABLED) private enabled: boolean,
41+
private oidcIntegrationConfig: OIDCIntegrationConfig,
4242
private session: Session,
4343
private resourceAssignments: ResourceAssignments,
4444
private oidcIntegrationStore: OIDCIntegrationStore,
@@ -47,7 +47,7 @@ export class OIDCIntegrationsProvider {
4747
}
4848

4949
isEnabled() {
50-
return this.enabled;
50+
return this.oidcIntegrationConfig.isEnabled;
5151
}
5252

5353
async canViewerManageIntegrationForOrganization(organizationId: string) {
@@ -64,6 +64,13 @@ export class OIDCIntegrationsProvider {
6464
});
6565
}
6666

67+
async canViewerManageSCIMForOrganization(organization: Organization) {
68+
return (
69+
(await this.canViewerManageIntegrationForOrganization(organization.id)) &&
70+
(organization.featureFlags.scim || this.oidcIntegrationConfig.isSCIMEnabled)
71+
);
72+
}
73+
6774
async getOIDCIntegrationForOrganization(args: {
6875
organizationId: string;
6976
skipAccessCheck?: boolean;
@@ -416,6 +423,19 @@ export class OIDCIntegrationsProvider {
416423
},
417424
});
418425

426+
if (args.userProvisioningRequired === true) {
427+
const organization = await this.storage.getOrganization({
428+
organizationId: oidcIntegration.linkedOrganizationId,
429+
});
430+
431+
if (!organization.featureFlags.scim) {
432+
return {
433+
type: 'error',
434+
message: 'SCIM provisioning is disabled.',
435+
} as const;
436+
}
437+
}
438+
419439
return {
420440
type: 'ok',
421441
oidcIntegration: await this.storage.updateOIDCRestrictions(args),

packages/services/api/src/modules/oidc-integrations/providers/tokens.ts

Lines changed: 0 additions & 3 deletions
This file was deleted.

packages/services/api/src/modules/oidc-integrations/resolvers/Organization.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import type { OrganizationResolvers } from './../../../__generated__/types';
33

44
export const Organization: Pick<
55
OrganizationResolvers,
6-
'oidcIntegration' | 'viewerCanManageOIDCIntegration'
6+
'oidcIntegration' | 'viewerCanManageOIDCIntegration' | 'viewerCanManageSCIM'
77
> = {
88
oidcIntegration: async (organization, _, { injector }) => {
99
if (injector.get(OIDCIntegrationsProvider).isEnabled() === false) {
@@ -19,4 +19,7 @@ export const Organization: Pick<
1919
.get(OIDCIntegrationsProvider)
2020
.canViewerManageIntegrationForOrganization(organization.id);
2121
},
22+
viewerCanManageSCIM(organization, _, { injector }) {
23+
return injector.get(OIDCIntegrationsProvider).canViewerManageSCIMForOrganization(organization);
24+
},
2225
};

packages/services/api/src/modules/organization/providers/organization-access-tokens.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1000,7 +1000,8 @@ export class OrganizationAccessTokens {
10001000
return (id: Permission) =>
10011001
(!isAppDeploymentsEnabled && id.startsWith('appDeployment:')) ||
10021002
(!isOTELTracingEnabled && id.startsWith('traces:')) ||
1003-
(!isSchemaProposalsEnabled && id.startsWith('schemaProposal:'))
1003+
(!isSchemaProposalsEnabled && id.startsWith('schemaProposal:')) ||
1004+
(!organization.featureFlags.scim && id.startsWith('scim:provision'))
10041005
? false
10051006
: true;
10061007
}

0 commit comments

Comments
 (0)