From 664b8dc13ec83ddf36f2a18527dbd80e4bc12ea8 Mon Sep 17 00:00:00 2001 From: Chad Scharf <3904944+cscharf@users.noreply.github.com> Date: Fri, 24 Jun 2022 14:56:45 -0400 Subject: [PATCH] EC-265 - Initial stubs for SCIM config UI --- .../layouts/organization-layout.component.ts | 3 + .../manage/manage.component.html | 8 +++ .../organizations/manage/manage.component.ts | 2 + .../navigation-permissions.service.ts | 1 + apps/web/src/locales/en/messages.json | 4 ++ .../organizations/manage/scim.component.html | 16 +++++ .../organizations/manage/scim.component.ts | 63 +++++++++++++++++++ .../organizations-routing.module.ts | 9 +++ libs/common/src/abstractions/api.service.ts | 4 +- .../src/enums/organizationApiKeyType.ts | 1 + .../src/enums/organizationConnectionType.ts | 1 + libs/common/src/enums/permissions.ts | 1 + libs/common/src/enums/scimProviderType.ts | 8 +++ libs/common/src/models/api/permissionsApi.ts | 2 + libs/common/src/models/api/scimConfigApi.ts | 17 +++++ .../src/models/data/organizationData.ts | 2 + libs/common/src/models/domain/organization.ts | 7 +++ .../response/profileOrganizationResponse.ts | 2 + libs/common/src/services/api.service.ts | 16 ++--- 19 files changed, 158 insertions(+), 9 deletions(-) create mode 100644 bitwarden_license/bit-web/src/app/organizations/manage/scim.component.html create mode 100644 bitwarden_license/bit-web/src/app/organizations/manage/scim.component.ts create mode 100644 libs/common/src/enums/scimProviderType.ts create mode 100644 libs/common/src/models/api/scimConfigApi.ts 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); }