diff --git a/apps/web/src/app/organizations/layouts/organization-layout.component.ts b/apps/web/src/app/organizations/layouts/organization-layout.component.ts
index 2657e70f0a..c9dcbd5cf3 100644
--- a/apps/web/src/app/organizations/layouts/organization-layout.component.ts
+++ b/apps/web/src/app/organizations/layouts/organization-layout.component.ts
@@ -86,6 +86,9 @@ export class OrganizationLayoutComponent implements OnInit, OnDestroy {
case this.organization.canManageSso:
route = "manage/sso";
break;
+ case this.organization.canManageScim:
+ route = "manage/scim";
+ break;
case this.organization.canAccessEventLogs:
route = "manage/events";
break;
diff --git a/apps/web/src/app/organizations/manage/manage.component.html b/apps/web/src/app/organizations/manage/manage.component.html
index 63718db432..0ec25a1310 100644
--- a/apps/web/src/app/organizations/manage/manage.component.html
+++ b/apps/web/src/app/organizations/manage/manage.component.html
@@ -52,6 +52,14 @@
>
{{ "eventLogs" | i18n }}
+
+ {{ "singleSignOn" | i18n }}
+
diff --git a/apps/web/src/app/organizations/manage/manage.component.ts b/apps/web/src/app/organizations/manage/manage.component.ts
index aad670c5e1..410a4927d2 100644
--- a/apps/web/src/app/organizations/manage/manage.component.ts
+++ b/apps/web/src/app/organizations/manage/manage.component.ts
@@ -14,6 +14,7 @@ export class ManageComponent implements OnInit {
accessGroups = false;
accessEvents = false;
accessSso = false;
+ accessScim = false;
constructor(private route: ActivatedRoute, private organizationService: OrganizationService) {}
@@ -24,6 +25,7 @@ export class ManageComponent implements OnInit {
this.accessSso = this.organization.useSso;
this.accessEvents = this.organization.useEvents;
this.accessGroups = this.organization.useGroups;
+ this.accessScim = this.organization.useScim;
});
}
}
diff --git a/apps/web/src/app/organizations/services/navigation-permissions.service.ts b/apps/web/src/app/organizations/services/navigation-permissions.service.ts
index e85d30096f..cddd480ceb 100644
--- a/apps/web/src/app/organizations/services/navigation-permissions.service.ts
+++ b/apps/web/src/app/organizations/services/navigation-permissions.service.ts
@@ -13,6 +13,7 @@ const permissions = {
Permissions.ManageUsers,
Permissions.ManagePolicies,
Permissions.ManageSso,
+ Permissions.ManageScim,
],
tools: [Permissions.AccessImportExport, Permissions.AccessReports],
settings: [Permissions.ManageOrganization],
diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json
index 0e32e2dd53..32f007074c 100644
--- a/apps/web/src/locales/en/messages.json
+++ b/apps/web/src/locales/en/messages.json
@@ -5165,5 +5165,9 @@
"example": "My Email"
}
}
+ },
+ "scim": {
+ "message": "SCIM Provisioning",
+ "description": "This text, 'SCIM', is an acronymn and should not be translated."
}
}
diff --git a/bitwarden_license/bit-web/src/app/organizations/manage/scim.component.html b/bitwarden_license/bit-web/src/app/organizations/manage/scim.component.html
new file mode 100644
index 0000000000..fddc0e2e51
--- /dev/null
+++ b/bitwarden_license/bit-web/src/app/organizations/manage/scim.component.html
@@ -0,0 +1,16 @@
+
+
+
+
+ {{ "loading" | i18n }}
+
+
+
diff --git a/bitwarden_license/bit-web/src/app/organizations/manage/scim.component.ts b/bitwarden_license/bit-web/src/app/organizations/manage/scim.component.ts
new file mode 100644
index 0000000000..a32d0b177e
--- /dev/null
+++ b/bitwarden_license/bit-web/src/app/organizations/manage/scim.component.ts
@@ -0,0 +1,63 @@
+import { Component, OnInit } from "@angular/core";
+import { ActivatedRoute } from "@angular/router";
+
+import { ApiService } from "@bitwarden/common/abstractions/api.service";
+import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
+import { OrganizationService } from "@bitwarden/common/abstractions/organization.service";
+import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
+import { OrganizationApiKeyType } from "@bitwarden/common/enums/organizationApiKeyType";
+import { OrganizationConnectionType } from "@bitwarden/common/enums/organizationConnectionType";
+import { ScimConfigApi } from "@bitwarden/common/models/api/scimConfigApi";
+import { Organization } from "@bitwarden/common/models/domain/organization";
+
+@Component({
+ selector: "app-org-manage-scim",
+ templateUrl: "scim.component.html",
+})
+export class ScimComponent implements OnInit {
+ loading = true;
+ organizationId: string;
+ organization: Organization;
+ formPromise: Promise;
+
+ endpointUrl: string;
+
+ constructor(
+ private route: ActivatedRoute,
+ private apiService: ApiService,
+ private platformUtilsService: PlatformUtilsService,
+ private i18nService: I18nService,
+ private organizationService: OrganizationService
+ ) {}
+
+ async ngOnInit() {
+ this.route.parent.parent.params.subscribe(async (params) => {
+ this.organizationId = params.organizationId;
+ await this.load();
+ });
+ }
+
+ async load() {
+ this.organization = await this.organizationService.get(this.organizationId);
+ //TODO: Load the SCIM configuration (connection) and API Key
+ this.endpointUrl = "https://example.com/callback/scim";
+ this.loading = false;
+ }
+
+ async submit() {
+ //this.validateForm();
+
+ //TODO: POST data update to server and update display
+ //this.formPromise = POST;
+
+ try {
+ //const response = await this.formPromise;
+ //this.populateForm(response);
+ this.platformUtilsService.showToast("success", null, this.i18nService.t("scimSettingsSaved"));
+ } catch {
+ // Logged by appApiAction, do nothing
+ }
+
+ this.formPromise = null;
+ }
+}
diff --git a/bitwarden_license/bit-web/src/app/organizations/organizations-routing.module.ts b/bitwarden_license/bit-web/src/app/organizations/organizations-routing.module.ts
index 0052f0d67b..1bfd51b7d3 100644
--- a/bitwarden_license/bit-web/src/app/organizations/organizations-routing.module.ts
+++ b/bitwarden_license/bit-web/src/app/organizations/organizations-routing.module.ts
@@ -9,6 +9,7 @@ import { OrganizationLayoutComponent } from "src/app/organizations/layouts/organ
import { ManageComponent } from "src/app/organizations/manage/manage.component";
import { NavigationPermissionsService } from "src/app/organizations/services/navigation-permissions.service";
+import { ScimComponent } from "./manage/scim.component";
import { SsoComponent } from "./manage/sso.component";
const routes: Routes = [
@@ -33,6 +34,14 @@ const routes: Routes = [
permissions: [Permissions.ManageSso],
},
},
+ {
+ path: "scim",
+ component: ScimComponent,
+ canActivate: [PermissionsGuard],
+ data: {
+ permissions: [Permissions.ManageScim],
+ },
+ },
],
},
],
diff --git a/libs/common/src/abstractions/api.service.ts b/libs/common/src/abstractions/api.service.ts
index d485328d0d..55590da7cc 100644
--- a/libs/common/src/abstractions/api.service.ts
+++ b/libs/common/src/abstractions/api.service.ts
@@ -1,3 +1,4 @@
+import { OrganizationApiKeyType } from "../enums/organizationApiKeyType";
import { OrganizationConnectionType } from "../enums/organizationConnectionType";
import { PolicyType } from "../enums/policyType";
import { SetKeyConnectorKeyRequest } from "../models/request/account/setKeyConnectorKeyRequest";
@@ -569,7 +570,8 @@ export abstract class ApiService {
request: OrganizationApiKeyRequest
) => Promise;
getOrganizationApiKeyInformation: (
- id: string
+ id: string,
+ type?: OrganizationApiKeyType
) => Promise>;
postOrganizationRotateApiKey: (
id: string,
diff --git a/libs/common/src/enums/organizationApiKeyType.ts b/libs/common/src/enums/organizationApiKeyType.ts
index 27479a8de0..44ba7f8391 100644
--- a/libs/common/src/enums/organizationApiKeyType.ts
+++ b/libs/common/src/enums/organizationApiKeyType.ts
@@ -1,4 +1,5 @@
export enum OrganizationApiKeyType {
Default = 0,
BillingSync = 1,
+ Scim = 2,
}
diff --git a/libs/common/src/enums/organizationConnectionType.ts b/libs/common/src/enums/organizationConnectionType.ts
index 1291874370..d2f9700a6a 100644
--- a/libs/common/src/enums/organizationConnectionType.ts
+++ b/libs/common/src/enums/organizationConnectionType.ts
@@ -1,3 +1,4 @@
export enum OrganizationConnectionType {
CloudBillingSync = 1,
+ Scim = 2,
}
diff --git a/libs/common/src/enums/permissions.ts b/libs/common/src/enums/permissions.ts
index bb0021506a..46ee906620 100644
--- a/libs/common/src/enums/permissions.ts
+++ b/libs/common/src/enums/permissions.ts
@@ -25,4 +25,5 @@ export enum Permissions {
DeleteAssignedCollections,
ManageSso,
ManageBilling,
+ ManageScim,
}
diff --git a/libs/common/src/enums/scimProviderType.ts b/libs/common/src/enums/scimProviderType.ts
new file mode 100644
index 0000000000..ac16ff8b94
--- /dev/null
+++ b/libs/common/src/enums/scimProviderType.ts
@@ -0,0 +1,8 @@
+export enum ScimProviderType {
+ AzureAd = 0,
+ Okta = 1,
+ OneLogin = 2,
+ JumpCloud = 3,
+ GoogleWorkspace = 4,
+ Rippling = 5,
+}
diff --git a/libs/common/src/models/api/permissionsApi.ts b/libs/common/src/models/api/permissionsApi.ts
index bac79bd3cf..c35cb69cb5 100644
--- a/libs/common/src/models/api/permissionsApi.ts
+++ b/libs/common/src/models/api/permissionsApi.ts
@@ -25,6 +25,7 @@ export class PermissionsApi extends BaseResponse {
managePolicies: boolean;
manageUsers: boolean;
manageResetPassword: boolean;
+ manageScim: boolean;
constructor(data: any = null) {
super(data);
@@ -51,5 +52,6 @@ export class PermissionsApi extends BaseResponse {
this.managePolicies = this.getResponseProperty("ManagePolicies");
this.manageUsers = this.getResponseProperty("ManageUsers");
this.manageResetPassword = this.getResponseProperty("ManageResetPassword");
+ this.manageScim = this.getResponseProperty("ManageScim");
}
}
diff --git a/libs/common/src/models/api/scimConfigApi.ts b/libs/common/src/models/api/scimConfigApi.ts
new file mode 100644
index 0000000000..2269ac5969
--- /dev/null
+++ b/libs/common/src/models/api/scimConfigApi.ts
@@ -0,0 +1,17 @@
+import { ScimProviderType } from "@bitwarden/common/enums/scimProviderType";
+
+import { BaseResponse } from "../response/baseResponse";
+
+export class ScimConfigApi extends BaseResponse {
+ enabled: boolean;
+ scimProvider: ScimProviderType;
+
+ constructor(data: any) {
+ super(data);
+ if (data == null) {
+ return;
+ }
+ this.enabled = this.getResponseProperty("Enabled");
+ this.scimProvider = this.getResponseProperty("ScimProvider");
+ }
+}
diff --git a/libs/common/src/models/data/organizationData.ts b/libs/common/src/models/data/organizationData.ts
index d093821fc3..5d5e522959 100644
--- a/libs/common/src/models/data/organizationData.ts
+++ b/libs/common/src/models/data/organizationData.ts
@@ -19,6 +19,7 @@ export class OrganizationData {
useApi: boolean;
useSso: boolean;
useKeyConnector: boolean;
+ useScim: boolean;
useResetPassword: boolean;
selfHost: boolean;
usersGetPremium: boolean;
@@ -58,6 +59,7 @@ export class OrganizationData {
this.useApi = response.useApi;
this.useSso = response.useSso;
this.useKeyConnector = response.useKeyConnector;
+ this.useScim = response.useScim;
this.useResetPassword = response.useResetPassword;
this.selfHost = response.selfHost;
this.usersGetPremium = response.usersGetPremium;
diff --git a/libs/common/src/models/domain/organization.ts b/libs/common/src/models/domain/organization.ts
index ba1c8e1a17..c15d936234 100644
--- a/libs/common/src/models/domain/organization.ts
+++ b/libs/common/src/models/domain/organization.ts
@@ -20,6 +20,7 @@ export class Organization {
useApi: boolean;
useSso: boolean;
useKeyConnector: boolean;
+ useScim: boolean;
useResetPassword: boolean;
selfHost: boolean;
usersGetPremium: boolean;
@@ -63,6 +64,7 @@ export class Organization {
this.useApi = obj.useApi;
this.useSso = obj.useSso;
this.useKeyConnector = obj.useKeyConnector;
+ this.useScim = obj.useScim;
this.useResetPassword = obj.useResetPassword;
this.selfHost = obj.selfHost;
this.usersGetPremium = obj.usersGetPremium;
@@ -173,6 +175,10 @@ export class Organization {
return this.isAdmin || this.permissions.manageSso;
}
+ get canManageScim() {
+ return this.isAdmin || this.permissions.manageScim;
+ }
+
get canManagePolicies() {
return this.isAdmin || this.permissions.managePolicies;
}
@@ -207,6 +213,7 @@ export class Organization {
(permissions.includes(Permissions.ManageUsers) && this.canManageUsers) ||
(permissions.includes(Permissions.ManageUsersPassword) && this.canManageUsersPassword) ||
(permissions.includes(Permissions.ManageSso) && this.canManageSso) ||
+ (permissions.includes(Permissions.ManageScim) && this.canManageScim) ||
(permissions.includes(Permissions.ManageBilling) && this.canManageBilling);
return specifiedPermissions && (this.enabled || this.isOwner);
diff --git a/libs/common/src/models/response/profileOrganizationResponse.ts b/libs/common/src/models/response/profileOrganizationResponse.ts
index 39b7644caa..0287ae96c9 100644
--- a/libs/common/src/models/response/profileOrganizationResponse.ts
+++ b/libs/common/src/models/response/profileOrganizationResponse.ts
@@ -17,6 +17,7 @@ export class ProfileOrganizationResponse extends BaseResponse {
useApi: boolean;
useSso: boolean;
useKeyConnector: boolean;
+ useScim: boolean;
useResetPassword: boolean;
selfHost: boolean;
usersGetPremium: boolean;
@@ -57,6 +58,7 @@ export class ProfileOrganizationResponse extends BaseResponse {
this.useApi = this.getResponseProperty("UseApi");
this.useSso = this.getResponseProperty("UseSso");
this.useKeyConnector = this.getResponseProperty("UseKeyConnector") ?? false;
+ this.useScim = this.getResponseProperty("UseScim") ?? false;
this.useResetPassword = this.getResponseProperty("UseResetPassword");
this.selfHost = this.getResponseProperty("SelfHost");
this.usersGetPremium = this.getResponseProperty("UsersGetPremium");
diff --git a/libs/common/src/services/api.service.ts b/libs/common/src/services/api.service.ts
index 9f2108663c..72da746370 100644
--- a/libs/common/src/services/api.service.ts
+++ b/libs/common/src/services/api.service.ts
@@ -4,6 +4,7 @@ import { EnvironmentService } from "../abstractions/environment.service";
import { PlatformUtilsService } from "../abstractions/platformUtils.service";
import { TokenService } from "../abstractions/token.service";
import { DeviceType } from "../enums/deviceType";
+import { OrganizationApiKeyType } from "../enums/organizationApiKeyType";
import { OrganizationConnectionType } from "../enums/organizationConnectionType";
import { PolicyType } from "../enums/policyType";
import { Utils } from "../misc/utils";
@@ -1840,15 +1841,14 @@ export class ApiService implements ApiServiceAbstraction {
}
async getOrganizationApiKeyInformation(
- id: string
+ id: string,
+ type: OrganizationApiKeyType = null
): Promise> {
- const r = await this.send(
- "GET",
- "/organizations/" + id + "/api-key-information",
- null,
- true,
- true
- );
+ const uri =
+ type === null
+ ? "/organizations/" + id + "/api-key-information"
+ : "/organizations/" + id + "/api-key-information/" + type;
+ const r = await this.send("GET", uri, null, true, true);
return new ListResponse(r, OrganizationApiKeyInformationResponse);
}