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>>;
|
||||
postEventsCollect: (request: EventRequest[]) => Promise<any>;
|
||||
|
||||
deleteSsoUser: (organizationId: string) => Promise<any>;
|
||||
getSsoUserIdentifier: () => Promise<string>;
|
||||
|
||||
getUserPublicKey: (id: string) => Promise<UserKeyResponse>;
|
||||
|
||||
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 = {
|
||||
type: 'password',
|
||||
length: 64,
|
||||
@ -75,26 +83,36 @@ export class SsoComponent {
|
||||
numbers: true,
|
||||
special: false,
|
||||
};
|
||||
let codeChallenge = this.codeChallenge;
|
||||
let state = this.state;
|
||||
|
||||
if (codeChallenge == null) {
|
||||
const codeVerifier = await this.passwordGenerationService.generatePassword(passwordOptions);
|
||||
const codeVerifierHash = await this.cryptoFunctionService.hash(codeVerifier, 'sha256');
|
||||
codeChallenge = Utils.fromBufferToUrlB64(codeVerifierHash);
|
||||
await this.storageService.save(ConstantsService.ssoCodeVerifierKey, codeVerifier);
|
||||
}
|
||||
|
||||
if (state == null) {
|
||||
state = await this.passwordGenerationService.generatePassword(passwordOptions);
|
||||
if (returnUri) {
|
||||
state += `_returnUri='${returnUri}'`;
|
||||
}
|
||||
|
||||
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) + '&' +
|
||||
'response_type=code&scope=api offline_access&' +
|
||||
'state=' + state + '&code_challenge=' + codeChallenge + '&' +
|
||||
'code_challenge_method=S256&response_mode=query&' +
|
||||
'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) {
|
||||
|
@ -17,11 +17,14 @@ export class OrganizationData {
|
||||
use2fa: boolean;
|
||||
useApi: boolean;
|
||||
useBusinessPortal: boolean;
|
||||
useSso: boolean;
|
||||
selfHost: boolean;
|
||||
usersGetPremium: boolean;
|
||||
seats: number;
|
||||
maxCollections: number;
|
||||
maxStorageGb?: number;
|
||||
ssoBound: boolean;
|
||||
identifier: string;
|
||||
|
||||
constructor(response: ProfileOrganizationResponse) {
|
||||
this.id = response.id;
|
||||
@ -37,10 +40,13 @@ export class OrganizationData {
|
||||
this.use2fa = response.use2fa;
|
||||
this.useApi = response.useApi;
|
||||
this.useBusinessPortal = response.useBusinessPortal;
|
||||
this.useSso = response.useSso;
|
||||
this.selfHost = response.selfHost;
|
||||
this.usersGetPremium = response.usersGetPremium;
|
||||
this.seats = response.seats;
|
||||
this.maxCollections = response.maxCollections;
|
||||
this.maxStorageGb = response.maxStorageGb;
|
||||
this.ssoBound = response.ssoBound;
|
||||
this.identifier = response.identifier;
|
||||
}
|
||||
}
|
||||
|
@ -17,11 +17,14 @@ export class Organization {
|
||||
use2fa: boolean;
|
||||
useApi: boolean;
|
||||
useBusinessPortal: boolean;
|
||||
useSso: boolean;
|
||||
selfHost: boolean;
|
||||
usersGetPremium: boolean;
|
||||
seats: number;
|
||||
maxCollections: number;
|
||||
maxStorageGb?: number;
|
||||
ssoBound: boolean;
|
||||
identifier: string;
|
||||
|
||||
constructor(obj?: OrganizationData) {
|
||||
if (obj == null) {
|
||||
@ -41,11 +44,14 @@ export class Organization {
|
||||
this.use2fa = obj.use2fa;
|
||||
this.useApi = obj.useApi;
|
||||
this.useBusinessPortal = obj.useBusinessPortal;
|
||||
this.useSso = obj.useSso;
|
||||
this.selfHost = obj.selfHost;
|
||||
this.usersGetPremium = obj.usersGetPremium;
|
||||
this.seats = obj.seats;
|
||||
this.maxCollections = obj.maxCollections;
|
||||
this.maxStorageGb = obj.maxStorageGb;
|
||||
this.ssoBound = obj.ssoBound;
|
||||
this.identifier = obj.identifier;
|
||||
}
|
||||
|
||||
get canAccess() {
|
||||
|
@ -14,6 +14,7 @@ export class ProfileOrganizationResponse extends BaseResponse {
|
||||
use2fa: boolean;
|
||||
useApi: boolean;
|
||||
useBusinessPortal: boolean;
|
||||
useSso: boolean;
|
||||
selfHost: boolean;
|
||||
usersGetPremium: boolean;
|
||||
seats: number;
|
||||
@ -23,6 +24,8 @@ export class ProfileOrganizationResponse extends BaseResponse {
|
||||
status: OrganizationUserStatusType;
|
||||
type: OrganizationUserType;
|
||||
enabled: boolean;
|
||||
ssoBound: boolean;
|
||||
identifier: string;
|
||||
|
||||
constructor(response: any) {
|
||||
super(response);
|
||||
@ -36,6 +39,7 @@ export class ProfileOrganizationResponse extends BaseResponse {
|
||||
this.use2fa = this.getResponseProperty('Use2fa');
|
||||
this.useApi = this.getResponseProperty('UseApi');
|
||||
this.useBusinessPortal = this.getResponseProperty('UseBusinessPortal');
|
||||
this.useSso = this.getResponseProperty('UseSso');
|
||||
this.selfHost = this.getResponseProperty('SelfHost');
|
||||
this.usersGetPremium = this.getResponseProperty('UsersGetPremium');
|
||||
this.seats = this.getResponseProperty('Seats');
|
||||
@ -45,5 +49,7 @@ export class ProfileOrganizationResponse extends BaseResponse {
|
||||
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');
|
||||
}
|
||||
}
|
||||
|
@ -348,6 +348,14 @@ export class ApiService implements ApiServiceAbstraction {
|
||||
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
|
||||
|
||||
async getFolder(id: string): Promise<FolderResponse> {
|
||||
@ -693,13 +701,6 @@ export class ApiService implements ApiServiceAbstraction {
|
||||
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> {
|
||||
return this.send('POST', '/organizations/' + organizationId + '/import', request, true, false);
|
||||
@ -717,6 +718,14 @@ export class ApiService implements ApiServiceAbstraction {
|
||||
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
|
||||
|
||||
async getTwoFactorProviders(): Promise<ListResponse<TwoFactorProviderResponse>> {
|
||||
|
Loading…
Reference in New Issue
Block a user