1
0
mirror of https://github.com/bitwarden/browser.git synced 2025-02-06 23:51:28 +01:00

[PM-15506] Implement vNextOrganizationService (#12839)

* [PM-15506] Wire up vNextOrganizationService for libs/common and libs/angular (#12683)

* Wire up vNextOrganizationService in PolicyService

* Wire vNextOrganizationService in SyncService

* wire vNextOrganizationService for EventCollectionService

* wire vNextOrganizationService for KeyConnectorService

* wire up vNextOrganizationService for CipherAuthorizationService

* Wire up vNextOrganizationService in PolicyService

* Wire vNextOrganizationService in SyncService

* wire vNextOrganizationService for EventCollectionService

* wire vNextOrganizationService for KeyConnectorService

* wire up vNextOrganizationService for CipherAuthorizationService

* wire vNextOrganizationService for share.component

* wire vNextOrganizationService for collections.component

* wire vNextOrganizationServcie for add-account-credit-dialog

* wire vNextOrganizationService for vault-filter.service

* fix browser errors for vNextOrganizationService implementation in libs

* fix desktop errors for vNextOrganizationService implementation for libs

* fix linter errors

* fix CLI errors on vNextOrganizationServcie implementations for libs

* [PM-15506] Wire up vNextOrganizationService for web client (#12810)

PR to a feature branch, no need to review until this goes to main.

* implement vNextOrganization service for browser client (#12844)

PR to feature branch, no need for review yet.

* wire vNextOrganizationService for licence and some web router guards

* wire vNextOrganizationService in tests

* remove vNext notation for OrganizationService and related

* Merge branch 'main' into ac/pm-15506-vNextOrganizationService

* fix tsstrict error

* fix test, fix ts strict error
This commit is contained in:
Brandon Treston 2025-01-22 15:20:25 -05:00 committed by GitHub
parent ba4d762dc1
commit a949f793ed
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
163 changed files with 1972 additions and 1246 deletions

View File

@ -25,7 +25,7 @@ import { InternalOrganizationServiceAbstraction } from "@bitwarden/common/admin-
import { PolicyApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/policy/policy-api.service.abstraction";
import { InternalPolicyService as InternalPolicyServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { ProviderService as ProviderServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/provider.service";
import { OrganizationService } from "@bitwarden/common/admin-console/services/organization/organization.service";
import { DefaultOrganizationService } from "@bitwarden/common/admin-console/services/organization/default-organization.service";
import { PolicyApiService } from "@bitwarden/common/admin-console/services/policy/policy-api.service";
import { PolicyService } from "@bitwarden/common/admin-console/services/policy/policy.service";
import { ProviderService } from "@bitwarden/common/admin-console/services/provider.service";
@ -669,7 +669,7 @@ export default class MainBackground {
this.appIdService = new AppIdService(this.storageService, this.logService);
this.userDecryptionOptionsService = new UserDecryptionOptionsService(this.stateProvider);
this.organizationService = new OrganizationService(this.stateProvider);
this.organizationService = new DefaultOrganizationService(this.stateProvider);
this.policyService = new PolicyService(this.stateProvider, this.organizationService);
this.vaultTimeoutSettingsService = new VaultTimeoutSettingsService(
@ -1248,6 +1248,7 @@ export default class MainBackground {
this.cipherAuthorizationService = new DefaultCipherAuthorizationService(
this.collectionService,
this.organizationService,
this.accountService,
);
this.inlineMenuFieldQualificationService = new InlineMenuFieldQualificationService();

View File

@ -31,8 +31,8 @@ import { LockService, LoginEmailService, PinServiceAbstraction } from "@bitwarde
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { EventCollectionService as EventCollectionServiceAbstraction } from "@bitwarden/common/abstractions/event/event-collection.service";
import { VaultTimeoutService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout.service";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { DefaultOrganizationService } from "@bitwarden/common/admin-console/services/organization/default-organization.service";
import {
AccountService,
AccountService as AccountServiceAbstraction,
@ -369,7 +369,7 @@ const safeProviders: SafeProvider[] = [
provide: VaultFilterService,
useClass: VaultFilterService,
deps: [
OrganizationService,
DefaultOrganizationService,
FolderServiceAbstraction,
CipherService,
CollectionService,

View File

@ -6,6 +6,10 @@ import { OrganizationService } from "@bitwarden/common/admin-console/abstraction
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { Policy } from "@bitwarden/common/admin-console/models/domain/policy";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { FakeAccountService, mockAccountServiceWith } from "@bitwarden/common/spec";
import { UserId } from "@bitwarden/common/types/guid";
import { FamiliesPolicyService } from "./families-policy.service"; // Adjust the import as necessary
@ -13,16 +17,20 @@ describe("FamiliesPolicyService", () => {
let service: FamiliesPolicyService;
let organizationService: MockProxy<OrganizationService>;
let policyService: MockProxy<PolicyService>;
let accountService: FakeAccountService;
const userId = Utils.newGuid() as UserId;
beforeEach(() => {
organizationService = mock<OrganizationService>();
policyService = mock<PolicyService>();
accountService = mockAccountServiceWith(userId);
TestBed.configureTestingModule({
providers: [
FamiliesPolicyService,
{ provide: OrganizationService, useValue: organizationService },
{ provide: PolicyService, useValue: policyService },
{ provide: AccountService, useValue: accountService },
],
});
@ -40,7 +48,7 @@ describe("FamiliesPolicyService", () => {
jest.spyOn(service, "hasSingleEnterpriseOrg$").mockReturnValue(of(true));
const organizations = [{ id: "org1", canManageSponsorships: true }] as Organization[];
organizationService.getAll$.mockReturnValue(of(organizations));
organizationService.organizations$.mockReturnValue(of(organizations));
const policies = [{ organizationId: "org1", enabled: true }] as Policy[];
policyService.getAll$.mockReturnValue(of(policies));
@ -53,7 +61,7 @@ describe("FamiliesPolicyService", () => {
jest.spyOn(service, "hasSingleEnterpriseOrg$").mockReturnValue(of(true));
const organizations = [{ id: "org1", canManageSponsorships: true }] as Organization[];
organizationService.getAll$.mockReturnValue(of(organizations));
organizationService.organizations$.mockReturnValue(of(organizations));
const policies = [{ organizationId: "org1", enabled: false }] as Policy[];
policyService.getAll$.mockReturnValue(of(policies));
@ -64,7 +72,7 @@ describe("FamiliesPolicyService", () => {
it("should return true when there is exactly one enterprise organization that can manage sponsorships", async () => {
const organizations = [{ id: "org1", canManageSponsorships: true }] as Organization[];
organizationService.getAll$.mockReturnValue(of(organizations));
organizationService.organizations$.mockReturnValue(of(organizations));
const result = await firstValueFrom(service.hasSingleEnterpriseOrg$());
expect(result).toBe(true);
@ -75,7 +83,7 @@ describe("FamiliesPolicyService", () => {
{ id: "org1", canManageSponsorships: true },
{ id: "org2", canManageSponsorships: true },
] as Organization[];
organizationService.getAll$.mockReturnValue(of(organizations));
organizationService.organizations$.mockReturnValue(of(organizations));
const result = await firstValueFrom(service.hasSingleEnterpriseOrg$());
expect(result).toBe(false);

View File

@ -4,17 +4,22 @@ import { map, Observable, of, switchMap } from "rxjs";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { PolicyType } from "@bitwarden/common/admin-console/enums";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { getUserId } from "@bitwarden/common/auth/services/account.service";
@Injectable({ providedIn: "root" })
export class FamiliesPolicyService {
constructor(
private policyService: PolicyService,
private organizationService: OrganizationService,
private accountService: AccountService,
) {}
hasSingleEnterpriseOrg$(): Observable<boolean> {
// Retrieve all organizations the user is part of
return this.organizationService.getAll$().pipe(
return getUserId(this.accountService.activeAccount$).pipe(
switchMap((userId) =>
this.organizationService.organizations$(userId).pipe(
map((organizations) => {
// Filter to only those organizations that can manage sponsorships
const sponsorshipOrgs = organizations.filter((org) => org.canManageSponsorships);
@ -25,6 +30,8 @@ export class FamiliesPolicyService {
// to the policy only when there is a single enterprise organization and the free family policy is turn.
return sponsorshipOrgs.length === 1;
}),
),
),
);
}
@ -34,7 +41,9 @@ export class FamiliesPolicyService {
if (!hasSingleEnterpriseOrg) {
return of(false);
}
return this.organizationService.getAll$().pipe(
return getUserId(this.accountService.activeAccount$).pipe(
switchMap((userId) =>
this.organizationService.organizations$(userId).pipe(
map((organizations) => organizations.find((org) => org.canManageSponsorships)?.id),
switchMap((enterpriseOrgId) =>
this.policyService
@ -42,8 +51,10 @@ export class FamiliesPolicyService {
.pipe(
map(
(policies) =>
policies.find((policy) => policy.organizationId === enterpriseOrgId)?.enabled ??
false,
policies.find((policy) => policy.organizationId === enterpriseOrgId)
?.enabled ?? false,
),
),
),
),
),

View File

@ -6,6 +6,7 @@ import { Observable, firstValueFrom, of, switchMap } from "rxjs";
import { JslibModule } from "@bitwarden/angular/jslib.module";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { getUserId } from "@bitwarden/common/auth/services/account.service";
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
import { DialogService, ItemModule } from "@bitwarden/components";
@ -43,6 +44,9 @@ export class MoreFromBitwardenPageV2Component {
private familiesPolicyService: FamiliesPolicyService,
private accountService: AccountService,
) {
this.familySponsorshipAvailable$ = getUserId(this.accountService.activeAccount$).pipe(
switchMap((userId) => this.organizationService.familySponsorshipAvailable$(userId)),
);
this.canAccessPremium$ = this.accountService.activeAccount$.pipe(
switchMap((account) =>
account
@ -50,7 +54,6 @@ export class MoreFromBitwardenPageV2Component {
: of(false),
),
);
this.familySponsorshipAvailable$ = this.organizationService.familySponsorshipAvailable$;
this.hasSingleEnterpriseOrg$ = this.familiesPolicyService.hasSingleEnterpriseOrg$();
this.isFreeFamilyPolicyEnabled$ = this.familiesPolicyService.isFreeFamilyPolicyEnabled$();
}

View File

@ -50,7 +50,7 @@ describe("OpenAttachmentsComponent", () => {
} as Organization;
const getCipher = jest.fn().mockResolvedValue(cipherDomain);
const getOrganization = jest.fn().mockResolvedValue(org);
const organizations$ = jest.fn().mockReturnValue(of([org]));
const showFilePopoutMessage = jest.fn().mockReturnValue(false);
const mockUserId = Utils.newGuid() as UserId;
@ -67,7 +67,7 @@ describe("OpenAttachmentsComponent", () => {
openCurrentPagePopout.mockClear();
getCipher.mockClear();
showToast.mockClear();
getOrganization.mockClear();
organizations$.mockClear();
showFilePopoutMessage.mockClear();
hasPremiumFromAnySource$.next(true);
@ -89,7 +89,7 @@ describe("OpenAttachmentsComponent", () => {
},
{
provide: OrganizationService,
useValue: { get: getOrganization },
useValue: { organizations$ },
},
{
provide: FilePopoutUtilsService,
@ -148,11 +148,11 @@ describe("OpenAttachmentsComponent", () => {
describe("Free Orgs", () => {
beforeEach(() => {
component.cipherIsAPartOfFreeOrg = undefined;
component.cipherIsAPartOfFreeOrg = false;
});
it("sets `cipherIsAPartOfFreeOrg` to false when the cipher is not a part of an organization", async () => {
cipherView.organizationId = null;
cipherView.organizationId = "";
await component.ngOnInit();
@ -162,6 +162,7 @@ describe("OpenAttachmentsComponent", () => {
it("sets `cipherIsAPartOfFreeOrg` to true when the cipher is a part of a free organization", async () => {
cipherView.organizationId = "888-333-333";
org.productTierType = ProductTierType.Free;
org.id = cipherView.organizationId;
await component.ngOnInit();
@ -171,6 +172,7 @@ describe("OpenAttachmentsComponent", () => {
it("sets `cipherIsAPartOfFreeOrg` to false when the organization is not free", async () => {
cipherView.organizationId = "888-333-333";
org.productTierType = ProductTierType.Families;
org.id = cipherView.organizationId;
await component.ngOnInit();

View File

@ -7,8 +7,12 @@ import { Router } from "@angular/router";
import { firstValueFrom, map, switchMap } from "rxjs";
import { JslibModule } from "@bitwarden/angular/jslib.module";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import {
getOrganizationById,
OrganizationService,
} from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { getUserId } from "@bitwarden/common/auth/services/account.service";
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions";
import { ProductTierType } from "@bitwarden/common/billing/enums";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
@ -86,7 +90,12 @@ export class OpenAttachmentsComponent implements OnInit {
return;
}
const org = await this.organizationService.get(cipher.organizationId);
const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$));
const org = await firstValueFrom(
this.organizationService
.organizations$(userId)
.pipe(getOrganizationById(cipher.organizationId)),
);
this.cipherIsAPartOfFreeOrg = org.productTierType === ProductTierType.Free;
}

View File

@ -9,6 +9,7 @@ import { filter } from "rxjs/operators";
import { JslibModule } from "@bitwarden/angular/jslib.module";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { getUserId } from "@bitwarden/common/auth/services/account.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { CipherRepromptType, CipherType } from "@bitwarden/common/vault/enums";
@ -88,7 +89,8 @@ export class ItemMoreOptionsComponent implements OnInit {
) {}
async ngOnInit(): Promise<void> {
this.hasOrganizations = await this.organizationService.hasOrganizations();
const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$));
this.hasOrganizations = await firstValueFrom(this.organizationService.hasOrganizations(userId));
}
get canEdit() {

View File

@ -6,10 +6,12 @@ import { CollectionService, CollectionView } from "@bitwarden/admin-console/comm
import { SearchService } from "@bitwarden/common/abstractions/search.service";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { ProductTierType } from "@bitwarden/common/billing/enums";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { SyncService } from "@bitwarden/common/platform/sync";
import { ObservableTracker } from "@bitwarden/common/spec";
import { CipherId } from "@bitwarden/common/types/guid";
import { ObservableTracker, mockAccountServiceWith } from "@bitwarden/common/spec";
import { CipherId, UserId } from "@bitwarden/common/types/guid";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { VaultSettingsService } from "@bitwarden/common/vault/abstractions/vault-settings/vault-settings.service";
import { CipherType } from "@bitwarden/common/vault/enums";
@ -43,6 +45,8 @@ describe("VaultPopupItemsService", () => {
const vaultAutofillServiceMock = mock<VaultPopupAutofillService>();
const syncServiceMock = mock<SyncService>();
const inlineMenuFieldQualificationServiceMock = mock<InlineMenuFieldQualificationService>();
const userId = Utils.newGuid() as UserId;
const accountServiceMock = mockAccountServiceWith(userId);
beforeEach(() => {
allCiphers = cipherFactory(10);
@ -99,7 +103,7 @@ describe("VaultPopupItemsService", () => {
{ id: "col2", name: "Collection 2" } as CollectionView,
];
organizationServiceMock.organizations$ = new BehaviorSubject([mockOrg]);
organizationServiceMock.organizations$.mockReturnValue(new BehaviorSubject([mockOrg]));
collectionService.decryptedCollections$ = new BehaviorSubject(mockCollections);
activeUserLastSync$ = new BehaviorSubject(new Date());
@ -111,6 +115,7 @@ describe("VaultPopupItemsService", () => {
{ provide: VaultSettingsService, useValue: vaultSettingsServiceMock },
{ provide: SearchService, useValue: searchService },
{ provide: OrganizationService, useValue: organizationServiceMock },
{ provide: AccountService, useValue: accountServiceMock },
{ provide: VaultPopupListFiltersService, useValue: vaultPopupListFiltersServiceMock },
{ provide: CollectionService, useValue: collectionService },
{ provide: VaultPopupAutofillService, useValue: vaultAutofillServiceMock },

View File

@ -24,6 +24,7 @@ import {
import { CollectionService } from "@bitwarden/admin-console/common";
import { SearchService } from "@bitwarden/common/abstractions/search.service";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { SyncService } from "@bitwarden/common/platform/sync";
import { CollectionId, OrganizationId } from "@bitwarden/common/types/guid";
@ -56,6 +57,9 @@ export class VaultPopupItemsService {
latestSearchText$: Observable<string> = this._searchText$.asObservable();
private organizations$ = this.accountService.activeAccount$.pipe(
switchMap((account) => this.organizationService.organizations$(account?.id)),
);
/**
* Observable that contains the list of other cipher types that should be shown
* in the autofill section of the Vault tab. Depends on vault settings.
@ -97,10 +101,7 @@ export class VaultPopupItemsService {
private _activeCipherList$: Observable<PopupCipherView[]> = this._allDecryptedCiphers$.pipe(
switchMap((ciphers) =>
combineLatest([
this.organizationService.organizations$,
this.collectionService.decryptedCollections$,
]).pipe(
combineLatest([this.organizations$, this.collectionService.decryptedCollections$]).pipe(
map(([organizations, collections]) => {
const orgMap = Object.fromEntries(organizations.map((org) => [org.id, org]));
const collectionMap = Object.fromEntries(collections.map((col) => [col.id, col]));
@ -232,7 +233,7 @@ export class VaultPopupItemsService {
/** Observable that indicates when the user should see the deactivated org state */
showDeactivatedOrg$: Observable<boolean> = combineLatest([
this.vaultPopupListFiltersService.filters$.pipe(distinctUntilKeyChanged("organization")),
this.organizationService.organizations$,
this.organizations$,
]).pipe(
map(([filters, orgs]) => {
if (!filters.organization || filters.organization.id === MY_VAULT_ID) {
@ -249,10 +250,7 @@ export class VaultPopupItemsService {
*/
deletedCiphers$: Observable<PopupCipherView[]> = this._allDecryptedCiphers$.pipe(
switchMap((ciphers) =>
combineLatest([
this.organizationService.organizations$,
this.collectionService.decryptedCollections$,
]).pipe(
combineLatest([this.organizations$, this.collectionService.decryptedCollections$]).pipe(
map(([organizations, collections]) => {
const orgMap = Object.fromEntries(organizations.map((org) => [org.id, org]));
const collectionMap = Object.fromEntries(collections.map((col) => [col.id, col]));
@ -281,6 +279,7 @@ export class VaultPopupItemsService {
private collectionService: CollectionService,
private vaultPopupAutofillService: VaultPopupAutofillService,
private syncService: SyncService,
private accountService: AccountService,
) {}
applyFilter(newSearchText: string) {

View File

@ -23,7 +23,9 @@ import { MY_VAULT_ID, VaultPopupListFiltersService } from "./vault-popup-list-fi
describe("VaultPopupListFiltersService", () => {
let service: VaultPopupListFiltersService;
const memberOrganizations$ = new BehaviorSubject<Organization[]>([]);
const _memberOrganizations$ = new BehaviorSubject<Organization[]>([]);
const memberOrganizations$ = (userId: UserId) => _memberOrganizations$;
const organizations$ = new BehaviorSubject<Organization[]>([]);
const folderViews$ = new BehaviorSubject([]);
const cipherViews$ = new BehaviorSubject({});
const decryptedCollections$ = new BehaviorSubject<CollectionView[]>([]);
@ -44,6 +46,7 @@ describe("VaultPopupListFiltersService", () => {
const organizationService = {
memberOrganizations$,
organizations$,
} as unknown as OrganizationService;
const i18nService = {
@ -58,7 +61,7 @@ describe("VaultPopupListFiltersService", () => {
const update = jest.fn().mockResolvedValue(undefined);
beforeEach(() => {
memberOrganizations$.next([]);
_memberOrganizations$.next([]);
decryptedCollections$.next([]);
policyAppliesToActiveUser$.next(false);
policyService.policyAppliesToActiveUser$.mockClear();
@ -135,7 +138,7 @@ describe("VaultPopupListFiltersService", () => {
describe("organizations$", () => {
it('does not add "myVault" to the list of organizations when there are no organizations', (done) => {
memberOrganizations$.next([]);
_memberOrganizations$.next([]);
service.organizations$.subscribe((organizations) => {
expect(organizations.map((o) => o.label)).toEqual([]);
@ -145,7 +148,7 @@ describe("VaultPopupListFiltersService", () => {
it('adds "myVault" to the list of organizations when there are other organizations', (done) => {
const orgs = [{ name: "bobby's org", id: "1234-3323-23223" }] as Organization[];
memberOrganizations$.next(orgs);
_memberOrganizations$.next(orgs);
service.organizations$.subscribe((organizations) => {
expect(organizations.map((o) => o.label)).toEqual(["myVault", "bobby's org"]);
@ -158,7 +161,7 @@ describe("VaultPopupListFiltersService", () => {
{ name: "bobby's org", id: "1234-3323-23223" },
{ name: "alice's org", id: "2223-4343-99888" },
] as Organization[];
memberOrganizations$.next(orgs);
_memberOrganizations$.next(orgs);
service.organizations$.subscribe((organizations) => {
expect(organizations.map((o) => o.label)).toEqual([
@ -179,7 +182,7 @@ describe("VaultPopupListFiltersService", () => {
it("returns an empty array when the policy applies and there is a single organization", (done) => {
policyAppliesToActiveUser$.next(true);
memberOrganizations$.next([
_memberOrganizations$.next([
{ name: "bobby's org", id: "1234-3323-23223" },
] as Organization[]);
@ -196,7 +199,7 @@ describe("VaultPopupListFiltersService", () => {
{ name: "alice's org", id: "2223-4343-99888" },
] as Organization[];
memberOrganizations$.next(orgs);
_memberOrganizations$.next(orgs);
service.organizations$.subscribe((organizations) => {
expect(organizations.map((o) => o.label)).toEqual([
@ -216,7 +219,7 @@ describe("VaultPopupListFiltersService", () => {
{ name: "catherine's org", id: "77733-4343-99888" },
] as Organization[];
memberOrganizations$.next(orgs);
_memberOrganizations$.next(orgs);
service.organizations$.subscribe((organizations) => {
expect(organizations.map((o) => o.label)).toEqual([
@ -240,7 +243,7 @@ describe("VaultPopupListFiltersService", () => {
},
] as Organization[];
memberOrganizations$.next(orgs);
_memberOrganizations$.next(orgs);
service.organizations$.subscribe((organizations) => {
expect(organizations.map((o) => o.icon)).toEqual(["bwi-user", "bwi-family"]);
@ -258,7 +261,7 @@ describe("VaultPopupListFiltersService", () => {
},
] as Organization[];
memberOrganizations$.next(orgs);
_memberOrganizations$.next(orgs);
service.organizations$.subscribe((organizations) => {
expect(organizations.map((o) => o.icon)).toEqual(["bwi-user", "bwi-family"]);
@ -276,7 +279,7 @@ describe("VaultPopupListFiltersService", () => {
},
] as Organization[];
memberOrganizations$.next(orgs);
_memberOrganizations$.next(orgs);
service.organizations$.subscribe((organizations) => {
expect(organizations.map((o) => o.icon)).toEqual([

View File

@ -208,7 +208,9 @@ export class VaultPopupListFiltersService {
* Organization array structured to be directly passed to `ChipSelectComponent`
*/
organizations$: Observable<ChipSelectOption<Organization>[]> = combineLatest([
this.organizationService.memberOrganizations$,
this.accountService.activeAccount$.pipe(
switchMap((account) => this.organizationService.memberOrganizations$(account?.id)),
),
this.policyService.policyAppliesToActiveUser$(PolicyType.PersonalOwnership),
]).pipe(
map(([orgs, personalOwnershipApplies]): [Organization[], boolean] => [

View File

@ -38,7 +38,7 @@ export class VaultFilterService extends BaseVaultFilterService {
this.vaultFilter.myVaultOnly = false;
this.vaultFilter.selectedOrganizationId = null;
this.accountService.activeAccount$.subscribe((account) => {
accountService.activeAccount$.subscribe((account) => {
this.setVaultFilter(this.allVaults);
});
}

View File

@ -10,6 +10,7 @@ import { SearchService } from "@bitwarden/common/abstractions/search.service";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { getUserId } from "@bitwarden/common/auth/services/account.service";
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
import { EventType } from "@bitwarden/common/enums";
import { CardExport } from "@bitwarden/common/models/export/card.export";
@ -479,10 +480,18 @@ export class GetCommand extends DownloadCommand {
private async getOrganization(id: string) {
let org: Organization = null;
const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$));
if (!userId) {
return Response.badRequest("No user found.");
}
if (Utils.isGuid(id)) {
org = await this.organizationService.getFromState(id);
org = await firstValueFrom(
this.organizationService
.organizations$(userId)
.pipe(map((organizations) => organizations.find((o) => o.id === id))),
);
} else if (id.trim() !== "") {
let orgs = await firstValueFrom(this.organizationService.organizations$);
let orgs = await firstValueFrom(this.organizationService.organizations$(userId));
orgs = CliUtils.searchOrganizations(orgs, id);
if (orgs.length > 1) {
return Response.multipleResults(orgs.map((c) => c.id));

View File

@ -13,6 +13,7 @@ import { EventCollectionService } from "@bitwarden/common/abstractions/event/eve
import { SearchService } from "@bitwarden/common/abstractions/search.service";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { getUserId } from "@bitwarden/common/auth/services/account.service";
import { EventType } from "@bitwarden/common/enums";
import { ListResponse as ApiListResponse } from "@bitwarden/common/models/response/list.response";
import { Utils } from "@bitwarden/common/platform/misc/utils";
@ -177,7 +178,15 @@ export class ListCommand {
if (!Utils.isGuid(options.organizationId)) {
return Response.badRequest("`" + options.organizationId + "` is not a GUID.");
}
const organization = await this.organizationService.getFromState(options.organizationId);
const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$));
if (!userId) {
return Response.badRequest("No user found.");
}
const organization = await firstValueFrom(
this.organizationService
.organizations$(userId)
.pipe(map((organizatons) => organizatons.find((o) => o.id == options.organizationId))),
);
if (organization == null) {
return Response.error("Organization not found.");
}
@ -210,7 +219,16 @@ export class ListCommand {
if (!Utils.isGuid(options.organizationId)) {
return Response.badRequest("`" + options.organizationId + "` is not a GUID.");
}
const organization = await this.organizationService.getFromState(options.organizationId);
const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$));
if (!userId) {
return Response.badRequest("No user found.");
}
const organization = await firstValueFrom(
this.organizationService
.organizations$(userId)
.pipe(map((organizatons) => organizatons.find((o) => o.id == options.organizationId))),
);
if (organization == null) {
return Response.error("Organization not found.");
}
@ -236,7 +254,12 @@ export class ListCommand {
}
private async listOrganizations(options: Options) {
let organizations = await firstValueFrom(this.organizationService.memberOrganizations$);
const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$));
if (!userId) {
return Response.badRequest("No user found.");
}
let organizations = await firstValueFrom(this.organizationService.memberOrganizations$(userId));
if (options.search != null && options.search.trim() !== "") {
organizations = CliUtils.searchOrganizations(organizations, options.search);

View File

@ -4,7 +4,7 @@ import * as fs from "fs";
import * as path from "path";
import * as jsdom from "jsdom";
import { firstValueFrom, map } from "rxjs";
import { firstValueFrom } from "rxjs";
import {
OrganizationUserApiService,
@ -25,8 +25,8 @@ import { EventUploadService as EventUploadServiceAbstraction } from "@bitwarden/
import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction";
import { PolicyApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/policy/policy-api.service.abstraction";
import { ProviderApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/provider/provider-api.service.abstraction";
import { DefaultOrganizationService } from "@bitwarden/common/admin-console/services/organization/default-organization.service";
import { OrganizationApiService } from "@bitwarden/common/admin-console/services/organization/organization-api.service";
import { OrganizationService } from "@bitwarden/common/admin-console/services/organization/organization.service";
import { PolicyApiService } from "@bitwarden/common/admin-console/services/policy/policy-api.service";
import { PolicyService } from "@bitwarden/common/admin-console/services/policy/policy.service";
import { ProviderApiService } from "@bitwarden/common/admin-console/services/provider/provider-api.service";
@ -36,7 +36,10 @@ import { AvatarService as AvatarServiceAbstraction } from "@bitwarden/common/aut
import { DeviceTrustServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust.service.abstraction";
import { DevicesApiServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices-api.service.abstraction";
import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction";
import { AccountServiceImplementation } from "@bitwarden/common/auth/services/account.service";
import {
AccountServiceImplementation,
getUserId,
} 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 { DeviceTrustService } from "@bitwarden/common/auth/services/device-trust.service.implementation";
@ -238,7 +241,8 @@ export class ServiceContainer {
stateService: StateService;
autofillSettingsService: AutofillSettingsServiceAbstraction;
domainSettingsService: DomainSettingsService;
organizationService: OrganizationService;
organizationService: DefaultOrganizationService;
DefaultOrganizationService: DefaultOrganizationService;
providerService: ProviderService;
twoFactorService: TwoFactorService;
folderApiService: FolderApiService;
@ -450,7 +454,7 @@ export class ServiceContainer {
this.biometricStateService = new DefaultBiometricStateService(this.stateProvider);
this.userDecryptionOptionsService = new UserDecryptionOptionsService(this.stateProvider);
this.organizationService = new OrganizationService(this.stateProvider);
this.organizationService = new DefaultOrganizationService(this.stateProvider);
this.policyService = new PolicyService(this.stateProvider, this.organizationService);
this.vaultTimeoutSettingsService = new VaultTimeoutSettingsService(
@ -824,6 +828,7 @@ export class ServiceContainer {
this.cipherAuthorizationService = new DefaultCipherAuthorizationService(
this.collectionService,
this.organizationService,
this.accountService,
);
}
@ -831,7 +836,7 @@ export class ServiceContainer {
this.authService.logOut(() => {
/* Do nothing */
});
const userId = await firstValueFrom(this.accountService.activeAccount$.pipe(map((a) => a?.id)));
const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$));
await Promise.all([
this.eventUploadService.uploadEvents(userId as UserId),
this.keyService.clearKeys(),

View File

@ -2,8 +2,10 @@
// @ts-strict-ignore
import { OptionValues } from "commander";
import * as inquirer from "inquirer";
import { firstValueFrom, map } from "rxjs";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
import { ImportServiceAbstraction, ImportType } from "@bitwarden/importer/core";
@ -16,12 +18,23 @@ export class ImportCommand {
private importService: ImportServiceAbstraction,
private organizationService: OrganizationService,
private syncService: SyncService,
private accountService: AccountService,
) {}
async run(format: ImportType, filepath: string, options: OptionValues): Promise<Response> {
const organizationId = options.organizationid;
if (organizationId != null) {
const organization = await this.organizationService.getFromState(organizationId);
const userId = await firstValueFrom(
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
);
if (!userId) {
return Response.badRequest("No user found.");
}
const organization = await firstValueFrom(
this.organizationService
.organizations$(userId)
.pipe(map((organizations) => organizations.find((o) => o.id === organizationId))),
);
if (organization == null) {
return Response.badRequest(

View File

@ -450,6 +450,7 @@ export class VaultProgram extends BaseProgram {
this.serviceContainer.importService,
this.serviceContainer.organizationService,
this.serviceContainer.syncService,
this.serviceContainer.accountService,
);
const response = await command.run(format, filepath, options);
this.processResponse(response);

View File

@ -202,7 +202,17 @@ export class CreateCommand {
if (orgKey == null) {
throw new Error("No encryption key for this organization.");
}
const organization = await this.organizationService.get(req.organizationId);
const userId = await firstValueFrom(
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
);
if (!userId) {
return Response.badRequest("No user found.");
}
const organization = await firstValueFrom(
this.organizationService
.organizations$(userId)
.pipe(map((organizations) => organizations.find((o) => o.id === req.organizationId))),
);
const currentOrgUserId = organization.organizationUserId;
const groups =

View File

@ -32,6 +32,7 @@ import { MasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstrac
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason";
import { getUserId } from "@bitwarden/common/auth/services/account.service";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { VaultTimeoutAction } from "@bitwarden/common/enums/vault-timeout-action.enum";
import { ProcessReloadServiceAbstraction } from "@bitwarden/common/key-management/abstractions/process-reload.service";
@ -872,9 +873,10 @@ export class AppComponent implements OnInit, OnDestroy {
}
private async deleteAccount() {
const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$));
await firstValueFrom(
this.configService.getFeatureFlag$(FeatureFlag.AccountDeprovisioning).pipe(
withLatestFrom(this.organizationService.organizations$),
withLatestFrom(this.organizationService.organizations$(userId)),
map(async ([accountDeprovisioningEnabled, organization]) => {
if (
accountDeprovisioningEnabled &&

View File

@ -5,11 +5,16 @@ import { TestBed } from "@angular/core/testing";
import { provideRouter } from "@angular/router";
import { RouterTestingHarness } from "@angular/router/testing";
import { MockProxy, any, mock } from "jest-mock-extended";
import { of } from "rxjs";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { OrganizationUserType } from "@bitwarden/common/admin-console/enums";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { ProductTierType } from "@bitwarden/common/billing/enums";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { FakeAccountService, mockAccountServiceWith } from "@bitwarden/common/spec";
import { UserId } from "@bitwarden/common/types/guid";
import { DialogService } from "@bitwarden/components";
import { isEnterpriseOrgGuard } from "./is-enterprise-org.guard";
@ -44,15 +49,19 @@ describe("Is Enterprise Org Guard", () => {
let organizationService: MockProxy<OrganizationService>;
let dialogService: MockProxy<DialogService>;
let routerHarness: RouterTestingHarness;
let accountService: FakeAccountService;
const userId = Utils.newGuid() as UserId;
beforeEach(async () => {
organizationService = mock<OrganizationService>();
dialogService = mock<DialogService>();
accountService = mockAccountServiceWith(userId);
TestBed.configureTestingModule({
providers: [
{ provide: OrganizationService, useValue: organizationService },
{ provide: DialogService, useValue: dialogService },
{ provide: AccountService, useValue: accountService },
provideRouter([
{
path: "",
@ -82,7 +91,7 @@ describe("Is Enterprise Org Guard", () => {
it("redirects to `/` if the organization id provided is not found", async () => {
const org = orgFactory();
organizationService.get.calledWith(org.id).mockResolvedValue(null);
organizationService.organizations$.calledWith(userId).mockReturnValue(of([]));
await routerHarness.navigateByUrl(`organizations/${org.id}/enterpriseOrgsOnly`);
expect(routerHarness.routeNativeElement?.querySelector("h1")?.textContent?.trim() ?? "").toBe(
"This is the home screen!",
@ -101,7 +110,7 @@ describe("Is Enterprise Org Guard", () => {
type: OrganizationUserType.User,
productTierType: productTierType,
});
organizationService.get.calledWith(org.id).mockResolvedValue(org);
organizationService.organizations$.calledWith(userId).mockReturnValue(of([org]));
await routerHarness.navigateByUrl(`organizations/${org.id}/enterpriseOrgsOnly`);
expect(dialogService.openSimpleDialog).toHaveBeenCalled();
expect(
@ -115,7 +124,7 @@ describe("Is Enterprise Org Guard", () => {
type: OrganizationUserType.Owner,
productTierType: ProductTierType.Teams,
});
organizationService.get.calledWith(org.id).mockResolvedValue(org);
organizationService.organizations$.calledWith(userId).mockReturnValue(of([org]));
dialogService.openSimpleDialog.calledWith(any()).mockResolvedValue(true);
await routerHarness.navigateByUrl(`organizations/${org.id}/enterpriseOrgsOnly`);
expect(routerHarness.routeNativeElement?.querySelector("h1")?.textContent?.trim() ?? "").toBe(
@ -133,7 +142,7 @@ describe("Is Enterprise Org Guard", () => {
type: OrganizationUserType.User,
productTierType: productTierType,
});
organizationService.get.calledWith(org.id).mockResolvedValue(org);
organizationService.organizations$.calledWith(userId).mockReturnValue(of([org]));
await routerHarness.navigateByUrl(`organizations/${org.id}/enterpriseOrgsOnlyNoError`);
expect(dialogService.openSimpleDialog).not.toHaveBeenCalled();
expect(
@ -143,7 +152,7 @@ describe("Is Enterprise Org Guard", () => {
it("proceeds with navigation if the organization in question is a enterprise organization", async () => {
const org = orgFactory({ productTierType: ProductTierType.Enterprise });
organizationService.get.calledWith(org.id).mockResolvedValue(org);
organizationService.organizations$.calledWith(userId).mockReturnValue(of([org]));
await routerHarness.navigateByUrl(`organizations/${org.id}/enterpriseOrgsOnly`);
expect(routerHarness.routeNativeElement?.querySelector("h1")?.textContent?.trim() ?? "").toBe(
"This component can only be accessed by a enterprise organization!",

View File

@ -7,8 +7,13 @@ import {
Router,
RouterStateSnapshot,
} from "@angular/router";
import { firstValueFrom, map } from "rxjs";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import {
getOrganizationById,
OrganizationService,
} from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { ProductTierType } from "@bitwarden/common/billing/enums";
import { DialogService } from "@bitwarden/components";
@ -23,9 +28,15 @@ export function isEnterpriseOrgGuard(showError: boolean = true): CanActivateFn {
return async (route: ActivatedRouteSnapshot, _state: RouterStateSnapshot) => {
const router = inject(Router);
const organizationService = inject(OrganizationService);
const accountService = inject(AccountService);
const dialogService = inject(DialogService);
const org = await organizationService.get(route.params.organizationId);
const userId = await firstValueFrom(accountService.activeAccount$.pipe(map((a) => a?.id)));
const org = await firstValueFrom(
organizationService
.organizations$(userId)
.pipe(getOrganizationById(route.params.organizationId)),
);
if (org == null) {
return router.createUrlTree(["/"]);

View File

@ -5,10 +5,15 @@ import { TestBed } from "@angular/core/testing";
import { provideRouter } from "@angular/router";
import { RouterTestingHarness } from "@angular/router/testing";
import { MockProxy, any, mock } from "jest-mock-extended";
import { of } from "rxjs";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { OrganizationUserType } from "@bitwarden/common/admin-console/enums";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { FakeAccountService, mockAccountServiceWith } from "@bitwarden/common/spec";
import { UserId } from "@bitwarden/common/types/guid";
import { DialogService } from "@bitwarden/components";
import { isPaidOrgGuard } from "./is-paid-org.guard";
@ -43,15 +48,19 @@ describe("Is Paid Org Guard", () => {
let organizationService: MockProxy<OrganizationService>;
let dialogService: MockProxy<DialogService>;
let routerHarness: RouterTestingHarness;
let accountService: FakeAccountService;
const userId = Utils.newGuid() as UserId;
beforeEach(async () => {
organizationService = mock<OrganizationService>();
dialogService = mock<DialogService>();
accountService = mockAccountServiceWith(userId);
TestBed.configureTestingModule({
providers: [
{ provide: OrganizationService, useValue: organizationService },
{ provide: DialogService, useValue: dialogService },
{ provide: AccountService, useValue: accountService },
provideRouter([
{
path: "",
@ -75,7 +84,7 @@ describe("Is Paid Org Guard", () => {
it("redirects to `/` if the organization id provided is not found", async () => {
const org = orgFactory();
organizationService.get.calledWith(org.id).mockResolvedValue(null);
organizationService.organizations$.calledWith(userId).mockReturnValue(of([]));
await routerHarness.navigateByUrl(`organizations/${org.id}/paidOrganizationsOnly`);
expect(routerHarness.routeNativeElement?.querySelector("h1")?.textContent?.trim() ?? "").toBe(
"This is the home screen!",
@ -86,7 +95,7 @@ describe("Is Paid Org Guard", () => {
// `useTotp` is the current indicator of a free org, it is the baseline
// feature offered above the free organization level.
const org = orgFactory({ type: OrganizationUserType.User, useTotp: false });
organizationService.get.calledWith(org.id).mockResolvedValue(org);
organizationService.organizations$.calledWith(userId).mockReturnValue(of([org]));
await routerHarness.navigateByUrl(`organizations/${org.id}/paidOrganizationsOnly`);
expect(dialogService.openSimpleDialog).toHaveBeenCalled();
expect(
@ -98,7 +107,7 @@ describe("Is Paid Org Guard", () => {
// `useTotp` is the current indicator of a free org, it is the baseline
// feature offered above the free organization level.
const org = orgFactory({ type: OrganizationUserType.Owner, useTotp: false });
organizationService.get.calledWith(org.id).mockResolvedValue(org);
organizationService.organizations$.calledWith(userId).mockReturnValue(of([org]));
dialogService.openSimpleDialog.calledWith(any()).mockResolvedValue(true);
await routerHarness.navigateByUrl(`organizations/${org.id}/paidOrganizationsOnly`);
expect(routerHarness.routeNativeElement?.querySelector("h1")?.textContent?.trim() ?? "").toBe(
@ -108,7 +117,7 @@ describe("Is Paid Org Guard", () => {
it("proceeds with navigation if the organization in question is a paid organization", async () => {
const org = orgFactory({ useTotp: true });
organizationService.get.calledWith(org.id).mockResolvedValue(org);
organizationService.organizations$.calledWith(userId).mockReturnValue(of([org]));
await routerHarness.navigateByUrl(`organizations/${org.id}/paidOrganizationsOnly`);
expect(routerHarness.routeNativeElement?.querySelector("h1")?.textContent?.trim() ?? "").toBe(
"This component can only be accessed by a paid organization!",

View File

@ -7,8 +7,13 @@ import {
Router,
RouterStateSnapshot,
} from "@angular/router";
import { firstValueFrom, map } from "rxjs";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import {
getOrganizationById,
OrganizationService,
} from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { DialogService } from "@bitwarden/components";
/**
@ -22,9 +27,15 @@ export function isPaidOrgGuard(): CanActivateFn {
return async (route: ActivatedRouteSnapshot, _state: RouterStateSnapshot) => {
const router = inject(Router);
const organizationService = inject(OrganizationService);
const accountService = inject(AccountService);
const dialogService = inject(DialogService);
const org = await organizationService.get(route.params.organizationId);
const userId = await firstValueFrom(accountService.activeAccount$.pipe(map((a) => a?.id)));
const org = await firstValueFrom(
organizationService
.organizations$(userId)
.pipe(getOrganizationById(route.params.organizationId)),
);
if (org == null) {
return router.createUrlTree(["/"]);

View File

@ -8,11 +8,16 @@ import {
RouterStateSnapshot,
} from "@angular/router";
import { mock, MockProxy } from "jest-mock-extended";
import { of } from "rxjs";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { OrganizationUserType } from "@bitwarden/common/admin-console/enums";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { FakeAccountService, mockAccountServiceWith } from "@bitwarden/common/spec";
import { UserId } from "@bitwarden/common/types/guid";
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
import { ToastService } from "@bitwarden/components";
@ -34,10 +39,13 @@ describe("Organization Permissions Guard", () => {
let organizationService: MockProxy<OrganizationService>;
let state: MockProxy<RouterStateSnapshot>;
let route: MockProxy<ActivatedRouteSnapshot>;
let accountService: FakeAccountService;
const userId = Utils.newGuid() as UserId;
beforeEach(() => {
router = mock<Router>();
organizationService = mock<OrganizationService>();
accountService = mockAccountServiceWith(userId);
state = mock<RouterStateSnapshot>();
route = mock<ActivatedRouteSnapshot>({
params: {
@ -48,6 +56,7 @@ describe("Organization Permissions Guard", () => {
TestBed.configureTestingModule({
providers: [
{ provide: Router, useValue: router },
{ provide: AccountService, useValue: accountService },
{ provide: OrganizationService, useValue: organizationService },
{ provide: ToastService, useValue: mock<ToastService>() },
{ provide: I18nService, useValue: mock<I18nService>() },
@ -57,7 +66,7 @@ describe("Organization Permissions Guard", () => {
});
it("blocks navigation if organization does not exist", async () => {
organizationService.get.mockReturnValue(null);
organizationService.organizations$.mockReturnValue(of([]));
const actual = await TestBed.runInInjectionContext(
async () => await organizationPermissionsGuard()(route, state),
@ -68,7 +77,7 @@ describe("Organization Permissions Guard", () => {
it("permits navigation if no permissions are specified", async () => {
const org = orgFactory();
organizationService.get.calledWith(org.id).mockResolvedValue(org);
organizationService.organizations$.calledWith(userId).mockReturnValue(of([org]));
const actual = await TestBed.runInInjectionContext(async () =>
organizationPermissionsGuard()(route, state),
@ -81,7 +90,7 @@ describe("Organization Permissions Guard", () => {
const permissionsCallback = jest.fn();
permissionsCallback.mockImplementation((_org) => true);
const org = orgFactory();
organizationService.get.calledWith(org.id).mockResolvedValue(org);
organizationService.organizations$.calledWith(userId).mockReturnValue(of([org]));
const actual = await TestBed.runInInjectionContext(
async () => await organizationPermissionsGuard(permissionsCallback)(route, state),
@ -103,7 +112,7 @@ describe("Organization Permissions Guard", () => {
});
const org = orgFactory();
organizationService.get.calledWith(org.id).mockResolvedValue(org);
organizationService.organizations$.calledWith(userId).mockReturnValue(of([org]));
const actual = await TestBed.runInInjectionContext(
async () => await organizationPermissionsGuard(permissionsCallback)(route, state),
@ -122,7 +131,7 @@ describe("Organization Permissions Guard", () => {
}),
});
const org = orgFactory();
organizationService.get.calledWith(org.id).mockResolvedValue(org);
organizationService.organizations$.calledWith(userId).mockReturnValue(of([org]));
const actual = await TestBed.runInInjectionContext(
async () => await organizationPermissionsGuard((_org: Organization) => false)(route, state),
@ -141,7 +150,7 @@ describe("Organization Permissions Guard", () => {
type: OrganizationUserType.Admin,
enabled: false,
});
organizationService.get.calledWith(org.id).mockResolvedValue(org);
organizationService.organizations$.calledWith(userId).mockReturnValue(of([org]));
const actual = await TestBed.runInInjectionContext(
async () => await organizationPermissionsGuard()(route, state),
@ -155,7 +164,7 @@ describe("Organization Permissions Guard", () => {
type: OrganizationUserType.Owner,
enabled: false,
});
organizationService.get.calledWith(org.id).mockResolvedValue(org);
organizationService.organizations$.calledWith(userId).mockReturnValue(of([org]));
const actual = await TestBed.runInInjectionContext(
async () => await organizationPermissionsGuard()(route, state),

View File

@ -7,12 +7,14 @@ import {
Router,
RouterStateSnapshot,
} from "@angular/router";
import { firstValueFrom, map } from "rxjs";
import {
canAccessOrgAdmin,
OrganizationService,
} from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
import { ToastService } from "@bitwarden/components";
@ -46,13 +48,21 @@ export function organizationPermissionsGuard(
const toastService = inject(ToastService);
const i18nService = inject(I18nService);
const syncService = inject(SyncService);
const accountService = inject(AccountService);
// TODO: We need to fix issue once and for all.
if ((await syncService.getLastSync()) == null) {
await syncService.fullSync(false);
}
const org = await organizationService.get(route.params.organizationId);
const userId = await firstValueFrom(accountService.activeAccount$.pipe(map((a) => a?.id)));
const org = await firstValueFrom(
organizationService
.organizations$(userId)
.pipe(map((organizations) => organizations.find((org) => route.params.organizationId))),
);
if (org == null) {
return router.createUrlTree(["/"]);
}

View File

@ -5,10 +5,15 @@ import { TestBed } from "@angular/core/testing";
import { provideRouter } from "@angular/router";
import { RouterTestingHarness } from "@angular/router/testing";
import { MockProxy, mock } from "jest-mock-extended";
import { of } from "rxjs";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { OrganizationUserType } from "@bitwarden/common/admin-console/enums";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { FakeAccountService, mockAccountServiceWith } from "@bitwarden/common/spec";
import { UserId } from "@bitwarden/common/types/guid";
import { organizationRedirectGuard } from "./org-redirect.guard";
@ -41,13 +46,17 @@ const orgFactory = (props: Partial<Organization> = {}) =>
describe("Organization Redirect Guard", () => {
let organizationService: MockProxy<OrganizationService>;
let routerHarness: RouterTestingHarness;
let accountService: FakeAccountService;
const userId = Utils.newGuid() as UserId;
beforeEach(async () => {
organizationService = mock<OrganizationService>();
accountService = mockAccountServiceWith(userId);
TestBed.configureTestingModule({
providers: [
{ provide: OrganizationService, useValue: organizationService },
{ provide: AccountService, useValue: accountService },
provideRouter([
{
path: "",
@ -89,16 +98,16 @@ describe("Organization Redirect Guard", () => {
it("redirects to `/` if the organization id provided is not found", async () => {
const org = orgFactory();
organizationService.get.calledWith(org.id).mockResolvedValue(null);
organizationService.organizations$.calledWith(userId).mockReturnValue(of([]));
await routerHarness.navigateByUrl(`organizations/${org.id}/noCallback`);
expect(routerHarness.routeNativeElement?.querySelector("h1")?.textContent?.trim() ?? "").toBe(
"This is the home screen!",
);
});
it("redirects to `/organizations/{id}` if no custom redirect is supplied but the user can access the admin onsole", async () => {
it("redirects to `/organizations/{id}` if no custom redirect is supplied but the user can access the admin console", async () => {
const org = orgFactory();
organizationService.get.calledWith(org.id).mockResolvedValue(org);
organizationService.organizations$.calledWith(userId).mockReturnValue(of([org]));
await routerHarness.navigateByUrl(`organizations/${org.id}/noCallback`);
expect(routerHarness.routeNativeElement?.querySelector("h1")?.textContent?.trim() ?? "").toBe(
"This is the admin console!",
@ -107,7 +116,7 @@ describe("Organization Redirect Guard", () => {
it("redirects properly when the redirect callback returns a single string", async () => {
const org = orgFactory();
organizationService.get.calledWith(org.id).mockResolvedValue(org);
organizationService.organizations$.calledWith(userId).mockReturnValue(of([org]));
await routerHarness.navigateByUrl(`organizations/${org.id}/stringCallback`);
expect(routerHarness.routeNativeElement?.querySelector("h1")?.textContent?.trim() ?? "").toBe(
"This is a subroute of the admin console!",
@ -116,7 +125,7 @@ describe("Organization Redirect Guard", () => {
it("redirects properly when the redirect callback returns an array of strings", async () => {
const org = orgFactory();
organizationService.get.calledWith(org.id).mockResolvedValue(org);
organizationService.organizations$.calledWith(userId).mockReturnValue(of([org]));
await routerHarness.navigateByUrl(`organizations/${org.id}/arrayCallback`);
expect(routerHarness.routeNativeElement?.querySelector("h1")?.textContent?.trim() ?? "").toBe(
"This is a subroute of the admin console!",

View File

@ -5,12 +5,14 @@ import {
Router,
RouterStateSnapshot,
} from "@angular/router";
import { firstValueFrom, map } from "rxjs";
import {
canAccessOrgAdmin,
OrganizationService,
} from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
/**
*
@ -25,8 +27,25 @@ export function organizationRedirectGuard(
return async (route: ActivatedRouteSnapshot, state: RouterStateSnapshot) => {
const router = inject(Router);
const organizationService = inject(OrganizationService);
const accountService = inject(AccountService);
const org = await organizationService.get(route.params.organizationId);
const userId = await firstValueFrom(accountService.activeAccount$.pipe(map((a) => a?.id)));
if (!userId) {
return router.createUrlTree(["/"]);
}
const org = await firstValueFrom(
organizationService
.organizations$(userId)
.pipe(
map((organizations) => organizations.find((o) => o.id === route.params.organizationId)),
),
);
if (!org) {
return router.createUrlTree(["/"]);
}
if (customRedirect != null) {
let redirectPath = customRedirect(org);

View File

@ -4,8 +4,12 @@ import { Component, OnInit } from "@angular/core";
import { ActivatedRoute } from "@angular/router";
import { Observable, switchMap } from "rxjs";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import {
getOrganizationById,
OrganizationService,
} from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { IntegrationType } from "@bitwarden/common/enums";
import { HeaderModule } from "../../../layouts/header/header.module";
@ -34,13 +38,22 @@ export class AdminConsoleIntegrationsComponent implements OnInit {
ngOnInit(): void {
this.organization$ = this.route.params.pipe(
switchMap((params) => this.organizationService.get$(params.organizationId)),
switchMap((params) =>
this.accountService.activeAccount$.pipe(
switchMap((account) =>
this.organizationService
.organizations$(account?.id)
.pipe(getOrganizationById(params.organizationId)),
),
),
),
);
}
constructor(
private route: ActivatedRoute,
private organizationService: OrganizationService,
private accountService: AccountService,
) {
this.integrationsList = [
{

View File

@ -3,7 +3,7 @@
import { CommonModule } from "@angular/common";
import { Component, OnInit } from "@angular/core";
import { ActivatedRoute, RouterModule } from "@angular/router";
import { combineLatest, filter, map, Observable, switchMap } from "rxjs";
import { combineLatest, filter, firstValueFrom, map, Observable, switchMap } from "rxjs";
import { JslibModule } from "@bitwarden/angular/jslib.module";
import {
@ -20,6 +20,8 @@ import { PolicyService } from "@bitwarden/common/admin-console/abstractions/poli
import { ProviderService } from "@bitwarden/common/admin-console/abstractions/provider.service";
import { PolicyType, ProviderStatusType } from "@bitwarden/common/admin-console/enums";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { getUserId } from "@bitwarden/common/auth/services/account.service";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
@ -66,14 +68,16 @@ export class OrganizationLayoutComponent implements OnInit {
private configService: ConfigService,
private policyService: PolicyService,
private providerService: ProviderService,
private accountService: AccountService,
) {}
async ngOnInit() {
document.body.classList.remove("layout_frontend");
const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$));
this.organization$ = this.route.params.pipe(
map((p) => p.organizationId),
switchMap((id) => this.organizationService.organizations$.pipe(getById(id))),
switchMap((id) => this.organizationService.organizations$(userId).pipe(getById(id))),
filter((org) => org != null),
);

View File

@ -2,14 +2,19 @@
// @ts-strict-ignore
import { Component, OnDestroy, OnInit } from "@angular/core";
import { ActivatedRoute, Router } from "@angular/router";
import { concatMap, Subject, takeUntil } from "rxjs";
import { concatMap, firstValueFrom, Subject, takeUntil } from "rxjs";
import { OrganizationUserApiService } from "@bitwarden/admin-console/common";
import { UserNamePipe } from "@bitwarden/angular/pipes/user-name.pipe";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import {
getOrganizationById,
OrganizationService,
} from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { ProviderService } from "@bitwarden/common/admin-console/abstractions/provider.service";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { getUserId } from "@bitwarden/common/auth/services/account.service";
import { EventSystemUser } from "@bitwarden/common/enums";
import { EventResponse } from "@bitwarden/common/models/response/event.response";
import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service";
@ -55,6 +60,7 @@ export class EventsComponent extends BaseEventsComponent implements OnInit, OnDe
private providerService: ProviderService,
fileDownloadService: FileDownloadService,
toastService: ToastService,
private accountService: AccountService,
) {
super(
eventService,
@ -68,11 +74,16 @@ export class EventsComponent extends BaseEventsComponent implements OnInit, OnDe
}
async ngOnInit() {
const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$));
this.route.params
.pipe(
concatMap(async (params) => {
this.organizationId = params.organizationId;
this.organization = await this.organizationService.get(this.organizationId);
this.organization = await firstValueFrom(
this.organizationService
.organizations$(userId)
.pipe(getOrganizationById(this.organizationId)),
);
if (this.organization == null || !this.organization.useEvents) {
await this.router.navigate(["/organizations", this.organizationId]);
return;

View File

@ -13,6 +13,7 @@ import {
of,
shareReplay,
Subject,
switchMap,
takeUntil,
} from "rxjs";
@ -22,7 +23,10 @@ import {
OrganizationUserApiService,
} from "@bitwarden/admin-console/common";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import {
getOrganizationById,
OrganizationService,
} from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { ErrorResponse } from "@bitwarden/common/models/response/error.response";
@ -97,9 +101,14 @@ export const openGroupAddEditDialog = (
templateUrl: "group-add-edit.component.html",
})
export class GroupAddEditComponent implements OnInit, OnDestroy {
private organization$ = this.organizationService
.get$(this.organizationId)
.pipe(shareReplay({ refCount: true }));
private organization$ = this.accountService.activeAccount$.pipe(
switchMap((account) =>
this.organizationService
.organizations$(account?.id)
.pipe(getOrganizationById(this.organizationId))
.pipe(shareReplay({ refCount: true })),
),
);
protected PermissionMode = PermissionMode;
protected ResultType = GroupAddEditDialogResultType;

View File

@ -22,7 +22,10 @@ import {
OrganizationUserApiService,
CollectionView,
} from "@bitwarden/admin-console/common";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import {
getOrganizationById,
OrganizationService,
} from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import {
OrganizationUserStatusType,
OrganizationUserType,
@ -156,9 +159,14 @@ export class MemberDialogComponent implements OnDestroy {
private toastService: ToastService,
private configService: ConfigService,
) {
this.organization$ = organizationService
.get$(this.params.organizationId)
.pipe(shareReplay({ refCount: true, bufferSize: 1 }));
this.organization$ = accountService.activeAccount$.pipe(
switchMap((account) =>
organizationService
.organizations$(account?.id)
.pipe(getOrganizationById(this.params.organizationId))
.pipe(shareReplay({ refCount: true, bufferSize: 1 })),
),
);
this.editMode = this.params.organizationUserId != null;
this.tabIndex = this.params.initialTab ?? MemberDialogTab.Role;

View File

@ -28,7 +28,10 @@ import { UserNamePipe } from "@bitwarden/angular/pipes/user-name.pipe";
import { ModalService } from "@bitwarden/angular/services/modal.service";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import {
getOrganizationById,
OrganizationService,
} from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { OrganizationManagementPreferencesService } from "@bitwarden/common/admin-console/abstractions/organization-management-preferences/organization-management-preferences.service";
import { PolicyApiServiceAbstraction as PolicyApiService } from "@bitwarden/common/admin-console/abstractions/policy/policy-api.service.abstraction";
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
@ -40,6 +43,7 @@ import {
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { Policy } from "@bitwarden/common/admin-console/models/domain/policy";
import { OrganizationKeysRequest } from "@bitwarden/common/admin-console/models/request/organization-keys.request";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions/billing-api.service.abstraction";
import { isNotSelfUpgradable, ProductTierType } from "@bitwarden/common/billing/enums";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
@ -122,6 +126,7 @@ export class MembersComponent extends BaseMembersComponent<OrganizationUserView>
private route: ActivatedRoute,
private syncService: SyncService,
private organizationService: OrganizationService,
private accountService: AccountService,
private organizationApiService: OrganizationApiServiceAbstraction,
private organizationUserApiService: OrganizationUserApiService,
private router: Router,
@ -144,7 +149,15 @@ export class MembersComponent extends BaseMembersComponent<OrganizationUserView>
);
const organization$ = this.route.params.pipe(
concatMap((params) => this.organizationService.get$(params.organizationId)),
concatMap((params) =>
this.accountService.activeAccount$.pipe(
switchMap((account) =>
this.organizationService
.organizations$(account?.id)
.pipe(getOrganizationById(params.organizationId)),
),
),
),
shareReplay({ refCount: true, bufferSize: 1 }),
);

View File

@ -1,6 +1,7 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { mock, MockProxy } from "jest-mock-extended";
import { of } from "rxjs";
import {
OrganizationUserApiService,
@ -164,10 +165,9 @@ describe("OrganizationUserResetPasswordService", () => {
describe("getRotatedData", () => {
beforeEach(() => {
organizationService.getAll.mockResolvedValue([
createOrganization("1", "org1"),
createOrganization("2", "org2"),
]);
organizationService.organizations$.mockReturnValue(
of([createOrganization("1", "org1"), createOrganization("2", "org2")]),
);
organizationApiService.getKeys.mockResolvedValue(
new OrganizationKeysResponse({
privateKey: "test-private-key",

View File

@ -1,6 +1,7 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { Injectable } from "@angular/core";
import { firstValueFrom } from "rxjs";
import {
OrganizationUserApiService,
@ -154,7 +155,7 @@ export class OrganizationUserResetPasswordService
throw new Error("New user key is required for rotation.");
}
const allOrgs = await this.organizationService.getAll();
const allOrgs = await firstValueFrom(this.organizationService.organizations$(userId));
if (!allOrgs) {
return;

View File

@ -2,11 +2,17 @@
// @ts-strict-ignore
import { Component, OnInit } from "@angular/core";
import { FormBuilder, FormGroup, Validators } from "@angular/forms";
import { firstValueFrom } from "rxjs";
import { ControlsOf } from "@bitwarden/angular/types/controls-of";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import {
getOrganizationById,
OrganizationService,
} from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { PolicyType } from "@bitwarden/common/admin-console/enums";
import { MasterPasswordPolicyOptions } from "@bitwarden/common/admin-console/models/domain/master-password-policy-options";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { getUserId } from "@bitwarden/common/auth/services/account.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
@ -43,6 +49,7 @@ export class MasterPasswordPolicyComponent extends BasePolicyComponent implement
private formBuilder: FormBuilder,
i18nService: I18nService,
private organizationService: OrganizationService,
private accountService: AccountService,
) {
super();
@ -58,7 +65,12 @@ export class MasterPasswordPolicyComponent extends BasePolicyComponent implement
async ngOnInit() {
super.ngOnInit();
const organization = await this.organizationService.get(this.policyResponse.organizationId);
const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$));
const organization = await firstValueFrom(
this.organizationService
.organizations$(userId)
.pipe(getOrganizationById(this.policyResponse.organizationId)),
);
this.showKeyConnectorInfo = organization.keyConnectorEnabled;
}
}

View File

@ -2,14 +2,18 @@
// @ts-strict-ignore
import { Component, OnInit, ViewChild, ViewContainerRef } from "@angular/core";
import { ActivatedRoute } from "@angular/router";
import { lastValueFrom } from "rxjs";
import { first } from "rxjs/operators";
import { firstValueFrom, lastValueFrom } from "rxjs";
import { first, map } from "rxjs/operators";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import {
getOrganizationById,
OrganizationService,
} from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { PolicyApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/policy/policy-api.service.abstraction";
import { PolicyType } from "@bitwarden/common/admin-console/enums";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { PolicyResponse } from "@bitwarden/common/admin-console/models/response/policy.response";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { DialogService } from "@bitwarden/components";
import { PolicyListService } from "../../core/policy-list.service";
@ -37,6 +41,7 @@ export class PoliciesComponent implements OnInit {
constructor(
private route: ActivatedRoute,
private organizationService: OrganizationService,
private accountService: AccountService,
private policyApiService: PolicyApiServiceAbstraction,
private policyListService: PolicyListService,
private dialogService: DialogService,
@ -46,7 +51,14 @@ export class PoliciesComponent implements OnInit {
// eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe
this.route.parent.parent.params.subscribe(async (params) => {
this.organizationId = params.organizationId;
this.organization = await this.organizationService.get(this.organizationId);
const userId = await firstValueFrom(
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
);
this.organization = await firstValueFrom(
this.organizationService
.organizations$(userId)
.pipe(getOrganizationById(this.organizationId)),
);
this.policies = this.policyListService.getPolicies();
await this.load();

View File

@ -1,9 +1,15 @@
import { Component, OnInit } from "@angular/core";
import { FormBuilder } from "@angular/forms";
import { firstValueFrom } from "rxjs";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import {
getOrganizationById,
OrganizationService,
} from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { PolicyType } from "@bitwarden/common/admin-console/enums";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { getUserId } from "@bitwarden/common/auth/services/account.service";
import { BasePolicy, BasePolicyComponent } from "./base-policy.component";
@ -31,13 +37,29 @@ export class ResetPasswordPolicyComponent extends BasePolicyComponent implements
constructor(
private formBuilder: FormBuilder,
private organizationService: OrganizationService,
private accountService: AccountService,
) {
super();
}
async ngOnInit() {
super.ngOnInit();
const organization = await this.organizationService.get(this.policyResponse.organizationId);
const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$));
if (!userId) {
throw new Error("No user found.");
}
const organization = await firstValueFrom(
this.organizationService
.organizations$(userId)
.pipe(getOrganizationById(this.policyResponse.organizationId)),
);
if (!organization) {
throw new Error("No organization found.");
}
this.showKeyConnectorInfo = organization.keyConnectorEnabled;
}
}

View File

@ -2,9 +2,14 @@
// @ts-strict-ignore
import { Component, OnInit } from "@angular/core";
import { ActivatedRoute, NavigationEnd, Router } from "@angular/router";
import { filter, map, Observable, startWith, concatMap } from "rxjs";
import { filter, map, Observable, startWith, concatMap, firstValueFrom } from "rxjs";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import {
getOrganizationById,
OrganizationService,
} from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { getUserId } from "@bitwarden/common/auth/services/account.service";
import { ProductTierType } from "@bitwarden/common/billing/enums";
import { ReportVariant, reports, ReportType, ReportEntry } from "../../../tools/reports";
@ -20,6 +25,7 @@ export class ReportsHomeComponent implements OnInit {
constructor(
private route: ActivatedRoute,
private organizationService: OrganizationService,
private accountService: AccountService,
private router: Router,
) {}
@ -30,8 +36,14 @@ export class ReportsHomeComponent implements OnInit {
startWith(this.isReportsHomepageRouteUrl(this.router.url)),
);
const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$));
this.reports$ = this.route.params.pipe(
concatMap((params) => this.organizationService.get$(params.organizationId)),
concatMap((params) =>
this.organizationService
.organizations$(userId)
.pipe(getOrganizationById(params.organizationId)),
),
map((org) => this.buildReports(org.productTierType)),
);
}

View File

@ -3,15 +3,29 @@
import { Component, OnDestroy, OnInit, ViewChild, ViewContainerRef } from "@angular/core";
import { FormBuilder, Validators } from "@angular/forms";
import { ActivatedRoute, Router } from "@angular/router";
import { combineLatest, from, lastValueFrom, of, Subject, switchMap, takeUntil } from "rxjs";
import {
combineLatest,
firstValueFrom,
from,
lastValueFrom,
of,
Subject,
switchMap,
takeUntil,
} from "rxjs";
import { ModalService } from "@bitwarden/angular/services/modal.service";
import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import {
getOrganizationById,
OrganizationService,
} from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { OrganizationCollectionManagementUpdateRequest } from "@bitwarden/common/admin-console/models/request/organization-collection-management-update.request";
import { OrganizationKeysRequest } from "@bitwarden/common/admin-console/models/request/organization-keys.request";
import { OrganizationUpdateRequest } from "@bitwarden/common/admin-console/models/request/organization-update.request";
import { OrganizationResponse } from "@bitwarden/common/admin-console/models/response/organization.response";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { getUserId } from "@bitwarden/common/auth/services/account.service";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
@ -77,6 +91,7 @@ export class AccountComponent implements OnInit, OnDestroy {
private platformUtilsService: PlatformUtilsService,
private keyService: KeyService,
private router: Router,
private accountService: AccountService,
private organizationService: OrganizationService,
private organizationApiService: OrganizationApiServiceAbstraction,
private dialogService: DialogService,
@ -88,9 +103,14 @@ export class AccountComponent implements OnInit, OnDestroy {
async ngOnInit() {
this.selfHosted = this.platformUtilsService.isSelfHost();
const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$));
this.route.params
.pipe(
switchMap((params) => this.organizationService.get$(params.organizationId)),
switchMap((params) =>
this.organizationService
.organizations$(userId)
.pipe(getOrganizationById(params.organizationId)),
),
switchMap((organization) => {
return combineLatest([
of(organization),

View File

@ -3,12 +3,17 @@
import { DIALOG_DATA, DialogConfig, DialogRef } from "@angular/cdk/dialog";
import { Component, Inject, OnDestroy, OnInit } from "@angular/core";
import { FormBuilder, FormControl, Validators } from "@angular/forms";
import { combineLatest, Subject, takeUntil } from "rxjs";
import { combineLatest, firstValueFrom, Subject, takeUntil } from "rxjs";
import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import {
getOrganizationById,
OrganizationService,
} from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
import { getUserId } from "@bitwarden/common/auth/services/account.service";
import { Verification } from "@bitwarden/common/auth/types/verification";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
@ -94,6 +99,7 @@ export class DeleteOrganizationDialogComponent implements OnInit, OnDestroy {
private userVerificationService: UserVerificationService,
private cipherService: CipherService,
private organizationService: OrganizationService,
private accountService: AccountService,
private organizationApiService: OrganizationApiServiceAbstraction,
private formBuilder: FormBuilder,
private toastService: ToastService,
@ -106,9 +112,12 @@ export class DeleteOrganizationDialogComponent implements OnInit, OnDestroy {
async ngOnInit(): Promise<void> {
this.deleteOrganizationRequestType = this.params.requestType;
const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$));
combineLatest([
this.organizationService.get$(this.params.organizationId),
this.organizationService
.organizations$(userId)
.pipe(getOrganizationById(this.params.organizationId)),
this.cipherService.getAllFromApiForOrganization(this.params.organizationId),
])
.pipe(takeUntil(this.destroy$))

View File

@ -2,13 +2,15 @@
// @ts-strict-ignore
import { Component, OnInit } from "@angular/core";
import { ActivatedRoute, Router } from "@angular/router";
import { firstValueFrom } from "rxjs";
import { firstValueFrom, map } from "rxjs";
import { CollectionAdminService } from "@bitwarden/admin-console/common";
import {
canAccessVaultTab,
OrganizationService,
} from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { getUserId } from "@bitwarden/common/auth/services/account.service";
import { ImportCollectionServiceAbstraction } from "@bitwarden/importer/core";
import { ImportComponent } from "@bitwarden/importer/ui";
@ -36,6 +38,7 @@ export class OrgImportComponent implements OnInit {
private route: ActivatedRoute,
private organizationService: OrganizationService,
private router: Router,
private accountService: AccountService,
) {}
ngOnInit(): void {
@ -46,7 +49,12 @@ export class OrgImportComponent implements OnInit {
* Callback that is called after a successful import.
*/
protected async onSuccessfulImport(organizationId: string): Promise<void> {
const organization = await firstValueFrom(this.organizationService.get$(organizationId));
const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$));
const organization = await firstValueFrom(
this.organizationService
.organizations$(userId)
.pipe(map((organizations) => organizations.find((o) => o.id === organizationId))),
);
if (organization == null) {
return;
}

View File

@ -3,16 +3,20 @@
import { DialogRef } from "@angular/cdk/dialog";
import { Component, OnInit } from "@angular/core";
import { ActivatedRoute } from "@angular/router";
import { concatMap, takeUntil, map, lastValueFrom } from "rxjs";
import { concatMap, takeUntil, map, lastValueFrom, firstValueFrom } from "rxjs";
import { first, tap } from "rxjs/operators";
import { ModalService } from "@bitwarden/angular/services/modal.service";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import {
getOrganizationById,
OrganizationService,
} from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type";
import { TwoFactorDuoResponse } from "@bitwarden/common/auth/models/response/two-factor-duo.response";
import { getUserId } from "@bitwarden/common/auth/services/account.service";
import { AuthResponse } from "@bitwarden/common/auth/types/auth-response";
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
@ -38,7 +42,7 @@ export class TwoFactorSetupComponent extends BaseTwoFactorSetupComponent impleme
private route: ActivatedRoute,
private organizationService: OrganizationService,
billingAccountProfileStateService: BillingAccountProfileStateService,
accountService: AccountService,
protected accountService: AccountService,
) {
super(
dialogService,
@ -52,11 +56,13 @@ export class TwoFactorSetupComponent extends BaseTwoFactorSetupComponent impleme
}
async ngOnInit() {
const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$));
this.route.params
.pipe(
concatMap((params) =>
this.organizationService
.get$(params.organizationId)
.organizations$(userId)
.pipe(getOrganizationById(params.organizationId))
.pipe(map((organization) => ({ params, organization }))),
),
tap(async (mapResponse) => {

View File

@ -3,7 +3,7 @@
import { Component, OnDestroy, OnInit, ViewChild } from "@angular/core";
import { FormBuilder, Validators } from "@angular/forms";
import { ActivatedRoute, Router } from "@angular/router";
import { lastValueFrom, Observable, Subject } from "rxjs";
import { firstValueFrom, lastValueFrom, Observable, Subject } from "rxjs";
import { first, map, takeUntil } from "rxjs/operators";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
@ -12,6 +12,8 @@ import { OrganizationUserType } from "@bitwarden/common/admin-console/enums";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { OrganizationSponsorshipRedeemRequest } from "@bitwarden/common/admin-console/models/request/organization/organization-sponsorship-redeem.request";
import { PreValidateSponsorshipResponse } from "@bitwarden/common/admin-console/models/response/pre-validate-sponsorship.response";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { getUserId } from "@bitwarden/common/auth/services/account.service";
import { PlanSponsorshipType, PlanType, ProductTierType } from "@bitwarden/common/billing/enums";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
@ -69,6 +71,7 @@ export class FamiliesForEnterpriseSetupComponent implements OnInit, OnDestroy {
private syncService: SyncService,
private validationService: ValidationService,
private organizationService: OrganizationService,
private accountService: AccountService,
private dialogService: DialogService,
private formBuilder: FormBuilder,
private toastService: ToastService,
@ -115,11 +118,15 @@ export class FamiliesForEnterpriseSetupComponent implements OnInit, OnDestroy {
this.loading = false;
});
this.existingFamilyOrganizations$ = this.organizationService.organizations$.pipe(
const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$));
this.existingFamilyOrganizations$ = this.organizationService
.organizations$(userId)
.pipe(
map((orgs) =>
orgs.filter(
(o) =>
o.productTierType === ProductTierType.Families && o.type === OrganizationUserType.Owner,
o.productTierType === ProductTierType.Families &&
o.type === OrganizationUserType.Owner,
),
),
);

View File

@ -2,10 +2,15 @@
// @ts-strict-ignore
import { Component, OnInit } from "@angular/core";
import { ActivatedRoute } from "@angular/router";
import { firstValueFrom, map } from "rxjs";
import { ModalService } from "@bitwarden/angular/services/modal.service";
import { AuditService } from "@bitwarden/common/abstractions/audit.service";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import {
getOrganizationById,
OrganizationService,
} from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
@ -32,6 +37,7 @@ export class ExposedPasswordsReportComponent
auditService: AuditService,
modalService: ModalService,
organizationService: OrganizationService,
protected accountService: AccountService,
private route: ActivatedRoute,
passwordRepromptService: PasswordRepromptService,
i18nService: I18nService,
@ -41,6 +47,7 @@ export class ExposedPasswordsReportComponent
cipherService,
auditService,
organizationService,
accountService,
modalService,
passwordRepromptService,
i18nService,
@ -52,7 +59,14 @@ export class ExposedPasswordsReportComponent
this.isAdminConsoleActive = true;
// eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe
this.route.parent.parent.params.subscribe(async (params) => {
this.organization = await this.organizationService.get(params.organizationId);
const userId = await firstValueFrom(
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
);
this.organization = await firstValueFrom(
this.organizationService
.organizations$(userId)
.pipe(getOrganizationById(params.organizationId)),
);
this.manageableCiphers = await this.cipherService.getAll();
});
}

View File

@ -2,9 +2,14 @@
// @ts-strict-ignore
import { Component, OnInit } from "@angular/core";
import { ActivatedRoute } from "@angular/router";
import { firstValueFrom, map } from "rxjs";
import { ModalService } from "@bitwarden/angular/services/modal.service";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import {
getOrganizationById,
OrganizationService,
} from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
@ -31,12 +36,14 @@ export class InactiveTwoFactorReportComponent
logService: LogService,
passwordRepromptService: PasswordRepromptService,
organizationService: OrganizationService,
accountService: AccountService,
i18nService: I18nService,
syncService: SyncService,
) {
super(
cipherService,
organizationService,
accountService,
modalService,
logService,
passwordRepromptService,
@ -49,7 +56,14 @@ export class InactiveTwoFactorReportComponent
this.isAdminConsoleActive = true;
// eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe
this.route.parent.parent.params.subscribe(async (params) => {
this.organization = await this.organizationService.get(params.organizationId);
const userId = await firstValueFrom(
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
);
this.organization = await firstValueFrom(
this.organizationService
.organizations$(userId)
.pipe(getOrganizationById(params.organizationId)),
);
await super.ngOnInit();
});
}

View File

@ -2,9 +2,14 @@
// @ts-strict-ignore
import { Component, OnInit } from "@angular/core";
import { ActivatedRoute } from "@angular/router";
import { firstValueFrom, map } from "rxjs";
import { ModalService } from "@bitwarden/angular/services/modal.service";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import {
getOrganizationById,
OrganizationService,
} from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
@ -31,6 +36,7 @@ export class ReusedPasswordsReportComponent
modalService: ModalService,
private route: ActivatedRoute,
organizationService: OrganizationService,
protected accountService: AccountService,
passwordRepromptService: PasswordRepromptService,
i18nService: I18nService,
syncService: SyncService,
@ -38,6 +44,7 @@ export class ReusedPasswordsReportComponent
super(
cipherService,
organizationService,
accountService,
modalService,
passwordRepromptService,
i18nService,
@ -49,7 +56,14 @@ export class ReusedPasswordsReportComponent
this.isAdminConsoleActive = true;
// eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe
this.route.parent.parent.params.subscribe(async (params) => {
this.organization = await this.organizationService.get(params.organizationId);
const userId = await firstValueFrom(
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
);
this.organization = await firstValueFrom(
this.organizationService
.organizations$(userId)
.pipe(getOrganizationById(params.organizationId)),
);
this.manageableCiphers = await this.cipherService.getAll();
await super.ngOnInit();
});

View File

@ -2,10 +2,15 @@
// @ts-strict-ignore
import { Component, OnInit } from "@angular/core";
import { ActivatedRoute } from "@angular/router";
import { firstValueFrom, map } from "rxjs";
import { CollectionService } from "@bitwarden/admin-console/common";
import { ModalService } from "@bitwarden/angular/services/modal.service";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import {
getOrganizationById,
OrganizationService,
} from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
@ -29,6 +34,7 @@ export class UnsecuredWebsitesReportComponent
modalService: ModalService,
private route: ActivatedRoute,
organizationService: OrganizationService,
protected accountService: AccountService,
passwordRepromptService: PasswordRepromptService,
i18nService: I18nService,
syncService: SyncService,
@ -37,6 +43,7 @@ export class UnsecuredWebsitesReportComponent
super(
cipherService,
organizationService,
accountService,
modalService,
passwordRepromptService,
i18nService,
@ -49,7 +56,14 @@ export class UnsecuredWebsitesReportComponent
this.isAdminConsoleActive = true;
// eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe
this.route.parent.parent.params.subscribe(async (params) => {
this.organization = await this.organizationService.get(params.organizationId);
const userId = await firstValueFrom(
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
);
this.organization = await firstValueFrom(
this.organizationService
.organizations$(userId)
.pipe(getOrganizationById(params.organizationId)),
);
await super.ngOnInit();
});
}

View File

@ -2,9 +2,14 @@
// @ts-strict-ignore
import { Component, OnInit } from "@angular/core";
import { ActivatedRoute } from "@angular/router";
import { firstValueFrom, map } from "rxjs";
import { ModalService } from "@bitwarden/angular/services/modal.service";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import {
getOrganizationById,
OrganizationService,
} from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/password-strength";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
@ -36,11 +41,13 @@ export class WeakPasswordsReportComponent
passwordRepromptService: PasswordRepromptService,
i18nService: I18nService,
syncService: SyncService,
protected accountService: AccountService,
) {
super(
cipherService,
passwordStrengthService,
organizationService,
accountService,
modalService,
passwordRepromptService,
i18nService,
@ -52,7 +59,14 @@ export class WeakPasswordsReportComponent
this.isAdminConsoleActive = true;
// eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe
this.route.parent.parent.params.subscribe(async (params) => {
this.organization = await this.organizationService.get(params.organizationId);
const userId = await firstValueFrom(
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
);
this.organization = await firstValueFrom(
this.organizationService
.organizations$(userId)
.pipe(getOrganizationById(params.organizationId)),
);
this.manageableCiphers = await this.cipherService.getAll();
await super.ngOnInit();
});

View File

@ -17,6 +17,7 @@ import { AccountService } from "@bitwarden/common/auth/abstractions/account.serv
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
import { KeyConnectorService } from "@bitwarden/common/auth/abstractions/key-connector.service";
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
import { getUserId } from "@bitwarden/common/auth/services/account.service";
import { ProcessReloadServiceAbstraction } from "@bitwarden/common/key-management/abstractions/process-reload.service";
import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
@ -219,7 +220,10 @@ export class AppComponent implements OnDestroy, OnInit {
break;
case "syncOrganizationStatusChanged": {
const { organizationId, enabled } = message;
const organizations = await firstValueFrom(this.organizationService.organizations$);
const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$));
const organizations = await firstValueFrom(
this.organizationService.organizations$(userId),
);
const organization = organizations.find((org) => org.id === organizationId);
if (organization) {
@ -227,21 +231,27 @@ export class AppComponent implements OnDestroy, OnInit {
...organization,
enabled: enabled,
};
await this.organizationService.upsert(updatedOrganization);
await this.organizationService.upsert(updatedOrganization, userId);
}
break;
}
case "syncOrganizationCollectionSettingChanged": {
const { organizationId, limitCollectionCreation, limitCollectionDeletion } = message;
const organizations = await firstValueFrom(this.organizationService.organizations$);
const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$));
const organizations = await firstValueFrom(
this.organizationService.organizations$(userId),
);
const organization = organizations.find((org) => org.id === organizationId);
if (organization) {
await this.organizationService.upsert({
await this.organizationService.upsert(
{
...organization,
limitCollectionCreation: limitCollectionCreation,
limitCollectionDeletion: limitCollectionDeletion,
});
},
userId,
);
}
break;
}
@ -291,7 +301,7 @@ export class AppComponent implements OnDestroy, OnInit {
// will prevent any toasts from being displayed long enough to be read
await this.eventUploadService.uploadEvents();
const userId = await firstValueFrom(this.accountService.activeAccount$.pipe(map((a) => a?.id)));
const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$));
const logoutPromise = firstValueFrom(
this.authService.authStatusFor$(userId).pipe(

View File

@ -1,11 +1,13 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { Component, OnInit, ViewChild, ViewContainerRef } from "@angular/core";
import { combineLatest, from, lastValueFrom, map, Observable } from "rxjs";
import { combineLatest, firstValueFrom, from, lastValueFrom, map, Observable } from "rxjs";
import { ModalService } from "@bitwarden/angular/services/modal.service";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
import { getUserId } from "@bitwarden/common/auth/services/account.service";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { DialogService } from "@bitwarden/components";
@ -33,14 +35,19 @@ export class AccountComponent implements OnInit {
private userVerificationService: UserVerificationService,
private configService: ConfigService,
private organizationService: OrganizationService,
private accountService: AccountService,
) {}
async ngOnInit() {
const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$));
const isAccountDeprovisioningEnabled$ = this.configService.getFeatureFlag$(
FeatureFlag.AccountDeprovisioning,
);
const userIsManagedByOrganization$ = this.organizationService.organizations$.pipe(
const userIsManagedByOrganization$ = this.organizationService
.organizations$(userId)
.pipe(
map((organizations) => organizations.some((o) => o.userIsManagedByOrganization === true)),
);

View File

@ -9,6 +9,7 @@ import { OrganizationService } from "@bitwarden/common/admin-console/abstraction
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { UpdateProfileRequest } from "@bitwarden/common/auth/models/request/update-profile.request";
import { getUserId } from "@bitwarden/common/auth/services/account.service";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { ProfileResponse } from "@bitwarden/common/models/response/profile.response";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
@ -49,12 +50,17 @@ export class ProfileComponent implements OnInit, OnDestroy {
this.fingerprintMaterial = await firstValueFrom(
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
);
const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$));
this.managingOrganization$ = this.configService
.getFeatureFlag$(FeatureFlag.AccountDeprovisioning)
.pipe(
switchMap((isAccountDeprovisioningEnabled) =>
isAccountDeprovisioningEnabled
? this.organizationService.organizations$.pipe(
? this.organizationService
.organizations$(userId)
.pipe(
map((organizations) =>
organizations.find((o) => o.userIsManagedByOrganization === true),
),

View File

@ -12,6 +12,7 @@ import { AccountService } from "@bitwarden/common/auth/abstractions/account.serv
import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction";
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
import { PasswordRequest } from "@bitwarden/common/auth/models/request/password.request";
import { getUserId } from "@bitwarden/common/auth/services/account.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
@ -188,7 +189,7 @@ export class ChangePasswordComponent
await this.kdfConfigService.getKdfConfig(),
);
const userId = await firstValueFrom(this.accountService.activeAccount$.pipe(map((a) => a?.id)));
const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$));
const newLocalKeyHash = await this.keyService.hashMasterKey(
this.masterPassword,
newMasterKey,

View File

@ -8,6 +8,7 @@ import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { OrganizationManagementPreferencesService } from "@bitwarden/common/admin-console/abstractions/organization-management-preferences/organization-management-preferences.service";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { getUserId } from "@bitwarden/common/auth/services/account.service";
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
@ -83,7 +84,8 @@ export class EmergencyAccessComponent implements OnInit {
}
async ngOnInit() {
const orgs = await this.organizationService.getAll();
const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$));
const orgs = await firstValueFrom(this.organizationService.organizations$(userId));
this.isOrganizationOwner = orgs.some((o) => o.isOwner);
// 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

View File

@ -43,6 +43,7 @@ describe("EmergencyViewDialogComponent", () => {
imports: [EmergencyViewDialogComponent, NoopAnimationsModule],
providers: [
{ provide: OrganizationService, useValue: mock<OrganizationService>() },
{ provide: AccountService, useValue: accountService },
{ provide: CollectionService, useValue: mock<CollectionService>() },
{ provide: FolderService, useValue: mock<FolderService>() },
{ provide: I18nService, useValue: { t: (...keys: string[]) => keys.join(" ") } },

View File

@ -71,7 +71,7 @@ export class TwoFactorSetupComponent implements OnInit, OnDestroy {
protected messagingService: MessagingService,
protected policyService: PolicyService,
billingAccountProfileStateService: BillingAccountProfileStateService,
private accountService: AccountService,
protected accountService: AccountService,
) {
this.canAccessPremium$ = this.accountService.activeAccount$.pipe(
switchMap((account) =>

View File

@ -1,15 +1,35 @@
import { inject } from "@angular/core";
import { ActivatedRouteSnapshot, CanActivateFn } from "@angular/router";
import { firstValueFrom, map } from "rxjs";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import {
getOrganizationById,
OrganizationService,
} from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { ProviderService } from "@bitwarden/common/admin-console/abstractions/provider.service";
import { ProviderStatusType } from "@bitwarden/common/admin-console/enums";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
export const organizationIsUnmanaged: CanActivateFn = async (route: ActivatedRouteSnapshot) => {
const organizationService = inject(OrganizationService);
const providerService = inject(ProviderService);
const accountService = inject(AccountService);
const organization = await organizationService.get(route.params.organizationId);
const userId = await firstValueFrom(accountService.activeAccount$.pipe(map((a) => a?.id)));
if (!userId) {
throw new Error("No user found.");
}
const organization = await firstValueFrom(
organizationService
.organizations$(userId)
.pipe(getOrganizationById(route.params.organizationId)),
);
if (!organization) {
throw new Error("No organization found.");
}
if (!organization.hasProvider) {
return true;

View File

@ -2,11 +2,16 @@
// @ts-strict-ignore
import { Component, EventEmitter, Input, OnDestroy, OnInit, Output } from "@angular/core";
import { FormBuilder, Validators } from "@angular/forms";
import { Subject, takeUntil } from "rxjs";
import { Subject, firstValueFrom, takeUntil } from "rxjs";
import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction";
import { InternalOrganizationServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import {
getOrganizationById,
InternalOrganizationServiceAbstraction,
} from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { OrganizationData } from "@bitwarden/common/admin-console/models/data/organization.data";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { getUserId } from "@bitwarden/common/auth/services/account.service";
import { OrganizationSubscriptionUpdateRequest } from "@bitwarden/common/billing/models/request/organization-subscription-update.request";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { ToastService } from "@bitwarden/components";
@ -37,6 +42,7 @@ export class AdjustSubscription implements OnInit, OnDestroy {
private formBuilder: FormBuilder,
private toastService: ToastService,
private internalOrganizationService: InternalOrganizationServiceAbstraction,
private accountService: AccountService,
) {}
ngOnInit() {
@ -73,14 +79,19 @@ export class AdjustSubscription implements OnInit, OnDestroy {
request,
);
const organization = await this.internalOrganizationService.get(this.organizationId);
const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$));
const organization = await firstValueFrom(
this.internalOrganizationService
.organizations$(userId)
.pipe(getOrganizationById(this.organizationId)),
);
const organizationData = new OrganizationData(response, {
isMember: organization.isMember,
isProviderUser: organization.isProviderUser,
});
await this.internalOrganizationService.upsert(organizationData);
await this.internalOrganizationService.upsert(organizationData, userId);
this.toastService.showToast({
variant: "success",

View File

@ -13,17 +13,21 @@ import {
} from "@angular/core";
import { FormBuilder, Validators } from "@angular/forms";
import { Router } from "@angular/router";
import { Subject, takeUntil } from "rxjs";
import { Subject, firstValueFrom, map, takeUntil } from "rxjs";
import { ManageTaxInformationComponent } from "@bitwarden/angular/billing/components";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import {
getOrganizationById,
OrganizationService,
} from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { PolicyType } from "@bitwarden/common/admin-console/enums";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { OrganizationKeysRequest } from "@bitwarden/common/admin-console/models/request/organization-keys.request";
import { OrganizationUpgradeRequest } from "@bitwarden/common/admin-console/models/request/organization-upgrade.request";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import {
BillingApiServiceAbstraction,
BillingInformation,
@ -209,6 +213,7 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy {
private configService: ConfigService,
private billingApiService: BillingApiServiceAbstraction,
private taxService: TaxServiceAbstraction,
private accountService: AccountService,
private organizationBillingService: OrganizationBillingService,
) {}
@ -226,7 +231,14 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy {
this.organizationId = this.dialogParams.organizationId;
this.currentPlan = this.sub?.plan;
this.selectedPlan = this.sub?.plan;
this.organization = await this.organizationService.get(this.organizationId);
const userId = await firstValueFrom(
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
);
this.organization = await firstValueFrom(
this.organizationService
.organizations$(userId)
.pipe(getOrganizationById(this.organizationId)),
);
if (this.deprecateStripeSourcesAPI) {
const { accountCredit, paymentSource } =
await this.billingApiService.getOrganizationPaymentMethod(this.organizationId);

View File

@ -11,13 +11,16 @@ import {
} from "@angular/core";
import { FormBuilder, Validators } from "@angular/forms";
import { Router } from "@angular/router";
import { Subject, takeUntil } from "rxjs";
import { debounceTime } from "rxjs/operators";
import { Subject, firstValueFrom, takeUntil } from "rxjs";
import { debounceTime, map } from "rxjs/operators";
import { ManageTaxInformationComponent } from "@bitwarden/angular/billing/components";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import {
getOrganizationById,
OrganizationService,
} from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { ProviderApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/provider/provider-api.service.abstraction";
import { PolicyType } from "@bitwarden/common/admin-console/enums";
@ -27,6 +30,7 @@ import { OrganizationKeysRequest } from "@bitwarden/common/admin-console/models/
import { OrganizationUpgradeRequest } from "@bitwarden/common/admin-console/models/request/organization-upgrade.request";
import { ProviderOrganizationCreateRequest } from "@bitwarden/common/admin-console/models/request/provider/provider-organization-create.request";
import { ProviderResponse } from "@bitwarden/common/admin-console/models/response/provider/provider.response";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions";
import { TaxServiceAbstraction } from "@bitwarden/common/billing/abstractions/tax.service.abstraction";
import { PaymentMethodType, PlanType, ProductTierType } from "@bitwarden/common/billing/enums";
@ -179,6 +183,7 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy {
private configService: ConfigService,
private billingApiService: BillingApiServiceAbstraction,
private taxService: TaxServiceAbstraction,
private accountService: AccountService,
) {
this.selfHosted = this.platformUtilsService.isSelfHost();
}
@ -189,7 +194,14 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy {
);
if (this.organizationId) {
this.organization = await this.organizationService.get(this.organizationId);
const userId = await firstValueFrom(
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
);
this.organization = await firstValueFrom(
this.organizationService
.organizations$(userId)
.pipe(getOrganizationById(this.organizationId)),
);
this.billing = await this.organizationApiService.getBilling(this.organizationId);
this.sub = await this.organizationApiService.getSubscription(this.organizationId);
this.taxInformation = await this.organizationApiService.getTaxInfo(this.organizationId);

View File

@ -6,9 +6,14 @@ import { firstValueFrom, lastValueFrom, Observable, Subject } from "rxjs";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import {
getOrganizationById,
OrganizationService,
} from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { OrganizationApiKeyType } from "@bitwarden/common/admin-console/enums";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { getUserId } from "@bitwarden/common/auth/services/account.service";
import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions";
import { PlanType, ProductTierType } from "@bitwarden/common/billing/enums";
import { OrganizationSubscriptionResponse } from "@bitwarden/common/billing/models/response/organization-subscription.response";
@ -76,6 +81,7 @@ export class OrganizationSubscriptionCloudComponent implements OnInit, OnDestroy
private i18nService: I18nService,
private logService: LogService,
private organizationService: OrganizationService,
private accountService: AccountService,
private organizationApiService: OrganizationApiServiceAbstraction,
private route: ActivatedRoute,
private dialogService: DialogService,
@ -117,7 +123,12 @@ export class OrganizationSubscriptionCloudComponent implements OnInit, OnDestroy
async load() {
this.loading = true;
this.locale = await firstValueFrom(this.i18nService.locale$);
this.userOrg = await this.organizationService.get(this.organizationId);
const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$));
this.userOrg = await firstValueFrom(
this.organizationService
.organizations$(userId)
.pipe(getOrganizationById(this.organizationId)),
);
const isIndependentOrganizationOwner = !this.userOrg.hasProvider && this.userOrg.isOwner;
const isResoldOrganizationOwner = this.userOrg.hasReseller && this.userOrg.isOwner;

View File

@ -7,10 +7,15 @@ import { concatMap, firstValueFrom, Subject, takeUntil } from "rxjs";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import {
getOrganizationById,
OrganizationService,
} from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { OrganizationConnectionType } from "@bitwarden/common/admin-console/enums";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { OrganizationConnectionResponse } from "@bitwarden/common/admin-console/models/response/organization-connection.response";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { getUserId } from "@bitwarden/common/auth/services/account.service";
import { ProductTierType } from "@bitwarden/common/billing/enums";
import { BillingSyncConfigApi } from "@bitwarden/common/billing/models/api/billing-sync-config.api";
import { SelfHostedOrganizationSubscriptionView } from "@bitwarden/common/billing/models/view/self-hosted-organization-subscription.view";
@ -80,6 +85,7 @@ export class OrganizationSubscriptionSelfhostComponent implements OnInit, OnDest
private messagingService: MessagingService,
private apiService: ApiService,
private organizationService: OrganizationService,
private accountService: AccountService,
private route: ActivatedRoute,
private organizationApiService: OrganizationApiServiceAbstraction,
private platformUtilsService: PlatformUtilsService,
@ -115,7 +121,12 @@ export class OrganizationSubscriptionSelfhostComponent implements OnInit, OnDest
return;
}
this.loading = true;
this.userOrg = await this.organizationService.get(this.organizationId);
const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$));
this.userOrg = await firstValueFrom(
this.organizationService
.organizations$(userId)
.pipe(getOrganizationById(this.organizationId)),
);
this.showAutomaticSyncAndManualUpload =
this.userOrg.productTierType == ProductTierType.Families ? false : true;
if (this.userOrg.canViewSubscription) {

View File

@ -4,11 +4,15 @@ import { Location } from "@angular/common";
import { Component, OnDestroy } from "@angular/core";
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
import { ActivatedRoute, Router } from "@angular/router";
import { from, lastValueFrom, switchMap } from "rxjs";
import { firstValueFrom, from, lastValueFrom, map, switchMap } from "rxjs";
import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import {
getOrganizationById,
OrganizationService,
} from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions";
import { PaymentMethodType } from "@bitwarden/common/billing/enums";
import { VerifyBankAccountRequest } from "@bitwarden/common/billing/models/request/verify-bank-account.request";
@ -60,6 +64,7 @@ export class OrganizationPaymentMethodComponent implements OnDestroy {
private location: Location,
private trialFlowService: TrialFlowService,
private organizationService: OrganizationService,
private accountService: AccountService,
protected syncService: SyncService,
) {
this.activatedRoute.params
@ -120,7 +125,14 @@ export class OrganizationPaymentMethodComponent implements OnDestroy {
const organizationSubscriptionPromise = this.organizationApiService.getSubscription(
this.organizationId,
);
const organizationPromise = this.organizationService.get(this.organizationId);
const userId = await firstValueFrom(
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
);
const organizationPromise = await firstValueFrom(
this.organizationService
.organizations$(userId)
.pipe(getOrganizationById(this.organizationId)),
);
[this.organizationSubscriptionResponse, this.organization] = await Promise.all([
organizationSubscriptionPromise,

View File

@ -2,11 +2,16 @@
// @ts-strict-ignore
import { Component, EventEmitter, Input, OnDestroy, OnInit, Output } from "@angular/core";
import { FormBuilder, Validators } from "@angular/forms";
import { Subject, takeUntil } from "rxjs";
import { Subject, firstValueFrom, takeUntil } from "rxjs";
import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction";
import { InternalOrganizationServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import {
getOrganizationById,
InternalOrganizationServiceAbstraction,
} from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { OrganizationData } from "@bitwarden/common/admin-console/models/data/organization.data";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { getUserId } from "@bitwarden/common/auth/services/account.service";
import { OrganizationSmSubscriptionUpdateRequest } from "@bitwarden/common/billing/models/request/organization-sm-subscription-update.request";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
@ -107,6 +112,7 @@ export class SecretsManagerAdjustSubscriptionComponent implements OnInit, OnDest
private platformUtilsService: PlatformUtilsService,
private toastService: ToastService,
private internalOrganizationService: InternalOrganizationServiceAbstraction,
private accountService: AccountService,
) {}
ngOnInit() {
@ -165,14 +171,19 @@ export class SecretsManagerAdjustSubscriptionComponent implements OnInit, OnDest
request,
);
const organization = await this.internalOrganizationService.get(this.organizationId);
const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$));
const organization = await firstValueFrom(
this.internalOrganizationService
.organizations$(userId)
.pipe(getOrganizationById(this.organizationId)),
);
const organizationData = new OrganizationData(response, {
isMember: organization.isMember,
isProviderUser: organization.isProviderUser,
});
await this.internalOrganizationService.upsert(organizationData);
await this.internalOrganizationService.upsert(organizationData, userId);
this.toastService.showToast({
variant: "success",

View File

@ -2,12 +2,15 @@
// @ts-strict-ignore
import { Component, EventEmitter, Input, Output } from "@angular/core";
import { FormBuilder } from "@angular/forms";
import { firstValueFrom } from "rxjs";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction";
import { InternalOrganizationServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { OrganizationData } from "@bitwarden/common/admin-console/models/data/organization.data";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { getUserId } from "@bitwarden/common/auth/services/account.service";
import { SecretsManagerSubscribeRequest } from "@bitwarden/common/billing/models/request/sm-subscribe.request";
import { BillingCustomerDiscount } from "@bitwarden/common/billing/models/response/organization-subscription.response";
import { PlanResponse } from "@bitwarden/common/billing/models/response/plan.response";
@ -37,6 +40,7 @@ export class SecretsManagerSubscribeStandaloneComponent {
private organizationApiService: OrganizationApiServiceAbstraction,
private organizationService: InternalOrganizationServiceAbstraction,
private toastService: ToastService,
private accountService: AccountService,
) {}
submit = async () => {
@ -56,7 +60,8 @@ export class SecretsManagerSubscribeStandaloneComponent {
isMember: this.organization.isMember,
isProviderUser: this.organization.isProviderUser,
});
await this.organizationService.upsert(organizationData);
const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$));
await this.organizationService.upsert(organizationData, userId);
/*
Because subscribing to Secrets Manager automatically provides access to Secrets Manager for the

View File

@ -5,6 +5,7 @@ import { OrganizationService } from "@bitwarden/common/admin-console/abstraction
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { PolicyType } from "@bitwarden/common/admin-console/enums";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
@ -25,15 +26,34 @@ export class FreeFamiliesPolicyService {
constructor(
private policyService: PolicyService,
private organizationService: OrganizationService,
private accountService: AccountService,
private configService: ConfigService,
) {}
canManageSponsorships$ = this.accountService.activeAccount$.pipe(
switchMap((account) => {
if (account?.id) {
return this.organizationService.canManageSponsorships$(account?.id);
} else {
return of();
}
}),
);
organizations$ = this.accountService.activeAccount$.pipe(
switchMap((account) => {
if (account?.id) {
return this.organizationService.organizations$(account?.id);
} else {
return of();
}
}),
);
get showFreeFamilies$(): Observable<boolean> {
return this.isFreeFamilyFlagEnabled$.pipe(
switchMap((isFreeFamilyFlagEnabled) =>
isFreeFamilyFlagEnabled
? this.getFreeFamiliesVisibility$()
: this.organizationService.canManageSponsorships$,
isFreeFamilyFlagEnabled ? this.getFreeFamiliesVisibility$() : this.canManageSponsorships$,
),
);
}
@ -41,7 +61,7 @@ export class FreeFamiliesPolicyService {
private getFreeFamiliesVisibility$(): Observable<boolean> {
return combineLatest([
this.checkEnterpriseOrganizationsAndFetchPolicy(),
this.organizationService.canManageSponsorships$,
this.canManageSponsorships$,
]).pipe(
map(([orgStatus, canManageSponsorships]) =>
this.shouldShowFreeFamilyLink(orgStatus, canManageSponsorships),
@ -61,7 +81,7 @@ export class FreeFamiliesPolicyService {
}
checkEnterpriseOrganizationsAndFetchPolicy(): Observable<EnterpriseOrgStatus> {
return this.organizationService.organizations$.pipe(
return this.organizations$.pipe(
filter((organizations) => Array.isArray(organizations) && organizations.length > 0),
switchMap((organizations) => this.fetchEnterpriseOrganizationPolicy(organizations)),
);

View File

@ -19,6 +19,7 @@ import { PolicyService } from "@bitwarden/common/admin-console/abstractions/poli
import { PolicyType } from "@bitwarden/common/admin-console/enums";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { getUserId } from "@bitwarden/common/auth/services/account.service";
import { PlanSponsorshipType } from "@bitwarden/common/billing/enums";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
@ -90,11 +91,13 @@ export class SponsoredFamiliesComponent implements OnInit, OnDestroy {
FeatureFlag.DisableFreeFamiliesSponsorship,
);
const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$));
if (this.isFreeFamilyFlagEnabled) {
await this.preventAccessToFreeFamiliesPage();
this.availableSponsorshipOrgs$ = combineLatest([
this.organizationService.organizations$,
this.organizationService.organizations$(userId),
this.policyService.getAll$(PolicyType.FreeFamiliesSponsorshipPolicy),
]).pipe(
map(([organizations, policies]) =>
@ -111,9 +114,9 @@ export class SponsoredFamiliesComponent implements OnInit, OnDestroy {
),
);
} else {
this.availableSponsorshipOrgs$ = this.organizationService.organizations$.pipe(
map((orgs) => orgs.filter((o) => o.familySponsorshipAvailable)),
);
this.availableSponsorshipOrgs$ = this.organizationService
.organizations$(userId)
.pipe(map((orgs) => orgs.filter((o) => o.familySponsorshipAvailable)));
}
this.availableSponsorshipOrgs$.pipe(takeUntil(this._destroy)).subscribe((orgs) => {
@ -126,9 +129,9 @@ export class SponsoredFamiliesComponent implements OnInit, OnDestroy {
this.anyOrgsAvailable$ = this.availableSponsorshipOrgs$.pipe(map((orgs) => orgs.length > 0));
this.activeSponsorshipOrgs$ = this.organizationService.organizations$.pipe(
map((orgs) => orgs.filter((o) => o.familySponsorshipFriendlyName !== null)),
);
this.activeSponsorshipOrgs$ = this.organizationService
.organizations$(userId)
.pipe(map((orgs) => orgs.filter((o) => o.familySponsorshipFriendlyName !== null)));
this.anyActiveSponsorships$ = this.activeSponsorshipOrgs$.pipe(map((orgs) => orgs.length > 0));

View File

@ -6,7 +6,10 @@ import { FormControl, FormGroup, Validators } from "@angular/forms";
import { firstValueFrom, map } from "rxjs";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import {
getOrganizationById,
OrganizationService,
} from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { PaymentMethodType } from "@bitwarden/common/billing/enums";
import { BitPayInvoiceRequest } from "@bitwarden/common/billing/models/request/bit-pay-invoice.request";
@ -77,7 +80,14 @@ export class AddCreditDialogComponent implements OnInit {
this.creditAmount = "20.00";
}
this.ppButtonCustomField = "organization_id:" + this.organizationId;
const org = await this.organizationService.get(this.organizationId);
const userId = await firstValueFrom(
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
);
const org = await firstValueFrom(
this.organizationService
.organizations$(userId)
.pipe(getOrganizationById(this.organizationId)),
);
if (org != null) {
this.subject = org.name;
this.name = org.name;

View File

@ -4,12 +4,16 @@ import { Location } from "@angular/common";
import { Component, OnDestroy, OnInit } from "@angular/core";
import { FormBuilder, FormControl, Validators } from "@angular/forms";
import { ActivatedRoute, Router } from "@angular/router";
import { lastValueFrom } from "rxjs";
import { firstValueFrom, lastValueFrom, map } from "rxjs";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import {
getOrganizationById,
OrganizationService,
} from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { PaymentMethodType } from "@bitwarden/common/billing/enums";
import { BillingPaymentResponse } from "@bitwarden/common/billing/models/response/billing-payment.response";
import { OrganizationSubscriptionResponse } from "@bitwarden/common/billing/models/response/organization-subscription.response";
@ -73,6 +77,7 @@ export class PaymentMethodComponent implements OnInit, OnDestroy {
private toastService: ToastService,
private trialFlowService: TrialFlowService,
private organizationService: OrganizationService,
private accountService: AccountService,
protected syncService: SyncService,
) {
const state = this.router.getCurrentNavigation()?.extras?.state;
@ -117,7 +122,14 @@ export class PaymentMethodComponent implements OnInit, OnDestroy {
const organizationSubscriptionPromise = this.organizationApiService.getSubscription(
this.organizationId,
);
const organizationPromise = this.organizationService.get(this.organizationId);
const userId = await firstValueFrom(
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
);
const organizationPromise = await firstValueFrom(
this.organizationService
.organizations$(userId)
.pipe(getOrganizationById(this.organizationId)),
);
[this.billing, this.org, this.organization] = await Promise.all([
billingPromise,

View File

@ -3,11 +3,12 @@
import { CommonModule } from "@angular/common";
import { Component, EventEmitter, Input, Output } from "@angular/core";
import { ActivatedRoute } from "@angular/router";
import { combineLatest, map, Observable } from "rxjs";
import { combineLatest, map, Observable, switchMap } from "rxjs";
import { JslibModule } from "@bitwarden/angular/jslib.module";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import type { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions/billing-api.service.abstraction";
import { DialogService, NavigationModule } from "@bitwarden/components";
@ -20,11 +21,16 @@ import { TrialFlowService } from "./../../billing/services/trial-flow.service";
imports: [CommonModule, JslibModule, NavigationModule],
})
export class OrgSwitcherComponent {
protected organizations$: Observable<Organization[]> =
this.organizationService.organizations$.pipe(
protected organizations$: Observable<Organization[]> = this.accountService.activeAccount$.pipe(
switchMap((account) =>
this.organizationService
.organizations$(account?.id)
.pipe(
map((orgs) =>
orgs.filter((org) => this.filter(org)).sort((a, b) => a.name.localeCompare(b.name)),
),
),
),
);
protected activeOrganization$: Observable<Organization> = combineLatest([
@ -61,6 +67,7 @@ export class OrgSwitcherComponent {
private organizationService: OrganizationService,
private trialFlowService: TrialFlowService,
protected billingApiService: BillingApiServiceAbstraction,
private accountService: AccountService,
) {}
protected toggle(event?: MouseEvent) {

View File

@ -1,15 +1,17 @@
import { Component, Directive, importProvidersFrom, Input } from "@angular/core";
import { RouterModule } from "@angular/router";
import { applicationConfig, Meta, moduleMetadata, StoryObj } from "@storybook/angular";
import { BehaviorSubject, firstValueFrom } from "rxjs";
import { BehaviorSubject, firstValueFrom, Observable, of } from "rxjs";
import { I18nPipe } from "@bitwarden/angular/platform/pipes/i18n.pipe";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { ProviderService } from "@bitwarden/common/admin-console/abstractions/provider.service";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { Provider } from "@bitwarden/common/admin-console/models/domain/provider";
import { AccountService, Account } from "@bitwarden/common/auth/abstractions/account.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { SyncService } from "@bitwarden/common/platform/sync";
import { UserId } from "@bitwarden/common/types/guid";
import { LayoutComponent, NavigationModule } from "@bitwarden/components";
import { I18nMockService } from "@bitwarden/components/src/utils/i18n-mock.service";
@ -22,11 +24,14 @@ import { NavigationProductSwitcherComponent } from "./navigation-switcher.compon
})
class MockOrganizationService implements Partial<OrganizationService> {
private static _orgs = new BehaviorSubject<Organization[]>([]);
organizations$ = MockOrganizationService._orgs; // eslint-disable-line rxjs/no-exposed-subjects
organizations$(): Observable<Organization[]> {
return MockOrganizationService._orgs.asObservable();
}
@Input()
set mockOrgs(orgs: Organization[]) {
this.organizations$.next(orgs);
MockOrganizationService._orgs.next(orgs);
}
}
@ -52,6 +57,15 @@ class MockSyncService implements Partial<SyncService> {
}
}
class MockAccountService implements Partial<AccountService> {
activeAccount$?: Observable<Account> = of({
id: "test-user-id" as UserId,
name: "Test User 1",
email: "test@email.com",
emailVerified: true,
});
}
@Component({
selector: "story-layout",
template: `<ng-content></ng-content>`,
@ -86,6 +100,7 @@ export default {
imports: [NavigationModule, RouterModule, LayoutComponent],
providers: [
{ provide: OrganizationService, useClass: MockOrganizationService },
{ provide: AccountService, useClass: MockAccountService },
{ provide: ProviderService, useClass: MockProviderService },
{ provide: SyncService, useClass: MockSyncService },
ProductSwitcherService,

View File

@ -1,15 +1,17 @@
import { Component, Directive, importProvidersFrom, Input } from "@angular/core";
import { RouterModule } from "@angular/router";
import { applicationConfig, Meta, moduleMetadata, StoryObj } from "@storybook/angular";
import { BehaviorSubject, firstValueFrom } from "rxjs";
import { BehaviorSubject, firstValueFrom, Observable, of } from "rxjs";
import { JslibModule } from "@bitwarden/angular/jslib.module";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { ProviderService } from "@bitwarden/common/admin-console/abstractions/provider.service";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { Provider } from "@bitwarden/common/admin-console/models/domain/provider";
import { AccountService, Account } from "@bitwarden/common/auth/abstractions/account.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { SyncService } from "@bitwarden/common/platform/sync";
import { UserId } from "@bitwarden/common/types/guid";
import { IconButtonModule, LinkModule, MenuModule } from "@bitwarden/components";
import { I18nMockService } from "@bitwarden/components/src/utils/i18n-mock.service";
@ -22,11 +24,14 @@ import { ProductSwitcherService } from "./shared/product-switcher.service";
})
class MockOrganizationService implements Partial<OrganizationService> {
private static _orgs = new BehaviorSubject<Organization[]>([]);
organizations$ = MockOrganizationService._orgs; // eslint-disable-line rxjs/no-exposed-subjects
organizations$(): Observable<Organization[]> {
return MockOrganizationService._orgs.asObservable();
}
@Input()
set mockOrgs(orgs: Organization[]) {
this.organizations$.next(orgs);
MockOrganizationService._orgs.next(orgs);
}
}
@ -52,6 +57,15 @@ class MockSyncService implements Partial<SyncService> {
}
}
class MockAccountService implements Partial<AccountService> {
activeAccount$?: Observable<Account> = of({
id: "test-user-id" as UserId,
name: "Test User 1",
email: "test@email.com",
emailVerified: true,
});
}
@Component({
selector: "story-layout",
template: `<ng-content></ng-content>`,
@ -78,6 +92,8 @@ export default {
],
imports: [JslibModule, MenuModule, IconButtonModule, LinkModule, RouterModule],
providers: [
{ provide: AccountService, useClass: MockAccountService },
MockAccountService,
{ provide: OrganizationService, useClass: MockOrganizationService },
MockOrganizationService,
{ provide: ProviderService, useClass: MockProviderService },
@ -134,7 +150,9 @@ export default {
],
} as Meta<ProductSwitcherComponent>;
type Story = StoryObj<ProductSwitcherComponent & MockProviderService & MockOrganizationService>;
type Story = StoryObj<
ProductSwitcherComponent & MockProviderService & MockOrganizationService & MockAccountService
>;
const Template: Story = {
render: (args) => ({

View File

@ -10,7 +10,11 @@ import { OrganizationService } from "@bitwarden/common/admin-console/abstraction
import { ProviderService } from "@bitwarden/common/admin-console/abstractions/provider.service";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { Provider } from "@bitwarden/common/admin-console/models/domain/provider";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { SyncService } from "@bitwarden/common/platform/sync";
import { FakeAccountService, mockAccountServiceWith } from "@bitwarden/common/spec";
import { UserId } from "@bitwarden/common/types/guid";
import { ProductSwitcherService } from "./product-switcher.service";
@ -19,8 +23,10 @@ describe("ProductSwitcherService", () => {
let router: { url: string; events: Observable<unknown> };
let organizationService: MockProxy<OrganizationService>;
let providerService: MockProxy<ProviderService>;
let accountService: FakeAccountService;
let activeRouteParams = convertToParamMap({ organizationId: "1234" });
const getLastSync = jest.fn().mockResolvedValue(new Date("2024-05-14"));
const userId = Utils.newGuid() as UserId;
// The service is dependent on the SyncService, which is behind a `setTimeout`
// Most of the tests don't need to test this aspect so `advanceTimersByTime`
@ -36,10 +42,11 @@ describe("ProductSwitcherService", () => {
router = mock<Router>();
organizationService = mock<OrganizationService>();
providerService = mock<ProviderService>();
accountService = mockAccountServiceWith(userId);
router.url = "/";
router.events = of({});
organizationService.organizations$ = of([{}] as Organization[]);
organizationService.organizations$.mockReturnValue(of([{}] as Organization[]));
providerService.getAll.mockResolvedValue([] as Provider[]);
TestBed.configureTestingModule({
@ -47,6 +54,7 @@ describe("ProductSwitcherService", () => {
{ provide: Router, useValue: router },
{ provide: OrganizationService, useValue: organizationService },
{ provide: ProviderService, useValue: providerService },
{ provide: AccountService, useValue: accountService },
{
provide: ActivatedRoute,
useValue: {
@ -111,13 +119,15 @@ describe("ProductSwitcherService", () => {
});
it("is included in bento when there is an organization with SM", async () => {
organizationService.organizations$ = of([
organizationService.organizations$.mockReturnValue(
of([
{
id: "1234",
canAccessSecretsManager: true,
enabled: true,
},
] as Organization[]);
] as Organization[]),
);
initiateService();
@ -138,7 +148,9 @@ describe("ProductSwitcherService", () => {
});
it("includes Admin Console in bento when a user has access to it", async () => {
organizationService.organizations$ = of([{ id: "1234", isOwner: true }] as Organization[]);
organizationService.organizations$.mockReturnValue(
of([{ id: "1234", isOwner: true }] as Organization[]),
);
initiateService();
@ -194,7 +206,9 @@ describe("ProductSwitcherService", () => {
});
it("marks Admin Console as active", async () => {
organizationService.organizations$ = of([{ id: "1234", isOwner: true }] as Organization[]);
organizationService.organizations$.mockReturnValue(
of([{ id: "1234", isOwner: true }] as Organization[]),
);
activeRouteParams = convertToParamMap({ organizationId: "1" });
router.url = "/organizations/";
@ -225,7 +239,8 @@ describe("ProductSwitcherService", () => {
it("updates secrets manager path when the org id is found in the path", async () => {
router.url = "/sm/4243";
organizationService.organizations$ = of([
organizationService.organizations$.mockReturnValue(
of([
{
id: "23443234",
canAccessSecretsManager: true,
@ -238,7 +253,8 @@ describe("ProductSwitcherService", () => {
enabled: true,
name: "Org 32",
},
] as Organization[]);
] as Organization[]),
);
initiateService();
@ -253,10 +269,12 @@ describe("ProductSwitcherService", () => {
it("updates admin console path when the org id is found in the path", async () => {
router.url = "/organizations/111-22-33";
organizationService.organizations$ = of([
organizationService.organizations$.mockReturnValue(
of([
{ id: "111-22-33", isOwner: true, name: "Test Org" },
{ id: "4243", isOwner: true, name: "My Org" },
] as Organization[]);
] as Organization[]),
);
initiateService();

View File

@ -2,7 +2,16 @@
// @ts-strict-ignore
import { Injectable } from "@angular/core";
import { ActivatedRoute, NavigationEnd, NavigationStart, ParamMap, Router } from "@angular/router";
import { combineLatest, concatMap, filter, map, Observable, ReplaySubject, startWith } from "rxjs";
import {
combineLatest,
concatMap,
filter,
map,
Observable,
ReplaySubject,
startWith,
switchMap,
} from "rxjs";
import { I18nPipe } from "@bitwarden/angular/platform/pipes/i18n.pipe";
import {
@ -11,6 +20,7 @@ import {
} from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { ProviderService } from "@bitwarden/common/admin-console/abstractions/provider.service";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { SyncService } from "@bitwarden/common/platform/sync";
export type ProductSwitcherItem = {
@ -90,18 +100,20 @@ export class ProductSwitcherService {
private router: Router,
private i18n: I18nPipe,
private syncService: SyncService,
private accountService: AccountService,
) {
this.pollUntilSynced();
}
organizations$ = this.accountService.activeAccount$.pipe(
map((a) => a?.id),
switchMap((id) => this.organizationService.organizations$(id)),
);
products$: Observable<{
bento: ProductSwitcherItem[];
other: ProductSwitcherItem[];
}> = combineLatest([
this.organizationService.organizations$,
this.route.paramMap,
this.triggerProductUpdate$,
]).pipe(
}> = combineLatest([this.organizations$, this.route.paramMap, this.triggerProductUpdate$]).pipe(
map(([orgs, ...rest]): [Organization[], ParamMap, void] => {
return [
// Sort orgs by name to match the order within the sidebar

View File

@ -3,9 +3,12 @@
import { Component, OnInit } from "@angular/core";
import { FormControl, FormGroup, Validators } from "@angular/forms";
import { Router } from "@angular/router";
import { firstValueFrom } from "rxjs";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { getUserId } from "@bitwarden/common/auth/services/account.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { Guid } from "@bitwarden/common/types/guid";
import { NoItemsModule, SearchModule, ToastService } from "@bitwarden/components";
@ -39,10 +42,12 @@ export class RequestSMAccessComponent implements OnInit {
private organizationService: OrganizationService,
private smLandingApiService: SmLandingApiService,
private toastService: ToastService,
private accountService: AccountService,
) {}
async ngOnInit() {
this.organizations = (await this.organizationService.getAll())
const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$));
this.organizations = (await firstValueFrom(this.organizationService.organizations$(userId)))
.filter((e) => e.enabled)
.sort((a, b) => a.name.localeCompare(b.name));

View File

@ -1,9 +1,12 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { Component, OnInit } from "@angular/core";
import { firstValueFrom } from "rxjs";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { getUserId } from "@bitwarden/common/auth/services/account.service";
import { NoItemsModule, SearchModule } from "@bitwarden/components";
import { HeaderModule } from "../../layouts/header/header.module";
@ -22,10 +25,16 @@ export class SMLandingComponent implements OnInit {
showSecretsManagerInformation: boolean = true;
showGiveMembersAccessInstructions: boolean = false;
constructor(private organizationService: OrganizationService) {}
constructor(
private organizationService: OrganizationService,
private accountService: AccountService,
) {}
async ngOnInit() {
const enabledOrganizations = (await this.organizationService.getAll()).filter((e) => e.enabled);
const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$));
const enabledOrganizations = (
await firstValueFrom(this.organizationService.organizations$(userId))
).filter((e) => e.enabled);
if (enabledOrganizations.length > 0) {
this.handleEnabledOrganizations(enabledOrganizations);

View File

@ -1,11 +1,12 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { Directive, ViewChild, ViewContainerRef, OnDestroy } from "@angular/core";
import { BehaviorSubject, Observable, Subject, takeUntil } from "rxjs";
import { BehaviorSubject, Observable, Subject, switchMap, takeUntil } from "rxjs";
import { ModalService } from "@bitwarden/angular/services/modal.service";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { OrganizationId } from "@bitwarden/common/types/guid";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
@ -45,10 +46,13 @@ export class CipherReportComponent implements OnDestroy {
private modalService: ModalService,
protected passwordRepromptService: PasswordRepromptService,
protected organizationService: OrganizationService,
protected accountService: AccountService,
protected i18nService: I18nService,
private syncService: SyncService,
) {
this.organizations$ = this.organizationService.organizations$;
this.organizations$ = this.accountService.activeAccount$.pipe(
switchMap((account) => this.organizationService.organizations$(account?.id)),
);
this.organizations$.pipe(takeUntil(this.destroyed$)).subscribe((orgs) => {
this.organizations = orgs;
});

View File

@ -7,7 +7,11 @@ import { I18nPipe } from "@bitwarden/angular/platform/pipes/i18n.pipe";
import { ModalService } from "@bitwarden/angular/services/modal.service";
import { AuditService } from "@bitwarden/common/abstractions/audit.service";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { FakeAccountService, mockAccountServiceWith } from "@bitwarden/common/spec";
import { UserId } from "@bitwarden/common/types/guid";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
import { PasswordRepromptService } from "@bitwarden/vault";
@ -21,12 +25,15 @@ describe("ExposedPasswordsReportComponent", () => {
let auditService: MockProxy<AuditService>;
let organizationService: MockProxy<OrganizationService>;
let syncServiceMock: MockProxy<SyncService>;
let accountService: FakeAccountService;
const userId = Utils.newGuid() as UserId;
beforeEach(() => {
syncServiceMock = mock<SyncService>();
auditService = mock<AuditService>();
organizationService = mock<OrganizationService>();
organizationService.organizations$ = of([]);
organizationService.organizations$.mockReturnValue(of([]));
accountService = mockAccountServiceWith(userId);
// 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
TestBed.configureTestingModule({
@ -44,6 +51,10 @@ describe("ExposedPasswordsReportComponent", () => {
provide: OrganizationService,
useValue: organizationService,
},
{
provide: AccountService,
useValue: accountService,
},
{
provide: ModalService,
useValue: mock<ModalService>(),

View File

@ -3,6 +3,7 @@ import { Component, OnInit } from "@angular/core";
import { ModalService } from "@bitwarden/angular/services/modal.service";
import { AuditService } from "@bitwarden/common/abstractions/audit.service";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
@ -25,6 +26,7 @@ export class ExposedPasswordsReportComponent extends CipherReportComponent imple
protected cipherService: CipherService,
protected auditService: AuditService,
protected organizationService: OrganizationService,
accountService: AccountService,
modalService: ModalService,
passwordRepromptService: PasswordRepromptService,
i18nService: I18nService,
@ -35,6 +37,7 @@ export class ExposedPasswordsReportComponent extends CipherReportComponent imple
modalService,
passwordRepromptService,
organizationService,
accountService,
i18nService,
syncService,
);

View File

@ -6,8 +6,12 @@ import { of } from "rxjs";
import { I18nPipe } from "@bitwarden/angular/platform/pipes/i18n.pipe";
import { ModalService } from "@bitwarden/angular/services/modal.service";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { FakeAccountService, mockAccountServiceWith } from "@bitwarden/common/spec";
import { UserId } from "@bitwarden/common/types/guid";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
import { PasswordRepromptService } from "@bitwarden/vault";
@ -20,11 +24,14 @@ describe("InactiveTwoFactorReportComponent", () => {
let fixture: ComponentFixture<InactiveTwoFactorReportComponent>;
let organizationService: MockProxy<OrganizationService>;
let syncServiceMock: MockProxy<SyncService>;
let accountService: FakeAccountService;
const userId = Utils.newGuid() as UserId;
beforeEach(() => {
organizationService = mock<OrganizationService>();
organizationService.organizations$ = of([]);
organizationService.organizations$.mockReturnValue(of([]));
syncServiceMock = mock<SyncService>();
accountService = mockAccountServiceWith(userId);
// 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
TestBed.configureTestingModule({
@ -38,6 +45,10 @@ describe("InactiveTwoFactorReportComponent", () => {
provide: OrganizationService,
useValue: organizationService,
},
{
provide: AccountService,
useValue: accountService,
},
{
provide: ModalService,
useValue: mock<ModalService>(),

View File

@ -4,6 +4,7 @@ import { Component, OnInit } from "@angular/core";
import { ModalService } from "@bitwarden/angular/services/modal.service";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
@ -27,6 +28,7 @@ export class InactiveTwoFactorReportComponent extends CipherReportComponent impl
constructor(
protected cipherService: CipherService,
protected organizationService: OrganizationService,
accountService: AccountService,
modalService: ModalService,
private logService: LogService,
passwordRepromptService: PasswordRepromptService,
@ -38,6 +40,7 @@ export class InactiveTwoFactorReportComponent extends CipherReportComponent impl
modalService,
passwordRepromptService,
organizationService,
accountService,
i18nService,
syncService,
);

View File

@ -6,7 +6,11 @@ import { of } from "rxjs";
import { I18nPipe } from "@bitwarden/angular/platform/pipes/i18n.pipe";
import { ModalService } from "@bitwarden/angular/services/modal.service";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { FakeAccountService, mockAccountServiceWith } from "@bitwarden/common/spec";
import { UserId } from "@bitwarden/common/types/guid";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
import { PasswordRepromptService } from "@bitwarden/vault";
@ -19,11 +23,15 @@ describe("ReusedPasswordsReportComponent", () => {
let fixture: ComponentFixture<ReusedPasswordsReportComponent>;
let organizationService: MockProxy<OrganizationService>;
let syncServiceMock: MockProxy<SyncService>;
let accountService: FakeAccountService;
const userId = Utils.newGuid() as UserId;
beforeEach(() => {
organizationService = mock<OrganizationService>();
organizationService.organizations$ = of([]);
organizationService.organizations$.mockReturnValue(of([]));
syncServiceMock = mock<SyncService>();
accountService = mockAccountServiceWith(userId);
// 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
TestBed.configureTestingModule({
@ -37,6 +45,10 @@ describe("ReusedPasswordsReportComponent", () => {
provide: OrganizationService,
useValue: organizationService,
},
{
provide: AccountService,
useValue: accountService,
},
{
provide: ModalService,
useValue: mock<ModalService>(),

View File

@ -4,6 +4,7 @@ import { Component, OnInit } from "@angular/core";
import { ModalService } from "@bitwarden/angular/services/modal.service";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
@ -24,6 +25,7 @@ export class ReusedPasswordsReportComponent extends CipherReportComponent implem
constructor(
protected cipherService: CipherService,
protected organizationService: OrganizationService,
accountService: AccountService,
modalService: ModalService,
passwordRepromptService: PasswordRepromptService,
i18nService: I18nService,
@ -34,6 +36,7 @@ export class ReusedPasswordsReportComponent extends CipherReportComponent implem
modalService,
passwordRepromptService,
organizationService,
accountService,
i18nService,
syncService,
);

View File

@ -7,7 +7,11 @@ import { CollectionService } from "@bitwarden/admin-console/common";
import { I18nPipe } from "@bitwarden/angular/platform/pipes/i18n.pipe";
import { ModalService } from "@bitwarden/angular/services/modal.service";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { FakeAccountService, mockAccountServiceWith } from "@bitwarden/common/spec";
import { UserId } from "@bitwarden/common/types/guid";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
import { PasswordRepromptService } from "@bitwarden/vault";
@ -21,12 +25,15 @@ describe("UnsecuredWebsitesReportComponent", () => {
let organizationService: MockProxy<OrganizationService>;
let syncServiceMock: MockProxy<SyncService>;
let collectionService: MockProxy<CollectionService>;
let accountService: FakeAccountService;
const userId = Utils.newGuid() as UserId;
beforeEach(() => {
organizationService = mock<OrganizationService>();
organizationService.organizations$ = of([]);
organizationService.organizations$.mockReturnValue(of([]));
syncServiceMock = mock<SyncService>();
collectionService = mock<CollectionService>();
accountService = mockAccountServiceWith(userId);
// 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
TestBed.configureTestingModule({
@ -40,6 +47,10 @@ describe("UnsecuredWebsitesReportComponent", () => {
provide: OrganizationService,
useValue: organizationService,
},
{
provide: AccountService,
useValue: accountService,
},
{
provide: ModalService,
useValue: mock<ModalService>(),

View File

@ -3,6 +3,7 @@ import { Component, OnInit } from "@angular/core";
import { CollectionService, Collection } from "@bitwarden/admin-console/common";
import { ModalService } from "@bitwarden/angular/services/modal.service";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
@ -22,6 +23,7 @@ export class UnsecuredWebsitesReportComponent extends CipherReportComponent impl
constructor(
protected cipherService: CipherService,
protected organizationService: OrganizationService,
accountService: AccountService,
modalService: ModalService,
passwordRepromptService: PasswordRepromptService,
i18nService: I18nService,
@ -33,6 +35,7 @@ export class UnsecuredWebsitesReportComponent extends CipherReportComponent impl
modalService,
passwordRepromptService,
organizationService,
accountService,
i18nService,
syncService,
);

View File

@ -6,8 +6,12 @@ import { of } from "rxjs";
import { I18nPipe } from "@bitwarden/angular/platform/pipes/i18n.pipe";
import { ModalService } from "@bitwarden/angular/services/modal.service";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { FakeAccountService, mockAccountServiceWith } from "@bitwarden/common/spec";
import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/password-strength";
import { UserId } from "@bitwarden/common/types/guid";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
import { PasswordRepromptService } from "@bitwarden/vault";
@ -21,12 +25,15 @@ describe("WeakPasswordsReportComponent", () => {
let passwordStrengthService: MockProxy<PasswordStrengthServiceAbstraction>;
let organizationService: MockProxy<OrganizationService>;
let syncServiceMock: MockProxy<SyncService>;
let accountService: FakeAccountService;
const userId = Utils.newGuid() as UserId;
beforeEach(() => {
syncServiceMock = mock<SyncService>();
passwordStrengthService = mock<PasswordStrengthServiceAbstraction>();
organizationService = mock<OrganizationService>();
organizationService.organizations$ = of([]);
organizationService.organizations$.mockReturnValue(of([]));
accountService = mockAccountServiceWith(userId);
// 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
TestBed.configureTestingModule({
@ -44,6 +51,10 @@ describe("WeakPasswordsReportComponent", () => {
provide: OrganizationService,
useValue: organizationService,
},
{
provide: AccountService,
useValue: accountService,
},
{
provide: ModalService,
useValue: mock<ModalService>(),

View File

@ -4,6 +4,7 @@ import { Component, OnInit } from "@angular/core";
import { ModalService } from "@bitwarden/angular/services/modal.service";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/password-strength";
@ -32,6 +33,7 @@ export class WeakPasswordsReportComponent extends CipherReportComponent implemen
protected cipherService: CipherService,
protected passwordStrengthService: PasswordStrengthServiceAbstraction,
protected organizationService: OrganizationService,
protected accountService: AccountService,
modalService: ModalService,
passwordRepromptService: PasswordRepromptService,
i18nService: I18nService,
@ -42,6 +44,7 @@ export class WeakPasswordsReportComponent extends CipherReportComponent implemen
modalService,
passwordRepromptService,
organizationService,
accountService,
i18nService,
syncService,
);

View File

@ -5,6 +5,7 @@ import { ChangeDetectorRef, Component, Inject, OnDestroy, OnInit } from "@angula
import { AbstractControl, FormBuilder, Validators } from "@angular/forms";
import {
combineLatest,
firstValueFrom,
map,
Observable,
of,
@ -24,8 +25,13 @@ import {
CollectionResponse,
CollectionView,
} from "@bitwarden/admin-console/common";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import {
getOrganizationById,
OrganizationService,
} from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { getUserId } from "@bitwarden/common/auth/services/account.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
@ -110,6 +116,7 @@ export class CollectionDialogComponent implements OnInit, OnDestroy {
private organizationUserApiService: OrganizationUserApiService,
private dialogService: DialogService,
private changeDetectorRef: ChangeDetectorRef,
private accountService: AccountService,
private toastService: ToastService,
) {
this.tabIndex = params.initialTab ?? CollectionDialogTabType.Info;
@ -122,7 +129,10 @@ export class CollectionDialogComponent implements OnInit, OnDestroy {
this.formGroup.controls.selectedOrg.valueChanges
.pipe(takeUntil(this.destroy$))
.subscribe((id) => this.loadOrg(id));
this.organizations$ = this.organizationService.organizations$.pipe(
const userId = await firstValueFrom(
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
);
this.organizations$ = this.organizationService.organizations$(userId).pipe(
first(),
map((orgs) =>
orgs
@ -140,8 +150,10 @@ export class CollectionDialogComponent implements OnInit, OnDestroy {
}
async loadOrg(orgId: string) {
const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$));
const organization$ = this.organizationService
.get$(orgId)
.organizations$(userId)
.pipe(getOrganizationById(orgId))
.pipe(shareReplay({ refCount: true, bufferSize: 1 }));
const groups$ = organization$.pipe(
switchMap((organization) => {

View File

@ -49,7 +49,7 @@ describe("AddEditComponentV2", () => {
} as Organization;
organizationService = mock<OrganizationService>();
organizationService.organizations$ = of([mockOrganization]);
organizationService.organizations$.mockReturnValue(of([mockOrganization]));
policyService = mock<PolicyService>();
policyService.policyAppliesToActiveUser$.mockImplementation((policyType: PolicyType) =>

View File

@ -8,6 +8,7 @@ import { CollectionService, CollectionView } from "@bitwarden/admin-console/comm
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { getUserId } from "@bitwarden/common/auth/services/account.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { Checkable, isChecked } from "@bitwarden/common/types/checkable";
@ -76,7 +77,8 @@ export class BulkShareDialogComponent implements OnInit, OnDestroy {
this.nonShareableCount = this.ciphers.length - this.shareableCiphers.length;
const allCollections = await this.collectionService.getAllDecrypted();
this.writeableCollections = allCollections.filter((c) => !c.readOnly);
this.organizations = await this.organizationService.getAll();
const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$));
this.organizations = await firstValueFrom(this.organizationService.organizations$(userId));
if (this.organizationId == null && this.organizations.length > 0) {
this.organizationId = this.organizations[0].id;
}

View File

@ -1,7 +1,16 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { Component, Inject, OnDestroy, OnInit } from "@angular/core";
import { combineLatest, map, Observable, of, Subject, switchMap, takeUntil } from "rxjs";
import {
combineLatest,
firstValueFrom,
map,
Observable,
of,
Subject,
switchMap,
takeUntil,
} from "rxjs";
import {
OrganizationUserApiService,
@ -15,7 +24,9 @@ import { PolicyService } from "@bitwarden/common/admin-console/abstractions/poli
import { PolicyType } from "@bitwarden/common/admin-console/enums";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { Policy } from "@bitwarden/common/admin-console/models/domain/policy";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
import { getUserId } from "@bitwarden/common/auth/services/account.service";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
@ -60,6 +71,7 @@ export class OrganizationOptionsComponent implements OnInit, OnDestroy {
private toastService: ToastService,
private configService: ConfigService,
private organizationService: OrganizationService,
private accountService: AccountService,
) {}
async ngOnInit() {
@ -67,12 +79,15 @@ export class OrganizationOptionsComponent implements OnInit, OnDestroy {
map((policies) => policies.filter((policy) => policy.type === PolicyType.ResetPassword)),
);
const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$));
const managingOrg$ = this.configService
.getFeatureFlag$(FeatureFlag.AccountDeprovisioning)
.pipe(
switchMap((isAccountDeprovisioningEnabled) =>
isAccountDeprovisioningEnabled
? this.organizationService.organizations$.pipe(
? this.organizationService
.organizations$(userId)
.pipe(
map((organizations) =>
organizations.find((o) => o.userIsManagedByOrganization === true),
),

View File

@ -62,7 +62,7 @@ describe("vault filter service", () => {
personalOwnershipPolicy = new ReplaySubject<boolean>(1);
singleOrgPolicy = new ReplaySubject<boolean>(1);
organizationService.memberOrganizations$ = organizations;
organizationService.memberOrganizations$.mockReturnValue(organizations);
folderService.folderViews$.mockReturnValue(folderViews);
collectionService.decryptedCollections$ = collectionViews;
policyService.policyAppliesToActiveUser$

View File

@ -48,8 +48,12 @@ const NestingDelimiter = "/";
export class VaultFilterService implements VaultFilterServiceAbstraction {
private activeUserId$ = this.accountService.activeAccount$.pipe(map((a) => a?.id));
memberOrganizations$ = this.activeUserId$.pipe(
switchMap((id) => this.organizationService.memberOrganizations$(id)),
);
organizationTree$: Observable<TreeNode<OrganizationFilter>> = combineLatest([
this.organizationService.memberOrganizations$,
this.memberOrganizations$,
this.policyService.policyAppliesToActiveUser$(PolicyType.SingleOrg),
this.policyService.policyAppliesToActiveUser$(PolicyType.PersonalOwnership),
]).pipe(

View File

@ -47,7 +47,10 @@ import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
import { SearchService } from "@bitwarden/common/abstractions/search.service";
import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import {
getOrganizationById,
OrganizationService,
} from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { OrganizationBillingServiceAbstraction } from "@bitwarden/common/billing/abstractions";
@ -193,7 +196,11 @@ export class VaultComponent implements OnInit, OnDestroy {
private hasSubscription$ = new BehaviorSubject<boolean>(false);
private vaultItemDialogRef?: DialogRef<VaultItemDialogResult> | undefined;
private readonly unpaidSubscriptionDialog$ = this.organizationService.organizations$.pipe(
private organizations$ = this.accountService.activeAccount$
.pipe(map((a) => a?.id))
.pipe(switchMap((id) => this.organizationService.organizations$(id)));
private readonly unpaidSubscriptionDialog$ = this.organizations$.pipe(
filter((organizations) => organizations.length === 1),
map(([organization]) => organization),
switchMap((organization) =>
@ -212,9 +219,8 @@ export class VaultComponent implements OnInit, OnDestroy {
),
),
);
protected organizationsPaymentStatus$: Observable<FreeTrial[]> = combineLatest([
this.organizationService.organizations$.pipe(
this.organizations$.pipe(
map(
(organizations) =>
organizations?.filter((org) => org.isOwner && org.canViewBillingHistory) ?? [],
@ -501,7 +507,7 @@ export class VaultComponent implements OnInit, OnDestroy {
filter$,
this.billingAccountProfileStateService.hasPremiumFromAnySource$(this.activeUserId),
allCollections$,
this.organizationService.organizations$,
this.organizations$,
ciphers$,
collections$,
selectedCollection$,
@ -646,7 +652,9 @@ export class VaultComponent implements OnInit, OnDestroy {
this.messagingService.send("premiumRequired");
return;
} else if (cipher.organizationId != null) {
const org = await this.organizationService.get(cipher.organizationId);
const org = await firstValueFrom(
this.organizations$.pipe(getOrganizationById(cipher.organizationId)),
);
if (org != null && (org.maxStorageGb == null || org.maxStorageGb === 0)) {
this.messagingService.send("upgradeOrganization", {
organizationId: cipher.organizationId,
@ -971,7 +979,9 @@ export class VaultComponent implements OnInit, OnDestroy {
}
async deleteCollection(collection: CollectionView): Promise<void> {
const organization = await this.organizationService.get(collection.organizationId);
const organization = await firstValueFrom(
this.organizations$.pipe(getOrganizationById(collection.organizationId)),
);
if (!collection.canDelete(organization)) {
this.showMissingPermissionsError();
return;
@ -1136,9 +1146,7 @@ export class VaultComponent implements OnInit, OnDestroy {
.filter((i) => i.cipher === undefined)
.map((i) => i.collection.organizationId);
const orgs = await firstValueFrom(
this.organizationService.organizations$.pipe(
map((orgs) => orgs.filter((o) => orgIds.includes(o.id))),
),
this.organizations$.pipe(map((orgs) => orgs.filter((o) => orgIds.includes(o.id)))),
);
await this.bulkDelete(ciphers, collections, orgs);
}

View File

@ -1,6 +1,7 @@
import { DIALOG_DATA, DialogRef } from "@angular/cdk/dialog";
import { ComponentFixture, TestBed } from "@angular/core/testing";
import { mock } from "jest-mock-extended";
import { of } from "rxjs";
import { CollectionService } from "@bitwarden/admin-console/common";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
@ -11,7 +12,8 @@ import { ConfigService } from "@bitwarden/common/platform/abstractions/config/co
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
import { mockAccountServiceWith } from "@bitwarden/common/spec";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { FakeAccountService, mockAccountServiceWith } from "@bitwarden/common/spec";
import { UserId } from "@bitwarden/common/types/guid";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction";
@ -41,6 +43,8 @@ describe("ViewComponent", () => {
const mockParams: ViewCipherDialogParams = {
cipher: mockCipher,
};
const userId = Utils.newGuid() as UserId;
const accountService: FakeAccountService = mockAccountServiceWith(userId);
beforeEach(async () => {
await TestBed.configureTestingModule({
@ -53,10 +57,14 @@ describe("ViewComponent", () => {
{ provide: CipherService, useValue: mock<CipherService>() },
{ provide: ToastService, useValue: mock<ToastService>() },
{ provide: MessagingService, useValue: mock<MessagingService>() },
{
provide: AccountService,
useValue: accountService,
},
{ provide: LogService, useValue: mock<LogService>() },
{
provide: OrganizationService,
useValue: { get: jest.fn().mockResolvedValue(mockOrganization) },
useValue: { organizations$: jest.fn().mockReturnValue(of([mockOrganization])) },
},
{ provide: CollectionService, useValue: mock<CollectionService>() },
{ provide: FolderService, useValue: mock<FolderService>() },

View File

@ -3,11 +3,13 @@
import { DIALOG_DATA, DialogConfig, DialogRef } from "@angular/cdk/dialog";
import { CommonModule } from "@angular/common";
import { Component, EventEmitter, Inject, OnInit } from "@angular/core";
import { Observable } from "rxjs";
import { Observable, firstValueFrom, map } from "rxjs";
import { CollectionView } from "@bitwarden/admin-console/common";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { getUserId } from "@bitwarden/common/auth/services/account.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
@ -94,6 +96,7 @@ export class ViewComponent implements OnInit {
private toastService: ToastService,
private organizationService: OrganizationService,
private cipherAuthorizationService: CipherAuthorizationService,
private accountService: AccountService,
) {}
/**
@ -103,8 +106,17 @@ export class ViewComponent implements OnInit {
this.cipher = this.params.cipher;
this.collections = this.params.collections;
this.cipherTypeString = this.getCipherViewTypeString();
const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$));
if (this.cipher.organizationId) {
this.organization = await this.organizationService.get(this.cipher.organizationId);
this.organization = await firstValueFrom(
this.organizationService
.organizations$(userId)
.pipe(
map((organizations) => organizations.find((o) => o.id === this.cipher.organizationId)),
),
);
}
this.canDeleteCipher$ = this.cipherAuthorizationService.canDeleteCipher$(this.cipher, [

View File

@ -10,8 +10,12 @@ import {
OrganizationUserApiService,
CollectionView,
} from "@bitwarden/admin-console/common";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import {
getOrganizationById,
OrganizationService,
} from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { DialogService, ToastService } from "@bitwarden/components";
@ -63,6 +67,7 @@ export class BulkCollectionsDialogComponent implements OnDestroy {
private dialogRef: DialogRef<BulkCollectionsDialogResult>,
private formBuilder: FormBuilder,
private organizationService: OrganizationService,
private accountService: AccountService,
private groupService: GroupApiService,
private organizationUserApiService: OrganizationUserApiService,
private platformUtilsService: PlatformUtilsService,
@ -71,7 +76,13 @@ export class BulkCollectionsDialogComponent implements OnDestroy {
private toastService: ToastService,
) {
this.numCollections = this.params.collections.length;
const organization$ = this.organizationService.get$(this.params.organizationId);
const organization$ = this.accountService.activeAccount$.pipe(
switchMap((account) =>
this.organizationService
.organizations$(account?.id)
.pipe(getOrganizationById(this.params.organizationId)),
),
);
const groups$ = organization$.pipe(
switchMap((organization) => {
if (!organization.useGroups) {

View File

@ -7,9 +7,11 @@ import { OrganizationService } from "@bitwarden/common/admin-console/abstraction
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { OrganizationUserStatusType } from "@bitwarden/common/admin-console/enums";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { CipherId } from "@bitwarden/common/types/guid";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { Account } from "../../../../../../../libs/importer/src/importers/lastpass/access/models";
import { RoutedVaultFilterService } from "../../individual-vault/vault-filter/services/routed-vault-filter.service";
import { AdminConsoleCipherFormConfigService } from "./admin-console-cipher-form-config.service";
@ -50,8 +52,7 @@ describe("AdminConsoleCipherFormConfigService", () => {
readOnly: false,
} as CollectionAdminView;
const organization$ = new BehaviorSubject<Organization>(testOrg as Organization);
const organizations$ = new BehaviorSubject<Organization[]>([testOrg, testOrg2] as Organization[]);
const orgs$ = new BehaviorSubject<Organization[]>([testOrg, testOrg2] as Organization[]);
const getCipherAdmin = jest.fn().mockResolvedValue(null);
const getCipher = jest.fn().mockResolvedValue(null);
@ -65,7 +66,7 @@ describe("AdminConsoleCipherFormConfigService", () => {
TestBed.configureTestingModule({
providers: [
AdminConsoleCipherFormConfigService,
{ provide: OrganizationService, useValue: { get$: () => organization$, organizations$ } },
{ provide: OrganizationService, useValue: { organizations$: () => orgs$ } },
{
provide: CollectionAdminService,
useValue: { getAll: () => Promise.resolve([collection, collection2]) },
@ -80,6 +81,10 @@ describe("AdminConsoleCipherFormConfigService", () => {
},
{ provide: ApiService, useValue: { getCipherAdmin } },
{ provide: CipherService, useValue: { get: getCipher } },
{
provide: AccountService,
useValue: { activeAccount$: new BehaviorSubject<Account>(new Account()) },
},
],
});
adminConsoleConfigService = TestBed.inject(AdminConsoleCipherFormConfigService);

View File

@ -9,6 +9,7 @@ import { OrganizationService } from "@bitwarden/common/admin-console/abstraction
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { OrganizationUserStatusType, PolicyType } from "@bitwarden/common/admin-console/enums";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { CipherId } from "@bitwarden/common/types/guid";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { CipherType } from "@bitwarden/common/vault/enums";
@ -31,6 +32,7 @@ export class AdminConsoleCipherFormConfigService implements CipherFormConfigServ
private collectionAdminService: CollectionAdminService = inject(CollectionAdminService);
private cipherService: CipherService = inject(CipherService);
private apiService: ApiService = inject(ApiService);
private accountService: AccountService = inject(AccountService);
private allowPersonalOwnership$ = this.policyService
.policyAppliesToActiveUser$(PolicyType.PersonalOwnership)
@ -41,12 +43,16 @@ export class AdminConsoleCipherFormConfigService implements CipherFormConfigServ
filter((filter) => filter !== undefined),
);
private allOrganizations$ = this.organizationService.organizations$.pipe(
private allOrganizations$ = this.accountService.activeAccount$.pipe(
switchMap((account) =>
this.organizationService.organizations$(account?.id).pipe(
map((orgs) => {
return orgs.filter(
(o) => o.isMember && o.enabled && o.status === OrganizationUserStatusType.Confirmed,
);
}),
),
),
);
private organization$ = combineLatest([this.allOrganizations$, this.organizationId$]).pipe(

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