diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json index 4e6ad1bd08..7281036de6 100644 --- a/apps/browser/src/_locales/en/messages.json +++ b/apps/browser/src/_locales/en/messages.json @@ -2120,6 +2120,9 @@ } } }, + "autofillPageLoadPolicyActivated": { + "message": "Your organization policies have turned on auto-fill on page load." + }, "howToAutofill": { "message": "How to auto-fill" }, diff --git a/apps/browser/src/services/browser-policy.service.ts b/apps/browser/src/services/browser-policy.service.ts index 19bd14dbbe..6bdf3f6f35 100644 --- a/apps/browser/src/services/browser-policy.service.ts +++ b/apps/browser/src/services/browser-policy.service.ts @@ -1,6 +1,9 @@ -import { BehaviorSubject } from "rxjs"; +import { BehaviorSubject, filter, map, Observable, switchMap, tap } from "rxjs"; import { Jsonify } from "type-fest"; +import { OrganizationService } from "@bitwarden/common/abstractions/organization/organization.service.abstraction"; +import { StateService } from "@bitwarden/common/abstractions/state.service"; +import { PolicyType } from "@bitwarden/common/enums/policyType"; import { Policy } from "@bitwarden/common/models/domain/policy"; import { PolicyService } from "@bitwarden/common/services/policy/policy.service"; @@ -13,4 +16,29 @@ export class BrowserPolicyService extends PolicyService { initializeAs: "array", }) protected _policies: BehaviorSubject; + + constructor(stateService: StateService, organizationService: OrganizationService) { + super(stateService, organizationService); + this._policies.pipe(this.handleActivateAutofillPolicy.bind(this)).subscribe(); + } + + /** + * If the ActivateAutofill policy is enabled, save a flag indicating if we need to + * enable Autofill on page load. + */ + private handleActivateAutofillPolicy(policies$: Observable) { + return policies$.pipe( + map((policies) => policies.find((p) => p.type == PolicyType.ActivateAutofill && p.enabled)), + filter((p) => p != null), + switchMap(async (_) => [ + await this.stateService.getActivateAutoFillOnPageLoadFromPolicy(), + await this.stateService.getEnableAutoFillOnPageLoad(), + ]), + tap(([activated, autofillEnabled]) => { + if (activated === undefined) { + this.stateService.setActivateAutoFillOnPageLoadFromPolicy(!autofillEnabled); + } + }) + ); + } } diff --git a/apps/browser/src/vault/popup/components/vault/current-tab.component.ts b/apps/browser/src/vault/popup/components/vault/current-tab.component.ts index 1c5c83c0b8..a420a28f61 100644 --- a/apps/browser/src/vault/popup/components/vault/current-tab.component.ts +++ b/apps/browser/src/vault/popup/components/vault/current-tab.component.ts @@ -116,6 +116,17 @@ export class CurrentTabComponent implements OnInit, OnDestroy { this.search$ .pipe(debounceTime(500), takeUntil(this.destroy$)) .subscribe(() => this.searchVault()); + + // activate autofill on page load if policy is set + if (await this.stateService.getActivateAutoFillOnPageLoadFromPolicy()) { + await this.stateService.setEnableAutoFillOnPageLoad(true); + await this.stateService.setActivateAutoFillOnPageLoadFromPolicy(false); + this.platformUtilsService.showToast( + "info", + null, + this.i18nService.t("autofillPageLoadPolicyActivated") + ); + } } ngOnDestroy() { diff --git a/apps/browser/src/vault/popup/components/vault/vault-filter.component.ts b/apps/browser/src/vault/popup/components/vault/vault-filter.component.ts index e40040c052..0068ae6164 100644 --- a/apps/browser/src/vault/popup/components/vault/vault-filter.component.ts +++ b/apps/browser/src/vault/popup/components/vault/vault-filter.component.ts @@ -6,6 +6,7 @@ import { first } from "rxjs/operators"; import { VaultFilter } from "@bitwarden/angular/vault/vault-filter/models/vault-filter.model"; import { BroadcasterService } from "@bitwarden/common/abstractions/broadcaster.service"; +import { I18nService } from "@bitwarden/common/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service"; import { SearchService } from "@bitwarden/common/abstractions/search.service"; import { TreeNode } from "@bitwarden/common/models/domain/tree-node"; @@ -72,6 +73,7 @@ export class VaultFilterComponent implements OnInit, OnDestroy { private allCiphers: CipherView[] = null; constructor( + private i18nService: I18nService, private cipherService: CipherService, private router: Router, private ngZone: NgZone, diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index 013f6fa6bc..d95bf7172d 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -4853,6 +4853,18 @@ "personalVaultExportPolicyInEffect": { "message": "One or more organization policies prevents you from exporting your individual vault." }, + "activateAutofill": { + "message": "Activate auto-fill" + }, + "activateAutofillDesc": { + "message": "Activate the auto-fill with page load settings on the browser extension for all existing and new members." + }, + "experimentalFeature": { + "message": "Compromised or untrusted websites can exploit auto-fill on page load." + }, + "learnMoreAboutAutofill": { + "message": "Learn more about auto-fill" + }, "selectType": { "message": "Select SSO type" }, diff --git a/bitwarden_license/bit-web/src/app/app.component.ts b/bitwarden_license/bit-web/src/app/app.component.ts index a66456cc33..34b7acbd9d 100644 --- a/bitwarden_license/bit-web/src/app/app.component.ts +++ b/bitwarden_license/bit-web/src/app/app.component.ts @@ -2,6 +2,7 @@ import { Component } from "@angular/core"; import { AppComponent as BaseAppComponent } from "@bitwarden/web-vault/app/app.component"; +import { ActivateAutofillPolicy } from "./policies/activate-autofill.component"; import { DisablePersonalVaultExportPolicy } from "./policies/disable-personal-vault-export.component"; import { MaximumVaultTimeoutPolicy } from "./policies/maximum-vault-timeout.component"; @@ -16,6 +17,7 @@ export class AppComponent extends BaseAppComponent { this.policyListService.addPolicies([ new MaximumVaultTimeoutPolicy(), new DisablePersonalVaultExportPolicy(), + new ActivateAutofillPolicy(), ]); } } diff --git a/bitwarden_license/bit-web/src/app/app.module.ts b/bitwarden_license/bit-web/src/app/app.module.ts index c26b04ff30..e7d59d4fab 100644 --- a/bitwarden_license/bit-web/src/app/app.module.ts +++ b/bitwarden_license/bit-web/src/app/app.module.ts @@ -15,6 +15,7 @@ import { WildcardRoutingModule } from "@bitwarden/web-vault/app/wildcard-routing import { AppRoutingModule } from "./app-routing.module"; import { AppComponent } from "./app.component"; import { OrganizationsModule } from "./organizations/organizations.module"; +import { ActivateAutofillPolicyComponent } from "./policies/activate-autofill.component"; import { DisablePersonalVaultExportPolicyComponent } from "./policies/disable-personal-vault-export.component"; import { MaximumVaultTimeoutPolicyComponent } from "./policies/maximum-vault-timeout.component"; @@ -39,6 +40,7 @@ import { MaximumVaultTimeoutPolicyComponent } from "./policies/maximum-vault-tim AppComponent, DisablePersonalVaultExportPolicyComponent, MaximumVaultTimeoutPolicyComponent, + ActivateAutofillPolicyComponent, ], bootstrap: [AppComponent], }) diff --git a/bitwarden_license/bit-web/src/app/policies/activate-autofill.component.html b/bitwarden_license/bit-web/src/app/policies/activate-autofill.component.html new file mode 100644 index 0000000000..cbff9e5e68 --- /dev/null +++ b/bitwarden_license/bit-web/src/app/policies/activate-autofill.component.html @@ -0,0 +1,19 @@ + + {{ "experimentalFeature" | i18n }} + {{ + "learnMoreAboutAutofill" | i18n + }} + + +
+
+ + +
+
diff --git a/bitwarden_license/bit-web/src/app/policies/activate-autofill.component.ts b/bitwarden_license/bit-web/src/app/policies/activate-autofill.component.ts new file mode 100644 index 0000000000..da39a2fc45 --- /dev/null +++ b/bitwarden_license/bit-web/src/app/policies/activate-autofill.component.ts @@ -0,0 +1,25 @@ +import { Component } from "@angular/core"; + +import { PolicyType } from "@bitwarden/common/enums/policyType"; +import { Organization } from "@bitwarden/common/models/domain/organization"; +import { + BasePolicy, + BasePolicyComponent, +} from "@bitwarden/web-vault/app/organizations/policies/base-policy.component"; + +export class ActivateAutofillPolicy extends BasePolicy { + name = "activateAutofill"; + description = "activateAutofillDesc"; + type = PolicyType.ActivateAutofill; + component = ActivateAutofillPolicyComponent; + + display(organization: Organization) { + return organization.useActivateAutofillPolicy; + } +} + +@Component({ + selector: "policy-activate-autofill", + templateUrl: "activate-autofill.component.html", +}) +export class ActivateAutofillPolicyComponent extends BasePolicyComponent {} diff --git a/libs/common/src/abstractions/state.service.ts b/libs/common/src/abstractions/state.service.ts index 2e7217f391..c14bc90461 100644 --- a/libs/common/src/abstractions/state.service.ts +++ b/libs/common/src/abstractions/state.service.ts @@ -358,7 +358,13 @@ export abstract class StateService { getAvatarColor: (options?: StorageOptions) => Promise; setAvatarColor: (value: string, options?: StorageOptions) => Promise; - + getActivateAutoFillOnPageLoadFromPolicy: ( + options?: StorageOptions + ) => Promise; + setActivateAutoFillOnPageLoadFromPolicy: ( + value: boolean, + options?: StorageOptions + ) => Promise; getSMOnboardingTasks: ( options?: StorageOptions ) => Promise>>; diff --git a/libs/common/src/enums/policyType.ts b/libs/common/src/enums/policyType.ts index 02dce41ebb..3680a46ef7 100644 --- a/libs/common/src/enums/policyType.ts +++ b/libs/common/src/enums/policyType.ts @@ -10,4 +10,5 @@ export enum PolicyType { ResetPassword = 8, // Allows orgs to use reset password : also can enable auto-enrollment during invite flow MaximumVaultTimeout = 9, // Sets the maximum allowed vault timeout DisablePersonalVaultExport = 10, // Disable personal vault export + ActivateAutofill = 11, // Activates autofill with page load on the browser extension } diff --git a/libs/common/src/models/data/organization.data.ts b/libs/common/src/models/data/organization.data.ts index 838381e4ac..5ed04f7532 100644 --- a/libs/common/src/models/data/organization.data.ts +++ b/libs/common/src/models/data/organization.data.ts @@ -23,6 +23,7 @@ export class OrganizationData { useCustomPermissions: boolean; useResetPassword: boolean; useSecretsManager: boolean; + useActivateAutofillPolicy: boolean; selfHost: boolean; usersGetPremium: boolean; seats: number; @@ -66,6 +67,7 @@ export class OrganizationData { this.useCustomPermissions = response.useCustomPermissions; this.useResetPassword = response.useResetPassword; this.useSecretsManager = response.useSecretsManager; + this.useActivateAutofillPolicy = response.useActivateAutofillPolicy; this.selfHost = response.selfHost; this.usersGetPremium = response.usersGetPremium; this.seats = response.seats; diff --git a/libs/common/src/models/domain/account.ts b/libs/common/src/models/domain/account.ts index 175eeaa3b4..9eebeae6f8 100644 --- a/libs/common/src/models/domain/account.ts +++ b/libs/common/src/models/domain/account.ts @@ -238,6 +238,7 @@ export class AccountSettings { serverConfig?: ServerConfigData; approveLoginRequests?: boolean; avatarColor?: string; + activateAutoFillOnPageLoadFromPolicy?: boolean; smOnboardingTasks?: Record>; static fromJSON(obj: Jsonify): AccountSettings { diff --git a/libs/common/src/models/domain/organization.ts b/libs/common/src/models/domain/organization.ts index 96b197d3ee..ea6c235a8a 100644 --- a/libs/common/src/models/domain/organization.ts +++ b/libs/common/src/models/domain/organization.ts @@ -25,6 +25,7 @@ export class Organization { useCustomPermissions: boolean; useResetPassword: boolean; useSecretsManager: boolean; + useActivateAutofillPolicy: boolean; selfHost: boolean; usersGetPremium: boolean; seats: number; @@ -72,6 +73,7 @@ export class Organization { this.useCustomPermissions = obj.useCustomPermissions; this.useResetPassword = obj.useResetPassword; this.useSecretsManager = obj.useSecretsManager; + this.useActivateAutofillPolicy = obj.useActivateAutofillPolicy; this.selfHost = obj.selfHost; this.usersGetPremium = obj.usersGetPremium; this.seats = obj.seats; diff --git a/libs/common/src/models/response/profile-organization.response.ts b/libs/common/src/models/response/profile-organization.response.ts index d12153152f..608a126e91 100644 --- a/libs/common/src/models/response/profile-organization.response.ts +++ b/libs/common/src/models/response/profile-organization.response.ts @@ -21,6 +21,7 @@ export class ProfileOrganizationResponse extends BaseResponse { useCustomPermissions: boolean; useResetPassword: boolean; useSecretsManager: boolean; + useActivateAutofillPolicy: boolean; selfHost: boolean; usersGetPremium: boolean; seats: number; @@ -65,6 +66,7 @@ export class ProfileOrganizationResponse extends BaseResponse { this.useCustomPermissions = this.getResponseProperty("UseCustomPermissions") ?? false; this.useResetPassword = this.getResponseProperty("UseResetPassword"); this.useSecretsManager = this.getResponseProperty("UseSecretsManager"); + this.useActivateAutofillPolicy = this.getResponseProperty("UseActivateAutofillPolicy"); this.selfHost = this.getResponseProperty("SelfHost"); this.usersGetPremium = this.getResponseProperty("UsersGetPremium"); this.seats = this.getResponseProperty("Seats"); diff --git a/libs/common/src/services/policy/policy.service.ts b/libs/common/src/services/policy/policy.service.ts index 8b1868f013..2a837b807c 100644 --- a/libs/common/src/services/policy/policy.service.ts +++ b/libs/common/src/services/policy/policy.service.ts @@ -21,7 +21,7 @@ export class PolicyService implements InternalPolicyServiceAbstraction { policies$ = this._policies.asObservable(); constructor( - private stateService: StateService, + protected stateService: StateService, private organizationService: OrganizationService ) { this.stateService.activeAccountUnlocked$ diff --git a/libs/common/src/services/state.service.ts b/libs/common/src/services/state.service.ts index 0a96784711..103b6f16a7 100644 --- a/libs/common/src/services/state.service.ts +++ b/libs/common/src/services/state.service.ts @@ -2365,6 +2365,26 @@ export class StateService< ); } + async getActivateAutoFillOnPageLoadFromPolicy(options?: StorageOptions): Promise { + return ( + await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskLocalOptions())) + )?.settings?.activateAutoFillOnPageLoadFromPolicy; + } + + async setActivateAutoFillOnPageLoadFromPolicy( + value: boolean, + options?: StorageOptions + ): Promise { + const account = await this.getAccount( + this.reconcileOptions(options, await this.defaultOnDiskLocalOptions()) + ); + account.settings.activateAutoFillOnPageLoadFromPolicy = value; + return await this.saveAccount( + account, + this.reconcileOptions(options, await this.defaultOnDiskLocalOptions()) + ); + } + async getSMOnboardingTasks( options?: StorageOptions ): Promise>> {