diff --git a/common/src/abstractions/api.service.ts b/common/src/abstractions/api.service.ts index 67131df8d96..bc8c4d87e84 100644 --- a/common/src/abstractions/api.service.ts +++ b/common/src/abstractions/api.service.ts @@ -51,6 +51,15 @@ import { PasswordVerificationRequest } from '../models/request/passwordVerificat import { PaymentRequest } from '../models/request/paymentRequest'; import { PolicyRequest } from '../models/request/policyRequest'; import { PreloginRequest } from '../models/request/preloginRequest'; +import { ProviderAddOrganizationRequest } from '../models/request/provider/providerAddOrganizationRequest'; +import { ProviderSetupRequest } from '../models/request/provider/providerSetupRequest'; +import { ProviderUpdateRequest } from '../models/request/provider/providerUpdateRequest'; +import { ProviderUserAcceptRequest } from '../models/request/provider/providerUserAcceptRequest'; +import { ProviderUserBulkConfirmRequest } from '../models/request/provider/providerUserBulkConfirmRequest'; +import { ProviderUserBulkRequest } from '../models/request/provider/providerUserBulkRequest'; +import { ProviderUserConfirmRequest } from '../models/request/provider/providerUserConfirmRequest'; +import { ProviderUserInviteRequest } from '../models/request/provider/providerUserInviteRequest'; +import { ProviderUserUpdateRequest } from '../models/request/provider/providerUserUpdateRequest'; import { RegisterRequest } from '../models/request/registerRequest'; import { SeatRequest } from '../models/request/seatRequest'; import { SelectionReadOnlyRequest } from '../models/request/selectionReadOnlyRequest'; @@ -117,6 +126,11 @@ import { PlanResponse } from '../models/response/planResponse'; import { PolicyResponse } from '../models/response/policyResponse'; import { PreloginResponse } from '../models/response/preloginResponse'; import { ProfileResponse } from '../models/response/profileResponse'; +import { ProviderOrganizationOrganizationDetailsResponse } from '../models/response/provider/providerOrganizationResponse'; +import { ProviderResponse } from '../models/response/provider/providerResponse'; +import { ProviderUserBulkPublicKeyResponse } from '../models/response/provider/providerUserBulkPublicKeyResponse'; +import { ProviderUserBulkResponse } from '../models/response/provider/providerUserBulkResponse'; +import { ProviderUserResponse, ProviderUserUserDetailsResponse } from '../models/response/provider/providerUserResponse'; import { SelectionReadOnlyResponse } from '../models/response/selectionReadOnlyResponse'; import { SendAccessResponse } from '../models/response/sendAccessResponse'; import { SendFileDownloadDataResponse } from '../models/response/sendFileDownloadDataResponse'; @@ -379,12 +393,36 @@ export abstract class ApiService { getOrganizationKeys: (id: string) => Promise; postOrganizationKeys: (id: string, request: OrganizationKeysRequest) => Promise; + postProviderSetup: (id: string, request: ProviderSetupRequest) => Promise; + getProvider: (id: string) => Promise; + putProvider: (id: string, request: ProviderUpdateRequest) => Promise; + + getProviderUsers: (providerId: string) => Promise>; + getProviderUser: (providerId: string, id: string) => Promise; + postProviderUserInvite: (providerId: string, request: ProviderUserInviteRequest) => Promise; + postProviderUserReinvite: (providerId: string, id: string) => Promise; + postManyProviderUserReinvite: (providerId: string, request: ProviderUserBulkRequest) => Promise>; + postProviderUserAccept: (providerId: string, id: string, request: ProviderUserAcceptRequest) => Promise; + postProviderUserConfirm: (providerId: string, id: string, request: ProviderUserConfirmRequest) => Promise; + postProviderUsersPublicKey: (providerId: string, request: ProviderUserBulkRequest) => + Promise>; + postProviderUserBulkConfirm: (providerId: string, request: ProviderUserBulkConfirmRequest) => Promise>; + putProviderUser: (providerId: string, id: string, request: ProviderUserUpdateRequest) => Promise; + deleteProviderUser: (organizationId: string, id: string) => Promise; + deleteManyProviderUsers: (providerId: string, request: ProviderUserBulkRequest) => Promise>; + getProviderClients: (providerId: string) => Promise>; + postProviderAddOrganization: (providerId: string, request: ProviderAddOrganizationRequest) => Promise; + postProviderCreateOrganization: (providerId: string, request: OrganizationCreateRequest) => Promise; + deleteProviderOrganization: (providerId: string, organizationId: string) => Promise; + getEvents: (start: string, end: string, token: string) => Promise>; getEventsCipher: (id: string, start: string, end: string, token: string) => Promise>; getEventsOrganization: (id: string, start: string, end: string, token: string) => Promise>; getEventsOrganizationUser: (organizationId: string, id: string, start: string, end: string, token: string) => Promise>; + getEventsProvider: (id: string, start: string, end: string, token: string) => Promise>; + getEventsProviderUser: (providerId: string, id: string, start: string, end: string, token: string) => Promise>; postEventsCollect: (request: EventRequest[]) => Promise; deleteSsoUser: (organizationId: string) => Promise; diff --git a/common/src/abstractions/crypto.service.ts b/common/src/abstractions/crypto.service.ts index 17a8294a21a..841e61ab07a 100644 --- a/common/src/abstractions/crypto.service.ts +++ b/common/src/abstractions/crypto.service.ts @@ -3,9 +3,12 @@ import { EncString } from '../models/domain/encString'; import { SymmetricCryptoKey } from '../models/domain/symmetricCryptoKey'; import { ProfileOrganizationResponse } from '../models/response/profileOrganizationResponse'; +import { ProfileProviderOrganizationResponse } from '../models/response/profileProviderOrganizationResponse'; +import { ProfileProviderResponse } from '../models/response/profileProviderResponse'; import { HashPurpose } from '../enums/hashPurpose'; import { KdfType } from '../enums/kdfType'; + import { KeySuffixOptions } from './storage.service'; export abstract class CryptoService { @@ -13,7 +16,8 @@ export abstract class CryptoService { setKeyHash: (keyHash: string) => Promise<{}>; setEncKey: (encKey: string) => Promise<{}>; setEncPrivateKey: (encPrivateKey: string) => Promise<{}>; - setOrgKeys: (orgs: ProfileOrganizationResponse[]) => Promise<{}>; + setOrgKeys: (orgs: ProfileOrganizationResponse[], providerOrgs: ProfileProviderOrganizationResponse[]) => Promise<{}>; + setProviderKeys: (orgs: ProfileProviderResponse[]) => Promise<{}>; getKey: (keySuffix?: KeySuffixOptions) => Promise; getKeyFromStorage: (keySuffix: KeySuffixOptions) => Promise; getKeyHash: () => Promise; @@ -24,6 +28,7 @@ export abstract class CryptoService { getFingerprint: (userId: string, publicKey?: ArrayBuffer) => Promise; getOrgKeys: () => Promise>; getOrgKey: (orgId: string) => Promise; + getProviderKey: (providerId: string) => Promise; hasKey: () => Promise; hasKeyInMemory: () => boolean; hasKeyStored: (keySuffix?: KeySuffixOptions) => Promise; @@ -33,6 +38,7 @@ export abstract class CryptoService { clearEncKey: (memoryOnly?: boolean) => Promise; clearKeyPair: (memoryOnly?: boolean) => Promise; clearOrgKeys: (memoryOnly?: boolean) => Promise; + clearProviderKeys: (memoryOnly?: boolean) => Promise; clearPinProtectedKey: () => Promise; clearKeys: () => Promise; toggleKey: () => Promise; diff --git a/common/src/abstractions/user.service.ts b/common/src/abstractions/user.service.ts index ed7e43c38a1..8480c100816 100644 --- a/common/src/abstractions/user.service.ts +++ b/common/src/abstractions/user.service.ts @@ -1,5 +1,7 @@ import { OrganizationData } from '../models/data/organizationData'; +import { ProviderData } from '../models/data/providerData'; import { Organization } from '../models/domain/organization'; +import { Provider } from '../models/domain/provider'; import { KdfType } from '../enums/kdfType'; @@ -20,4 +22,8 @@ export abstract class UserService { getAllOrganizations: () => Promise; replaceOrganizations: (organizations: { [id: string]: OrganizationData; }) => Promise; clearOrganizations: (userId: string) => Promise; + getProvider: (id: string) => Promise; + getAllProviders: () => Promise; + replaceProviders: (providers: { [id: string]: ProviderData; }) => Promise; + clearProviders: (userId: string) => Promise; } diff --git a/common/src/enums/eventType.ts b/common/src/enums/eventType.ts index e2d59a20f59..39416d448b8 100644 --- a/common/src/enums/eventType.ts +++ b/common/src/enums/eventType.ts @@ -50,4 +50,9 @@ export enum EventType { // Organization_ClientExportedVault = 1602, Policy_Updated = 1700, + + ProviderUser_Invited = 1800, + ProviderUser_Confirmed = 1801, + ProviderUser_Updated = 1802, + ProviderUser_Removed = 1803, } diff --git a/common/src/enums/permissions.ts b/common/src/enums/permissions.ts index 7d0b4232baa..7eede95ed8a 100644 --- a/common/src/enums/permissions.ts +++ b/common/src/enums/permissions.ts @@ -8,6 +8,7 @@ export enum Permissions { ManageGroups, ManageOrganization, ManagePolicies, + ManageProvider, ManageUsers, ManageUsersPassword, } diff --git a/common/src/enums/providerUserStatusType.ts b/common/src/enums/providerUserStatusType.ts new file mode 100644 index 00000000000..8b0e55de686 --- /dev/null +++ b/common/src/enums/providerUserStatusType.ts @@ -0,0 +1,5 @@ +export enum ProviderUserStatusType { + Invited = 0, + Accepted = 1, + Confirmed = 2, +} diff --git a/common/src/enums/providerUserType.ts b/common/src/enums/providerUserType.ts new file mode 100644 index 00000000000..326fa0aaea2 --- /dev/null +++ b/common/src/enums/providerUserType.ts @@ -0,0 +1,4 @@ +export enum ProviderUserType { + ProviderAdmin = 0, + ServiceUser = 1, +} diff --git a/common/src/models/data/organizationData.ts b/common/src/models/data/organizationData.ts index 985fa059f37..21979fbceda 100644 --- a/common/src/models/data/organizationData.ts +++ b/common/src/models/data/organizationData.ts @@ -31,6 +31,9 @@ export class OrganizationData { resetPasswordEnrolled: boolean; userId: string; hasPublicAndPrivateKeys: boolean; + providerId: string; + providerName: string; + isProviderUser: boolean; constructor(response: ProfileOrganizationResponse) { this.id = response.id; @@ -59,5 +62,7 @@ export class OrganizationData { this.resetPasswordEnrolled = response.resetPasswordEnrolled; this.userId = response.userId; this.hasPublicAndPrivateKeys = response.hasPublicAndPrivateKeys; + this.providerId = response.providerId; + this.providerName = response.providerName; } } diff --git a/common/src/models/data/providerData.ts b/common/src/models/data/providerData.ts new file mode 100644 index 00000000000..990a01900ce --- /dev/null +++ b/common/src/models/data/providerData.ts @@ -0,0 +1,24 @@ +import { ProfileProviderResponse } from '../response/profileProviderResponse'; + +import { ProviderUserStatusType } from '../../enums/providerUserStatusType'; +import { ProviderUserType } from '../../enums/providerUserType'; + +export class ProviderData { + id: string; + name: string; + status: ProviderUserStatusType; + type: ProviderUserType; + enabled: boolean; + userId: string; + useEvents: boolean; + + constructor(response: ProfileProviderResponse) { + this.id = response.id; + this.name = response.name; + this.status = response.status; + this.type = response.type; + this.enabled = response.enabled; + this.userId = response.userId; + this.useEvents = response.useEvents; + } +} diff --git a/common/src/models/domain/organization.ts b/common/src/models/domain/organization.ts index 435be1b003e..b6bc5e0e759 100644 --- a/common/src/models/domain/organization.ts +++ b/common/src/models/domain/organization.ts @@ -32,6 +32,9 @@ export class Organization { resetPasswordEnrolled: boolean; userId: string; hasPublicAndPrivateKeys: boolean; + providerId: string; + providerName: string; + isProviderUser: boolean; constructor(obj?: OrganizationData) { if (obj == null) { @@ -64,6 +67,9 @@ export class Organization { this.resetPasswordEnrolled = obj.resetPasswordEnrolled; this.userId = obj.userId; this.hasPublicAndPrivateKeys = obj.hasPublicAndPrivateKeys; + this.providerId = obj.providerId; + this.providerName = obj.providerName; + this.isProviderUser = obj.isProviderUser; } get canAccess() { diff --git a/common/src/models/domain/provider.ts b/common/src/models/domain/provider.ts new file mode 100644 index 00000000000..24031ea190c --- /dev/null +++ b/common/src/models/domain/provider.ts @@ -0,0 +1,50 @@ +import { ProviderUserStatusType } from '../../enums/providerUserStatusType'; +import { ProviderUserType } from '../../enums/providerUserType'; +import { ProviderData } from '../data/providerData'; + +export class Provider { + id: string; + name: string; + status: ProviderUserStatusType; + type: ProviderUserType; + enabled: boolean; + userId: string; + useEvents: boolean; + + constructor(obj?: ProviderData) { + if (obj == null) { + return; + } + + this.id = obj.id; + this.name = obj.name; + this.status = obj.status; + this.type = obj.type; + this.enabled = obj.enabled; + this.userId = obj.userId; + this.useEvents = obj.useEvents; + } + + get canAccess() { + if (this.isProviderAdmin) { + return true; + } + return this.enabled && this.status === ProviderUserStatusType.Confirmed; + } + + get canCreateOrganizations() { + return this.enabled && this.isProviderAdmin; + } + + get canManageUsers() { + return this.isProviderAdmin; + } + + get canAccessEventLogs() { + return this.isProviderAdmin; + } + + get isProviderAdmin() { + return this.type === ProviderUserType.ProviderAdmin; + } +} diff --git a/common/src/models/request/provider/providerAddOrganizationRequest.ts b/common/src/models/request/provider/providerAddOrganizationRequest.ts new file mode 100644 index 00000000000..ebdd1bc6a4f --- /dev/null +++ b/common/src/models/request/provider/providerAddOrganizationRequest.ts @@ -0,0 +1,4 @@ +export class ProviderAddOrganizationRequest { + organizationId: string; + key: string; +} diff --git a/common/src/models/request/provider/providerSetupRequest.ts b/common/src/models/request/provider/providerSetupRequest.ts new file mode 100644 index 00000000000..5063fec067c --- /dev/null +++ b/common/src/models/request/provider/providerSetupRequest.ts @@ -0,0 +1,7 @@ +export class ProviderSetupRequest { + name: string; + businessName: string; + billingEmail: string; + token: string; + key: string; +} diff --git a/common/src/models/request/provider/providerUpdateRequest.ts b/common/src/models/request/provider/providerUpdateRequest.ts new file mode 100644 index 00000000000..d30e6346bde --- /dev/null +++ b/common/src/models/request/provider/providerUpdateRequest.ts @@ -0,0 +1,5 @@ +export class ProviderUpdateRequest { + name: string; + businessName: string; + billingEmail: string; +} diff --git a/common/src/models/request/provider/providerUserAcceptRequest.ts b/common/src/models/request/provider/providerUserAcceptRequest.ts new file mode 100644 index 00000000000..15e0370b451 --- /dev/null +++ b/common/src/models/request/provider/providerUserAcceptRequest.ts @@ -0,0 +1,3 @@ +export class ProviderUserAcceptRequest { + token: string; +} diff --git a/common/src/models/request/provider/providerUserBulkConfirmRequest.ts b/common/src/models/request/provider/providerUserBulkConfirmRequest.ts new file mode 100644 index 00000000000..cb5a252c368 --- /dev/null +++ b/common/src/models/request/provider/providerUserBulkConfirmRequest.ts @@ -0,0 +1,12 @@ +type ProviderUserBulkRequestEntry = { + id: string; + key: string; +}; + +export class ProviderUserBulkConfirmRequest { + keys: ProviderUserBulkRequestEntry[]; + + constructor(keys: ProviderUserBulkRequestEntry[]) { + this.keys = keys; + } +} diff --git a/common/src/models/request/provider/providerUserBulkRequest.ts b/common/src/models/request/provider/providerUserBulkRequest.ts new file mode 100644 index 00000000000..e676b1f861d --- /dev/null +++ b/common/src/models/request/provider/providerUserBulkRequest.ts @@ -0,0 +1,7 @@ +export class ProviderUserBulkRequest { + ids: string[]; + + constructor(ids: string[]) { + this.ids = ids == null ? [] : ids; + } +} diff --git a/common/src/models/request/provider/providerUserConfirmRequest.ts b/common/src/models/request/provider/providerUserConfirmRequest.ts new file mode 100644 index 00000000000..8d7203d6001 --- /dev/null +++ b/common/src/models/request/provider/providerUserConfirmRequest.ts @@ -0,0 +1,3 @@ +export class ProviderUserConfirmRequest { + key: string; +} diff --git a/common/src/models/request/provider/providerUserInviteRequest.ts b/common/src/models/request/provider/providerUserInviteRequest.ts new file mode 100644 index 00000000000..d8daedd2a6a --- /dev/null +++ b/common/src/models/request/provider/providerUserInviteRequest.ts @@ -0,0 +1,6 @@ +import { ProviderUserType } from '../../../enums/providerUserType'; + +export class ProviderUserInviteRequest { + emails: string[] = []; + type: ProviderUserType; +} diff --git a/common/src/models/request/provider/providerUserUpdateRequest.ts b/common/src/models/request/provider/providerUserUpdateRequest.ts new file mode 100644 index 00000000000..77519e247b3 --- /dev/null +++ b/common/src/models/request/provider/providerUserUpdateRequest.ts @@ -0,0 +1,5 @@ +import { ProviderUserType } from '../../../enums/providerUserType'; + +export class ProviderUserUpdateRequest { + type: ProviderUserType; +} diff --git a/common/src/models/response/eventResponse.ts b/common/src/models/response/eventResponse.ts index 770133720e0..a9d9e6b040b 100644 --- a/common/src/models/response/eventResponse.ts +++ b/common/src/models/response/eventResponse.ts @@ -7,11 +7,13 @@ export class EventResponse extends BaseResponse { type: EventType; userId: string; organizationId: string; + providerId: string; cipherId: string; collectionId: string; groupId: string; policyId: string; organizationUserId: string; + providerUserId: string; actingUserId: string; date: string; deviceType: DeviceType; @@ -22,11 +24,13 @@ export class EventResponse extends BaseResponse { this.type = this.getResponseProperty('Type'); this.userId = this.getResponseProperty('UserId'); this.organizationId = this.getResponseProperty('OrganizationId'); + this.providerId = this.getResponseProperty('ProviderId'); this.cipherId = this.getResponseProperty('CipherId'); this.collectionId = this.getResponseProperty('CollectionId'); this.groupId = this.getResponseProperty('GroupId'); this.policyId = this.getResponseProperty('PolicyId'); this.organizationUserId = this.getResponseProperty('OrganizationUserId'); + this.providerUserId = this.getResponseProperty('ProviderUserId'); this.actingUserId = this.getResponseProperty('ActingUserId'); this.date = this.getResponseProperty('Date'); this.deviceType = this.getResponseProperty('DeviceType'); diff --git a/common/src/models/response/profileOrganizationResponse.ts b/common/src/models/response/profileOrganizationResponse.ts index 3a09c2e27f7..4ed13015ebd 100644 --- a/common/src/models/response/profileOrganizationResponse.ts +++ b/common/src/models/response/profileOrganizationResponse.ts @@ -32,6 +32,8 @@ export class ProfileOrganizationResponse extends BaseResponse { permissions: PermissionsApi; resetPasswordEnrolled: boolean; userId: string; + providerId: string; + providerName: string; constructor(response: any) { super(response); @@ -62,5 +64,7 @@ export class ProfileOrganizationResponse extends BaseResponse { this.permissions = new PermissionsApi(this.getResponseProperty('permissions')); this.resetPasswordEnrolled = this.getResponseProperty('ResetPasswordEnrolled'); this.userId = this.getResponseProperty('UserId'); + this.providerId = this.getResponseProperty('ProviderId'); + this.providerName = this.getResponseProperty('ProviderName'); } } diff --git a/common/src/models/response/profileProviderOrganizationResponse.ts b/common/src/models/response/profileProviderOrganizationResponse.ts new file mode 100644 index 00000000000..1519bf79fa8 --- /dev/null +++ b/common/src/models/response/profileProviderOrganizationResponse.ts @@ -0,0 +1,70 @@ +import { BaseResponse } from './baseResponse'; + +import { OrganizationUserStatusType } from '../../enums/organizationUserStatusType'; +import { OrganizationUserType } from '../../enums/organizationUserType'; +import { PermissionsApi } from '../api/permissionsApi'; + +export class ProfileProviderOrganizationResponse extends BaseResponse { + id: string; + name: string; + usePolicies: boolean; + useGroups: boolean; + useDirectory: boolean; + useEvents: boolean; + useTotp: boolean; + use2fa: boolean; + useApi: boolean; + useBusinessPortal: boolean; + useSso: boolean; + useResetPassword: boolean; + selfHost: boolean; + usersGetPremium: boolean; + seats: number; + maxCollections: number; + maxStorageGb?: number; + key: string; + hasPublicAndPrivateKeys: boolean; + status: OrganizationUserStatusType; + type: OrganizationUserType; + enabled: boolean; + ssoBound: boolean; + identifier: string; + permissions: PermissionsApi; + resetPasswordEnrolled: boolean; + userId: string; + providerId: string; + providerName: string; + + constructor(response: any) { + super(response); + this.id = this.getResponseProperty('Id'); + this.name = this.getResponseProperty('Name'); + this.usePolicies = this.getResponseProperty('UsePolicies'); + this.useGroups = this.getResponseProperty('UseGroups'); + this.useDirectory = this.getResponseProperty('UseDirectory'); + this.useEvents = this.getResponseProperty('UseEvents'); + this.useTotp = this.getResponseProperty('UseTotp'); + this.use2fa = this.getResponseProperty('Use2fa'); + this.useApi = this.getResponseProperty('UseApi'); + this.useBusinessPortal = this.getResponseProperty('UseBusinessPortal'); + this.useSso = this.getResponseProperty('UseSso'); + this.useResetPassword = this.getResponseProperty('UseResetPassword'); + this.selfHost = this.getResponseProperty('SelfHost'); + this.usersGetPremium = this.getResponseProperty('UsersGetPremium'); + this.seats = this.getResponseProperty('Seats'); + this.maxCollections = this.getResponseProperty('MaxCollections'); + this.maxStorageGb = this.getResponseProperty('MaxStorageGb'); + this.key = this.getResponseProperty('Key'); + this.hasPublicAndPrivateKeys = this.getResponseProperty('HasPublicAndPrivateKeys'); + this.status = this.getResponseProperty('Status'); + this.type = this.getResponseProperty('Type'); + this.enabled = this.getResponseProperty('Enabled'); + this.ssoBound = this.getResponseProperty('SsoBound'); + this.identifier = this.getResponseProperty('Identifier'); + this.permissions = new PermissionsApi(this.getResponseProperty('permissions')); + this.resetPasswordEnrolled = this.getResponseProperty('ResetPasswordEnrolled'); + this.userId = this.getResponseProperty('UserId'); + this.providerId = this.getResponseProperty('ProviderId'); + this.providerName = this.getResponseProperty('ProviderName'); + } +} diff --git a/common/src/models/response/profileProviderResponse.ts b/common/src/models/response/profileProviderResponse.ts new file mode 100644 index 00000000000..b3c234040ba --- /dev/null +++ b/common/src/models/response/profileProviderResponse.ts @@ -0,0 +1,31 @@ +import { BaseResponse } from './baseResponse'; + +import { ProviderUserStatusType } from '../../enums/providerUserStatusType'; +import { ProviderUserType } from '../../enums/providerUserType'; + +import { PermissionsApi } from '../api/permissionsApi'; + +export class ProfileProviderResponse extends BaseResponse { + id: string; + name: string; + key: string; + status: ProviderUserStatusType; + type: ProviderUserType; + enabled: boolean; + permissions: PermissionsApi; + userId: string; + useEvents: boolean; + + constructor(response: any) { + super(response); + this.id = this.getResponseProperty('Id'); + this.name = this.getResponseProperty('Name'); + this.key = this.getResponseProperty('Key'); + this.status = this.getResponseProperty('Status'); + this.type = this.getResponseProperty('Type'); + this.enabled = this.getResponseProperty('Enabled'); + this.permissions = new PermissionsApi(this.getResponseProperty('permissions')); + this.userId = this.getResponseProperty('UserId'); + this.useEvents = this.getResponseProperty('UseEvents'); + } +} diff --git a/common/src/models/response/profileResponse.ts b/common/src/models/response/profileResponse.ts index 95897d94897..fadda34235a 100644 --- a/common/src/models/response/profileResponse.ts +++ b/common/src/models/response/profileResponse.ts @@ -1,5 +1,7 @@ import { BaseResponse } from './baseResponse'; import { ProfileOrganizationResponse } from './profileOrganizationResponse'; +import { ProfileProviderOrganizationResponse } from './profileProviderOrganizationResponse'; +import { ProfileProviderResponse } from './profileProviderResponse'; export class ProfileResponse extends BaseResponse { id: string; @@ -14,6 +16,8 @@ export class ProfileResponse extends BaseResponse { privateKey: string; securityStamp: string; organizations: ProfileOrganizationResponse[] = []; + providers: ProfileProviderResponse[] = []; + providerOrganizations: ProfileProviderOrganizationResponse[] = []; constructor(response: any) { super(response); @@ -33,5 +37,13 @@ export class ProfileResponse extends BaseResponse { if (organizations != null) { this.organizations = organizations.map((o: any) => new ProfileOrganizationResponse(o)); } + const providers = this.getResponseProperty('Providers'); + if (providers != null) { + this.providers = providers.map((o: any) => new ProfileProviderResponse(o)); + } + const providerOrganizations = this.getResponseProperty('ProviderOrganizations'); + if (providerOrganizations != null) { + this.providerOrganizations = providerOrganizations.map((o: any) => new ProfileProviderOrganizationResponse(o)); + } } } diff --git a/common/src/models/response/provider/providerOrganizationResponse.ts b/common/src/models/response/provider/providerOrganizationResponse.ts new file mode 100644 index 00000000000..91fa6a46aea --- /dev/null +++ b/common/src/models/response/provider/providerOrganizationResponse.ts @@ -0,0 +1,31 @@ +import { BaseResponse } from '../baseResponse'; + +export class ProviderOrganizationResponse extends BaseResponse { + id: string; + providerId: string; + organizationId: string; + key: string; + settings: string; + creationDate: string; + revisionDate: string; + + constructor(response: any) { + super(response); + this.id = this.getResponseProperty('Id'); + this.providerId = this.getResponseProperty('ProviderId'); + this.organizationId = this.getResponseProperty('OrganizationId'); + this.key = this.getResponseProperty('Key'); + this.settings = this.getResponseProperty('Settings'); + this.creationDate = this.getResponseProperty('CreationDate'); + this.revisionDate = this.getResponseProperty('RevisionDate'); + } +} + +export class ProviderOrganizationOrganizationDetailsResponse extends ProviderOrganizationResponse { + organizationName: string; + + constructor(response: any) { + super(response); + this.organizationName = this.getResponseProperty('OrganizationName'); + } +} diff --git a/common/src/models/response/provider/providerResponse.ts b/common/src/models/response/provider/providerResponse.ts new file mode 100644 index 00000000000..d3d2364e2c6 --- /dev/null +++ b/common/src/models/response/provider/providerResponse.ts @@ -0,0 +1,16 @@ +import { BaseResponse } from '../baseResponse'; + +export class ProviderResponse extends BaseResponse { + id: string; + name: string; + businessName: string; + billingEmail: string; + + constructor(response: any) { + super(response); + this.id = this.getResponseProperty('Id'); + this.name = this.getResponseProperty('Name'); + this.businessName = this.getResponseProperty('BusinessName'); + this.billingEmail = this.getResponseProperty('BillingEmail'); + } +} diff --git a/common/src/models/response/provider/providerUserBulkPublicKeyResponse.ts b/common/src/models/response/provider/providerUserBulkPublicKeyResponse.ts new file mode 100644 index 00000000000..122be2aa90e --- /dev/null +++ b/common/src/models/response/provider/providerUserBulkPublicKeyResponse.ts @@ -0,0 +1,5 @@ +import { OrganizationUserBulkPublicKeyResponse } from '../organizationUserBulkPublicKeyResponse'; + +export class ProviderUserBulkPublicKeyResponse extends OrganizationUserBulkPublicKeyResponse { + +} diff --git a/common/src/models/response/provider/providerUserBulkResponse.ts b/common/src/models/response/provider/providerUserBulkResponse.ts new file mode 100644 index 00000000000..019ee4f5e95 --- /dev/null +++ b/common/src/models/response/provider/providerUserBulkResponse.ts @@ -0,0 +1,12 @@ +import { BaseResponse } from '../baseResponse'; + +export class ProviderUserBulkResponse extends BaseResponse { + id: string; + error: string; + + constructor(response: any) { + super(response); + this.id = this.getResponseProperty('Id'); + this.error = this.getResponseProperty('Error'); + } +} diff --git a/common/src/models/response/provider/providerUserResponse.ts b/common/src/models/response/provider/providerUserResponse.ts new file mode 100644 index 00000000000..728b708f806 --- /dev/null +++ b/common/src/models/response/provider/providerUserResponse.ts @@ -0,0 +1,34 @@ +import { BaseResponse } from '../baseResponse'; + +import { PermissionsApi } from '../../api/permissionsApi'; + +import { ProviderUserStatusType } from '../../../enums/providerUserStatusType'; +import { ProviderUserType } from '../../../enums/providerUserType'; + +export class ProviderUserResponse extends BaseResponse { + id: string; + userId: string; + type: ProviderUserType; + status: ProviderUserStatusType; + permissions: PermissionsApi; + + constructor(response: any) { + super(response); + this.id = this.getResponseProperty('Id'); + this.userId = this.getResponseProperty('UserId'); + this.type = this.getResponseProperty('Type'); + this.status = this.getResponseProperty('Status'); + this.permissions = new PermissionsApi(this.getResponseProperty('Permissions')); + } +} + +export class ProviderUserUserDetailsResponse extends ProviderUserResponse { + name: string; + email: string; + + constructor(response: any) { + super(response); + this.name = this.getResponseProperty('Name'); + this.email = this.getResponseProperty('Email'); + } +} diff --git a/common/src/services/api.service.ts b/common/src/services/api.service.ts index 51c1c1456eb..1d3fed88da5 100644 --- a/common/src/services/api.service.ts +++ b/common/src/services/api.service.ts @@ -55,6 +55,15 @@ import { PasswordVerificationRequest } from '../models/request/passwordVerificat import { PaymentRequest } from '../models/request/paymentRequest'; import { PolicyRequest } from '../models/request/policyRequest'; import { PreloginRequest } from '../models/request/preloginRequest'; +import { ProviderAddOrganizationRequest } from '../models/request/provider/providerAddOrganizationRequest'; +import { ProviderSetupRequest } from '../models/request/provider/providerSetupRequest'; +import { ProviderUpdateRequest } from '../models/request/provider/providerUpdateRequest'; +import { ProviderUserAcceptRequest } from '../models/request/provider/providerUserAcceptRequest'; +import { ProviderUserBulkConfirmRequest } from '../models/request/provider/providerUserBulkConfirmRequest'; +import { ProviderUserBulkRequest } from '../models/request/provider/providerUserBulkRequest'; +import { ProviderUserConfirmRequest } from '../models/request/provider/providerUserConfirmRequest'; +import { ProviderUserInviteRequest } from '../models/request/provider/providerUserInviteRequest'; +import { ProviderUserUpdateRequest } from '../models/request/provider/providerUserUpdateRequest'; import { RegisterRequest } from '../models/request/registerRequest'; import { SeatRequest } from '../models/request/seatRequest'; import { SelectionReadOnlyRequest } from '../models/request/selectionReadOnlyRequest'; @@ -81,6 +90,7 @@ import { VerifyDeleteRecoverRequest } from '../models/request/verifyDeleteRecove import { VerifyEmailRequest } from '../models/request/verifyEmailRequest'; import { Utils } from '../misc/utils'; + import { ApiKeyResponse } from '../models/response/apiKeyResponse'; import { AttachmentResponse } from '../models/response/attachmentResponse'; import { AttachmentUploadDataResponse } from '../models/response/attachmentUploadDataResponse'; @@ -123,6 +133,14 @@ import { PlanResponse } from '../models/response/planResponse'; import { PolicyResponse } from '../models/response/policyResponse'; import { PreloginResponse } from '../models/response/preloginResponse'; import { ProfileResponse } from '../models/response/profileResponse'; +import { ProviderOrganizationOrganizationDetailsResponse } from '../models/response/provider/providerOrganizationResponse'; +import { ProviderResponse } from '../models/response/provider/providerResponse'; +import { ProviderUserBulkPublicKeyResponse } from '../models/response/provider/providerUserBulkPublicKeyResponse'; +import { ProviderUserBulkResponse } from '../models/response/provider/providerUserBulkResponse'; +import { + ProviderUserResponse, + ProviderUserUserDetailsResponse +} from '../models/response/provider/providerUserResponse'; import { SelectionReadOnlyResponse } from '../models/response/selectionReadOnlyResponse'; import { SendAccessResponse } from '../models/response/sendAccessResponse'; import { SendFileDownloadDataResponse } from '../models/response/sendFileDownloadDataResponse'; @@ -1230,6 +1248,101 @@ export class ApiService implements ApiServiceAbstraction { return new OrganizationKeysResponse(r); } + // Provider APIs + + async postProviderSetup(id: string, request: ProviderSetupRequest) { + const r = await this.send('POST', '/providers/' + id + '/setup', request, true, true); + return new ProviderResponse(r); + } + + async getProvider(id: string) { + const r = await this.send('GET', '/providers/' + id, null, true, true); + return new ProviderResponse(r); + } + + async putProvider(id: string, request: ProviderUpdateRequest) { + const r = await this.send('PUT', '/providers/' + id, request, true, true); + return new ProviderResponse(r); + } + + // Provider User APIs + + async getProviderUsers(providerId: string): Promise> { + const r = await this.send('GET', '/providers/' + providerId + '/users', null, true, true); + return new ListResponse(r, ProviderUserUserDetailsResponse); + } + + async getProviderUser(providerId: string, id: string): Promise { + const r = await this.send('GET', '/providers/' + providerId + '/users/' + id, null, true, true); + return new ProviderUserResponse(r); + } + + postProviderUserInvite(providerId: string, request: ProviderUserInviteRequest): Promise { + return this.send('POST', '/providers/' + providerId + '/users/invite', request, true, false); + } + + postProviderUserReinvite(providerId: string, id: string): Promise { + return this.send('POST', '/providers/' + providerId + '/users/' + id + '/reinvite', null, true, false); + } + + async postManyProviderUserReinvite(providerId: string, request: ProviderUserBulkRequest): Promise> { + const r = await this.send('POST', '/providers/' + providerId + '/users/reinvite', request, true, true); + return new ListResponse(r, ProviderUserBulkResponse); + } + + async postProviderUserBulkConfirm(providerId: string, request: ProviderUserBulkConfirmRequest): Promise> { + const r = await this.send('POST', '/providers/' + providerId + '/users/confirm', request, true, true); + return new ListResponse(r, ProviderUserBulkResponse); + } + + async deleteManyProviderUsers(providerId: string, request: ProviderUserBulkRequest): Promise> { + const r = await this.send('DELETE', '/providers/' + providerId + '/users', request, true, true); + return new ListResponse(r, ProviderUserBulkResponse); + } + + postProviderUserAccept(providerId: string, id: string, request: ProviderUserAcceptRequest): Promise { + return this.send('POST', '/providers/' + providerId + '/users/' + id + '/accept', request, true, false); + } + + postProviderUserConfirm(providerId: string, id: string, request: ProviderUserConfirmRequest): Promise { + return this.send('POST', '/providers/' + providerId + '/users/' + id + '/confirm', + request, true, false); + } + + async postProviderUsersPublicKey(providerId: string, request: ProviderUserBulkRequest): Promise> { + const r = await this.send('POST', '/providers/' + providerId + '/users/public-keys', request, true, true); + return new ListResponse(r, ProviderUserBulkPublicKeyResponse); + } + + + putProviderUser(providerId: string, id: string, request: ProviderUserUpdateRequest): Promise { + return this.send('PUT', '/providers/' + providerId + '/users/' + id, request, true, false); + } + + deleteProviderUser(providerId: string, id: string): Promise { + return this.send('DELETE', '/providers/' + providerId + '/users/' + id, null, true, false); + } + + // Provider Organization APIs + + async getProviderClients(providerId: string): Promise> { + const r = await this.send('GET', '/providers/' + providerId + '/organizations', null, true, true); + return new ListResponse(r, ProviderOrganizationOrganizationDetailsResponse); + } + + postProviderAddOrganization(providerId: string, request: ProviderAddOrganizationRequest): Promise { + return this.send('POST', '/providers/' + providerId + '/organizations/add', request, true, false); + } + + async postProviderCreateOrganization(providerId: string, request: OrganizationCreateRequest): Promise { + const r = await this.send('POST', '/providers/' + providerId + '/organizations', request, true, true); + return new OrganizationResponse(r); + } + + deleteProviderOrganization(providerId: string, id: string): Promise { + return this.send('DELETE', '/providers/' + providerId + '/organizations/' + id, null, true, false); + } + // Event APIs async getEvents(start: string, end: string, token: string): Promise> { @@ -1259,6 +1372,19 @@ export class ApiService implements ApiServiceAbstraction { return new ListResponse(r, EventResponse); } + async getEventsProvider(id: string, start: string, end: string, token: string): Promise> { + const r = await this.send('GET', this.addEventParameters('/providers/' + id + '/events', start, end, token), null, true, true); + return new ListResponse(r, EventResponse); + } + + async getEventsProviderUser(providerId: string, id: string, + start: string, end: string, token: string): Promise> { + const r = await this.send('GET', + this.addEventParameters('/providers/' + providerId + '/users/' + id + '/events', start, end, token), + null, true, true); + return new ListResponse(r, EventResponse); + } + async postEventsCollect(request: EventRequest[]): Promise { const authHeader = await this.getActiveBearerToken(); const headers = new Headers({ diff --git a/common/src/services/crypto.service.ts b/common/src/services/crypto.service.ts index c25d2c9cd74..dd4d73caeb4 100644 --- a/common/src/services/crypto.service.ts +++ b/common/src/services/crypto.service.ts @@ -24,10 +24,13 @@ import { ConstantsService } from './constants.service'; import { sequentialize } from '../misc/sequentialize'; import { Utils } from '../misc/utils'; import { EEFLongWordList } from '../misc/wordlist'; +import { ProfileProviderOrganizationResponse } from '../models/response/profileProviderOrganizationResponse'; +import { ProfileProviderResponse } from '../models/response/profileProviderResponse'; export const Keys = { key: 'key', // Master Key encOrgKeys: 'encOrgKeys', + encProviderKeys: 'encProviderKeys', encPrivateKey: 'encPrivateKey', encKey: 'encKey', // Generated Symmetric Key keyHash: 'keyHash', @@ -41,6 +44,7 @@ export class CryptoService implements CryptoServiceAbstraction { private publicKey: ArrayBuffer; private privateKey: ArrayBuffer; private orgKeys: Map; + private providerKeys: Map; constructor(private storageService: StorageService, protected secureStorageService: StorageService, private cryptoFunctionService: CryptoFunctionService, protected platformUtilService: PlatformUtilsService, @@ -76,16 +80,33 @@ export class CryptoService implements CryptoServiceAbstraction { this.privateKey = null; } - setOrgKeys(orgs: ProfileOrganizationResponse[]): Promise<{}> { + async setOrgKeys(orgs: ProfileOrganizationResponse[], providerOrgs: ProfileProviderOrganizationResponse[]): Promise<{}> { const orgKeys: any = {}; orgs.forEach(org => { orgKeys[org.id] = org.key; }); + for (const providerOrg of providerOrgs) { + // Convert provider encrypted keys to user encrypted. + const providerKey = await this.getProviderKey(providerOrg.providerId); + const decValue = await this.decryptToBytes(new EncString(providerOrg.key), providerKey); + orgKeys[providerOrg.id] = await (await this.rsaEncrypt(decValue)).encryptedString; + } + this.orgKeys = null; return this.storageService.save(Keys.encOrgKeys, orgKeys); } + setProviderKeys(providers: ProfileProviderResponse[]): Promise<{}> { + const providerKeys: any = {}; + providers.forEach(provider => { + providerKeys[provider.id] = provider.key; + }); + + this.providerKeys = null; + return this.storageService.save(Keys.encProviderKeys, providerKeys); + } + async getKey(keySuffix?: KeySuffixOptions): Promise { if (this.key != null) { return this.key; @@ -270,6 +291,50 @@ export class CryptoService implements CryptoServiceAbstraction { return orgKeys.get(orgId); } + @sequentialize(() => 'getProviderKeys') + async getProviderKeys(): Promise> { + if (this.providerKeys != null && this.providerKeys.size > 0) { + return this.providerKeys; + } + + const encProviderKeys = await this.storageService.get(Keys.encProviderKeys); + if (encProviderKeys == null) { + return null; + } + + const providerKeys: Map = new Map(); + let setKey = false; + + for (const orgId in encProviderKeys) { + if (!encProviderKeys.hasOwnProperty(orgId)) { + continue; + } + + const decValue = await this.rsaDecrypt(encProviderKeys[orgId]); + providerKeys.set(orgId, new SymmetricCryptoKey(decValue)); + setKey = true; + } + + if (setKey) { + this.providerKeys = providerKeys; + } + + return this.providerKeys; + } + + async getProviderKey(providerId: string): Promise { + if (providerId == null) { + return null; + } + + const providerKeys = await this.getProviderKeys(); + if (providerKeys == null || !providerKeys.has(providerId)) { + return null; + } + + return providerKeys.get(providerId); + } + async hasKey(): Promise { return this.hasKeyInMemory() || await this.hasKeyStored('auto') || await this.hasKeyStored('biometric'); } @@ -329,6 +394,14 @@ export class CryptoService implements CryptoServiceAbstraction { return this.storageService.remove(Keys.encOrgKeys); } + clearProviderKeys(memoryOnly?: boolean): Promise { + this.providerKeys = null; + if (memoryOnly) { + return Promise.resolve(); + } + return this.storageService.remove(Keys.encOrgKeys); + } + clearPinProtectedKey(): Promise { return this.storageService.remove(ConstantsService.pinProtectedKey); } @@ -337,6 +410,7 @@ export class CryptoService implements CryptoServiceAbstraction { await this.clearKey(); await this.clearKeyHash(); await this.clearOrgKeys(); + await this.clearProviderKeys(); await this.clearEncKey(); await this.clearKeyPair(); await this.clearPinProtectedKey(); diff --git a/common/src/services/sync.service.ts b/common/src/services/sync.service.ts index ce1b5b7598c..2ae723840b1 100644 --- a/common/src/services/sync.service.ts +++ b/common/src/services/sync.service.ts @@ -16,6 +16,7 @@ import { CollectionData } from '../models/data/collectionData'; import { FolderData } from '../models/data/folderData'; import { OrganizationData } from '../models/data/organizationData'; import { PolicyData } from '../models/data/policyData'; +import { ProviderData } from '../models/data/providerData'; import { SendData } from '../models/data/sendData'; import { CipherResponse } from '../models/response/cipherResponse'; @@ -286,7 +287,8 @@ export class SyncService implements SyncServiceAbstraction { await this.cryptoService.setEncKey(response.key); await this.cryptoService.setEncPrivateKey(response.privateKey); - await this.cryptoService.setOrgKeys(response.organizations); + await this.cryptoService.setProviderKeys(response.providers); + await this.cryptoService.setOrgKeys(response.organizations, response.providerOrganizations); await this.userService.setSecurityStamp(response.securityStamp); await this.userService.setEmailVerified(response.emailVerified); @@ -294,7 +296,22 @@ export class SyncService implements SyncServiceAbstraction { response.organizations.forEach(o => { organizations[o.id] = new OrganizationData(o); }); - return await this.userService.replaceOrganizations(organizations); + + const providers: { [id: string]: ProviderData; } = {}; + response.providers.forEach(p => { + providers[p.id] = new ProviderData(p); + }); + + response.providerOrganizations.forEach(o => { + if (organizations[o.id] == null) { + organizations[o.id] = new OrganizationData(o); + organizations[o.id].isProviderUser = true; + } + }); + return Promise.all([ + this.userService.replaceOrganizations(organizations), + this.userService.replaceProviders(providers), + ]); } private async syncFolders(userId: string, response: FolderResponse[]) { diff --git a/common/src/services/user.service.ts b/common/src/services/user.service.ts index b62494e9d40..15481a6646e 100644 --- a/common/src/services/user.service.ts +++ b/common/src/services/user.service.ts @@ -6,6 +6,8 @@ import { OrganizationData } from '../models/data/organizationData'; import { Organization } from '../models/domain/organization'; import { KdfType } from '../enums/kdfType'; +import { ProviderData } from '../models/data/providerData'; +import { Provider } from '../models/domain/provider'; const Keys = { userId: 'userId', @@ -14,6 +16,7 @@ const Keys = { kdf: 'kdf', kdfIterations: 'kdfIterations', organizationsPrefix: 'organizations_', + providersPrefix: 'providers_', emailVerified: 'emailVerified', }; @@ -100,6 +103,7 @@ export class UserService implements UserServiceAbstraction { await this.storageService.remove(Keys.kdf); await this.storageService.remove(Keys.kdfIterations); await this.clearOrganizations(userId); + await this.clearProviders(userId); this.userId = this.email = this.stamp = null; this.kdf = null; @@ -153,7 +157,7 @@ export class UserService implements UserServiceAbstraction { Keys.organizationsPrefix + userId); const response: Organization[] = []; for (const id in organizations) { - if (organizations.hasOwnProperty(id)) { + if (organizations.hasOwnProperty(id) && !organizations[id].isProviderUser) { response.push(new Organization(organizations[id])); } } @@ -168,4 +172,37 @@ export class UserService implements UserServiceAbstraction { async clearOrganizations(userId: string): Promise { await this.storageService.remove(Keys.organizationsPrefix + userId); } + + async getProvider(id: string): Promise { + const userId = await this.getUserId(); + const providers = await this.storageService.get<{ [id: string]: ProviderData; }>( + Keys.providersPrefix + userId); + if (providers == null || !providers.hasOwnProperty(id)) { + return null; + } + + return new Provider(providers[id]); + } + + async getAllProviders(): Promise { + const userId = await this.getUserId(); + const providers = await this.storageService.get<{ [id: string]: ProviderData; }>( + Keys.providersPrefix + userId); + const response: Provider[] = []; + for (const id in providers) { + if (providers.hasOwnProperty(id)) { + response.push(new Provider(providers[id])); + } + } + return response; + } + + async replaceProviders(providers: { [id: string]: ProviderData; }): Promise { + const userId = await this.getUserId(); + await this.storageService.save(Keys.providersPrefix + userId, providers); + } + + async clearProviders(userId: string): Promise { + await this.storageService.remove(Keys.providersPrefix + userId); + } }