mirror of
https://github.com/bitwarden/browser
synced 2026-02-13 06:54:07 +00:00
Merge branch 'main' into ps/extension-refresh
This commit is contained in:
@@ -7,6 +7,27 @@ import { DomQueryService as DomQueryServiceInterface } from "./abstractions/dom-
|
||||
export class DomQueryService implements DomQueryServiceInterface {
|
||||
private pageContainsShadowDom: boolean;
|
||||
private useTreeWalkerStrategyFlagSet = true;
|
||||
private ignoredTreeWalkerNodes = new Set([
|
||||
"svg",
|
||||
"script",
|
||||
"noscript",
|
||||
"head",
|
||||
"style",
|
||||
"link",
|
||||
"meta",
|
||||
"title",
|
||||
"base",
|
||||
"img",
|
||||
"picture",
|
||||
"video",
|
||||
"audio",
|
||||
"object",
|
||||
"source",
|
||||
"track",
|
||||
"param",
|
||||
"map",
|
||||
"area",
|
||||
]);
|
||||
|
||||
constructor() {
|
||||
void this.init();
|
||||
@@ -21,6 +42,7 @@ export class DomQueryService implements DomQueryServiceInterface {
|
||||
* @param treeWalkerFilter - The filter callback to use for the treeWalker query
|
||||
* @param mutationObserver - The MutationObserver to use for observing shadow roots
|
||||
* @param forceDeepQueryAttempt - Whether to force a deep query attempt
|
||||
* @param ignoredTreeWalkerNodesOverride - An optional set of node names to ignore when using the treeWalker strategy
|
||||
*/
|
||||
query<T>(
|
||||
root: Document | ShadowRoot | Element,
|
||||
@@ -28,15 +50,28 @@ export class DomQueryService implements DomQueryServiceInterface {
|
||||
treeWalkerFilter: CallableFunction,
|
||||
mutationObserver?: MutationObserver,
|
||||
forceDeepQueryAttempt?: boolean,
|
||||
ignoredTreeWalkerNodesOverride?: Set<string>,
|
||||
): T[] {
|
||||
const ignoredTreeWalkerNodes = ignoredTreeWalkerNodesOverride || this.ignoredTreeWalkerNodes;
|
||||
|
||||
if (!forceDeepQueryAttempt && this.pageContainsShadowDomElements()) {
|
||||
return this.queryAllTreeWalkerNodes<T>(root, treeWalkerFilter, mutationObserver);
|
||||
return this.queryAllTreeWalkerNodes<T>(
|
||||
root,
|
||||
treeWalkerFilter,
|
||||
ignoredTreeWalkerNodes,
|
||||
mutationObserver,
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
return this.deepQueryElements<T>(root, queryString, mutationObserver);
|
||||
} catch {
|
||||
return this.queryAllTreeWalkerNodes<T>(root, treeWalkerFilter, mutationObserver);
|
||||
return this.queryAllTreeWalkerNodes<T>(
|
||||
root,
|
||||
treeWalkerFilter,
|
||||
ignoredTreeWalkerNodes,
|
||||
mutationObserver,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -207,11 +242,13 @@ export class DomQueryService implements DomQueryServiceInterface {
|
||||
* and returns a collection of nodes.
|
||||
* @param rootNode
|
||||
* @param filterCallback
|
||||
* @param ignoredTreeWalkerNodes
|
||||
* @param mutationObserver
|
||||
*/
|
||||
private queryAllTreeWalkerNodes<T>(
|
||||
rootNode: Node,
|
||||
filterCallback: CallableFunction,
|
||||
ignoredTreeWalkerNodes: Set<string>,
|
||||
mutationObserver?: MutationObserver,
|
||||
): T[] {
|
||||
const treeWalkerQueryResults: T[] = [];
|
||||
@@ -220,6 +257,7 @@ export class DomQueryService implements DomQueryServiceInterface {
|
||||
rootNode,
|
||||
treeWalkerQueryResults,
|
||||
filterCallback,
|
||||
ignoredTreeWalkerNodes,
|
||||
mutationObserver,
|
||||
);
|
||||
|
||||
@@ -233,15 +271,21 @@ export class DomQueryService implements DomQueryServiceInterface {
|
||||
* @param rootNode
|
||||
* @param treeWalkerQueryResults
|
||||
* @param filterCallback
|
||||
* @param ignoredTreeWalkerNodes
|
||||
* @param mutationObserver
|
||||
*/
|
||||
private buildTreeWalkerNodesQueryResults<T>(
|
||||
rootNode: Node,
|
||||
treeWalkerQueryResults: T[],
|
||||
filterCallback: CallableFunction,
|
||||
ignoredTreeWalkerNodes: Set<string>,
|
||||
mutationObserver?: MutationObserver,
|
||||
) {
|
||||
const treeWalker = document?.createTreeWalker(rootNode, NodeFilter.SHOW_ELEMENT);
|
||||
const treeWalker = document?.createTreeWalker(rootNode, NodeFilter.SHOW_ELEMENT, (node) =>
|
||||
ignoredTreeWalkerNodes.has(node.nodeName?.toLowerCase())
|
||||
? NodeFilter.FILTER_REJECT
|
||||
: NodeFilter.FILTER_ACCEPT,
|
||||
);
|
||||
let currentNode = treeWalker?.currentNode;
|
||||
|
||||
while (currentNode) {
|
||||
@@ -263,6 +307,7 @@ export class DomQueryService implements DomQueryServiceInterface {
|
||||
nodeShadowRoot,
|
||||
treeWalkerQueryResults,
|
||||
filterCallback,
|
||||
ignoredTreeWalkerNodes,
|
||||
mutationObserver,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -12,11 +12,14 @@ import {
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { OrgDomainApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization-domain/org-domain-api.service.abstraction";
|
||||
import { OrganizationDomainSsoDetailsResponse } from "@bitwarden/common/admin-console/abstractions/organization-domain/responses/organization-domain-sso-details.response";
|
||||
import { VerifiedOrganizationDomainSsoDetailsResponse } from "@bitwarden/common/admin-console/abstractions/organization-domain/responses/verified-organization-domain-sso-details.response";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction";
|
||||
import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction";
|
||||
import { HttpStatusCode } from "@bitwarden/common/enums";
|
||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
import { ErrorResponse } from "@bitwarden/common/models/response/error.response";
|
||||
import { ListResponse } from "@bitwarden/common/models/response/list.response";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service";
|
||||
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
|
||||
@@ -107,13 +110,24 @@ export class SsoComponent extends BaseSsoComponent implements OnInit {
|
||||
// show loading spinner
|
||||
this.loggingIn = true;
|
||||
try {
|
||||
const response: OrganizationDomainSsoDetailsResponse =
|
||||
await this.orgDomainApiService.getClaimedOrgDomainByEmail(qParams.email);
|
||||
if (await this.configService.getFeatureFlag(FeatureFlag.VerifiedSsoDomainEndpoint)) {
|
||||
const response: ListResponse<VerifiedOrganizationDomainSsoDetailsResponse> =
|
||||
await this.orgDomainApiService.getVerifiedOrgDomainsByEmail(qParams.email);
|
||||
|
||||
if (response?.ssoAvailable && response?.verifiedDate) {
|
||||
this.identifierFormControl.setValue(response.organizationIdentifier);
|
||||
await this.submit();
|
||||
return;
|
||||
if (response.data.length > 0) {
|
||||
this.identifierFormControl.setValue(response.data[0].organizationIdentifier);
|
||||
await this.submit();
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
const response: OrganizationDomainSsoDetailsResponse =
|
||||
await this.orgDomainApiService.getClaimedOrgDomainByEmail(qParams.email);
|
||||
|
||||
if (response?.ssoAvailable && response?.verifiedDate) {
|
||||
this.identifierFormControl.setValue(response.organizationIdentifier);
|
||||
await this.submit();
|
||||
return;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
this.handleGetClaimedDomainByEmailError(error);
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
unauthGuardFn,
|
||||
} from "@bitwarden/angular/auth/guards";
|
||||
import { canAccessFeature } from "@bitwarden/angular/platform/guard/feature-flag.guard";
|
||||
import { generatorSwap } from "@bitwarden/angular/tools/generator/generator-swap";
|
||||
import { extensionRefreshSwap } from "@bitwarden/angular/utils/extension-refresh-swap";
|
||||
import {
|
||||
AnonLayoutWrapperComponent,
|
||||
@@ -70,6 +71,7 @@ import { RequestSMAccessComponent } from "./secrets-manager/secrets-manager-land
|
||||
import { SMLandingComponent } from "./secrets-manager/secrets-manager-landing/sm-landing.component";
|
||||
import { DomainRulesComponent } from "./settings/domain-rules.component";
|
||||
import { PreferencesComponent } from "./settings/preferences.component";
|
||||
import { CredentialGeneratorComponent } from "./tools/credential-generator/credential-generator.component";
|
||||
import { GeneratorComponent } from "./tools/generator.component";
|
||||
import { ReportsModule } from "./tools/reports";
|
||||
import { AccessComponent } from "./tools/send/access.component";
|
||||
@@ -598,11 +600,10 @@ const routes: Routes = [
|
||||
titleId: "exportVault",
|
||||
} satisfies RouteDataProperties,
|
||||
},
|
||||
{
|
||||
...generatorSwap(GeneratorComponent, CredentialGeneratorComponent, {
|
||||
path: "generator",
|
||||
component: GeneratorComponent,
|
||||
data: { titleId: "generator" } satisfies RouteDataProperties,
|
||||
},
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
<app-header></app-header>
|
||||
|
||||
<bit-container>
|
||||
<tools-credential-generator />
|
||||
</bit-container>
|
||||
@@ -0,0 +1,14 @@
|
||||
import { Component } from "@angular/core";
|
||||
|
||||
import { GeneratorModule } from "@bitwarden/generator-components";
|
||||
|
||||
import { HeaderModule } from "../../layouts/header/header.module";
|
||||
import { SharedModule } from "../../shared";
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
selector: "credential-generator",
|
||||
templateUrl: "credential-generator.component.html",
|
||||
imports: [SharedModule, HeaderModule, GeneratorModule],
|
||||
})
|
||||
export class CredentialGeneratorComponent {}
|
||||
@@ -1500,7 +1500,12 @@
|
||||
"description": "Minimum special characters"
|
||||
},
|
||||
"ambiguous": {
|
||||
"message": "Avoid ambiguous characters"
|
||||
"message": "Avoid ambiguous characters",
|
||||
"description": "deprecated. Use avoidAmbiguous instead."
|
||||
},
|
||||
"avoidAmbiguous": {
|
||||
"message": "Avoid ambiguous characters",
|
||||
"description": "Label for the avoid ambiguous characters checkbox."
|
||||
},
|
||||
"regeneratePassword": {
|
||||
"message": "Regenerate password"
|
||||
@@ -1513,18 +1518,51 @@
|
||||
},
|
||||
"uppercase": {
|
||||
"message": "Uppercase (A-Z)",
|
||||
"description": "Include uppercase letters in the password generator."
|
||||
"description": "deprecated. Use uppercaseLabel instead."
|
||||
},
|
||||
"lowercase": {
|
||||
"message": "Lowercase (a-z)",
|
||||
"description": "Include lowercase letters in the password generator."
|
||||
"description": "deprecated. Use lowercaseLabel instead."
|
||||
},
|
||||
"numbers": {
|
||||
"message": "Numbers (0-9)"
|
||||
"message": "Numbers (0-9)",
|
||||
"description": "deprecated. Use numbersLabel instead."
|
||||
},
|
||||
"specialCharacters": {
|
||||
"message": "Special characters (!@#$%^&*)"
|
||||
},
|
||||
"uppercaseDescription": {
|
||||
"message": "Include uppercase characters",
|
||||
"description": "Tooltip for the password generator uppercase character checkbox"
|
||||
},
|
||||
"uppercaseLabel": {
|
||||
"message": "A-Z",
|
||||
"description": "Label for the password generator uppercase character checkbox"
|
||||
},
|
||||
"lowercaseDescription": {
|
||||
"message": "Include lowercase characters",
|
||||
"description": "Full description for the password generator lowercase character checkbox"
|
||||
},
|
||||
"lowercaseLabel": {
|
||||
"message": "a-z",
|
||||
"description": "Label for the password generator lowercase character checkbox"
|
||||
},
|
||||
"numbersDescription": {
|
||||
"message": "Include numbers",
|
||||
"description": "Full description for the password generator numbers checkbox"
|
||||
},
|
||||
"numbersLabel": {
|
||||
"message": "0-9",
|
||||
"description": "Label for the password generator numbers checkbox"
|
||||
},
|
||||
"specialCharactersDescription": {
|
||||
"message": "Include special characters",
|
||||
"description": "Full description for the password generator special characters checkbox"
|
||||
},
|
||||
"specialCharactersLabel": {
|
||||
"message": "!@#$%^&*",
|
||||
"description": "Label for the password generator special characters checkbox"
|
||||
},
|
||||
"numWords": {
|
||||
"message": "Number of words"
|
||||
},
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import { ListResponse } from "@bitwarden/common/models/response/list.response";
|
||||
|
||||
import { OrganizationDomainRequest } from "../../services/organization-domain/requests/organization-domain.request";
|
||||
|
||||
import { OrganizationDomainSsoDetailsResponse } from "./responses/organization-domain-sso-details.response";
|
||||
import { OrganizationDomainResponse } from "./responses/organization-domain.response";
|
||||
import { VerifiedOrganizationDomainSsoDetailsResponse } from "./responses/verified-organization-domain-sso-details.response";
|
||||
|
||||
export abstract class OrgDomainApiServiceAbstraction {
|
||||
getAllByOrgId: (orgId: string) => Promise<Array<OrganizationDomainResponse>>;
|
||||
@@ -16,4 +19,7 @@ export abstract class OrgDomainApiServiceAbstraction {
|
||||
verify: (orgId: string, orgDomainId: string) => Promise<OrganizationDomainResponse>;
|
||||
delete: (orgId: string, orgDomainId: string) => Promise<any>;
|
||||
getClaimedOrgDomainByEmail: (email: string) => Promise<OrganizationDomainSsoDetailsResponse>;
|
||||
getVerifiedOrgDomainsByEmail: (
|
||||
email: string,
|
||||
) => Promise<ListResponse<VerifiedOrganizationDomainSsoDetailsResponse>>;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
import { BaseResponse } from "@bitwarden/common/models/response/base.response";
|
||||
|
||||
export class VerifiedOrganizationDomainSsoDetailsResponse extends BaseResponse {
|
||||
organizationName: string;
|
||||
organizationIdentifier: string;
|
||||
domainName: string;
|
||||
|
||||
constructor(response: any) {
|
||||
super(response);
|
||||
|
||||
this.organizationName = this.getResponseProperty("organizationName");
|
||||
this.organizationIdentifier = this.getResponseProperty("organizationIdentifier");
|
||||
this.domainName = this.getResponseProperty("domainName");
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,9 @@
|
||||
import { mock } from "jest-mock-extended";
|
||||
import { lastValueFrom } from "rxjs";
|
||||
|
||||
import { VerifiedOrganizationDomainSsoDetailsResponse } from "@bitwarden/common/admin-console/abstractions/organization-domain/responses/verified-organization-domain-sso-details.response";
|
||||
import { ListResponse } from "@bitwarden/common/models/response/list.response";
|
||||
|
||||
import { ApiService } from "../../../abstractions/api.service";
|
||||
import { I18nService } from "../../../platform/abstractions/i18n.service";
|
||||
import { PlatformUtilsService } from "../../../platform/abstractions/platform-utils.service";
|
||||
@@ -81,6 +84,19 @@ const mockedOrganizationDomainSsoDetailsResponse = new OrganizationDomainSsoDeta
|
||||
mockedOrganizationDomainSsoDetailsServerResponse,
|
||||
);
|
||||
|
||||
const mockedVerifiedOrganizationDomain = {
|
||||
organizationIdentifier: "fake-org-identifier",
|
||||
organizationName: "fake-org",
|
||||
domainName: "fake-domain-name",
|
||||
};
|
||||
|
||||
const mockedVerifiedOrganizationDomainSsoResponse =
|
||||
new VerifiedOrganizationDomainSsoDetailsResponse(mockedVerifiedOrganizationDomain);
|
||||
|
||||
const mockedVerifiedOrganizationDomainSsoDetailsListResponse = {
|
||||
data: [mockedVerifiedOrganizationDomain],
|
||||
} as ListResponse<VerifiedOrganizationDomainSsoDetailsResponse>;
|
||||
|
||||
describe("Org Domain API Service", () => {
|
||||
let orgDomainApiService: OrgDomainApiService;
|
||||
|
||||
@@ -229,4 +245,21 @@ describe("Org Domain API Service", () => {
|
||||
|
||||
expect(result).toEqual(mockedOrganizationDomainSsoDetailsResponse);
|
||||
});
|
||||
|
||||
it("getVerifiedOrgDomainsByEmail should call ApiService.send with correct parameters and return response", async () => {
|
||||
const email = "test@example.com";
|
||||
apiService.send.mockResolvedValue(mockedVerifiedOrganizationDomainSsoDetailsListResponse);
|
||||
|
||||
const result = await orgDomainApiService.getVerifiedOrgDomainsByEmail(email);
|
||||
|
||||
expect(apiService.send).toHaveBeenCalledWith(
|
||||
"POST",
|
||||
"/organizations/domain/sso/verified",
|
||||
new OrganizationDomainSsoDetailsRequest(email),
|
||||
false, //anonymous
|
||||
true,
|
||||
);
|
||||
|
||||
expect(result.data).toContainEqual(mockedVerifiedOrganizationDomainSsoResponse);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -4,6 +4,7 @@ import { OrgDomainApiServiceAbstraction } from "../../abstractions/organization-
|
||||
import { OrgDomainInternalServiceAbstraction } from "../../abstractions/organization-domain/org-domain.service.abstraction";
|
||||
import { OrganizationDomainSsoDetailsResponse } from "../../abstractions/organization-domain/responses/organization-domain-sso-details.response";
|
||||
import { OrganizationDomainResponse } from "../../abstractions/organization-domain/responses/organization-domain.response";
|
||||
import { VerifiedOrganizationDomainSsoDetailsResponse } from "../../abstractions/organization-domain/responses/verified-organization-domain-sso-details.response";
|
||||
|
||||
import { OrganizationDomainSsoDetailsRequest } from "./requests/organization-domain-sso-details.request";
|
||||
import { OrganizationDomainRequest } from "./requests/organization-domain.request";
|
||||
@@ -109,4 +110,18 @@ export class OrgDomainApiService implements OrgDomainApiServiceAbstraction {
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
async getVerifiedOrgDomainsByEmail(
|
||||
email: string,
|
||||
): Promise<ListResponse<VerifiedOrganizationDomainSsoDetailsResponse>> {
|
||||
const result = await this.apiService.send(
|
||||
"POST",
|
||||
`/organizations/domain/sso/verified`,
|
||||
new OrganizationDomainSsoDetailsRequest(email),
|
||||
false, // anonymous
|
||||
true,
|
||||
);
|
||||
|
||||
return new ListResponse(result, VerifiedOrganizationDomainSsoDetailsResponse);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ export enum FeatureFlag {
|
||||
NotificationBarAddLoginImprovements = "notification-bar-add-login-improvements",
|
||||
AC2476_DeprecateStripeSourcesAPI = "AC-2476-deprecate-stripe-sources-api",
|
||||
CipherKeyEncryption = "cipher-key-encryption",
|
||||
VerifiedSsoDomainEndpoint = "pm-12337-refactor-sso-details-endpoint",
|
||||
PM11901_RefactorSelfHostingLicenseUploader = "PM-11901-refactor-self-hosting-license-uploader",
|
||||
Pm3478RefactorOrganizationUserApi = "pm-3478-refactor-organizationuser-api",
|
||||
AccessIntelligence = "pm-13227-access-intelligence",
|
||||
@@ -75,6 +76,7 @@ export const DefaultFeatureFlagValue = {
|
||||
[FeatureFlag.NotificationBarAddLoginImprovements]: FALSE,
|
||||
[FeatureFlag.AC2476_DeprecateStripeSourcesAPI]: FALSE,
|
||||
[FeatureFlag.CipherKeyEncryption]: FALSE,
|
||||
[FeatureFlag.VerifiedSsoDomainEndpoint]: FALSE,
|
||||
[FeatureFlag.PM11901_RefactorSelfHostingLicenseUploader]: FALSE,
|
||||
[FeatureFlag.Pm3478RefactorOrganizationUserApi]: FALSE,
|
||||
[FeatureFlag.AccessIntelligence]: FALSE,
|
||||
|
||||
@@ -12,7 +12,8 @@
|
||||
>
|
||||
</bit-form-field>
|
||||
<bit-form-field>
|
||||
<bit-label>{{ "password" | i18n }}</bit-label>
|
||||
<bit-label *ngIf="!originalSendView || !hasPassword">{{ "password" | i18n }}</bit-label>
|
||||
<bit-label *ngIf="originalSendView && hasPassword">{{ "newPassword" | i18n }}</bit-label>
|
||||
<input bitInput type="password" formControlName="password" />
|
||||
<button
|
||||
data-testid="toggle-visibility-for-password"
|
||||
|
||||
Reference in New Issue
Block a user