1
0
mirror of https://github.com/bitwarden/browser.git synced 2025-03-12 13:39:14 +01:00

Merge branch 'main' into autofill/pm-6546-blurring-of-autofilled-elements-causes-problems-in-blur-event-listeners

This commit is contained in:
Cesar Gonzalez 2024-03-15 02:51:37 -05:00 committed by GitHub
commit d59373fafb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
126 changed files with 1586 additions and 775 deletions

View File

@ -119,7 +119,7 @@
"message": "Дадаць лагін"
},
"addCardMenu": {
"message": "Add card"
"message": "Дадаць картку"
},
"addIdentityMenu": {
"message": "Add identity"
@ -269,7 +269,7 @@
"message": "Даўжыня"
},
"passwordMinLength": {
"message": "Minimum password length"
"message": "Мінімальная даўжыня пароля"
},
"uppercase": {
"message": "Вялікія літары (A-Z)"
@ -369,7 +369,7 @@
"message": "Iншае"
},
"unlockMethodNeededToChangeTimeoutActionDesc": {
"message": "Set up an unlock method to change your vault timeout action."
"message": "Наладзіць метад разблакіроўкі для змянення дзеяння часу чакання вашага сховішча."
},
"unlockMethodNeeded": {
"message": "Set up an unlock method in Settings"
@ -415,7 +415,7 @@
"message": "Заблакіраваць зараз"
},
"lockAll": {
"message": "Lock all"
"message": "Заблакаваць усе"
},
"immediately": {
"message": "Адразу"
@ -494,7 +494,7 @@
"message": "Ваш уліковы запіс створаны! Цяпер вы можаце ўвайсці ў яго."
},
"youSuccessfullyLoggedIn": {
"message": "You successfully logged in"
"message": "Вы паспяхова аўтарызаваны"
},
"youMayCloseThisWindow": {
"message": "You may close this window"
@ -2005,7 +2005,7 @@
"message": "Выбраць папку..."
},
"noFoldersFound": {
"message": "No folders found",
"message": "Папкі не знойдзены",
"description": "Used as a message within the notification bar when no folders are found"
},
"orgPermissionsUpdatedMustSetPassword": {
@ -2215,7 +2215,7 @@
"message": "Версія сервера"
},
"selfHostedServer": {
"message": "self-hosted"
"message": "уласнае размяшчэнне"
},
"thirdParty": {
"message": "Іншы пастаўшчык"
@ -2356,25 +2356,25 @@
}
},
"loggingInOn": {
"message": "Logging in on"
"message": "Увайсці на"
},
"opensInANewWindow": {
"message": "Адкрываць у новым акне"
},
"deviceApprovalRequired": {
"message": "Device approval required. Select an approval option below:"
"message": "Патрабуецца ўхваленне прылады. Выберыце параметры ўхвалення ніжэй:"
},
"rememberThisDevice": {
"message": "Remember this device"
"message": "Запомніць гэту прыладу"
},
"uncheckIfPublicDevice": {
"message": "Uncheck if using a public device"
"message": "Здыміце пазнаку, калі выкарыстоўваеце агульнадаступную прыладу"
},
"approveFromYourOtherDevice": {
"message": "Approve from your other device"
"message": "Ухваліць з іншай вашай прылады"
},
"requestAdminApproval": {
"message": "Request admin approval"
"message": "Запытаць ухваленне адміністратара"
},
"approveWithMasterPassword": {
"message": "Approve with master password"
@ -2402,7 +2402,7 @@
"message": "Адлюстраванне"
},
"accountSuccessfullyCreated": {
"message": "Account successfully created!"
"message": "Уліковы запіс паспяхова створаны!"
},
"adminApprovalRequested": {
"message": "Admin approval requested"

View File

@ -688,7 +688,7 @@
"message": "A bejelentkezési jelszó frissítésének kérése, ha változást lett érzékelve egy webhelyen. Minden bejelentkezett fiókra vonatkozik."
},
"enableUsePasskeys": {
"message": "Kérés a jhozzáférési kulcs mentésére és használatára"
"message": "Kérés a hozzáférési kulcs mentésére és használatára"
},
"usePasskeysDesc": {
"message": "Kérés az új hozzáféréi kulcsok mentésére vagy bejelentkezés a széfben tárolt hozzáférési kulcsokkal. Minden bejelentkezett fiókra vonatkozik."

View File

@ -0,0 +1,38 @@
import { AvatarService as AvatarServiceAbstraction } from "@bitwarden/common/auth/abstractions/avatar.service";
import { AvatarService } from "@bitwarden/common/auth/services/avatar.service";
import {
ApiServiceInitOptions,
apiServiceFactory,
} from "../../../platform/background/service-factories/api-service.factory";
import {
CachedServices,
factory,
FactoryOptions,
} from "../../../platform/background/service-factories/factory-options";
import {
stateProviderFactory,
StateProviderInitOptions,
} from "../../../platform/background/service-factories/state-provider.factory";
type AvatarServiceFactoryOptions = FactoryOptions;
export type AvatarServiceInitOptions = AvatarServiceFactoryOptions &
ApiServiceInitOptions &
StateProviderInitOptions;
export function avatarServiceFactory(
cache: { avatarService?: AvatarServiceAbstraction } & CachedServices,
opts: AvatarServiceInitOptions,
): Promise<AvatarServiceAbstraction> {
return factory(
cache,
"avatarService",
opts,
async () =>
new AvatarService(
await apiServiceFactory(cache, opts),
await stateProviderFactory(cache, opts),
),
);
}

View File

@ -1,32 +1,61 @@
import { Location } from "@angular/common";
import { Component } from "@angular/core";
import { ActivatedRoute, Router } from "@angular/router";
import { Observable, combineLatest, switchMap } from "rxjs";
import { CurrentAccountService } from "./services/current-account.service";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { AvatarService } from "@bitwarden/common/auth/abstractions/avatar.service";
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
import { UserId } from "@bitwarden/common/types/guid";
export type CurrentAccount = {
id: UserId;
name: string | undefined;
email: string;
status: AuthenticationStatus;
avatarColor: string;
};
@Component({
selector: "app-current-account",
templateUrl: "current-account.component.html",
})
export class CurrentAccountComponent {
currentAccount$: Observable<CurrentAccount>;
constructor(
private currentAccountService: CurrentAccountService,
private accountService: AccountService,
private avatarService: AvatarService,
private router: Router,
private location: Location,
private route: ActivatedRoute,
) {}
) {
this.currentAccount$ = combineLatest([
this.accountService.activeAccount$,
this.avatarService.avatarColor$,
]).pipe(
switchMap(async ([account, avatarColor]) => {
if (account == null) {
return null;
}
const currentAccount: CurrentAccount = {
id: account.id,
name: account.name || account.email,
email: account.email,
status: account.status,
avatarColor,
};
get currentAccount$() {
return this.currentAccountService.currentAccount$;
return currentAccount;
}),
);
}
async currentAccountClicked() {
if (this.route.snapshot.data.state.includes("account-switcher")) {
this.location.back();
} else {
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
// eslint-disable-next-line @typescript-eslint/no-floating-promises
this.router.navigate(["/account-switcher"]);
await this.router.navigate(["/account-switcher"]);
}
}
}

View File

@ -1,12 +1,12 @@
import { matches, mock } from "jest-mock-extended";
import { BehaviorSubject, firstValueFrom, timeout } from "rxjs";
import { BehaviorSubject, firstValueFrom, of, timeout } from "rxjs";
import { AccountInfo, AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { AvatarService } from "@bitwarden/common/auth/abstractions/avatar.service";
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import { UserId } from "@bitwarden/common/types/guid";
import { AccountSwitcherService } from "./account-switcher.service";
@ -16,7 +16,7 @@ describe("AccountSwitcherService", () => {
const activeAccountSubject = new BehaviorSubject<{ id: UserId } & AccountInfo>(null);
const accountService = mock<AccountService>();
const stateService = mock<StateService>();
const avatarService = mock<AvatarService>();
const messagingService = mock<MessagingService>();
const environmentService = mock<EnvironmentService>();
const logService = mock<LogService>();
@ -25,11 +25,13 @@ describe("AccountSwitcherService", () => {
beforeEach(() => {
jest.resetAllMocks();
accountService.accounts$ = accountsSubject;
accountService.activeAccount$ = activeAccountSubject;
accountSwitcherService = new AccountSwitcherService(
accountService,
stateService,
avatarService,
messagingService,
environmentService,
logService,
@ -44,6 +46,7 @@ describe("AccountSwitcherService", () => {
status: AuthenticationStatus.Unlocked,
};
avatarService.getUserAvatarColor$.mockReturnValue(of("#cccccc"));
accountsSubject.next({
"1": user1AccountInfo,
} as Record<UserId, AccountInfo>);
@ -72,6 +75,7 @@ describe("AccountSwitcherService", () => {
status: AuthenticationStatus.Unlocked,
};
}
avatarService.getUserAvatarColor$.mockReturnValue(of("#cccccc"));
accountsSubject.next(seedAccounts);
activeAccountSubject.next(
Object.assign(seedAccounts["1" as UserId], { id: "1" as UserId }),

View File

@ -11,11 +11,11 @@ import {
} from "rxjs";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { AvatarService } from "@bitwarden/common/auth/abstractions/avatar.service";
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import { UserId } from "@bitwarden/common/types/guid";
import { fromChromeEvent } from "../../../../platform/browser/from-chrome-event";
@ -44,7 +44,7 @@ export class AccountSwitcherService {
constructor(
private accountService: AccountService,
private stateService: StateService,
private avatarService: AvatarService,
private messagingService: MessagingService,
private environmentService: EnvironmentService,
private logService: LogService,
@ -68,7 +68,9 @@ export class AccountSwitcherService {
server: await this.environmentService.getHost(id),
status: account.status,
isActive: id === activeAccount?.id,
avatarColor: await this.stateService.getAvatarColor({ userId: id }),
avatarColor: await firstValueFrom(
this.avatarService.getUserAvatarColor$(id as UserId),
),
};
}),
);

View File

@ -1,44 +0,0 @@
import { Injectable } from "@angular/core";
import { Observable, switchMap } from "rxjs";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import { UserId } from "@bitwarden/common/types/guid";
export type CurrentAccount = {
id: UserId;
name: string | undefined;
email: string;
status: AuthenticationStatus;
avatarColor: string;
};
@Injectable({
providedIn: "root",
})
export class CurrentAccountService {
currentAccount$: Observable<CurrentAccount>;
constructor(
private accountService: AccountService,
private stateService: StateService,
) {
this.currentAccount$ = this.accountService.activeAccount$.pipe(
switchMap(async (account) => {
if (account == null) {
return null;
}
const currentAccount: CurrentAccount = {
id: account.id,
name: account.name || account.email,
email: account.email,
status: account.status,
avatarColor: await this.stateService.getAvatarColor({ userId: account.id }),
};
return currentAccount;
}),
);
}
}

View File

@ -3380,6 +3380,34 @@ describe("AutofillService", () => {
expect(value).toBe(false);
});
it("validates attribute identifiers with mixed camel case and non-alpha characters", () => {
const attributes: Record<string, boolean> = {
_$1_go_look: true,
go_look: true,
goLook: true,
go1look: true,
"go look": true,
look_go: true,
findPerson: true,
query$1: true,
look_goo: false,
golook: false,
lookgo: false,
logonField: false,
ego_input: false,
"Gold Password": false,
searching_for: false,
person_finder: false,
};
const autofillFieldMocks = Object.keys(attributes).map((key) =>
createAutofillFieldMock({ htmlID: key }),
);
autofillFieldMocks.forEach((field) => {
const value = AutofillService["isSearchField"](field);
expect(value).toBe(attributes[field.htmlID]);
});
});
});
describe("isFieldMatch", () => {

View File

@ -44,6 +44,7 @@ export default class AutofillService implements AutofillServiceInterface {
private openPasswordRepromptPopoutDebounce: NodeJS.Timeout;
private currentlyOpeningPasswordRepromptPopout = false;
private autofillScriptPortsSet = new Set<chrome.runtime.Port>();
static searchFieldNamesSet = new Set(AutoFillConstants.SearchFieldNames);
constructor(
private cipherService: CipherService,
@ -1380,11 +1381,33 @@ export default class AutofillService implements AutofillServiceInterface {
return excludedTypes.indexOf(type) > -1;
}
/**
* Identifies if a passed field contains text artifacts that identify it as a search field.
*
* @param field - The autofill field that we are validating as a search field
*/
private static isSearchField(field: AutofillField) {
const matchFieldAttributeValues = [field.type, field.htmlName, field.htmlID, field.placeholder];
const matchPattern = new RegExp(AutoFillConstants.SearchFieldNames.join("|"), "gi");
for (let attrIndex = 0; attrIndex < matchFieldAttributeValues.length; attrIndex++) {
if (!matchFieldAttributeValues[attrIndex]) {
continue;
}
return Boolean(matchFieldAttributeValues.join(" ").match(matchPattern));
// Separate camel case words and case them to lower case values
const camelCaseSeparatedFieldAttribute = matchFieldAttributeValues[attrIndex]
.replace(/([a-z])([A-Z])/g, "$1 $2")
.toLowerCase();
// Split the attribute by non-alphabetical characters to get the keywords
const attributeKeywords = camelCaseSeparatedFieldAttribute.split(/[^a-z]/gi);
for (let keywordIndex = 0; keywordIndex < attributeKeywords.length; keywordIndex++) {
if (AutofillService.searchFieldNamesSet.has(attributeKeywords[keywordIndex])) {
return true;
}
}
}
return false;
}
static isExcludedFieldType(field: AutofillField, excludedTypes: string[]) {
@ -1397,11 +1420,7 @@ export default class AutofillService implements AutofillServiceInterface {
}
// Check if the input is an untyped/mistyped search input
if (this.isSearchField(field)) {
return true;
}
return false;
return this.isSearchField(field);
}
/**
@ -1525,11 +1544,7 @@ export default class AutofillService implements AutofillServiceInterface {
return false;
}
if (AutoFillConstants.PasswordFieldExcludeList.some((i) => cleanedValue.indexOf(i) > -1)) {
return false;
}
return true;
return !AutoFillConstants.PasswordFieldExcludeList.some((i) => cleanedValue.indexOf(i) > -1);
}
static fieldHasDisqualifyingAttributeValue(field: AutofillField) {
@ -1572,7 +1587,11 @@ export default class AutofillService implements AutofillServiceInterface {
const arr: AutofillField[] = [];
pageDetails.fields.forEach((f) => {
if (AutofillService.isExcludedFieldType(f, AutoFillConstants.ExcludedAutofillLoginTypes)) {
const isPassword = f.type === "password";
if (
!isPassword &&
AutofillService.isExcludedFieldType(f, AutoFillConstants.ExcludedAutofillLoginTypes)
) {
return;
}
@ -1581,23 +1600,16 @@ export default class AutofillService implements AutofillServiceInterface {
return;
}
const isPassword = f.type === "password";
const isLikePassword = () => {
if (f.type !== "text") {
return false;
}
if (AutofillService.valueIsLikePassword(f.htmlID)) {
return true;
}
if (AutofillService.valueIsLikePassword(f.htmlName)) {
return true;
}
if (AutofillService.valueIsLikePassword(f.placeholder)) {
return true;
const testedValues = [f.htmlID, f.htmlName, f.placeholder];
for (let i = 0; i < testedValues.length; i++) {
if (AutofillService.valueIsLikePassword(testedValues[i])) {
return true;
}
}
return false;

View File

@ -755,6 +755,9 @@ class CollectAutofillContentService implements CollectAutofillContentServiceInte
// Prioritize capturing text content from elements rather than nodes.
currentElement = currentElement.parentElement || currentElement.parentNode;
if (!currentElement) {
return textContentItems;
}
let siblingElement = nodeIsElement(currentElement)
? currentElement.previousElementSibling

View File

@ -8,7 +8,6 @@ import {
AuthRequestServiceAbstraction,
AuthRequestService,
} from "@bitwarden/auth/common";
import { AvatarUpdateService as AvatarUpdateServiceAbstraction } from "@bitwarden/common/abstractions/account/avatar-update.service";
import { ApiService as ApiServiceAbstraction } from "@bitwarden/common/abstractions/api.service";
import { AuditService as AuditServiceAbstraction } from "@bitwarden/common/abstractions/audit.service";
import { EventCollectionService as EventCollectionServiceAbstraction } from "@bitwarden/common/abstractions/event/event-collection.service";
@ -39,6 +38,7 @@ import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authenticatio
import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason";
import { AccountServiceImplementation } from "@bitwarden/common/auth/services/account.service";
import { AuthService } from "@bitwarden/common/auth/services/auth.service";
import { AvatarService } from "@bitwarden/common/auth/services/avatar.service";
import { DeviceTrustCryptoService } from "@bitwarden/common/auth/services/device-trust-crypto.service.implementation";
import { DevicesServiceImplementation } from "@bitwarden/common/auth/services/devices/devices.service.implementation";
import { DevicesApiServiceImplementation } from "@bitwarden/common/auth/services/devices-api.service.implementation";
@ -117,7 +117,6 @@ import { DefaultStateProvider } from "@bitwarden/common/platform/state/implement
import { StateEventRegistrarService } from "@bitwarden/common/platform/state/state-event-registrar.service";
/* eslint-enable import/no-restricted-paths */
import { DefaultThemeStateService } from "@bitwarden/common/platform/theming/theme-state.service";
import { AvatarUpdateService } from "@bitwarden/common/services/account/avatar-update.service";
import { ApiService } from "@bitwarden/common/services/api.service";
import { AuditService } from "@bitwarden/common/services/audit.service";
import { EventCollectionService } from "@bitwarden/common/services/event/event-collection.service";
@ -125,6 +124,7 @@ import { EventUploadService } from "@bitwarden/common/services/event/event-uploa
import { NotificationsService } from "@bitwarden/common/services/notifications.service";
import { SearchService } from "@bitwarden/common/services/search.service";
import { VaultTimeoutSettingsService } from "@bitwarden/common/services/vault-timeout/vault-timeout-settings.service";
import { AvatarService as AvatarServiceAbstraction } from "@bitwarden/common/src/auth/abstractions/avatar.service";
import {
PasswordGenerationService,
PasswordGenerationServiceAbstraction,
@ -288,7 +288,7 @@ export default class MainBackground {
fido2UserInterfaceService: Fido2UserInterfaceServiceAbstraction;
fido2AuthenticatorService: Fido2AuthenticatorServiceAbstraction;
fido2ClientService: Fido2ClientServiceAbstraction;
avatarUpdateService: AvatarUpdateServiceAbstraction;
avatarService: AvatarServiceAbstraction;
mainContextMenuHandler: MainContextMenuHandler;
cipherContextMenuHandler: CipherContextMenuHandler;
configService: BrowserConfigService;
@ -409,8 +409,7 @@ export default class MainBackground {
);
this.activeUserStateProvider = new DefaultActiveUserStateProvider(
this.accountService,
storageServiceProvider,
stateEventRegistrarService,
this.singleUserStateProvider,
);
this.derivedStateProvider = new BackgroundDerivedStateProvider(
this.memoryStorageForStateProviders,
@ -685,7 +684,11 @@ export default class MainBackground {
this.fileUploadService,
this.sendService,
);
this.avatarService = new AvatarService(this.apiService, this.stateProvider);
this.providerService = new ProviderService(this.stateProvider);
this.syncService = new SyncService(
this.apiService,
this.domainSettingsService,
@ -703,6 +706,7 @@ export default class MainBackground {
this.folderApiService,
this.organizationService,
this.sendApiService,
this.avatarService,
logoutCallback,
);
this.eventUploadService = new EventUploadService(
@ -943,8 +947,6 @@ export default class MainBackground {
this.apiService,
);
this.avatarUpdateService = new AvatarUpdateService(this.apiService, this.stateService);
if (!this.popupOnlyContext) {
this.mainContextMenuHandler = new MainContextMenuHandler(
this.stateService,

View File

@ -97,6 +97,10 @@ export default class RuntimeBackground {
case "unlocked": {
let item: LockedVaultPendingNotificationsData;
if (msg.command === "loggedIn") {
await this.sendBwInstalledMessageToVault();
}
if (this.lockedVaultPendingNotifications?.length > 0) {
item = this.lockedVaultPendingNotifications.pop();
await closeUnlockPopout();
@ -130,9 +134,6 @@ export default class RuntimeBackground {
await this.main.refreshBadge();
await this.main.refreshMenu();
}, 2000);
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
// eslint-disable-next-line @typescript-eslint/no-floating-promises
this.main.avatarUpdateService.loadColorFromState();
this.configService.triggerServerConfigFetch();
}
break;
@ -354,8 +355,6 @@ export default class RuntimeBackground {
if (await this.environmentService.hasManagedEnvironment()) {
await this.environmentService.setUrlsToManagedEnvironment();
}
await this.sendBwInstalledMessageToVault();
}
this.onInstalledReason = null;

View File

@ -9,20 +9,15 @@ import {
import { CachedServices, FactoryOptions, factory } from "./factory-options";
import {
StateEventRegistrarServiceInitOptions,
stateEventRegistrarServiceFactory,
} from "./state-event-registrar-service.factory";
import {
StorageServiceProviderInitOptions,
storageServiceProviderFactory,
} from "./storage-service-provider.factory";
SingleUserStateProviderInitOptions,
singleUserStateProviderFactory,
} from "./single-user-state-provider.factory";
type ActiveUserStateProviderFactory = FactoryOptions;
export type ActiveUserStateProviderInitOptions = ActiveUserStateProviderFactory &
AccountServiceInitOptions &
StorageServiceProviderInitOptions &
StateEventRegistrarServiceInitOptions;
SingleUserStateProviderInitOptions;
export async function activeUserStateProviderFactory(
cache: { activeUserStateProvider?: ActiveUserStateProvider } & CachedServices,
@ -35,8 +30,7 @@ export async function activeUserStateProviderFactory(
async () =>
new DefaultActiveUserStateProvider(
await accountServiceFactory(cache, opts),
await storageServiceProviderFactory(cache, opts),
await stateEventRegistrarServiceFactory(cache, opts),
await singleUserStateProviderFactory(cache, opts),
),
);
}

View File

@ -114,8 +114,10 @@ export class OptionsComponent implements OnInit {
this.autofillSettingsService.enableContextMenu$,
);
this.showCardsCurrentTab = !(await this.stateService.getDontShowCardsCurrentTab());
this.showIdentitiesCurrentTab = !(await this.stateService.getDontShowIdentitiesCurrentTab());
this.showCardsCurrentTab = await firstValueFrom(this.vaultSettingsService.showCardsCurrentTab$);
this.showIdentitiesCurrentTab = await firstValueFrom(
this.vaultSettingsService.showIdentitiesCurrentTab$,
);
this.enableAutoTotpCopy = await firstValueFrom(this.autofillSettingsService.autoCopyTotp$);
@ -178,11 +180,11 @@ export class OptionsComponent implements OnInit {
}
async updateShowCardsCurrentTab() {
await this.stateService.setDontShowCardsCurrentTab(!this.showCardsCurrentTab);
await this.vaultSettingsService.setShowCardsCurrentTab(this.showCardsCurrentTab);
}
async updateShowIdentitiesCurrentTab() {
await this.stateService.setDontShowIdentitiesCurrentTab(!this.showIdentitiesCurrentTab);
await this.vaultSettingsService.setShowIdentitiesCurrentTab(this.showIdentitiesCurrentTab);
}
async saveTheme() {

View File

@ -10,10 +10,10 @@ import { AutofillSettingsServiceAbstraction } from "@bitwarden/common/autofill/s
import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
import { VaultSettingsService } from "@bitwarden/common/vault/abstractions/vault-settings/vault-settings.service";
import { CipherType } from "@bitwarden/common/vault/enums";
import { CipherRepromptType } from "@bitwarden/common/vault/enums/cipher-reprompt-type";
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
@ -65,11 +65,11 @@ export class CurrentTabComponent implements OnInit, OnDestroy {
private changeDetectorRef: ChangeDetectorRef,
private syncService: SyncService,
private searchService: SearchService,
private stateService: StateService,
private autofillSettingsService: AutofillSettingsServiceAbstraction,
private passwordRepromptService: PasswordRepromptService,
private organizationService: OrganizationService,
private vaultFilterService: VaultFilterService,
private vaultSettingsService: VaultSettingsService,
) {}
async ngOnInit() {
@ -271,8 +271,10 @@ export class CurrentTabComponent implements OnInit, OnDestroy {
});
const otherTypes: CipherType[] = [];
const dontShowCards = await this.stateService.getDontShowCardsCurrentTab();
const dontShowIdentities = await this.stateService.getDontShowIdentitiesCurrentTab();
const dontShowCards = !(await firstValueFrom(this.vaultSettingsService.showCardsCurrentTab$));
const dontShowIdentities = !(await firstValueFrom(
this.vaultSettingsService.showIdentitiesCurrentTab$,
));
this.showOrganizations = this.organizationService.hasOrganizations();
if (!dontShowCards) {
otherTypes.push(CipherType.Card);

View File

@ -23,10 +23,12 @@ import { PolicyApiService } from "@bitwarden/common/admin-console/services/polic
import { PolicyService } from "@bitwarden/common/admin-console/services/policy/policy.service";
import { ProviderService } from "@bitwarden/common/admin-console/services/provider.service";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { AvatarService as AvatarServiceAbstraction } from "@bitwarden/common/auth/abstractions/avatar.service";
import { DeviceTrustCryptoServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust-crypto.service.abstraction";
import { DevicesApiServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices-api.service.abstraction";
import { AccountServiceImplementation } from "@bitwarden/common/auth/services/account.service";
import { AuthService } from "@bitwarden/common/auth/services/auth.service";
import { AvatarService } from "@bitwarden/common/auth/services/avatar.service";
import { DeviceTrustCryptoService } from "@bitwarden/common/auth/services/device-trust-crypto.service.implementation";
import { DevicesApiServiceImplementation } from "@bitwarden/common/auth/services/devices-api.service.implementation";
import { KeyConnectorService } from "@bitwarden/common/auth/services/key-connector.service";
@ -216,6 +218,7 @@ export class Main {
derivedStateProvider: DerivedStateProvider;
stateProvider: StateProvider;
loginStrategyService: LoginStrategyServiceAbstraction;
avatarService: AvatarServiceAbstraction;
stateEventRunnerService: StateEventRunnerService;
biometricStateService: BiometricStateService;
@ -291,8 +294,7 @@ export class Main {
this.activeUserStateProvider = new DefaultActiveUserStateProvider(
this.accountService,
storageServiceProvider,
stateEventRegistrarService,
this.singleUserStateProvider,
);
this.derivedStateProvider = new DefaultDerivedStateProvider(
@ -555,6 +557,8 @@ export class Main {
null,
);
this.avatarService = new AvatarService(this.apiService, this.stateProvider);
this.syncService = new SyncService(
this.apiService,
this.domainSettingsService,
@ -572,6 +576,7 @@ export class Main {
this.folderApiService,
this.organizationService,
this.sendApiService,
this.avatarService,
async (expired: boolean) => await this.logout(),
);

View File

@ -2,9 +2,10 @@ import { animate, state, style, transition, trigger } from "@angular/animations"
import { ConnectedPosition } from "@angular/cdk/overlay";
import { Component, OnDestroy, OnInit } from "@angular/core";
import { Router } from "@angular/router";
import { concatMap, Subject, takeUntil } from "rxjs";
import { concatMap, firstValueFrom, Subject, takeUntil } from "rxjs";
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
import { AvatarService } from "@bitwarden/common/auth/abstractions/avatar.service";
import { TokenService } from "@bitwarden/common/auth/abstractions/token.service";
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
@ -12,6 +13,7 @@ import { MessagingService } from "@bitwarden/common/platform/abstractions/messag
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { Account } from "@bitwarden/common/platform/models/domain/account";
import { UserId } from "@bitwarden/common/types/guid";
type ActiveAccount = {
id: string;
@ -84,6 +86,7 @@ export class AccountSwitcherComponent implements OnInit, OnDestroy {
constructor(
private stateService: StateService,
private authService: AuthService,
private avatarService: AvatarService,
private messagingService: MessagingService,
private router: Router,
private tokenService: TokenService,
@ -101,7 +104,7 @@ export class AccountSwitcherComponent implements OnInit, OnDestroy {
id: await this.tokenService.getUserId(),
name: (await this.tokenService.getName()) ?? (await this.tokenService.getEmail()),
email: await this.tokenService.getEmail(),
avatarColor: await this.stateService.getAvatarColor(),
avatarColor: await firstValueFrom(this.avatarService.avatarColor$),
server: await this.environmentService.getHost(),
};
} catch {
@ -154,7 +157,7 @@ export class AccountSwitcherComponent implements OnInit, OnDestroy {
name: baseAccounts[userId].profile.name,
email: baseAccounts[userId].profile.email,
authenticationStatus: await this.authService.getAuthStatus(userId),
avatarColor: await this.stateService.getAvatarColor({ userId: userId }),
avatarColor: await firstValueFrom(this.avatarService.getUserAvatarColor$(userId as UserId)),
server: await this.environmentService.getHost(userId),
};
}

View File

@ -124,13 +124,14 @@ export class Main {
storageServiceProvider,
);
const singleUserStateProvider = new DefaultSingleUserStateProvider(
storageServiceProvider,
stateEventRegistrarService,
);
const stateProvider = new DefaultStateProvider(
new DefaultActiveUserStateProvider(
accountService,
storageServiceProvider,
stateEventRegistrarService,
),
new DefaultSingleUserStateProvider(storageServiceProvider, stateEventRegistrarService),
new DefaultActiveUserStateProvider(accountService, singleUserStateProvider),
singleUserStateProvider,
globalStateProvider,
new DefaultDerivedStateProvider(this.memoryStorageForStateProviders),
);

View File

@ -399,7 +399,11 @@
</bit-tab>
<bit-tab *ngIf="organization.useGroups" [label]="'groups' | i18n">
<div class="tw-mb-6">
{{ "groupAccessUserDesc" | i18n }}
{{
(restrictedAccess$ | async)
? ("restrictedGroupAccess" | i18n)
: ("groupAccessUserDesc" | i18n)
}}
</div>
<bit-access-selector
formControlName="groups"
@ -408,10 +412,14 @@
[selectorLabelText]="'selectGroups' | i18n"
[emptySelectionText]="'noGroupsAdded' | i18n"
[flexibleCollectionsEnabled]="organization.flexibleCollections"
[hideMultiSelect]="restrictedAccess$ | async"
></bit-access-selector>
</bit-tab>
<bit-tab [label]="'collections' | i18n">
<div *ngIf="organization.useGroups" class="tw-mb-6">
<div class="tw-mb-6" *ngIf="restrictedAccess$ | async">
{{ "restrictedCollectionAccess" | i18n }}
</div>
<div *ngIf="organization.useGroups && !(restrictedAccess$ | async)" class="tw-mb-6">
{{ "userPermissionOverrideHelper" | i18n }}
</div>
<div *ngIf="!organization.flexibleCollections" class="tw-mb-6">
@ -441,6 +449,7 @@
[selectorLabelText]="'selectCollections' | i18n"
[emptySelectionText]="'noCollectionsAdded' | i18n"
[flexibleCollectionsEnabled]="organization.flexibleCollections"
[hideMultiSelect]="restrictedAccess$ | async"
></bit-access-selector
></bit-tab>
</bit-tab-group>

View File

@ -4,6 +4,7 @@ import { FormBuilder, Validators } from "@angular/forms";
import {
combineLatest,
firstValueFrom,
map,
Observable,
of,
shareReplay,
@ -20,7 +21,9 @@ import {
} from "@bitwarden/common/admin-console/enums";
import { PermissionsApi } from "@bitwarden/common/admin-console/models/api/permissions.api";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { ProductType } from "@bitwarden/common/enums";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
@ -99,6 +102,8 @@ export class MemberDialogComponent implements OnDestroy {
groups: [[] as AccessItemValue[]],
});
protected restrictedAccess$: Observable<boolean>;
protected permissionsGroup = this.formBuilder.group({
manageAssignedCollectionsGroup: this.formBuilder.group<Record<string, boolean>>({
manageAssignedCollections: false,
@ -144,6 +149,7 @@ export class MemberDialogComponent implements OnDestroy {
private organizationUserService: OrganizationUserService,
private dialogService: DialogService,
private configService: ConfigServiceAbstraction,
private accountService: AccountService,
organizationService: OrganizationService,
) {
this.organization$ = organizationService
@ -162,12 +168,43 @@ export class MemberDialogComponent implements OnDestroy {
),
);
const userDetails$ = this.params.organizationUserId
? this.userService.get(this.params.organizationId, this.params.organizationUserId)
: of(null);
// The orgUser cannot manage their own Group assignments if collection access is restricted
// TODO: fix disabled state of access-selector rows so that any controls are hidden
this.restrictedAccess$ = combineLatest([
this.organization$,
userDetails$,
this.accountService.activeAccount$,
this.configService.getFeatureFlag$(FeatureFlag.FlexibleCollectionsV1),
]).pipe(
map(
([organization, userDetails, activeAccount, flexibleCollectionsV1Enabled]) =>
// Feature flag conditionals
flexibleCollectionsV1Enabled &&
organization.flexibleCollections &&
// Business logic conditionals
userDetails != null &&
userDetails.userId == activeAccount.id &&
!organization.allowAdminAccessToAllCollectionItems,
),
shareReplay({ refCount: true, bufferSize: 1 }),
);
this.restrictedAccess$.pipe(takeUntil(this.destroy$)).subscribe((restrictedAccess) => {
if (restrictedAccess) {
this.formGroup.controls.groups.disable();
} else {
this.formGroup.controls.groups.enable();
}
});
combineLatest({
organization: this.organization$,
collections: this.collectionAdminService.getAll(this.params.organizationId),
userDetails: this.params.organizationUserId
? this.userService.get(this.params.organizationId, this.params.organizationUserId)
: of(null),
userDetails: userDetails$,
groups: groups$,
})
.pipe(takeUntil(this.destroy$))
@ -369,7 +406,11 @@ export class MemberDialogComponent implements OnDestroy {
userView.collections = this.formGroup.value.access
.filter((v) => v.type === AccessItemType.Collection)
.map(convertToSelectionView);
userView.groups = this.formGroup.value.groups.map((m) => m.id);
userView.groups = (await firstValueFrom(this.restrictedAccess$))
? null
: this.formGroup.value.groups.map((m) => m.id);
userView.accessSecretsManager = this.formGroup.value.accessSecretsManager;
if (this.editMode) {

View File

@ -1,6 +1,6 @@
<!-- Please remove this disable statement when editing this file! -->
<!-- eslint-disable tailwindcss/no-custom-classname -->
<div class="tw-flex">
<div class="tw-flex" *ngIf="!hideMultiSelect">
<bit-form-field *ngIf="permissionMode == 'edit'" class="tw-mr-3 tw-shrink-0">
<bit-label>{{ "permission" | i18n }}</bit-label>
<!--

View File

@ -197,6 +197,11 @@ export class AccessSelectorComponent implements ControlValueAccessor, OnInit, On
this.permissionList = getPermissionList(value);
}
/**
* Hide the multi-select so that new items cannot be added
*/
@Input() hideMultiSelect = false;
private _flexibleCollectionsEnabled: boolean;
constructor(

View File

@ -9,9 +9,9 @@ import {
ViewChild,
ViewEncapsulation,
} from "@angular/core";
import { BehaviorSubject, debounceTime, Subject, takeUntil } from "rxjs";
import { BehaviorSubject, debounceTime, firstValueFrom, Subject, takeUntil } from "rxjs";
import { AvatarUpdateService } from "@bitwarden/common/abstractions/account/avatar-update.service";
import { AvatarService } from "@bitwarden/common/auth/abstractions/avatar.service";
import { ProfileResponse } from "@bitwarden/common/models/response/profile.response";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
@ -55,7 +55,7 @@ export class ChangeAvatarComponent implements OnInit, OnDestroy {
private i18nService: I18nService,
private platformUtilsService: PlatformUtilsService,
private logService: LogService,
private accountUpdateService: AvatarUpdateService,
private avatarService: AvatarService,
) {}
async ngOnInit() {
@ -73,9 +73,7 @@ export class ChangeAvatarComponent implements OnInit, OnDestroy {
this.currentSelection = color;
});
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
// eslint-disable-next-line @typescript-eslint/no-floating-promises
this.setSelection(await this.accountUpdateService.loadColorFromState());
await this.setSelection(await firstValueFrom(this.avatarService.avatarColor$));
}
async showCustomPicker() {
@ -93,7 +91,7 @@ export class ChangeAvatarComponent implements OnInit, OnDestroy {
async submit() {
try {
if (Utils.validateHexColor(this.currentSelection) || this.currentSelection == null) {
await this.accountUpdateService.pushUpdate(this.currentSelection);
await this.avatarService.setAvatarColor(this.currentSelection);
this.changeColor.emit(this.currentSelection);
this.platformUtilsService.showToast("success", null, this.i18nService.t("avatarUpdated"));
} else {

View File

@ -1,7 +1,7 @@
import { Component, Input, OnDestroy } from "@angular/core";
import { Observable, Subject } from "rxjs";
import { Subject } from "rxjs";
import { AvatarUpdateService } from "@bitwarden/common/abstractions/account/avatar-update.service";
import { AvatarService } from "@bitwarden/common/auth/abstractions/avatar.service";
import { SharedModule } from "../shared";
@ -29,14 +29,14 @@ export class DynamicAvatarComponent implements OnDestroy {
@Input() text: string;
@Input() title: string;
@Input() size: SizeTypes = "default";
color$: Observable<string | null>;
private destroy$ = new Subject<void>();
constructor(private accountUpdateService: AvatarUpdateService) {
color$ = this.avatarService.avatarColor$;
constructor(private avatarService: AvatarService) {
if (this.text) {
this.text = this.text.toUpperCase();
}
this.color$ = this.accountUpdateService.avatarUpdate$;
}
async ngOnDestroy() {

View File

@ -1,12 +1,12 @@
import { importProvidersFrom } from "@angular/core";
import { RouterModule } from "@angular/router";
import { applicationConfig, Meta, moduleMetadata, Story } from "@storybook/angular";
import { BehaviorSubject } from "rxjs";
import { BehaviorSubject, of } from "rxjs";
import { AvatarUpdateService } from "@bitwarden/common/abstractions/account/avatar-update.service";
import { SettingsService } from "@bitwarden/common/abstractions/settings.service";
import { OrganizationUserType } from "@bitwarden/common/admin-console/enums";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { AvatarService } from "@bitwarden/common/auth/abstractions/avatar.service";
import { TokenService } from "@bitwarden/common/auth/abstractions/token.service";
import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction";
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
@ -72,12 +72,10 @@ export default {
} as Partial<SettingsService>,
},
{
provide: AvatarUpdateService,
provide: AvatarService,
useValue: {
async loadColorFromState() {
return "#FF0000";
},
} as Partial<AvatarUpdateService>,
avatarColor$: of("#FF0000"),
} as Partial<AvatarService>,
},
{
provide: TokenService,

View File

@ -1,6 +1,7 @@
import { Component, Input, OnChanges } from "@angular/core";
import { firstValueFrom } from "rxjs";
import { AvatarUpdateService } from "@bitwarden/common/abstractions/account/avatar-update.service";
import { AvatarService } from "@bitwarden/common/auth/abstractions/avatar.service";
import { TokenService } from "@bitwarden/common/auth/abstractions/token.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
@ -24,7 +25,7 @@ export class OrganizationNameBadgeComponent implements OnChanges {
constructor(
private i18nService: I18nService,
private avatarService: AvatarUpdateService,
private avatarService: AvatarService,
private tokenService: TokenService,
) {}
@ -35,7 +36,7 @@ export class OrganizationNameBadgeComponent implements OnChanges {
if (this.isMe) {
this.name = this.i18nService.t("me");
this.color = await this.avatarService.loadColorFromState();
this.color = await firstValueFrom(this.avatarService.avatarColor$);
if (this.color == null) {
const userId = await this.tokenService.getUserId();
if (userId != null) {

View File

@ -7605,5 +7605,11 @@
},
"providerPortal": {
"message": "Provider Portal"
},
"restrictedGroupAccess": {
"message": "You cannot add yourself to groups."
},
"restrictedCollectionAccess": {
"message": "You cannot add yourself to collections."
}
}

View File

@ -7605,5 +7605,11 @@
},
"providerPortal": {
"message": "Provider Portal"
},
"restrictedGroupAccess": {
"message": "You cannot add yourself to groups."
},
"restrictedCollectionAccess": {
"message": "You cannot add yourself to collections."
}
}

View File

@ -7605,5 +7605,11 @@
},
"providerPortal": {
"message": "Provayder Portal"
},
"restrictedGroupAccess": {
"message": "You cannot add yourself to groups."
},
"restrictedCollectionAccess": {
"message": "You cannot add yourself to collections."
}
}

View File

@ -7605,5 +7605,11 @@
},
"providerPortal": {
"message": "Provider Portal"
},
"restrictedGroupAccess": {
"message": "You cannot add yourself to groups."
},
"restrictedCollectionAccess": {
"message": "You cannot add yourself to collections."
}
}

View File

@ -7605,5 +7605,11 @@
},
"providerPortal": {
"message": "Портал за доставчици"
},
"restrictedGroupAccess": {
"message": "You cannot add yourself to groups."
},
"restrictedCollectionAccess": {
"message": "You cannot add yourself to collections."
}
}

View File

@ -7605,5 +7605,11 @@
},
"providerPortal": {
"message": "Provider Portal"
},
"restrictedGroupAccess": {
"message": "You cannot add yourself to groups."
},
"restrictedCollectionAccess": {
"message": "You cannot add yourself to collections."
}
}

View File

@ -7605,5 +7605,11 @@
},
"providerPortal": {
"message": "Provider Portal"
},
"restrictedGroupAccess": {
"message": "You cannot add yourself to groups."
},
"restrictedCollectionAccess": {
"message": "You cannot add yourself to collections."
}
}

View File

@ -7605,5 +7605,11 @@
},
"providerPortal": {
"message": "Portal del proveïdor"
},
"restrictedGroupAccess": {
"message": "You cannot add yourself to groups."
},
"restrictedCollectionAccess": {
"message": "You cannot add yourself to collections."
}
}

View File

@ -7605,5 +7605,11 @@
},
"providerPortal": {
"message": "Portál poskytovatele"
},
"restrictedGroupAccess": {
"message": "You cannot add yourself to groups."
},
"restrictedCollectionAccess": {
"message": "You cannot add yourself to collections."
}
}

View File

@ -7605,5 +7605,11 @@
},
"providerPortal": {
"message": "Provider Portal"
},
"restrictedGroupAccess": {
"message": "You cannot add yourself to groups."
},
"restrictedCollectionAccess": {
"message": "You cannot add yourself to collections."
}
}

View File

@ -7605,5 +7605,11 @@
},
"providerPortal": {
"message": "Udbyderportal"
},
"restrictedGroupAccess": {
"message": "You cannot add yourself to groups."
},
"restrictedCollectionAccess": {
"message": "You cannot add yourself to collections."
}
}

View File

@ -7605,5 +7605,11 @@
},
"providerPortal": {
"message": "Anbieterportal"
},
"restrictedGroupAccess": {
"message": "You cannot add yourself to groups."
},
"restrictedCollectionAccess": {
"message": "You cannot add yourself to collections."
}
}

View File

@ -7605,5 +7605,11 @@
},
"providerPortal": {
"message": "Provider Portal"
},
"restrictedGroupAccess": {
"message": "You cannot add yourself to groups."
},
"restrictedCollectionAccess": {
"message": "You cannot add yourself to collections."
}
}

View File

@ -7605,5 +7605,11 @@
},
"providerPortal": {
"message": "Provider Portal"
},
"restrictedGroupAccess": {
"message": "You cannot add yourself to groups."
},
"restrictedCollectionAccess": {
"message": "You cannot add yourself to collections."
}
}

View File

@ -7605,5 +7605,11 @@
},
"providerPortal": {
"message": "Provider Portal"
},
"restrictedGroupAccess": {
"message": "You cannot add yourself to groups."
},
"restrictedCollectionAccess": {
"message": "You cannot add yourself to collections."
}
}

View File

@ -7605,5 +7605,11 @@
},
"providerPortal": {
"message": "Provider Portal"
},
"restrictedGroupAccess": {
"message": "You cannot add yourself to groups."
},
"restrictedCollectionAccess": {
"message": "You cannot add yourself to collections."
}
}

View File

@ -7605,5 +7605,11 @@
},
"providerPortal": {
"message": "Provider Portal"
},
"restrictedGroupAccess": {
"message": "You cannot add yourself to groups."
},
"restrictedCollectionAccess": {
"message": "You cannot add yourself to collections."
}
}

View File

@ -7605,5 +7605,11 @@
},
"providerPortal": {
"message": "Provider Portal"
},
"restrictedGroupAccess": {
"message": "You cannot add yourself to groups."
},
"restrictedCollectionAccess": {
"message": "You cannot add yourself to collections."
}
}

View File

@ -7605,5 +7605,11 @@
},
"providerPortal": {
"message": "Provider Portal"
},
"restrictedGroupAccess": {
"message": "You cannot add yourself to groups."
},
"restrictedCollectionAccess": {
"message": "You cannot add yourself to collections."
}
}

View File

@ -7605,5 +7605,11 @@
},
"providerPortal": {
"message": "Provider Portal"
},
"restrictedGroupAccess": {
"message": "You cannot add yourself to groups."
},
"restrictedCollectionAccess": {
"message": "You cannot add yourself to collections."
}
}

View File

@ -7605,5 +7605,11 @@
},
"providerPortal": {
"message": "Provider Portal"
},
"restrictedGroupAccess": {
"message": "You cannot add yourself to groups."
},
"restrictedCollectionAccess": {
"message": "You cannot add yourself to collections."
}
}

View File

@ -7605,5 +7605,11 @@
},
"providerPortal": {
"message": "Toimittajaportaali"
},
"restrictedGroupAccess": {
"message": "You cannot add yourself to groups."
},
"restrictedCollectionAccess": {
"message": "You cannot add yourself to collections."
}
}

View File

@ -7605,5 +7605,11 @@
},
"providerPortal": {
"message": "Provider Portal"
},
"restrictedGroupAccess": {
"message": "You cannot add yourself to groups."
},
"restrictedCollectionAccess": {
"message": "You cannot add yourself to collections."
}
}

View File

@ -7605,5 +7605,11 @@
},
"providerPortal": {
"message": "Portail fournisseur"
},
"restrictedGroupAccess": {
"message": "You cannot add yourself to groups."
},
"restrictedCollectionAccess": {
"message": "You cannot add yourself to collections."
}
}

View File

@ -7605,5 +7605,11 @@
},
"providerPortal": {
"message": "Provider Portal"
},
"restrictedGroupAccess": {
"message": "You cannot add yourself to groups."
},
"restrictedCollectionAccess": {
"message": "You cannot add yourself to collections."
}
}

View File

@ -7605,5 +7605,11 @@
},
"providerPortal": {
"message": "Provider Portal"
},
"restrictedGroupAccess": {
"message": "You cannot add yourself to groups."
},
"restrictedCollectionAccess": {
"message": "You cannot add yourself to collections."
}
}

View File

@ -7605,5 +7605,11 @@
},
"providerPortal": {
"message": "Provider Portal"
},
"restrictedGroupAccess": {
"message": "You cannot add yourself to groups."
},
"restrictedCollectionAccess": {
"message": "You cannot add yourself to collections."
}
}

View File

@ -7605,5 +7605,11 @@
},
"providerPortal": {
"message": "Provider Portal"
},
"restrictedGroupAccess": {
"message": "You cannot add yourself to groups."
},
"restrictedCollectionAccess": {
"message": "You cannot add yourself to collections."
}
}

View File

@ -7605,5 +7605,11 @@
},
"providerPortal": {
"message": "Szolgáltató portál"
},
"restrictedGroupAccess": {
"message": "You cannot add yourself to groups."
},
"restrictedCollectionAccess": {
"message": "You cannot add yourself to collections."
}
}

View File

@ -7605,5 +7605,11 @@
},
"providerPortal": {
"message": "Provider Portal"
},
"restrictedGroupAccess": {
"message": "You cannot add yourself to groups."
},
"restrictedCollectionAccess": {
"message": "You cannot add yourself to collections."
}
}

View File

@ -7605,5 +7605,11 @@
},
"providerPortal": {
"message": "Portale Fornitori"
},
"restrictedGroupAccess": {
"message": "You cannot add yourself to groups."
},
"restrictedCollectionAccess": {
"message": "You cannot add yourself to collections."
}
}

View File

@ -7605,5 +7605,11 @@
},
"providerPortal": {
"message": "プロバイダーポータル"
},
"restrictedGroupAccess": {
"message": "あなた自身をグループに追加することはできません。"
},
"restrictedCollectionAccess": {
"message": "コレクションに自分自身を追加することはできません。"
}
}

View File

@ -7605,5 +7605,11 @@
},
"providerPortal": {
"message": "Provider Portal"
},
"restrictedGroupAccess": {
"message": "You cannot add yourself to groups."
},
"restrictedCollectionAccess": {
"message": "You cannot add yourself to collections."
}
}

View File

@ -7605,5 +7605,11 @@
},
"providerPortal": {
"message": "Provider Portal"
},
"restrictedGroupAccess": {
"message": "You cannot add yourself to groups."
},
"restrictedCollectionAccess": {
"message": "You cannot add yourself to collections."
}
}

View File

@ -7605,5 +7605,11 @@
},
"providerPortal": {
"message": "Provider Portal"
},
"restrictedGroupAccess": {
"message": "You cannot add yourself to groups."
},
"restrictedCollectionAccess": {
"message": "You cannot add yourself to collections."
}
}

View File

@ -7605,5 +7605,11 @@
},
"providerPortal": {
"message": "Provider Portal"
},
"restrictedGroupAccess": {
"message": "You cannot add yourself to groups."
},
"restrictedCollectionAccess": {
"message": "You cannot add yourself to collections."
}
}

View File

@ -7605,5 +7605,11 @@
},
"providerPortal": {
"message": "Nodrošinātāju portāls"
},
"restrictedGroupAccess": {
"message": "You cannot add yourself to groups."
},
"restrictedCollectionAccess": {
"message": "You cannot add yourself to collections."
}
}

View File

@ -7605,5 +7605,11 @@
},
"providerPortal": {
"message": "Provider Portal"
},
"restrictedGroupAccess": {
"message": "You cannot add yourself to groups."
},
"restrictedCollectionAccess": {
"message": "You cannot add yourself to collections."
}
}

View File

@ -7605,5 +7605,11 @@
},
"providerPortal": {
"message": "Provider Portal"
},
"restrictedGroupAccess": {
"message": "You cannot add yourself to groups."
},
"restrictedCollectionAccess": {
"message": "You cannot add yourself to collections."
}
}

View File

@ -7605,5 +7605,11 @@
},
"providerPortal": {
"message": "Provider Portal"
},
"restrictedGroupAccess": {
"message": "You cannot add yourself to groups."
},
"restrictedCollectionAccess": {
"message": "You cannot add yourself to collections."
}
}

View File

@ -7605,5 +7605,11 @@
},
"providerPortal": {
"message": "Provider Portal"
},
"restrictedGroupAccess": {
"message": "You cannot add yourself to groups."
},
"restrictedCollectionAccess": {
"message": "You cannot add yourself to collections."
}
}

View File

@ -7605,5 +7605,11 @@
},
"providerPortal": {
"message": "Provider Portal"
},
"restrictedGroupAccess": {
"message": "You cannot add yourself to groups."
},
"restrictedCollectionAccess": {
"message": "You cannot add yourself to collections."
}
}

View File

@ -7605,5 +7605,11 @@
},
"providerPortal": {
"message": "Providerportaal"
},
"restrictedGroupAccess": {
"message": "You cannot add yourself to groups."
},
"restrictedCollectionAccess": {
"message": "You cannot add yourself to collections."
}
}

View File

@ -7605,5 +7605,11 @@
},
"providerPortal": {
"message": "Provider Portal"
},
"restrictedGroupAccess": {
"message": "You cannot add yourself to groups."
},
"restrictedCollectionAccess": {
"message": "You cannot add yourself to collections."
}
}

View File

@ -7605,5 +7605,11 @@
},
"providerPortal": {
"message": "Provider Portal"
},
"restrictedGroupAccess": {
"message": "You cannot add yourself to groups."
},
"restrictedCollectionAccess": {
"message": "You cannot add yourself to collections."
}
}

View File

@ -7605,5 +7605,11 @@
},
"providerPortal": {
"message": "Portal dostawcy"
},
"restrictedGroupAccess": {
"message": "You cannot add yourself to groups."
},
"restrictedCollectionAccess": {
"message": "You cannot add yourself to collections."
}
}

View File

@ -7605,5 +7605,11 @@
},
"providerPortal": {
"message": "Provider Portal"
},
"restrictedGroupAccess": {
"message": "You cannot add yourself to groups."
},
"restrictedCollectionAccess": {
"message": "You cannot add yourself to collections."
}
}

View File

@ -7605,5 +7605,11 @@
},
"providerPortal": {
"message": "Portal do fornecedor"
},
"restrictedGroupAccess": {
"message": "Não se pode adicionar a si próprio a grupos."
},
"restrictedCollectionAccess": {
"message": "Não se pode adicionar a si próprio a coleções."
}
}

View File

@ -7605,5 +7605,11 @@
},
"providerPortal": {
"message": "Provider Portal"
},
"restrictedGroupAccess": {
"message": "You cannot add yourself to groups."
},
"restrictedCollectionAccess": {
"message": "You cannot add yourself to collections."
}
}

View File

@ -7605,5 +7605,11 @@
},
"providerPortal": {
"message": "Портал провайдера"
},
"restrictedGroupAccess": {
"message": "You cannot add yourself to groups."
},
"restrictedCollectionAccess": {
"message": "You cannot add yourself to collections."
}
}

View File

@ -7605,5 +7605,11 @@
},
"providerPortal": {
"message": "Provider Portal"
},
"restrictedGroupAccess": {
"message": "You cannot add yourself to groups."
},
"restrictedCollectionAccess": {
"message": "You cannot add yourself to collections."
}
}

View File

@ -7605,5 +7605,11 @@
},
"providerPortal": {
"message": "Portál poskytovateľa"
},
"restrictedGroupAccess": {
"message": "You cannot add yourself to groups."
},
"restrictedCollectionAccess": {
"message": "You cannot add yourself to collections."
}
}

View File

@ -7605,5 +7605,11 @@
},
"providerPortal": {
"message": "Provider Portal"
},
"restrictedGroupAccess": {
"message": "You cannot add yourself to groups."
},
"restrictedCollectionAccess": {
"message": "You cannot add yourself to collections."
}
}

View File

@ -7605,5 +7605,11 @@
},
"providerPortal": {
"message": "Портал провајдера"
},
"restrictedGroupAccess": {
"message": "You cannot add yourself to groups."
},
"restrictedCollectionAccess": {
"message": "You cannot add yourself to collections."
}
}

View File

@ -7605,5 +7605,11 @@
},
"providerPortal": {
"message": "Provider Portal"
},
"restrictedGroupAccess": {
"message": "You cannot add yourself to groups."
},
"restrictedCollectionAccess": {
"message": "You cannot add yourself to collections."
}
}

View File

@ -7605,5 +7605,11 @@
},
"providerPortal": {
"message": "Provider Portal"
},
"restrictedGroupAccess": {
"message": "You cannot add yourself to groups."
},
"restrictedCollectionAccess": {
"message": "You cannot add yourself to collections."
}
}

View File

@ -7605,5 +7605,11 @@
},
"providerPortal": {
"message": "Provider Portal"
},
"restrictedGroupAccess": {
"message": "You cannot add yourself to groups."
},
"restrictedCollectionAccess": {
"message": "You cannot add yourself to collections."
}
}

View File

@ -7605,5 +7605,11 @@
},
"providerPortal": {
"message": "Provider Portal"
},
"restrictedGroupAccess": {
"message": "You cannot add yourself to groups."
},
"restrictedCollectionAccess": {
"message": "You cannot add yourself to collections."
}
}

View File

@ -7605,5 +7605,11 @@
},
"providerPortal": {
"message": "Sağlayıcı Portalı"
},
"restrictedGroupAccess": {
"message": "You cannot add yourself to groups."
},
"restrictedCollectionAccess": {
"message": "You cannot add yourself to collections."
}
}

View File

@ -7605,5 +7605,11 @@
},
"providerPortal": {
"message": "Портал провайдера"
},
"restrictedGroupAccess": {
"message": "You cannot add yourself to groups."
},
"restrictedCollectionAccess": {
"message": "You cannot add yourself to collections."
}
}

View File

@ -7605,5 +7605,11 @@
},
"providerPortal": {
"message": "Provider Portal"
},
"restrictedGroupAccess": {
"message": "You cannot add yourself to groups."
},
"restrictedCollectionAccess": {
"message": "You cannot add yourself to collections."
}
}

View File

@ -7605,5 +7605,11 @@
},
"providerPortal": {
"message": "提供商门户"
},
"restrictedGroupAccess": {
"message": "You cannot add yourself to groups."
},
"restrictedCollectionAccess": {
"message": "You cannot add yourself to collections."
}
}

View File

@ -7605,5 +7605,11 @@
},
"providerPortal": {
"message": "Provider Portal"
},
"restrictedGroupAccess": {
"message": "You cannot add yourself to groups."
},
"restrictedCollectionAccess": {
"message": "You cannot add yourself to collections."
}
}

View File

@ -9,7 +9,6 @@ import {
LoginStrategyServiceAbstraction,
LoginStrategyService,
} from "@bitwarden/auth/common";
import { AvatarUpdateService as AccountUpdateServiceAbstraction } from "@bitwarden/common/abstractions/account/avatar-update.service";
import { ApiService as ApiServiceAbstraction } from "@bitwarden/common/abstractions/api.service";
import { AuditService as AuditServiceAbstraction } from "@bitwarden/common/abstractions/audit.service";
import { EventCollectionService as EventCollectionServiceAbstraction } from "@bitwarden/common/abstractions/event/event-collection.service";
@ -51,6 +50,7 @@ import {
} from "@bitwarden/common/auth/abstractions/account.service";
import { AnonymousHubService as AnonymousHubServiceAbstraction } from "@bitwarden/common/auth/abstractions/anonymous-hub.service";
import { AuthService as AuthServiceAbstraction } from "@bitwarden/common/auth/abstractions/auth.service";
import { AvatarService as AvatarServiceAbstraction } from "@bitwarden/common/auth/abstractions/avatar.service";
import { DeviceTrustCryptoServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust-crypto.service.abstraction";
import { DevicesServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices/devices.service.abstraction";
import { DevicesApiServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices-api.service.abstraction";
@ -69,6 +69,7 @@ import { AccountApiServiceImplementation } from "@bitwarden/common/auth/services
import { AccountServiceImplementation } from "@bitwarden/common/auth/services/account.service";
import { AnonymousHubService } from "@bitwarden/common/auth/services/anonymous-hub.service";
import { AuthService } from "@bitwarden/common/auth/services/auth.service";
import { AvatarService } from "@bitwarden/common/auth/services/avatar.service";
import { DeviceTrustCryptoService } from "@bitwarden/common/auth/services/device-trust-crypto.service.implementation";
import { DevicesServiceImplementation } from "@bitwarden/common/auth/services/devices/devices.service.implementation";
import { DevicesApiServiceImplementation } from "@bitwarden/common/auth/services/devices-api.service.implementation";
@ -163,7 +164,6 @@ import {
DefaultThemeStateService,
ThemeStateService,
} from "@bitwarden/common/platform/theming/theme-state.service";
import { AvatarUpdateService } from "@bitwarden/common/services/account/avatar-update.service";
import { ApiService } from "@bitwarden/common/services/api.service";
import { AuditService } from "@bitwarden/common/services/audit.service";
import { EventCollectionService } from "@bitwarden/common/services/event/event-collection.service";
@ -452,9 +452,9 @@ const typesafeProviders: Array<SafeProvider> = [
useExisting: InternalAccountService,
}),
safeProvider({
provide: AccountUpdateServiceAbstraction,
useClass: AvatarUpdateService,
deps: [ApiServiceAbstraction, StateServiceAbstraction],
provide: AvatarServiceAbstraction,
useClass: AvatarService,
deps: [ApiServiceAbstraction, StateProvider],
}),
safeProvider({ provide: LogService, useFactory: () => new ConsoleLogService(false), deps: [] }),
safeProvider({
@ -561,6 +561,7 @@ const typesafeProviders: Array<SafeProvider> = [
FolderApiServiceAbstraction,
InternalOrganizationServiceAbstraction,
SendApiServiceAbstraction,
AvatarServiceAbstraction,
LOGOUT_CALLBACK,
],
}),
@ -953,7 +954,7 @@ const typesafeProviders: Array<SafeProvider> = [
safeProvider({
provide: ActiveUserStateProvider,
useClass: DefaultActiveUserStateProvider,
deps: [AccountServiceAbstraction, StorageServiceProvider, StateEventRegistrarService],
deps: [AccountServiceAbstraction, SingleUserStateProvider],
}),
safeProvider({
provide: SingleUserStateProvider,

View File

@ -119,7 +119,7 @@ export class FakeActiveUserStateProvider implements ActiveUserStateProvider {
states: Map<string, FakeActiveUserState<unknown>> = new Map();
constructor(public accountService: FakeAccountService) {
this.activeUserId$ = accountService.activeAccountSubject.asObservable().pipe(map((a) => a.id));
this.activeUserId$ = accountService.activeAccountSubject.asObservable().pipe(map((a) => a?.id));
}
get<T>(keyDefinition: KeyDefinition<T> | UserKeyDefinition<T>): ActiveUserState<T> {

View File

@ -1,8 +0,0 @@
import { Observable } from "rxjs";
import { ProfileResponse } from "../../models/response/profile.response";
export abstract class AvatarUpdateService {
avatarUpdate$ = new Observable<string | null>();
abstract pushUpdate(color: string): Promise<ProfileResponse | void>;
abstract loadColorFromState(): Promise<string | null>;
}

View File

@ -8,7 +8,6 @@ import {
OrganizationUserInviteRequest,
OrganizationUserResetPasswordEnrollmentRequest,
OrganizationUserResetPasswordRequest,
OrganizationUserUpdateGroupsRequest,
OrganizationUserUpdateRequest,
} from "./requests";
import {
@ -165,18 +164,6 @@ export abstract class OrganizationUserService {
request: OrganizationUserUpdateRequest,
): Promise<void>;
/**
* Update an organization user's groups
* @param organizationId - Identifier for the organization the user belongs to
* @param id - Organization user identifier
* @param groupIds - List of group ids to associate the user with
*/
abstract putOrganizationUserGroups(
organizationId: string,
id: string,
groupIds: OrganizationUserUpdateGroupsRequest,
): Promise<void>;
/**
* Update an organization user's reset password enrollment
* @param organizationId - Identifier for the organization the user belongs to

View File

@ -6,4 +6,3 @@ export * from "./organization-user-invite.request";
export * from "./organization-user-reset-password.request";
export * from "./organization-user-reset-password-enrollment.request";
export * from "./organization-user-update.request";
export * from "./organization-user-update-groups.request";

View File

@ -1,3 +0,0 @@
export class OrganizationUserUpdateGroupsRequest {
groupIds: string[] = [];
}

View File

@ -9,7 +9,6 @@ import {
OrganizationUserInviteRequest,
OrganizationUserResetPasswordEnrollmentRequest,
OrganizationUserResetPasswordRequest,
OrganizationUserUpdateGroupsRequest,
OrganizationUserUpdateRequest,
} from "../../abstractions/organization-user/requests";
import {
@ -233,20 +232,6 @@ export class OrganizationUserServiceImplementation implements OrganizationUserSe
);
}
putOrganizationUserGroups(
organizationId: string,
id: string,
request: OrganizationUserUpdateGroupsRequest,
): Promise<void> {
return this.apiService.send(
"PUT",
"/organizations/" + organizationId + "/users/" + id + "/groups",
request,
true,
false,
);
}
putOrganizationUserResetPasswordEnrollment(
organizationId: string,
userId: string,

View File

@ -0,0 +1,29 @@
import { Observable } from "rxjs";
import { UserId } from "../../types/guid";
export abstract class AvatarService {
/**
* An observable monitoring the active user's avatar color.
* The observable updates when the avatar color changes.
*/
avatarColor$: Observable<string | null>;
/**
* Sets the avatar color of the active user
*
* @param color the color to set the avatar color to
* @returns a promise that resolves when the avatar color is set
*/
abstract setAvatarColor(color: string): Promise<void>;
/**
* Gets the avatar color of the specified user.
*
* @remarks This is most useful for account switching where we show an
* avatar for each account. If you only need the active user's
* avatar color, use the avatarColor$ observable above instead.
*
* @param userId the userId of the user whose avatar color should be retreived
* @return an Observable that emits a string of the avatar color of the specified user
*/
abstract getUserAvatarColor$(userId: UserId): Observable<string | null>;
}

View File

@ -0,0 +1,33 @@
import { Observable } from "rxjs";
import { ApiService } from "../../abstractions/api.service";
import { UpdateAvatarRequest } from "../../models/request/update-avatar.request";
import { AVATAR_DISK, StateProvider, UserKeyDefinition } from "../../platform/state";
import { UserId } from "../../types/guid";
import { AvatarService as AvatarServiceAbstraction } from "../abstractions/avatar.service";
const AVATAR_COLOR = new UserKeyDefinition<string>(AVATAR_DISK, "avatarColor", {
deserializer: (value) => value,
clearOn: [],
});
export class AvatarService implements AvatarServiceAbstraction {
avatarColor$: Observable<string>;
constructor(
private apiService: ApiService,
private stateProvider: StateProvider,
) {
this.avatarColor$ = this.stateProvider.getActive(AVATAR_COLOR).state$;
}
async setAvatarColor(color: string): Promise<void> {
const { avatarColor } = await this.apiService.putAvatar(new UpdateAvatarRequest(color));
await this.stateProvider.setUserState(AVATAR_COLOR, avatarColor);
}
getUserAvatarColor$(userId: UserId): Observable<string | null> {
return this.stateProvider.getUser(userId, AVATAR_COLOR).state$;
}
}

View File

@ -42,7 +42,7 @@ const AUTOFILL_ON_PAGE_LOAD_POLICY_TOAST_HAS_DISPLAYED = new KeyDefinition(
);
const AUTO_COPY_TOTP = new KeyDefinition(AUTOFILL_SETTINGS_DISK, "autoCopyTotp", {
deserializer: (value: boolean) => value ?? false,
deserializer: (value: boolean) => value ?? true,
});
const INLINE_MENU_VISIBILITY = new KeyDefinition(
@ -144,7 +144,7 @@ export class AutofillSettingsService implements AutofillSettingsServiceAbstracti
);
this.autoCopyTotpState = this.stateProvider.getActive(AUTO_COPY_TOTP);
this.autoCopyTotp$ = this.autoCopyTotpState.state$.pipe(map((x) => x ?? false));
this.autoCopyTotp$ = this.autoCopyTotpState.state$.pipe(map((x) => x ?? true));
this.inlineMenuVisibilityState = this.stateProvider.getGlobal(INLINE_MENU_VISIBILITY);
this.inlineMenuVisibility$ = this.inlineMenuVisibilityState.state$.pipe(

View File

@ -4,7 +4,7 @@ import { ProfileOrganizationResponse } from "../../admin-console/models/response
import { ProfileProviderOrganizationResponse } from "../../admin-console/models/response/profile-provider-organization.response";
import { ProfileProviderResponse } from "../../admin-console/models/response/profile-provider.response";
import { KdfConfig } from "../../auth/models/domain/kdf-config";
import { OrganizationId, ProviderId } from "../../types/guid";
import { OrganizationId, ProviderId, UserId } from "../../types/guid";
import { UserKey, MasterKey, OrgKey, ProviderKey, PinKey, CipherKey } from "../../types/key";
import { KeySuffixOptions, KdfType, HashPurpose } from "../enums";
import { EncArrayBuffer } from "../models/domain/enc-array-buffer";
@ -62,12 +62,15 @@ export abstract class CryptoService {
getUserKeyFromStorage: (keySuffix: KeySuffixOptions, userId?: string) => Promise<UserKey>;
/**
* Determines whether the user key is available for the given user.
* @param userId The desired user. If not provided, the active user will be used. If no active user exists, the method will return false.
* @returns True if the user key is available
*/
hasUserKey: () => Promise<boolean>;
hasUserKey: (userId?: UserId) => Promise<boolean>;
/**
* @param userId The desired user
* @returns True if the user key is set in memory
* Determines whether the user key is available for the given user in memory.
* @param userId The desired user. If not provided, the active user will be used. If no active user exists, the method will return false.
* @returns True if the user key is available
*/
hasUserKeyInMemory: (userId?: string) => Promise<boolean>;
/**

Some files were not shown because too many files have changed in this diff Show More