mirror of
https://github.com/bitwarden/browser.git
synced 2024-11-19 11:15:21 +01:00
Link existing user to sso (#158)
* facilite linking an existing user to an org sso * fixed a broken import * added ssoBound and identifier to an org model * added user identifier to sso callout url * changed url for delete sso user api method * facilite linking an existing user to an org sso * fixed a broken import * added ssoBound and identifier to an org model * added user identifier to sso callout url * changed url for delete sso user api method * added a token to the existing user sso link flow * facilite linking an existing user to an org sso * fixed a broken import * facilite linking an existing user to an org sso * fixed a broken import * added ssoBound and identifier to an org model * added user identifier to sso callout url * changed url for delete sso user api method * added a token to the existing user sso link flow * facilite linking an existing user to an org sso * fixed a broken import * removed an extra line * encoded the user identifier on sso link * code review cleanup for link sso * removed a blank line
This commit is contained in:
parent
8f27110754
commit
e07526a1b6
@ -293,6 +293,9 @@ export abstract class ApiService {
|
|||||||
start: string, end: string, token: string) => Promise<ListResponse<EventResponse>>;
|
start: string, end: string, token: string) => Promise<ListResponse<EventResponse>>;
|
||||||
postEventsCollect: (request: EventRequest[]) => Promise<any>;
|
postEventsCollect: (request: EventRequest[]) => Promise<any>;
|
||||||
|
|
||||||
|
deleteSsoUser: (organizationId: string) => Promise<any>;
|
||||||
|
getSsoUserIdentifier: () => Promise<string>;
|
||||||
|
|
||||||
getUserPublicKey: (id: string) => Promise<UserKeyResponse>;
|
getUserPublicKey: (id: string) => Promise<UserKeyResponse>;
|
||||||
|
|
||||||
getHibpBreach: (username: string) => Promise<BreachAccountResponse[]>;
|
getHibpBreach: (username: string) => Promise<BreachAccountResponse[]>;
|
||||||
|
@ -66,7 +66,15 @@ export class SsoComponent {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async submit() {
|
async submit(returnUri?: string, includeUserIdentifier?: boolean) {
|
||||||
|
const authorizeUrl = await this.buildAuthorizeUrl(returnUri, includeUserIdentifier);
|
||||||
|
this.platformUtilsService.launchUri(authorizeUrl, { sameWindow: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async buildAuthorizeUrl(returnUri?: string, includeUserIdentifier?: boolean): Promise<string> {
|
||||||
|
let codeChallenge = this.codeChallenge;
|
||||||
|
let state = this.state;
|
||||||
|
|
||||||
const passwordOptions: any = {
|
const passwordOptions: any = {
|
||||||
type: 'password',
|
type: 'password',
|
||||||
length: 64,
|
length: 64,
|
||||||
@ -75,26 +83,36 @@ export class SsoComponent {
|
|||||||
numbers: true,
|
numbers: true,
|
||||||
special: false,
|
special: false,
|
||||||
};
|
};
|
||||||
let codeChallenge = this.codeChallenge;
|
|
||||||
let state = this.state;
|
|
||||||
if (codeChallenge == null) {
|
if (codeChallenge == null) {
|
||||||
const codeVerifier = await this.passwordGenerationService.generatePassword(passwordOptions);
|
const codeVerifier = await this.passwordGenerationService.generatePassword(passwordOptions);
|
||||||
const codeVerifierHash = await this.cryptoFunctionService.hash(codeVerifier, 'sha256');
|
const codeVerifierHash = await this.cryptoFunctionService.hash(codeVerifier, 'sha256');
|
||||||
codeChallenge = Utils.fromBufferToUrlB64(codeVerifierHash);
|
codeChallenge = Utils.fromBufferToUrlB64(codeVerifierHash);
|
||||||
await this.storageService.save(ConstantsService.ssoCodeVerifierKey, codeVerifier);
|
await this.storageService.save(ConstantsService.ssoCodeVerifierKey, codeVerifier);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state == null) {
|
if (state == null) {
|
||||||
state = await this.passwordGenerationService.generatePassword(passwordOptions);
|
state = await this.passwordGenerationService.generatePassword(passwordOptions);
|
||||||
|
if (returnUri) {
|
||||||
|
state += `_returnUri='${returnUri}'`;
|
||||||
|
}
|
||||||
|
|
||||||
await this.storageService.save(ConstantsService.ssoStateKey, state);
|
await this.storageService.save(ConstantsService.ssoStateKey, state);
|
||||||
}
|
}
|
||||||
|
|
||||||
const authorizeUrl = this.apiService.identityBaseUrl + '/connect/authorize?' +
|
let authorizeUrl = this.apiService.identityBaseUrl + '/connect/authorize?' +
|
||||||
'client_id=' + this.clientId + '&redirect_uri=' + encodeURIComponent(this.redirectUri) + '&' +
|
'client_id=' + this.clientId + '&redirect_uri=' + encodeURIComponent(this.redirectUri) + '&' +
|
||||||
'response_type=code&scope=api offline_access&' +
|
'response_type=code&scope=api offline_access&' +
|
||||||
'state=' + state + '&code_challenge=' + codeChallenge + '&' +
|
'state=' + state + '&code_challenge=' + codeChallenge + '&' +
|
||||||
'code_challenge_method=S256&response_mode=query&' +
|
'code_challenge_method=S256&response_mode=query&' +
|
||||||
'domain_hint=' + encodeURIComponent(this.identifier);
|
'domain_hint=' + encodeURIComponent(this.identifier);
|
||||||
this.platformUtilsService.launchUri(authorizeUrl, { sameWindow: true });
|
|
||||||
|
if (includeUserIdentifier) {
|
||||||
|
const userIdentifier = await this.apiService.getSsoUserIdentifier();
|
||||||
|
authorizeUrl += `&user_identifier=${encodeURIComponent(userIdentifier)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return authorizeUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async logIn(code: string, codeVerifier: string) {
|
private async logIn(code: string, codeVerifier: string) {
|
||||||
|
@ -17,11 +17,14 @@ export class OrganizationData {
|
|||||||
use2fa: boolean;
|
use2fa: boolean;
|
||||||
useApi: boolean;
|
useApi: boolean;
|
||||||
useBusinessPortal: boolean;
|
useBusinessPortal: boolean;
|
||||||
|
useSso: boolean;
|
||||||
selfHost: boolean;
|
selfHost: boolean;
|
||||||
usersGetPremium: boolean;
|
usersGetPremium: boolean;
|
||||||
seats: number;
|
seats: number;
|
||||||
maxCollections: number;
|
maxCollections: number;
|
||||||
maxStorageGb?: number;
|
maxStorageGb?: number;
|
||||||
|
ssoBound: boolean;
|
||||||
|
identifier: string;
|
||||||
|
|
||||||
constructor(response: ProfileOrganizationResponse) {
|
constructor(response: ProfileOrganizationResponse) {
|
||||||
this.id = response.id;
|
this.id = response.id;
|
||||||
@ -37,10 +40,13 @@ export class OrganizationData {
|
|||||||
this.use2fa = response.use2fa;
|
this.use2fa = response.use2fa;
|
||||||
this.useApi = response.useApi;
|
this.useApi = response.useApi;
|
||||||
this.useBusinessPortal = response.useBusinessPortal;
|
this.useBusinessPortal = response.useBusinessPortal;
|
||||||
|
this.useSso = response.useSso;
|
||||||
this.selfHost = response.selfHost;
|
this.selfHost = response.selfHost;
|
||||||
this.usersGetPremium = response.usersGetPremium;
|
this.usersGetPremium = response.usersGetPremium;
|
||||||
this.seats = response.seats;
|
this.seats = response.seats;
|
||||||
this.maxCollections = response.maxCollections;
|
this.maxCollections = response.maxCollections;
|
||||||
this.maxStorageGb = response.maxStorageGb;
|
this.maxStorageGb = response.maxStorageGb;
|
||||||
|
this.ssoBound = response.ssoBound;
|
||||||
|
this.identifier = response.identifier;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,11 +17,14 @@ export class Organization {
|
|||||||
use2fa: boolean;
|
use2fa: boolean;
|
||||||
useApi: boolean;
|
useApi: boolean;
|
||||||
useBusinessPortal: boolean;
|
useBusinessPortal: boolean;
|
||||||
|
useSso: boolean;
|
||||||
selfHost: boolean;
|
selfHost: boolean;
|
||||||
usersGetPremium: boolean;
|
usersGetPremium: boolean;
|
||||||
seats: number;
|
seats: number;
|
||||||
maxCollections: number;
|
maxCollections: number;
|
||||||
maxStorageGb?: number;
|
maxStorageGb?: number;
|
||||||
|
ssoBound: boolean;
|
||||||
|
identifier: string;
|
||||||
|
|
||||||
constructor(obj?: OrganizationData) {
|
constructor(obj?: OrganizationData) {
|
||||||
if (obj == null) {
|
if (obj == null) {
|
||||||
@ -41,11 +44,14 @@ export class Organization {
|
|||||||
this.use2fa = obj.use2fa;
|
this.use2fa = obj.use2fa;
|
||||||
this.useApi = obj.useApi;
|
this.useApi = obj.useApi;
|
||||||
this.useBusinessPortal = obj.useBusinessPortal;
|
this.useBusinessPortal = obj.useBusinessPortal;
|
||||||
|
this.useSso = obj.useSso;
|
||||||
this.selfHost = obj.selfHost;
|
this.selfHost = obj.selfHost;
|
||||||
this.usersGetPremium = obj.usersGetPremium;
|
this.usersGetPremium = obj.usersGetPremium;
|
||||||
this.seats = obj.seats;
|
this.seats = obj.seats;
|
||||||
this.maxCollections = obj.maxCollections;
|
this.maxCollections = obj.maxCollections;
|
||||||
this.maxStorageGb = obj.maxStorageGb;
|
this.maxStorageGb = obj.maxStorageGb;
|
||||||
|
this.ssoBound = obj.ssoBound;
|
||||||
|
this.identifier = obj.identifier;
|
||||||
}
|
}
|
||||||
|
|
||||||
get canAccess() {
|
get canAccess() {
|
||||||
|
@ -14,6 +14,7 @@ export class ProfileOrganizationResponse extends BaseResponse {
|
|||||||
use2fa: boolean;
|
use2fa: boolean;
|
||||||
useApi: boolean;
|
useApi: boolean;
|
||||||
useBusinessPortal: boolean;
|
useBusinessPortal: boolean;
|
||||||
|
useSso: boolean;
|
||||||
selfHost: boolean;
|
selfHost: boolean;
|
||||||
usersGetPremium: boolean;
|
usersGetPremium: boolean;
|
||||||
seats: number;
|
seats: number;
|
||||||
@ -23,6 +24,8 @@ export class ProfileOrganizationResponse extends BaseResponse {
|
|||||||
status: OrganizationUserStatusType;
|
status: OrganizationUserStatusType;
|
||||||
type: OrganizationUserType;
|
type: OrganizationUserType;
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
|
ssoBound: boolean;
|
||||||
|
identifier: string;
|
||||||
|
|
||||||
constructor(response: any) {
|
constructor(response: any) {
|
||||||
super(response);
|
super(response);
|
||||||
@ -36,6 +39,7 @@ export class ProfileOrganizationResponse extends BaseResponse {
|
|||||||
this.use2fa = this.getResponseProperty('Use2fa');
|
this.use2fa = this.getResponseProperty('Use2fa');
|
||||||
this.useApi = this.getResponseProperty('UseApi');
|
this.useApi = this.getResponseProperty('UseApi');
|
||||||
this.useBusinessPortal = this.getResponseProperty('UseBusinessPortal');
|
this.useBusinessPortal = this.getResponseProperty('UseBusinessPortal');
|
||||||
|
this.useSso = this.getResponseProperty('UseSso');
|
||||||
this.selfHost = this.getResponseProperty('SelfHost');
|
this.selfHost = this.getResponseProperty('SelfHost');
|
||||||
this.usersGetPremium = this.getResponseProperty('UsersGetPremium');
|
this.usersGetPremium = this.getResponseProperty('UsersGetPremium');
|
||||||
this.seats = this.getResponseProperty('Seats');
|
this.seats = this.getResponseProperty('Seats');
|
||||||
@ -45,5 +49,7 @@ export class ProfileOrganizationResponse extends BaseResponse {
|
|||||||
this.status = this.getResponseProperty('Status');
|
this.status = this.getResponseProperty('Status');
|
||||||
this.type = this.getResponseProperty('Type');
|
this.type = this.getResponseProperty('Type');
|
||||||
this.enabled = this.getResponseProperty('Enabled');
|
this.enabled = this.getResponseProperty('Enabled');
|
||||||
|
this.ssoBound = this.getResponseProperty('SsoBound');
|
||||||
|
this.identifier = this.getResponseProperty('Identifier');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -348,6 +348,14 @@ export class ApiService implements ApiServiceAbstraction {
|
|||||||
return r as string;
|
return r as string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async deleteSsoUser(organizationId: string): Promise<any> {
|
||||||
|
return this.send('DELETE', '/accounts/sso/' + organizationId, null, true, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getSsoUserIdentifier(): Promise<string> {
|
||||||
|
return this.send('GET', '/accounts/sso/user-identifier', null, true, true)
|
||||||
|
}
|
||||||
|
|
||||||
// Folder APIs
|
// Folder APIs
|
||||||
|
|
||||||
async getFolder(id: string): Promise<FolderResponse> {
|
async getFolder(id: string): Promise<FolderResponse> {
|
||||||
@ -693,13 +701,6 @@ export class ApiService implements ApiServiceAbstraction {
|
|||||||
return new ListResponse(r, PlanResponse);
|
return new ListResponse(r, PlanResponse);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sync APIs
|
|
||||||
|
|
||||||
async getSync(): Promise<SyncResponse> {
|
|
||||||
const path = this.isDesktopClient || this.isWebClient ? '/sync?excludeDomains=true' : '/sync';
|
|
||||||
const r = await this.send('GET', path, null, true, true);
|
|
||||||
return new SyncResponse(r);
|
|
||||||
}
|
|
||||||
|
|
||||||
async postImportDirectory(organizationId: string, request: ImportDirectoryRequest): Promise<any> {
|
async postImportDirectory(organizationId: string, request: ImportDirectoryRequest): Promise<any> {
|
||||||
return this.send('POST', '/organizations/' + organizationId + '/import', request, true, false);
|
return this.send('POST', '/organizations/' + organizationId + '/import', request, true, false);
|
||||||
@ -717,6 +718,14 @@ export class ApiService implements ApiServiceAbstraction {
|
|||||||
return new DomainsResponse(r);
|
return new DomainsResponse(r);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sync APIs
|
||||||
|
|
||||||
|
async getSync(): Promise<SyncResponse> {
|
||||||
|
const path = this.isDesktopClient || this.isWebClient ? '/sync?excludeDomains=true' : '/sync';
|
||||||
|
const r = await this.send('GET', path, null, true, true);
|
||||||
|
return new SyncResponse(r);
|
||||||
|
}
|
||||||
|
|
||||||
// Two-factor APIs
|
// Two-factor APIs
|
||||||
|
|
||||||
async getTwoFactorProviders(): Promise<ListResponse<TwoFactorProviderResponse>> {
|
async getTwoFactorProviders(): Promise<ListResponse<TwoFactorProviderResponse>> {
|
||||||
|
Loading…
Reference in New Issue
Block a user