mirror of
https://github.com/bitwarden/browser
synced 2025-12-06 00:13:28 +00:00
feat(CLI-SSO-Login): [Auth/PM-21116] CLI - SSO Login - Add SSO Org Identifier option (#14605)
* Add --identifier option for SSO on CLI * Add option for identifier * Moved auto-submit after the setting of client arguments * Adjusted comment * Changed to pass in as SSO option * Renamed to orgSsoIdentifier for clarity * Added more changes to orgSsoIdentifier.
This commit is contained in:
@@ -106,6 +106,8 @@ export class LoginCommand {
|
|||||||
return Response.badRequest("client_secret is required.");
|
return Response.badRequest("client_secret is required.");
|
||||||
}
|
}
|
||||||
} else if (options.sso != null && this.canInteract) {
|
} else if (options.sso != null && this.canInteract) {
|
||||||
|
// If the optional Org SSO Identifier isn't provided, the option value is `true`.
|
||||||
|
const orgSsoIdentifier = options.sso === true ? null : options.sso;
|
||||||
const passwordOptions: any = {
|
const passwordOptions: any = {
|
||||||
type: "password",
|
type: "password",
|
||||||
length: 64,
|
length: 64,
|
||||||
@@ -119,7 +121,7 @@ export class LoginCommand {
|
|||||||
const codeVerifierHash = await this.cryptoFunctionService.hash(ssoCodeVerifier, "sha256");
|
const codeVerifierHash = await this.cryptoFunctionService.hash(ssoCodeVerifier, "sha256");
|
||||||
const codeChallenge = Utils.fromBufferToUrlB64(codeVerifierHash);
|
const codeChallenge = Utils.fromBufferToUrlB64(codeVerifierHash);
|
||||||
try {
|
try {
|
||||||
const ssoParams = await this.openSsoPrompt(codeChallenge, state);
|
const ssoParams = await this.openSsoPrompt(codeChallenge, state, orgSsoIdentifier);
|
||||||
ssoCode = ssoParams.ssoCode;
|
ssoCode = ssoParams.ssoCode;
|
||||||
orgIdentifier = ssoParams.orgIdentifier;
|
orgIdentifier = ssoParams.orgIdentifier;
|
||||||
} catch {
|
} catch {
|
||||||
@@ -664,6 +666,7 @@ export class LoginCommand {
|
|||||||
private async openSsoPrompt(
|
private async openSsoPrompt(
|
||||||
codeChallenge: string,
|
codeChallenge: string,
|
||||||
state: string,
|
state: string,
|
||||||
|
orgSsoIdentifier: string,
|
||||||
): Promise<{ ssoCode: string; orgIdentifier: string }> {
|
): Promise<{ ssoCode: string; orgIdentifier: string }> {
|
||||||
const env = await firstValueFrom(this.environmentService.environment$);
|
const env = await firstValueFrom(this.environmentService.environment$);
|
||||||
|
|
||||||
@@ -712,6 +715,8 @@ export class LoginCommand {
|
|||||||
this.ssoRedirectUri,
|
this.ssoRedirectUri,
|
||||||
state,
|
state,
|
||||||
codeChallenge,
|
codeChallenge,
|
||||||
|
null,
|
||||||
|
orgSsoIdentifier,
|
||||||
);
|
);
|
||||||
this.platformUtilsService.launchUri(webAppSsoUrl);
|
this.platformUtilsService.launchUri(webAppSsoUrl);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -118,7 +118,10 @@ export class Program extends BaseProgram {
|
|||||||
.description("Log into a user account.")
|
.description("Log into a user account.")
|
||||||
.option("--method <method>", "Two-step login method.")
|
.option("--method <method>", "Two-step login method.")
|
||||||
.option("--code <code>", "Two-step login code.")
|
.option("--code <code>", "Two-step login code.")
|
||||||
.option("--sso", "Log in with Single-Sign On.")
|
.option(
|
||||||
|
"--sso [identifier]",
|
||||||
|
"Log in with Single-Sign On with optional organization identifier.",
|
||||||
|
)
|
||||||
.option("--apikey", "Log in with an Api Key.")
|
.option("--apikey", "Log in with an Api Key.")
|
||||||
.option("--passwordenv <passwordenv>", "Environment variable storing your password")
|
.option("--passwordenv <passwordenv>", "Environment variable storing your password")
|
||||||
.option(
|
.option(
|
||||||
|
|||||||
@@ -155,7 +155,14 @@ export class SsoComponent implements OnInit {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Detect if we have landed here but only have an SSO identifier in the URL.
|
// Detect if we are on the first portion of the SSO flow
|
||||||
|
// and have been sent here from another client with the info in query params.
|
||||||
|
// If so, we want to initialize the SSO flow with those values.
|
||||||
|
if (this.hasParametersFromOtherClientRedirect(qParams)) {
|
||||||
|
this.initializeFromRedirectFromOtherClient(qParams);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Detect if we have landed here with an SSO identifier in the URL.
|
||||||
// This is used by integrations that want to "short-circuit" the login to send users
|
// This is used by integrations that want to "short-circuit" the login to send users
|
||||||
// directly to their IdP to simulate IdP-initiated SSO, so we submit automatically.
|
// directly to their IdP to simulate IdP-initiated SSO, so we submit automatically.
|
||||||
if (qParams.identifier != null) {
|
if (qParams.identifier != null) {
|
||||||
@@ -165,13 +172,6 @@ export class SsoComponent implements OnInit {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Detect if we are on the first portion of the SSO flow
|
|
||||||
// and have been sent here from another client with the info in query params.
|
|
||||||
// If so, we want to initialize the SSO flow with those values.
|
|
||||||
if (this.hasParametersFromOtherClientRedirect(qParams)) {
|
|
||||||
this.initializeFromRedirectFromOtherClient(qParams);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try to determine the identifier using claimed domain or local state
|
// Try to determine the identifier using claimed domain or local state
|
||||||
// persisted from the user's last login attempt.
|
// persisted from the user's last login attempt.
|
||||||
await this.initializeIdentifierFromEmailOrStorage();
|
await this.initializeIdentifierFromEmailOrStorage();
|
||||||
|
|||||||
@@ -92,4 +92,27 @@ describe("SsoUrlService", () => {
|
|||||||
);
|
);
|
||||||
expect(result).toBe(expectedUrl);
|
expect(result).toBe(expectedUrl);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should build CLI SSO URL with Org SSO Identifier correctly", () => {
|
||||||
|
const baseUrl = "https://web-vault.bitwarden.com";
|
||||||
|
const clientType = ClientType.Cli;
|
||||||
|
const redirectUri = "https://localhost:1000";
|
||||||
|
const state = "abc123";
|
||||||
|
const codeChallenge = "xyz789";
|
||||||
|
const email = "test@bitwarden.com";
|
||||||
|
const orgSsoIdentifier = "test-org";
|
||||||
|
|
||||||
|
const expectedUrl = `${baseUrl}/#/sso?clientId=cli&redirectUri=${encodeURIComponent(redirectUri)}&state=${state}&codeChallenge=${codeChallenge}&email=${encodeURIComponent(email)}&identifier=${encodeURIComponent(orgSsoIdentifier)}`;
|
||||||
|
|
||||||
|
const result = service.buildSsoUrl(
|
||||||
|
baseUrl,
|
||||||
|
clientType,
|
||||||
|
redirectUri,
|
||||||
|
state,
|
||||||
|
codeChallenge,
|
||||||
|
email,
|
||||||
|
orgSsoIdentifier,
|
||||||
|
);
|
||||||
|
expect(result).toBe(expectedUrl);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ export class SsoUrlService {
|
|||||||
* @param state A state value that will be peristed through the SSO flow
|
* @param state A state value that will be peristed through the SSO flow
|
||||||
* @param codeChallenge A challenge value that will be used to verify the SSO code after authentication
|
* @param codeChallenge A challenge value that will be used to verify the SSO code after authentication
|
||||||
* @param email The optional email adddress of the user initiating SSO, which will be used to look up the org SSO identifier
|
* @param email The optional email adddress of the user initiating SSO, which will be used to look up the org SSO identifier
|
||||||
|
* @param orgSsoIdentifier The optional SSO identifier of the org that is initiating SSO
|
||||||
* @returns The URL for redirecting users to the web app SSO component
|
* @returns The URL for redirecting users to the web app SSO component
|
||||||
*/
|
*/
|
||||||
buildSsoUrl(
|
buildSsoUrl(
|
||||||
@@ -20,6 +21,7 @@ export class SsoUrlService {
|
|||||||
state: string,
|
state: string,
|
||||||
codeChallenge: string,
|
codeChallenge: string,
|
||||||
email?: string,
|
email?: string,
|
||||||
|
orgSsoIdentifier?: string,
|
||||||
): string {
|
): string {
|
||||||
let url =
|
let url =
|
||||||
webAppUrl +
|
webAppUrl +
|
||||||
@@ -36,6 +38,10 @@ export class SsoUrlService {
|
|||||||
url += "&email=" + encodeURIComponent(email);
|
url += "&email=" + encodeURIComponent(email);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (orgSsoIdentifier) {
|
||||||
|
url += "&identifier=" + encodeURIComponent(orgSsoIdentifier);
|
||||||
|
}
|
||||||
|
|
||||||
return url;
|
return url;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user