[Reset Password] Admin Actions (#349)

* [Reset Password] Admin Actions

* Updated password reset apis to use orgUser.Id instead of orgUser.UserId

* Added new ResetPassword policy type

* Added all necessary changes for organization changes

* Added get/post for org keys // Added org keys reponse model

* Fixed lint errors
This commit is contained in:
Vincent Salucci 2021-05-19 09:40:48 -05:00 committed by GitHub
parent c8eca37183
commit 73ec484b17
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 128 additions and 8 deletions

View File

@ -31,6 +31,7 @@ import { ImportOrganizationCiphersRequest } from '../models/request/importOrgani
import { KdfRequest } from '../models/request/kdfRequest';
import { KeysRequest } from '../models/request/keysRequest';
import { OrganizationCreateRequest } from '../models/request/organizationCreateRequest';
import { OrganizationKeysRequest } from '../models/request/organizationKeysRequest';
import { OrganizationTaxInfoUpdateRequest } from '../models/request/organizationTaxInfoUpdateRequest';
import { OrganizationUpdateRequest } from '../models/request/organizationUpdateRequest';
import { OrganizationUpgradeRequest } from '../models/request/organizationUpgradeRequest';
@ -39,6 +40,7 @@ import { OrganizationUserBulkRequest } from '../models/request/organizationUserB
import { OrganizationUserConfirmRequest } from '../models/request/organizationUserConfirmRequest';
import { OrganizationUserInviteRequest } from '../models/request/organizationUserInviteRequest';
import { OrganizationUserResetPasswordEnrollmentRequest } from '../models/request/organizationUserResetPasswordEnrollmentRequest';
import { OrganizationUserResetPasswordRequest } from '../models/request/organizationUserResetPasswordRequest';
import { OrganizationUserUpdateGroupsRequest } from '../models/request/organizationUserUpdateGroupsRequest';
import { OrganizationUserUpdateRequest } from '../models/request/organizationUserUpdateRequest';
import { PasswordHintRequest } from '../models/request/passwordHintRequest';
@ -98,10 +100,12 @@ import {
import { IdentityTokenResponse } from '../models/response/identityTokenResponse';
import { IdentityTwoFactorResponse } from '../models/response/identityTwoFactorResponse';
import { ListResponse } from '../models/response/listResponse';
import { OrganizationKeysResponse } from '../models/response/organizationKeysResponse';
import { OrganizationResponse } from '../models/response/organizationResponse';
import { OrganizationSubscriptionResponse } from '../models/response/organizationSubscriptionResponse';
import {
OrganizationUserDetailsResponse,
OrganizationUserResetPasswordDetailsReponse,
OrganizationUserUserDetailsResponse,
} from '../models/response/organizationUserResponse';
import { PaymentResponse } from '../models/response/paymentResponse';
@ -271,6 +275,8 @@ export abstract class ApiService {
getOrganizationUser: (organizationId: string, id: string) => Promise<OrganizationUserDetailsResponse>;
getOrganizationUserGroups: (organizationId: string, id: string) => Promise<string[]>;
getOrganizationUsers: (organizationId: string) => Promise<ListResponse<OrganizationUserUserDetailsResponse>>;
getOrganizationUserResetPasswordDetails: (organizationId: string, id: string)
=> Promise<OrganizationUserResetPasswordDetailsReponse>;
postOrganizationUserInvite: (organizationId: string, request: OrganizationUserInviteRequest) => Promise<any>;
postOrganizationUserReinvite: (organizationId: string, id: string) => Promise<any>;
postManyOrganizationUserReinvite: (organizationId: string, request: OrganizationUserBulkRequest) => Promise<any>;
@ -283,6 +289,8 @@ export abstract class ApiService {
request: OrganizationUserUpdateGroupsRequest) => Promise<any>;
putOrganizationUserResetPasswordEnrollment: (organizationId: string, userId: string,
request: OrganizationUserResetPasswordEnrollmentRequest) => Promise<any>;
putOrganizationUserResetPassword: (organizationId: string, id: string,
request: OrganizationUserResetPasswordRequest) => Promise<any>;
deleteOrganizationUser: (organizationId: string, id: string) => Promise<any>;
deleteManyOrganizationUsers: (organizationId: string, request: OrganizationUserBulkRequest) => Promise<any>;
@ -359,6 +367,8 @@ export abstract class ApiService {
deleteOrganization: (id: string, request: PasswordVerificationRequest) => Promise<any>;
getPlans: () => Promise<ListResponse<PlanResponse>>;
getTaxRates: () => Promise<ListResponse<TaxRateResponse>>;
getOrganizationKeys: (id: string) => Promise<OrganizationKeysResponse>;
postOrganizationKeys: (id: string, request: OrganizationKeysRequest) => Promise<OrganizationKeysResponse>;
getEvents: (start: string, end: string, token: string) => Promise<ListResponse<EventResponse>>;
getEventsCipher: (id: string, start: string, end: string, token: string) => Promise<ListResponse<EventResponse>>;

View File

@ -2,6 +2,7 @@ import { PolicyData } from '../models/data/policyData';
import { MasterPasswordPolicyOptions } from '../models/domain/masterPasswordPolicyOptions';
import { Policy } from '../models/domain/policy';
import { ResetPasswordPolicyOptions } from '../models/domain/resetPasswordPolicyOptions';
import { PolicyType } from '../enums/policyType';
@ -15,4 +16,5 @@ export abstract class PolicyService {
getMasterPasswordPolicyOptions: (policies?: Policy[]) => Promise<MasterPasswordPolicyOptions>;
evaluateMasterPassword: (passwordStrength: number, newPassword: string,
enforcedPolicyOptions?: MasterPasswordPolicyOptions) => boolean;
getResetPasswordPolicyOptions: (policy: Policy) => ResetPasswordPolicyOptions;
}

View File

@ -9,4 +9,5 @@ export enum Permissions {
ManageOrganization,
ManagePolicies,
ManageUsers,
ManageUsersPassword,
}

View File

@ -7,4 +7,5 @@ export enum PolicyType {
PersonalOwnership = 5, // Disables personal vault ownership for adding/cloning items
DisableSend = 6, // Disables the ability to create and edit Bitwarden Sends
SendOptions = 7, // Sets restrictions or defaults for Bitwarden Sends
ResetPassword = 8, // Allows orgs to use reset password : also can enable auto-enrollment during invite flow
}

View File

@ -19,6 +19,7 @@ export class OrganizationData {
useApi: boolean;
useBusinessPortal: boolean;
useSso: boolean;
useResetPassword: boolean;
selfHost: boolean;
usersGetPremium: boolean;
seats: number;
@ -27,8 +28,9 @@ export class OrganizationData {
ssoBound: boolean;
identifier: string;
permissions: PermissionsApi;
resetPasswordKey: string;
resetPasswordEnrolled: boolean;
userId: string;
hasPublicAndPrivateKeys: boolean;
constructor(response: ProfileOrganizationResponse) {
this.id = response.id;
@ -45,6 +47,7 @@ export class OrganizationData {
this.useApi = response.useApi;
this.useBusinessPortal = response.useBusinessPortal;
this.useSso = response.useSso;
this.useResetPassword = response.useResetPassword;
this.selfHost = response.selfHost;
this.usersGetPremium = response.usersGetPremium;
this.seats = response.seats;
@ -53,7 +56,8 @@ export class OrganizationData {
this.ssoBound = response.ssoBound;
this.identifier = response.identifier;
this.permissions = response.permissions;
this.resetPasswordKey = response.resetPasswordKey;
this.resetPasswordEnrolled = response.resetPasswordEnrolled;
this.userId = response.userId;
this.hasPublicAndPrivateKeys = response.hasPublicAndPrivateKeys;
}
}

View File

@ -20,6 +20,7 @@ export class Organization {
useApi: boolean;
useBusinessPortal: boolean;
useSso: boolean;
useResetPassword: boolean;
selfHost: boolean;
usersGetPremium: boolean;
seats: number;
@ -28,8 +29,9 @@ export class Organization {
ssoBound: boolean;
identifier: string;
permissions: PermissionsApi;
resetPasswordKey: string;
resetPasswordEnrolled: boolean;
userId: string;
hasPublicAndPrivateKeys: boolean;
constructor(obj?: OrganizationData) {
if (obj == null) {
@ -50,6 +52,7 @@ export class Organization {
this.useApi = obj.useApi;
this.useBusinessPortal = obj.useBusinessPortal;
this.useSso = obj.useSso;
this.useResetPassword = obj.useResetPassword;
this.selfHost = obj.selfHost;
this.usersGetPremium = obj.usersGetPremium;
this.seats = obj.seats;
@ -58,8 +61,9 @@ export class Organization {
this.ssoBound = obj.ssoBound;
this.identifier = obj.identifier;
this.permissions = obj.permissions;
this.resetPasswordKey = obj.resetPasswordKey;
this.resetPasswordEnrolled = obj.resetPasswordEnrolled;
this.userId = obj.userId;
this.hasPublicAndPrivateKeys = obj.hasPublicAndPrivateKeys;
}
get canAccess() {
@ -122,7 +126,7 @@ export class Organization {
return this.isAdmin || this.permissions.manageUsers;
}
get isResetPasswordEnrolled() {
return this.resetPasswordKey != null;
get canManageUsersPassword() {
return this.isAdmin || this.permissions.manageResetPassword;
}
}

View File

@ -0,0 +1,5 @@
import Domain from './domainBase';
export class ResetPasswordPolicyOptions extends Domain {
autoEnrollEnabled: boolean = false;
}

View File

@ -1,12 +1,15 @@
import { PaymentMethodType } from '../../enums/paymentMethodType';
import { PlanType } from '../../enums/planType';
import { OrganizationKeysRequest } from './organizationKeysRequest';
export class OrganizationCreateRequest {
name: string;
businessName: string;
billingEmail: string;
planType: PlanType;
key: string;
keys: OrganizationKeysRequest;
paymentMethodType: PaymentMethodType;
paymentToken: string;
additionalSeats: number;

View File

@ -0,0 +1,7 @@
import { KeysRequest } from './keysRequest';
export class OrganizationKeysRequest extends KeysRequest {
constructor(publicKey: string, encryptedPrivateKey: string) {
super(publicKey, encryptedPrivateKey);
}
}

View File

@ -1,6 +1,9 @@
import { OrganizationKeysRequest } from './organizationKeysRequest';
export class OrganizationUpdateRequest {
name: string;
identifier: string;
businessName: string;
billingEmail: string;
keys: OrganizationKeysRequest;
}

View File

@ -1,5 +1,7 @@
import { PlanType } from '../../enums/planType';
import { OrganizationKeysRequest } from './organizationKeysRequest';
export class OrganizationUpgradeRequest {
businessName: string;
planType: PlanType;
@ -8,4 +10,5 @@ export class OrganizationUpgradeRequest {
premiumAccessAddon: boolean;
billingAddressCountry: string;
billingAddressPostalCode: string;
keys: OrganizationKeysRequest;
}

View File

@ -0,0 +1,4 @@
export class OrganizationUserResetPasswordRequest {
newMasterPasswordHash: string;
key: string;
}

View File

@ -0,0 +1,7 @@
import { KeysResponse } from './keysResponse';
export class OrganizationKeysResponse extends KeysResponse {
constructor(response: any) {
super(response);
}
}

View File

@ -25,6 +25,8 @@ export class OrganizationResponse extends BaseResponse {
useTotp: boolean;
use2fa: boolean;
useApi: boolean;
useResetPassword: boolean;
hasPublicAndPrivateKeys: boolean;
constructor(response: any) {
super(response);
@ -50,5 +52,7 @@ export class OrganizationResponse extends BaseResponse {
this.useTotp = this.getResponseProperty('UseTotp');
this.use2fa = this.getResponseProperty('Use2fa');
this.useApi = this.getResponseProperty('UseApi');
this.useResetPassword = this.getResponseProperty('UseResetPassword');
this.hasPublicAndPrivateKeys = this.getResponseProperty('HasPublicAndPrivateKeys');
}
}

View File

@ -3,6 +3,7 @@ import { SelectionReadOnlyResponse } from './selectionReadOnlyResponse';
import { PermissionsApi } from '../api/permissionsApi';
import { KdfType } from '../../enums/kdfType';
import { OrganizationUserStatusType } from '../../enums/organizationUserStatusType';
import { OrganizationUserType } from '../../enums/organizationUserType';
@ -13,6 +14,7 @@ export class OrganizationUserResponse extends BaseResponse {
status: OrganizationUserStatusType;
accessAll: boolean;
permissions: PermissionsApi;
resetPasswordEnrolled: boolean;
constructor(response: any) {
super(response);
@ -22,6 +24,7 @@ export class OrganizationUserResponse extends BaseResponse {
this.status = this.getResponseProperty('Status');
this.permissions = new PermissionsApi(this.getResponseProperty('Permissions'));
this.accessAll = this.getResponseProperty('AccessAll');
this.resetPasswordEnrolled = this.getResponseProperty('ResetPasswordEnrolled');
}
}
@ -49,3 +52,18 @@ export class OrganizationUserDetailsResponse extends OrganizationUserResponse {
}
}
}
export class OrganizationUserResetPasswordDetailsReponse extends BaseResponse {
kdf: KdfType;
kdfIterations: number;
resetPasswordKey: string;
encryptedPrivateKey: string;
constructor(response: any) {
super(response);
this.kdf = this.getResponseProperty('Kdf');
this.kdfIterations = this.getResponseProperty('KdfIterations');
this.resetPasswordKey = this.getResponseProperty('ResetPasswordKey');
this.encryptedPrivateKey = this.getResponseProperty('EncryptedPrivateKey');
}
}

View File

@ -32,6 +32,7 @@ export class PlanResponse extends BaseResponse {
has2fa: boolean;
hasApi: boolean;
hasSso: boolean;
hasResetPassword: boolean;
usersGetPremium: boolean;
upgradeSortOrder: number;
@ -76,6 +77,7 @@ export class PlanResponse extends BaseResponse {
this.has2fa = this.getResponseProperty('Has2fa');
this.hasApi = this.getResponseProperty('HasApi');
this.hasSso = this.getResponseProperty('HasSso');
this.hasResetPassword = this.getResponseProperty('HasResetPassword');
this.usersGetPremium = this.getResponseProperty('UsersGetPremium');
this.upgradeSortOrder = this.getResponseProperty('UpgradeSortOrder');
this.displaySortOrder = this.getResponseProperty('SortOrder');

View File

@ -16,19 +16,21 @@ export class ProfileOrganizationResponse extends BaseResponse {
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;
resetPasswordKey: string;
resetPasswordEnrolled: boolean;
userId: string;
constructor(response: any) {
@ -44,19 +46,21 @@ export class ProfileOrganizationResponse extends BaseResponse {
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.resetPasswordKey = this.getResponseProperty('ResetPasswordKey');
this.resetPasswordEnrolled = this.getResponseProperty('ResetPasswordEnrolled');
this.userId = this.getResponseProperty('UserId');
}
}

View File

@ -35,6 +35,7 @@ import { ImportOrganizationCiphersRequest } from '../models/request/importOrgani
import { KdfRequest } from '../models/request/kdfRequest';
import { KeysRequest } from '../models/request/keysRequest';
import { OrganizationCreateRequest } from '../models/request/organizationCreateRequest';
import { OrganizationKeysRequest } from '../models/request/organizationKeysRequest';
import { OrganizationTaxInfoUpdateRequest } from '../models/request/organizationTaxInfoUpdateRequest';
import { OrganizationUpdateRequest } from '../models/request/organizationUpdateRequest';
import { OrganizationUpgradeRequest } from '../models/request/organizationUpgradeRequest';
@ -43,6 +44,7 @@ import { OrganizationUserBulkRequest } from '../models/request/organizationUserB
import { OrganizationUserConfirmRequest } from '../models/request/organizationUserConfirmRequest';
import { OrganizationUserInviteRequest } from '../models/request/organizationUserInviteRequest';
import { OrganizationUserResetPasswordEnrollmentRequest } from '../models/request/organizationUserResetPasswordEnrollmentRequest';
import { OrganizationUserResetPasswordRequest } from '../models/request/organizationUserResetPasswordRequest';
import { OrganizationUserUpdateGroupsRequest } from '../models/request/organizationUserUpdateGroupsRequest';
import { OrganizationUserUpdateRequest } from '../models/request/organizationUserUpdateRequest';
import { PasswordHintRequest } from '../models/request/passwordHintRequest';
@ -104,10 +106,12 @@ import {
import { IdentityTokenResponse } from '../models/response/identityTokenResponse';
import { IdentityTwoFactorResponse } from '../models/response/identityTwoFactorResponse';
import { ListResponse } from '../models/response/listResponse';
import { OrganizationKeysResponse } from '../models/response/organizationKeysResponse';
import { OrganizationResponse } from '../models/response/organizationResponse';
import { OrganizationSubscriptionResponse } from '../models/response/organizationSubscriptionResponse';
import {
OrganizationUserDetailsResponse,
OrganizationUserResetPasswordDetailsReponse,
OrganizationUserUserDetailsResponse,
} from '../models/response/organizationUserResponse';
import { PaymentResponse } from '../models/response/paymentResponse';
@ -794,6 +798,13 @@ export class ApiService implements ApiServiceAbstraction {
return new ListResponse(r, OrganizationUserUserDetailsResponse);
}
async getOrganizationUserResetPasswordDetails(organizationId: string, id: string):
Promise<OrganizationUserResetPasswordDetailsReponse> {
const r = await this.send('GET', '/organizations/' + organizationId + '/users/' + id +
'/reset-password-details', null, true, true);
return new OrganizationUserResetPasswordDetailsReponse(r);
}
postOrganizationUserInvite(organizationId: string, request: OrganizationUserInviteRequest): Promise<any> {
return this.send('POST', '/organizations/' + organizationId + '/users/invite', request, true, false);
}
@ -832,6 +843,12 @@ export class ApiService implements ApiServiceAbstraction {
request, true, false);
}
putOrganizationUserResetPassword(organizationId: string, id: string,
request: OrganizationUserResetPasswordRequest): Promise<any> {
return this.send('PUT', '/organizations/' + organizationId + '/users/' + id + '/reset-password',
request, true, false);
}
deleteOrganizationUser(organizationId: string, id: string): Promise<any> {
return this.send('DELETE', '/organizations/' + organizationId + '/users/' + id, null, true, false);
}
@ -1176,6 +1193,16 @@ export class ApiService implements ApiServiceAbstraction {
return this.send('DELETE', '/organizations/' + id, request, true, false);
}
async getOrganizationKeys(id: string): Promise<OrganizationKeysResponse> {
const r = await this.send('GET', '/organizations/' + id + '/keys', null, true, true);
return new OrganizationKeysResponse(r);
}
async postOrganizationKeys(id: string, request: OrganizationKeysRequest): Promise<OrganizationKeysResponse> {
const r = await this.send('POST', '/organizations/' + id + '/keys', request, true, true);
return new OrganizationKeysResponse(r);
}
// Event APIs
async getEvents(start: string, end: string, token: string): Promise<ListResponse<EventResponse>> {

View File

@ -8,6 +8,7 @@ import { MasterPasswordPolicyOptions } from '../models/domain/masterPasswordPoli
import { Policy } from '../models/domain/policy';
import { PolicyType } from '../enums/policyType';
import { ResetPasswordPolicyOptions } from '../models/domain/resetPasswordPolicyOptions';
const Keys = {
policiesPrefix: 'policies_',
@ -138,4 +139,14 @@ export class PolicyService implements PolicyServiceAbstraction {
return true;
}
getResetPasswordPolicyOptions(policy: Policy): ResetPasswordPolicyOptions {
const resetPasswordPolicyOptions = new ResetPasswordPolicyOptions();
if (policy != null && policy.enabled && policy.data != null) {
resetPasswordPolicyOptions.autoEnrollEnabled = policy.data.autoEnrollEnabled;
}
return resetPasswordPolicyOptions;
}
}