mirror of
https://github.com/bitwarden/browser
synced 2025-12-12 14:23:32 +00:00
[PM-24146] Remove stateProvider.activeUserId from ProviderService (#16258)
* Refactor provider service calls to include userId parameter - Updated multiple components and services to pass userId when fetching provider data. - Adjusted the ProviderService interface to require userId for get, get$, and getAll methods. - Ensured consistent handling of userId across various components, enhancing data retrieval based on active user context. * Remove deprecated type safety comments and use the getById utility for fetching providers. * Update ProviderService methods to return undefined for non-existent providers - Modified the return types of get$ and get methods in ProviderService to allow for undefined values, enhancing type safety. - Adjusted the providers$ method to return only defined Provider arrays, ensuring consistent handling of provider data. * Enhance provider permissions guard tests to include userId parameter - Updated test cases in provider-permissions.guard.spec.ts to pass userId when calling ProviderService methods. - Mocked AccountService to provide active account details for improved test coverage. - Ensured consistent handling of userId across all relevant test scenarios. * remove promise based api's from provider service, continue refactor * cleanup observable logic * cleanup --------- Co-authored-by: Brandon <btreston@bitwarden.com>
This commit is contained in:
@@ -121,8 +121,13 @@ export class OrganizationLayoutComponent implements OnInit {
|
|||||||
switchMap((userId) => this.policyService.policyAppliesToUser$(PolicyType.SingleOrg, userId)),
|
switchMap((userId) => this.policyService.policyAppliesToUser$(PolicyType.SingleOrg, userId)),
|
||||||
);
|
);
|
||||||
|
|
||||||
const provider$ = this.organization$.pipe(
|
const provider$ = combineLatest([
|
||||||
switchMap((organization) => this.providerService.get$(organization.providerId)),
|
this.organization$,
|
||||||
|
this.accountService.activeAccount$.pipe(getUserId),
|
||||||
|
]).pipe(
|
||||||
|
switchMap(([organization, userId]) =>
|
||||||
|
this.providerService.get$(organization.providerId, userId),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
this.organizationIsUnmanaged$ = combineLatest([this.organization$, provider$]).pipe(
|
this.organizationIsUnmanaged$ = combineLatest([this.organization$, provider$]).pipe(
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
// @ts-strict-ignore
|
// @ts-strict-ignore
|
||||||
import { Component, OnDestroy, OnInit } from "@angular/core";
|
import { Component, OnDestroy, OnInit } from "@angular/core";
|
||||||
import { ActivatedRoute } from "@angular/router";
|
import { ActivatedRoute } from "@angular/router";
|
||||||
import { concatMap, firstValueFrom, lastValueFrom, takeUntil } from "rxjs";
|
import { concatMap, filter, firstValueFrom, lastValueFrom, map, switchMap, takeUntil } from "rxjs";
|
||||||
|
|
||||||
import { OrganizationUserApiService } from "@bitwarden/admin-console/common";
|
import { OrganizationUserApiService } from "@bitwarden/admin-console/common";
|
||||||
import { UserNamePipe } from "@bitwarden/angular/pipes/user-name.pipe";
|
import { UserNamePipe } from "@bitwarden/angular/pipes/user-name.pipe";
|
||||||
@@ -136,22 +136,24 @@ export class EventsComponent extends BaseEventsComponent implements OnInit, OnDe
|
|||||||
|
|
||||||
if (this.organization.providerId != null) {
|
if (this.organization.providerId != null) {
|
||||||
try {
|
try {
|
||||||
const provider = await this.providerService.get(this.organization.providerId);
|
await firstValueFrom(
|
||||||
if (
|
this.accountService.activeAccount$.pipe(
|
||||||
provider != null &&
|
getUserId,
|
||||||
(await this.providerService.get(this.organization.providerId)).canManageUsers
|
switchMap((userId) => this.providerService.get$(this.organization.providerId, userId)),
|
||||||
) {
|
map((provider) => provider != null && provider.canManageUsers),
|
||||||
const providerUsersResponse = await this.apiService.getProviderUsers(
|
filter((result) => result),
|
||||||
this.organization.providerId,
|
switchMap(() => this.apiService.getProviderUsers(this.organization.id)),
|
||||||
);
|
map((providerUsersResponse) =>
|
||||||
providerUsersResponse.data.forEach((u) => {
|
providerUsersResponse.data.forEach((u) => {
|
||||||
const name = this.userNamePipe.transform(u);
|
const name = this.userNamePipe.transform(u);
|
||||||
this.orgUsersUserIdMap.set(u.userId, {
|
this.orgUsersUserIdMap.set(u.userId, {
|
||||||
name: `${name} (${this.organization.providerName})`,
|
name: `${name} (${this.organization.providerName})`,
|
||||||
email: u.email,
|
email: u.email,
|
||||||
});
|
});
|
||||||
});
|
}),
|
||||||
}
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.logService.warning(e);
|
this.logService.warning(e);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ export const organizationIsUnmanaged: CanActivateFn = async (route: ActivatedRou
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const provider = await providerService.get(organization.providerId);
|
const provider = await firstValueFrom(providerService.get$(organization.providerId, userId));
|
||||||
|
|
||||||
if (!provider) {
|
if (!provider) {
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Component, Directive, importProvidersFrom, Input } from "@angular/core";
|
import { Component, Directive, importProvidersFrom, Input } from "@angular/core";
|
||||||
import { RouterModule } from "@angular/router";
|
import { RouterModule } from "@angular/router";
|
||||||
import { applicationConfig, Meta, moduleMetadata, StoryObj } from "@storybook/angular";
|
import { applicationConfig, Meta, moduleMetadata, StoryObj } from "@storybook/angular";
|
||||||
import { BehaviorSubject, firstValueFrom, Observable, of } from "rxjs";
|
import { BehaviorSubject, Observable, of } from "rxjs";
|
||||||
|
|
||||||
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
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 { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
||||||
@@ -47,8 +47,8 @@ class MockOrganizationService implements Partial<OrganizationService> {
|
|||||||
class MockProviderService implements Partial<ProviderService> {
|
class MockProviderService implements Partial<ProviderService> {
|
||||||
private static _providers = new BehaviorSubject<Provider[]>([]);
|
private static _providers = new BehaviorSubject<Provider[]>([]);
|
||||||
|
|
||||||
async getAll() {
|
providers$() {
|
||||||
return await firstValueFrom(MockProviderService._providers);
|
return MockProviderService._providers.asObservable();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Input()
|
@Input()
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Component, Directive, importProvidersFrom, Input } from "@angular/core";
|
import { Component, Directive, importProvidersFrom, Input } from "@angular/core";
|
||||||
import { RouterModule } from "@angular/router";
|
import { RouterModule } from "@angular/router";
|
||||||
import { applicationConfig, Meta, moduleMetadata, StoryObj } from "@storybook/angular";
|
import { applicationConfig, Meta, moduleMetadata, StoryObj } from "@storybook/angular";
|
||||||
import { BehaviorSubject, firstValueFrom, Observable, of } from "rxjs";
|
import { BehaviorSubject, Observable, of } from "rxjs";
|
||||||
|
|
||||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||||
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||||
@@ -47,8 +47,8 @@ class MockOrganizationService implements Partial<OrganizationService> {
|
|||||||
class MockProviderService implements Partial<ProviderService> {
|
class MockProviderService implements Partial<ProviderService> {
|
||||||
private static _providers = new BehaviorSubject<Provider[]>([]);
|
private static _providers = new BehaviorSubject<Provider[]>([]);
|
||||||
|
|
||||||
async getAll() {
|
providers$() {
|
||||||
return await firstValueFrom(MockProviderService._providers);
|
return MockProviderService._providers.asObservable();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Input()
|
@Input()
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ describe("ProductSwitcherService", () => {
|
|||||||
router.url = "/";
|
router.url = "/";
|
||||||
router.events = of({});
|
router.events = of({});
|
||||||
organizationService.organizations$.mockReturnValue(of([{}] as Organization[]));
|
organizationService.organizations$.mockReturnValue(of([{}] as Organization[]));
|
||||||
providerService.getAll.mockResolvedValue([] as Provider[]);
|
providerService.providers$.mockReturnValue(of([]) as Observable<Provider[]>);
|
||||||
platformUtilsService.isSelfHost.mockReturnValue(false);
|
platformUtilsService.isSelfHost.mockReturnValue(false);
|
||||||
|
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
@@ -212,7 +212,7 @@ describe("ProductSwitcherService", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("is included when there are providers", async () => {
|
it("is included when there are providers", async () => {
|
||||||
providerService.getAll.mockResolvedValue([{ id: "67899" }] as Provider[]);
|
providerService.providers$.mockReturnValue(of([{ id: "67899" }]) as Observable<Provider[]>);
|
||||||
|
|
||||||
initiateService();
|
initiateService();
|
||||||
|
|
||||||
@@ -263,7 +263,7 @@ describe("ProductSwitcherService", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("marks Provider Portal as active", async () => {
|
it("marks Provider Portal as active", async () => {
|
||||||
providerService.getAll.mockResolvedValue([{ id: "67899" }] as Provider[]);
|
providerService.providers$.mockReturnValue(of([{ id: "67899" }]) as Observable<Provider[]>);
|
||||||
router.url = "/providers/";
|
router.url = "/providers/";
|
||||||
|
|
||||||
initiateService();
|
initiateService();
|
||||||
|
|||||||
@@ -2,17 +2,7 @@
|
|||||||
// @ts-strict-ignore
|
// @ts-strict-ignore
|
||||||
import { Injectable } from "@angular/core";
|
import { Injectable } from "@angular/core";
|
||||||
import { ActivatedRoute, NavigationEnd, NavigationStart, ParamMap, Router } from "@angular/router";
|
import { ActivatedRoute, NavigationEnd, NavigationStart, ParamMap, Router } from "@angular/router";
|
||||||
import {
|
import { combineLatest, filter, map, Observable, ReplaySubject, startWith, switchMap } from "rxjs";
|
||||||
combineLatest,
|
|
||||||
concatMap,
|
|
||||||
filter,
|
|
||||||
firstValueFrom,
|
|
||||||
map,
|
|
||||||
Observable,
|
|
||||||
ReplaySubject,
|
|
||||||
startWith,
|
|
||||||
switchMap,
|
|
||||||
} from "rxjs";
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
canAccessOrgAdmin,
|
canAccessOrgAdmin,
|
||||||
@@ -22,6 +12,7 @@ import { PolicyService } from "@bitwarden/common/admin-console/abstractions/poli
|
|||||||
import { ProviderService } from "@bitwarden/common/admin-console/abstractions/provider.service";
|
import { ProviderService } from "@bitwarden/common/admin-console/abstractions/provider.service";
|
||||||
import { PolicyType, ProviderType } from "@bitwarden/common/admin-console/enums";
|
import { PolicyType, ProviderType } from "@bitwarden/common/admin-console/enums";
|
||||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
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 { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||||
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
@@ -117,18 +108,37 @@ export class ProductSwitcherService {
|
|||||||
switchMap((id) => this.organizationService.organizations$(id)),
|
switchMap((id) => this.organizationService.organizations$(id)),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
providers$ = this.accountService.activeAccount$.pipe(
|
||||||
|
getUserId,
|
||||||
|
switchMap((id) => this.providerService.providers$(id)),
|
||||||
|
);
|
||||||
|
|
||||||
|
userHasSingleOrgPolicy$ = this.accountService.activeAccount$.pipe(
|
||||||
|
getUserId,
|
||||||
|
switchMap((userId) => this.policyService.policyAppliesToUser$(PolicyType.SingleOrg, userId)),
|
||||||
|
);
|
||||||
|
|
||||||
products$: Observable<{
|
products$: Observable<{
|
||||||
bento: ProductSwitcherItem[];
|
bento: ProductSwitcherItem[];
|
||||||
other: ProductSwitcherItem[];
|
other: ProductSwitcherItem[];
|
||||||
}> = combineLatest([this.organizations$, this.route.paramMap, this.triggerProductUpdate$]).pipe(
|
}> = combineLatest([
|
||||||
map(([orgs, ...rest]): [Organization[], ParamMap, void] => {
|
this.organizations$,
|
||||||
return [
|
this.providers$,
|
||||||
|
this.userHasSingleOrgPolicy$,
|
||||||
|
this.route.paramMap,
|
||||||
|
this.triggerProductUpdate$,
|
||||||
|
]).pipe(
|
||||||
|
map(
|
||||||
|
([orgs, providers, userHasSingleOrgPolicy, paramMap]: [
|
||||||
|
Organization[],
|
||||||
|
Provider[],
|
||||||
|
boolean,
|
||||||
|
ParamMap,
|
||||||
|
void,
|
||||||
|
]) => {
|
||||||
// Sort orgs by name to match the order within the sidebar
|
// Sort orgs by name to match the order within the sidebar
|
||||||
orgs.sort((a, b) => a.name.localeCompare(b.name)),
|
orgs.sort((a, b) => a.name.localeCompare(b.name));
|
||||||
...rest,
|
|
||||||
];
|
|
||||||
}),
|
|
||||||
concatMap(async ([orgs, paramMap]) => {
|
|
||||||
let routeOrg = orgs.find((o) => o.id === paramMap.get("organizationId"));
|
let routeOrg = orgs.find((o) => o.id === paramMap.get("organizationId"));
|
||||||
|
|
||||||
let organizationIdViaPath: string | null = null;
|
let organizationIdViaPath: string | null = null;
|
||||||
@@ -155,9 +165,6 @@ export class ProductSwitcherService {
|
|||||||
? routeOrg
|
? routeOrg
|
||||||
: orgs.find((o) => canAccessOrgAdmin(o));
|
: orgs.find((o) => canAccessOrgAdmin(o));
|
||||||
|
|
||||||
// TODO: This should be migrated to an Observable provided by the provider service and moved to the combineLatest above. See AC-2092.
|
|
||||||
const providers = await this.providerService.getAll();
|
|
||||||
|
|
||||||
const providerPortalName =
|
const providerPortalName =
|
||||||
providers[0]?.providerType === ProviderType.BusinessUnit
|
providers[0]?.providerType === ProviderType.BusinessUnit
|
||||||
? "Business Unit Portal"
|
? "Business Unit Portal"
|
||||||
@@ -239,12 +246,6 @@ export class ProductSwitcherService {
|
|||||||
if (acOrg) {
|
if (acOrg) {
|
||||||
bento.push(products.ac);
|
bento.push(products.ac);
|
||||||
} else {
|
} else {
|
||||||
const activeUserId = await firstValueFrom(
|
|
||||||
this.accountService.activeAccount$.pipe(getUserId),
|
|
||||||
);
|
|
||||||
const userHasSingleOrgPolicy = await firstValueFrom(
|
|
||||||
this.policyService.policyAppliesToUser$(PolicyType.SingleOrg, activeUserId),
|
|
||||||
);
|
|
||||||
if (!userHasSingleOrgPolicy) {
|
if (!userHasSingleOrgPolicy) {
|
||||||
other.push(products.orgs);
|
other.push(products.orgs);
|
||||||
}
|
}
|
||||||
@@ -258,7 +259,8 @@ export class ProductSwitcherService {
|
|||||||
bento,
|
bento,
|
||||||
other,
|
other,
|
||||||
};
|
};
|
||||||
}),
|
},
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
/** Poll the `syncService` until a sync is completed */
|
/** Poll the `syncService` until a sync is completed */
|
||||||
|
|||||||
@@ -11,7 +11,14 @@
|
|||||||
{{ o.name }}
|
{{ o.name }}
|
||||||
</td>
|
</td>
|
||||||
<td bitCell>
|
<td bitCell>
|
||||||
<button type="button" bitButton [bitAction]="add(o)" class="tw-float-right">Add</button>
|
<button
|
||||||
|
type="button"
|
||||||
|
bitButton
|
||||||
|
[bitAction]="add(o, provider$ | async)"
|
||||||
|
class="tw-float-right"
|
||||||
|
>
|
||||||
|
Add
|
||||||
|
</button>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
// FIXME: Update this file to be type safe and remove this and next line
|
// FIXME: Update this file to be type safe and remove this and next line
|
||||||
// @ts-strict-ignore
|
// @ts-strict-ignore
|
||||||
import { Component, Inject, OnInit } from "@angular/core";
|
import { Component, Inject, OnInit } from "@angular/core";
|
||||||
|
import { Observable, switchMap } from "rxjs";
|
||||||
|
|
||||||
import { ProviderService } from "@bitwarden/common/admin-console/abstractions/provider.service";
|
import { ProviderService } from "@bitwarden/common/admin-console/abstractions/provider.service";
|
||||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||||
import { Provider } from "@bitwarden/common/admin-console/models/domain/provider";
|
import { Provider } from "@bitwarden/common/admin-console/models/domain/provider";
|
||||||
|
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 { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||||
import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service";
|
import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service";
|
||||||
@@ -22,7 +25,7 @@ interface AddOrganizationDialogData {
|
|||||||
standalone: false,
|
standalone: false,
|
||||||
})
|
})
|
||||||
export class AddOrganizationComponent implements OnInit {
|
export class AddOrganizationComponent implements OnInit {
|
||||||
protected provider: Provider;
|
protected provider$: Observable<Provider>;
|
||||||
protected loading = true;
|
protected loading = true;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@@ -35,6 +38,7 @@ export class AddOrganizationComponent implements OnInit {
|
|||||||
private validationService: ValidationService,
|
private validationService: ValidationService,
|
||||||
private dialogService: DialogService,
|
private dialogService: DialogService,
|
||||||
private toastService: ToastService,
|
private toastService: ToastService,
|
||||||
|
private accountService: AccountService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
@@ -46,18 +50,21 @@ export class AddOrganizationComponent implements OnInit {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.provider = await this.providerService.get(this.data.providerId);
|
this.provider$ = this.accountService.activeAccount$.pipe(
|
||||||
|
getUserId,
|
||||||
|
switchMap((userId) => this.providerService.get$(this.data.providerId, userId)),
|
||||||
|
);
|
||||||
|
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
add(organization: Organization) {
|
add(organization: Organization, provider: Provider) {
|
||||||
return async () => {
|
return async () => {
|
||||||
const confirmed = await this.dialogService.openSimpleDialog({
|
const confirmed = await this.dialogService.openSimpleDialog({
|
||||||
title: organization.name,
|
title: organization.name,
|
||||||
content: {
|
content: {
|
||||||
key: "addOrganizationConfirmation",
|
key: "addOrganizationConfirmation",
|
||||||
placeholders: [organization.name, this.provider.name],
|
placeholders: [organization.name, provider.name],
|
||||||
},
|
},
|
||||||
type: "warning",
|
type: "warning",
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { Component } from "@angular/core";
|
|||||||
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
|
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
|
||||||
import { FormControl } from "@angular/forms";
|
import { FormControl } from "@angular/forms";
|
||||||
import { ActivatedRoute, Router, RouterModule } from "@angular/router";
|
import { ActivatedRoute, Router, RouterModule } from "@angular/router";
|
||||||
import { firstValueFrom, from, map, Observable, switchMap } from "rxjs";
|
import { combineLatest, firstValueFrom, from, map, Observable, switchMap } from "rxjs";
|
||||||
import { debounceTime, first } from "rxjs/operators";
|
import { debounceTime, first } from "rxjs/operators";
|
||||||
|
|
||||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||||
@@ -65,9 +65,10 @@ export class ClientsComponent {
|
|||||||
this.activatedRoute.parent?.params.pipe(map((params) => params.providerId as string)) ??
|
this.activatedRoute.parent?.params.pipe(map((params) => params.providerId as string)) ??
|
||||||
new Observable();
|
new Observable();
|
||||||
|
|
||||||
protected provider$ = this.providerId$.pipe(
|
protected provider$ = combineLatest([
|
||||||
switchMap((providerId) => this.providerService.get$(providerId)),
|
this.providerId$,
|
||||||
);
|
this.accountService.activeAccount$.pipe(getUserId),
|
||||||
|
]).pipe(switchMap(([providerId, userId]) => this.providerService.get$(providerId, userId)));
|
||||||
|
|
||||||
protected isAdminOrServiceUser$ = this.provider$.pipe(
|
protected isAdminOrServiceUser$ = this.provider$.pipe(
|
||||||
map(
|
map(
|
||||||
|
|||||||
@@ -3,12 +3,16 @@
|
|||||||
import { TestBed } from "@angular/core/testing";
|
import { TestBed } from "@angular/core/testing";
|
||||||
import { ActivatedRouteSnapshot, Router, RouterStateSnapshot } from "@angular/router";
|
import { ActivatedRouteSnapshot, Router, RouterStateSnapshot } from "@angular/router";
|
||||||
import { mock, MockProxy } from "jest-mock-extended";
|
import { mock, MockProxy } from "jest-mock-extended";
|
||||||
|
import { of } from "rxjs";
|
||||||
|
|
||||||
import { ProviderService } from "@bitwarden/common/admin-console/abstractions/provider.service";
|
import { ProviderService } from "@bitwarden/common/admin-console/abstractions/provider.service";
|
||||||
import { ProviderUserType } from "@bitwarden/common/admin-console/enums";
|
import { ProviderUserType } from "@bitwarden/common/admin-console/enums";
|
||||||
import { Provider } from "@bitwarden/common/admin-console/models/domain/provider";
|
import { Provider } from "@bitwarden/common/admin-console/models/domain/provider";
|
||||||
|
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
|
import { UserId } from "@bitwarden/common/types/guid";
|
||||||
import { ToastService } from "@bitwarden/components";
|
import { ToastService } from "@bitwarden/components";
|
||||||
|
import { newGuid } from "@bitwarden/guid";
|
||||||
|
|
||||||
import { providerPermissionsGuard } from "./provider-permissions.guard";
|
import { providerPermissionsGuard } from "./provider-permissions.guard";
|
||||||
|
|
||||||
@@ -25,11 +29,23 @@ const providerFactory = (props: Partial<Provider> = {}) =>
|
|||||||
|
|
||||||
describe("Provider Permissions Guard", () => {
|
describe("Provider Permissions Guard", () => {
|
||||||
let providerService: MockProxy<ProviderService>;
|
let providerService: MockProxy<ProviderService>;
|
||||||
|
let accountService: MockProxy<AccountService>;
|
||||||
let route: MockProxy<ActivatedRouteSnapshot>;
|
let route: MockProxy<ActivatedRouteSnapshot>;
|
||||||
let state: MockProxy<RouterStateSnapshot>;
|
let state: MockProxy<RouterStateSnapshot>;
|
||||||
|
|
||||||
|
const mockUserId = newGuid() as UserId;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
providerService = mock<ProviderService>();
|
providerService = mock<ProviderService>();
|
||||||
|
accountService = mock<AccountService>();
|
||||||
|
|
||||||
|
accountService.activeAccount$ = of({
|
||||||
|
id: mockUserId,
|
||||||
|
email: "test@example.com",
|
||||||
|
emailVerified: true,
|
||||||
|
name: "Test User",
|
||||||
|
});
|
||||||
|
|
||||||
route = mock<ActivatedRouteSnapshot>({
|
route = mock<ActivatedRouteSnapshot>({
|
||||||
params: {
|
params: {
|
||||||
providerId: providerFactory().id,
|
providerId: providerFactory().id,
|
||||||
@@ -44,12 +60,15 @@ describe("Provider Permissions Guard", () => {
|
|||||||
{ provide: ToastService, useValue: mock<ToastService>() },
|
{ provide: ToastService, useValue: mock<ToastService>() },
|
||||||
{ provide: I18nService, useValue: mock<I18nService>() },
|
{ provide: I18nService, useValue: mock<I18nService>() },
|
||||||
{ provide: Router, useValue: mock<Router>() },
|
{ provide: Router, useValue: mock<Router>() },
|
||||||
|
{ provide: AccountService, useValue: accountService },
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("blocks navigation if provider does not exist", async () => {
|
it("blocks navigation if provider does not exist", async () => {
|
||||||
providerService.get.mockResolvedValue(null);
|
providerService.get$
|
||||||
|
.calledWith(providerFactory().id, mockUserId)
|
||||||
|
.mockReturnValue(of(undefined));
|
||||||
|
|
||||||
const actual = await TestBed.runInInjectionContext(
|
const actual = await TestBed.runInInjectionContext(
|
||||||
async () => await providerPermissionsGuard()(route, state),
|
async () => await providerPermissionsGuard()(route, state),
|
||||||
@@ -60,7 +79,7 @@ describe("Provider Permissions Guard", () => {
|
|||||||
|
|
||||||
it("permits navigation if no permissions are specified", async () => {
|
it("permits navigation if no permissions are specified", async () => {
|
||||||
const provider = providerFactory();
|
const provider = providerFactory();
|
||||||
providerService.get.calledWith(provider.id).mockResolvedValue(provider);
|
providerService.get$.calledWith(provider.id, mockUserId).mockReturnValue(of(provider));
|
||||||
|
|
||||||
const actual = await TestBed.runInInjectionContext(
|
const actual = await TestBed.runInInjectionContext(
|
||||||
async () => await providerPermissionsGuard()(route, state),
|
async () => await providerPermissionsGuard()(route, state),
|
||||||
@@ -74,7 +93,7 @@ describe("Provider Permissions Guard", () => {
|
|||||||
permissionsCallback.mockImplementation((_provider) => true);
|
permissionsCallback.mockImplementation((_provider) => true);
|
||||||
|
|
||||||
const provider = providerFactory();
|
const provider = providerFactory();
|
||||||
providerService.get.calledWith(provider.id).mockResolvedValue(provider);
|
providerService.get$.calledWith(provider.id, mockUserId).mockReturnValue(of(provider));
|
||||||
|
|
||||||
const actual = await TestBed.runInInjectionContext(
|
const actual = await TestBed.runInInjectionContext(
|
||||||
async () => await providerPermissionsGuard(permissionsCallback)(route, state),
|
async () => await providerPermissionsGuard(permissionsCallback)(route, state),
|
||||||
@@ -88,7 +107,7 @@ describe("Provider Permissions Guard", () => {
|
|||||||
const permissionsCallback = jest.fn();
|
const permissionsCallback = jest.fn();
|
||||||
permissionsCallback.mockImplementation((_org) => false);
|
permissionsCallback.mockImplementation((_org) => false);
|
||||||
const provider = providerFactory();
|
const provider = providerFactory();
|
||||||
providerService.get.calledWith(provider.id).mockResolvedValue(provider);
|
providerService.get$.calledWith(provider.id, mockUserId).mockReturnValue(of(provider));
|
||||||
|
|
||||||
const actual = await TestBed.runInInjectionContext(
|
const actual = await TestBed.runInInjectionContext(
|
||||||
async () => await providerPermissionsGuard(permissionsCallback)(route, state),
|
async () => await providerPermissionsGuard(permissionsCallback)(route, state),
|
||||||
@@ -104,7 +123,7 @@ describe("Provider Permissions Guard", () => {
|
|||||||
type: ProviderUserType.ServiceUser,
|
type: ProviderUserType.ServiceUser,
|
||||||
enabled: false,
|
enabled: false,
|
||||||
});
|
});
|
||||||
providerService.get.calledWith(org.id).mockResolvedValue(org);
|
providerService.get$.calledWith(org.id, mockUserId).mockReturnValue(of(org));
|
||||||
|
|
||||||
const actual = await TestBed.runInInjectionContext(
|
const actual = await TestBed.runInInjectionContext(
|
||||||
async () => await providerPermissionsGuard()(route, state),
|
async () => await providerPermissionsGuard()(route, state),
|
||||||
@@ -118,7 +137,7 @@ describe("Provider Permissions Guard", () => {
|
|||||||
type: ProviderUserType.ProviderAdmin,
|
type: ProviderUserType.ProviderAdmin,
|
||||||
enabled: false,
|
enabled: false,
|
||||||
});
|
});
|
||||||
providerService.get.calledWith(org.id).mockResolvedValue(org);
|
providerService.get$.calledWith(org.id, mockUserId).mockReturnValue(of(org));
|
||||||
|
|
||||||
const actual = await TestBed.runInInjectionContext(
|
const actual = await TestBed.runInInjectionContext(
|
||||||
async () => await providerPermissionsGuard()(route, state),
|
async () => await providerPermissionsGuard()(route, state),
|
||||||
|
|||||||
@@ -7,9 +7,12 @@ import {
|
|||||||
Router,
|
Router,
|
||||||
RouterStateSnapshot,
|
RouterStateSnapshot,
|
||||||
} from "@angular/router";
|
} from "@angular/router";
|
||||||
|
import { firstValueFrom, switchMap } from "rxjs";
|
||||||
|
|
||||||
import { ProviderService } from "@bitwarden/common/admin-console/abstractions/provider.service";
|
import { ProviderService } from "@bitwarden/common/admin-console/abstractions/provider.service";
|
||||||
import { Provider } from "@bitwarden/common/admin-console/models/domain/provider";
|
import { Provider } from "@bitwarden/common/admin-console/models/domain/provider";
|
||||||
|
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 { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
import { ToastService } from "@bitwarden/components";
|
import { ToastService } from "@bitwarden/components";
|
||||||
|
|
||||||
@@ -40,8 +43,14 @@ export function providerPermissionsGuard(
|
|||||||
const router = inject(Router);
|
const router = inject(Router);
|
||||||
const i18nService = inject(I18nService);
|
const i18nService = inject(I18nService);
|
||||||
const toastService = inject(ToastService);
|
const toastService = inject(ToastService);
|
||||||
|
const accountService = inject(AccountService);
|
||||||
|
|
||||||
const provider = await providerService.get(route.params.providerId);
|
const provider = await firstValueFrom(
|
||||||
|
accountService.activeAccount$.pipe(
|
||||||
|
getUserId,
|
||||||
|
switchMap((userId) => providerService.get$(route.params.providerId, userId)),
|
||||||
|
),
|
||||||
|
);
|
||||||
if (provider == null) {
|
if (provider == null) {
|
||||||
return router.createUrlTree(["/"]);
|
return router.createUrlTree(["/"]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,12 +2,14 @@
|
|||||||
// @ts-strict-ignore
|
// @ts-strict-ignore
|
||||||
import { Component, OnInit } from "@angular/core";
|
import { Component, OnInit } from "@angular/core";
|
||||||
import { ActivatedRoute, Router } from "@angular/router";
|
import { ActivatedRoute, Router } from "@angular/router";
|
||||||
|
import { firstValueFrom, switchMap } from "rxjs";
|
||||||
|
|
||||||
import { UserNamePipe } from "@bitwarden/angular/pipes/user-name.pipe";
|
import { UserNamePipe } from "@bitwarden/angular/pipes/user-name.pipe";
|
||||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||||
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||||
import { ProviderService } from "@bitwarden/common/admin-console/abstractions/provider.service";
|
import { ProviderService } from "@bitwarden/common/admin-console/abstractions/provider.service";
|
||||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||||
|
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||||
import { EventResponse } from "@bitwarden/common/models/response/event.response";
|
import { EventResponse } from "@bitwarden/common/models/response/event.response";
|
||||||
import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service";
|
import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service";
|
||||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
@@ -64,7 +66,13 @@ export class EventsComponent extends BaseEventsComponent implements OnInit {
|
|||||||
// eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe
|
// eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe
|
||||||
this.route.parent.parent.params.subscribe(async (params) => {
|
this.route.parent.parent.params.subscribe(async (params) => {
|
||||||
this.providerId = params.providerId;
|
this.providerId = params.providerId;
|
||||||
const provider = await this.providerService.get(this.providerId);
|
const provider = await firstValueFrom(
|
||||||
|
this.accountService.activeAccount$.pipe(
|
||||||
|
getUserId,
|
||||||
|
switchMap((userId) => this.providerService.get$(this.providerId, userId)),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
if (provider == null || !provider.useEvents) {
|
if (provider == null || !provider.useEvents) {
|
||||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
// 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
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
import { Component } from "@angular/core";
|
import { Component } from "@angular/core";
|
||||||
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
|
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
|
||||||
import { ActivatedRoute, Router } from "@angular/router";
|
import { ActivatedRoute, Router } from "@angular/router";
|
||||||
import { combineLatest, lastValueFrom, switchMap } from "rxjs";
|
import { combineLatest, firstValueFrom, lastValueFrom, switchMap } from "rxjs";
|
||||||
import { first } from "rxjs/operators";
|
import { first } from "rxjs/operators";
|
||||||
|
|
||||||
import { UserNamePipe } from "@bitwarden/angular/pipes/user-name.pipe";
|
import { UserNamePipe } from "@bitwarden/angular/pipes/user-name.pipe";
|
||||||
@@ -14,6 +14,8 @@ import { ProviderUserStatusType, ProviderUserType } from "@bitwarden/common/admi
|
|||||||
import { ProviderUserBulkRequest } from "@bitwarden/common/admin-console/models/request/provider/provider-user-bulk.request";
|
import { ProviderUserBulkRequest } from "@bitwarden/common/admin-console/models/request/provider/provider-user-bulk.request";
|
||||||
import { ProviderUserConfirmRequest } from "@bitwarden/common/admin-console/models/request/provider/provider-user-confirm.request";
|
import { ProviderUserConfirmRequest } from "@bitwarden/common/admin-console/models/request/provider/provider-user-confirm.request";
|
||||||
import { ProviderUserUserDetailsResponse } from "@bitwarden/common/admin-console/models/response/provider/provider-user.response";
|
import { ProviderUserUserDetailsResponse } from "@bitwarden/common/admin-console/models/response/provider/provider-user.response";
|
||||||
|
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||||
|
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||||
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
|
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
|
||||||
import { ListResponse } from "@bitwarden/common/models/response/list.response";
|
import { ListResponse } from "@bitwarden/common/models/response/list.response";
|
||||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
@@ -73,6 +75,7 @@ export class MembersComponent extends BaseMembersComponent<ProviderUser> {
|
|||||||
private activatedRoute: ActivatedRoute,
|
private activatedRoute: ActivatedRoute,
|
||||||
private providerService: ProviderService,
|
private providerService: ProviderService,
|
||||||
private router: Router,
|
private router: Router,
|
||||||
|
private accountService: AccountService,
|
||||||
) {
|
) {
|
||||||
super(
|
super(
|
||||||
apiService,
|
apiService,
|
||||||
@@ -96,7 +99,13 @@ export class MembersComponent extends BaseMembersComponent<ProviderUser> {
|
|||||||
this.dataSource.filter = peopleFilter(queryParams.search, null);
|
this.dataSource.filter = peopleFilter(queryParams.search, null);
|
||||||
|
|
||||||
this.providerId = urlParams.providerId;
|
this.providerId = urlParams.providerId;
|
||||||
const provider = await this.providerService.get(this.providerId);
|
const provider = await firstValueFrom(
|
||||||
|
this.accountService.activeAccount$.pipe(
|
||||||
|
getUserId,
|
||||||
|
switchMap((userId) => this.providerService.get$(this.providerId, userId)),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
if (!provider || !provider.canManageUsers) {
|
if (!provider || !provider.canManageUsers) {
|
||||||
return await this.router.navigate(["../"], { relativeTo: this.activatedRoute });
|
return await this.router.navigate(["../"], { relativeTo: this.activatedRoute });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ import { BusinessUnitPortalLogo, Icon, ProviderPortalLogo } from "@bitwarden/ass
|
|||||||
import { ProviderService } from "@bitwarden/common/admin-console/abstractions/provider.service";
|
import { ProviderService } from "@bitwarden/common/admin-console/abstractions/provider.service";
|
||||||
import { ProviderStatusType, ProviderType } from "@bitwarden/common/admin-console/enums";
|
import { ProviderStatusType, ProviderType } from "@bitwarden/common/admin-console/enums";
|
||||||
import { Provider } from "@bitwarden/common/admin-console/models/domain/provider";
|
import { Provider } from "@bitwarden/common/admin-console/models/domain/provider";
|
||||||
|
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 { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||||
import { IconModule } from "@bitwarden/components";
|
import { IconModule } from "@bitwarden/components";
|
||||||
@@ -56,6 +58,7 @@ export class ProvidersLayoutComponent implements OnInit, OnDestroy {
|
|||||||
private providerService: ProviderService,
|
private providerService: ProviderService,
|
||||||
private configService: ConfigService,
|
private configService: ConfigService,
|
||||||
private providerWarningsService: ProviderWarningsService,
|
private providerWarningsService: ProviderWarningsService,
|
||||||
|
private accountService: AccountService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
@@ -65,8 +68,11 @@ export class ProvidersLayoutComponent implements OnInit, OnDestroy {
|
|||||||
map((params) => params.providerId),
|
map((params) => params.providerId),
|
||||||
);
|
);
|
||||||
|
|
||||||
this.provider$ = providerId$.pipe(
|
this.provider$ = combineLatest([
|
||||||
switchMap((providerId) => this.providerService.get$(providerId)),
|
providerId$,
|
||||||
|
this.accountService.activeAccount$.pipe(getUserId),
|
||||||
|
]).pipe(
|
||||||
|
switchMap(([providerId, userId]) => this.providerService.get$(providerId, userId)),
|
||||||
takeUntil(this.destroy$),
|
takeUntil(this.destroy$),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
@let providers = providers$ | async;
|
||||||
<app-header></app-header>
|
<app-header></app-header>
|
||||||
|
|
||||||
<bit-container>
|
<bit-container>
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
// FIXME: Update this file to be type safe and remove this and next line
|
// FIXME: Update this file to be type safe and remove this and next line
|
||||||
// @ts-strict-ignore
|
// @ts-strict-ignore
|
||||||
import { Component, OnInit } from "@angular/core";
|
import { Component, OnInit } from "@angular/core";
|
||||||
|
import { map, Observable, switchMap, tap } from "rxjs";
|
||||||
|
|
||||||
import { ProviderService } from "@bitwarden/common/admin-console/abstractions/provider.service";
|
import { ProviderService } from "@bitwarden/common/admin-console/abstractions/provider.service";
|
||||||
import { Provider } from "@bitwarden/common/admin-console/models/domain/provider";
|
import { Provider } from "@bitwarden/common/admin-console/models/domain/provider";
|
||||||
|
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 { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||||
|
|
||||||
@@ -13,24 +16,27 @@ import { Utils } from "@bitwarden/common/platform/misc/utils";
|
|||||||
standalone: false,
|
standalone: false,
|
||||||
})
|
})
|
||||||
export class ProvidersComponent implements OnInit {
|
export class ProvidersComponent implements OnInit {
|
||||||
providers: Provider[];
|
providers$: Observable<Provider[]>;
|
||||||
loaded = false;
|
loaded = false;
|
||||||
actionPromise: Promise<any>;
|
actionPromise: Promise<any>;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private providerService: ProviderService,
|
private providerService: ProviderService,
|
||||||
private i18nService: I18nService,
|
private i18nService: I18nService,
|
||||||
|
private accountService: AccountService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async ngOnInit() {
|
ngOnInit() {
|
||||||
document.body.classList.remove("layout_frontend");
|
document.body.classList.remove("layout_frontend");
|
||||||
await this.load();
|
this.load();
|
||||||
}
|
}
|
||||||
|
|
||||||
async load() {
|
load() {
|
||||||
const providers = await this.providerService.getAll();
|
this.providers$ = this.accountService.activeAccount$.pipe(
|
||||||
providers.sort(Utils.getSortFunction(this.i18nService, "name"));
|
getUserId,
|
||||||
this.providers = providers;
|
switchMap((userId) => this.providerService.providers$(userId)),
|
||||||
this.loaded = true;
|
map((p) => p.sort(Utils.getSortFunction(this.i18nService, "name"))),
|
||||||
|
tap(() => (this.loaded = true)),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,8 @@ import {
|
|||||||
} from "@bitwarden/common/admin-console/enums";
|
} from "@bitwarden/common/admin-console/enums";
|
||||||
import { Provider } from "@bitwarden/common/admin-console/models/domain/provider";
|
import { Provider } from "@bitwarden/common/admin-console/models/domain/provider";
|
||||||
import { ProviderOrganizationOrganizationDetailsResponse } from "@bitwarden/common/admin-console/models/response/provider/provider-organization.response";
|
import { ProviderOrganizationOrganizationDetailsResponse } from "@bitwarden/common/admin-console/models/response/provider/provider-organization.response";
|
||||||
|
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 { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions";
|
||||||
import { PlanResponse } from "@bitwarden/common/billing/models/response/plan.response";
|
import { PlanResponse } from "@bitwarden/common/billing/models/response/plan.response";
|
||||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||||
@@ -87,9 +89,10 @@ export class ManageClientsComponent {
|
|||||||
this.activatedRoute.parent?.params.pipe(map((params) => params.providerId as string)) ??
|
this.activatedRoute.parent?.params.pipe(map((params) => params.providerId as string)) ??
|
||||||
new Observable();
|
new Observable();
|
||||||
|
|
||||||
protected provider$ = this.providerId$.pipe(
|
protected provider$ = combineLatest([
|
||||||
switchMap((providerId) => this.providerService.get$(providerId)),
|
this.providerId$,
|
||||||
);
|
this.accountService.activeAccount$.pipe(getUserId),
|
||||||
|
]).pipe(switchMap(([providerId, userId]) => this.providerService.get$(providerId, userId)));
|
||||||
|
|
||||||
protected isAdminOrServiceUser$ = this.provider$.pipe(
|
protected isAdminOrServiceUser$ = this.provider$.pipe(
|
||||||
map(
|
map(
|
||||||
@@ -126,6 +129,7 @@ export class ManageClientsComponent {
|
|||||||
private webProviderService: WebProviderService,
|
private webProviderService: WebProviderService,
|
||||||
private billingNotificationService: BillingNotificationService,
|
private billingNotificationService: BillingNotificationService,
|
||||||
private configService: ConfigService,
|
private configService: ConfigService,
|
||||||
|
private accountService: AccountService,
|
||||||
) {
|
) {
|
||||||
this.activatedRoute.queryParams.pipe(first(), takeUntilDestroyed()).subscribe((queryParams) => {
|
this.activatedRoute.queryParams.pipe(first(), takeUntilDestroyed()).subscribe((queryParams) => {
|
||||||
this.searchControl.setValue(queryParams.search);
|
this.searchControl.setValue(queryParams.search);
|
||||||
@@ -133,7 +137,7 @@ export class ManageClientsComponent {
|
|||||||
|
|
||||||
this.provider$
|
this.provider$
|
||||||
.pipe(
|
.pipe(
|
||||||
map((provider: Provider) => {
|
map((provider: Provider | undefined) => {
|
||||||
if (provider?.providerStatus !== ProviderStatusType.Billable) {
|
if (provider?.providerStatus !== ProviderStatusType.Billable) {
|
||||||
return from(
|
return from(
|
||||||
this.router.navigate(["../clients"], {
|
this.router.navigate(["../clients"], {
|
||||||
@@ -158,7 +162,8 @@ export class ManageClientsComponent {
|
|||||||
async load() {
|
async load() {
|
||||||
try {
|
try {
|
||||||
const providerId = await firstValueFrom(this.providerId$);
|
const providerId = await firstValueFrom(this.providerId$);
|
||||||
const provider = await firstValueFrom(this.providerService.get$(providerId));
|
const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$));
|
||||||
|
const provider = await firstValueFrom(this.providerService.get$(providerId, userId));
|
||||||
if (provider?.providerType === ProviderType.BusinessUnit) {
|
if (provider?.providerType === ProviderType.BusinessUnit) {
|
||||||
this.pageTitle = this.i18nService.t("businessUnits");
|
this.pageTitle = this.i18nService.t("businessUnits");
|
||||||
this.clientColumnHeader = this.i18nService.t("businessUnit");
|
this.clientColumnHeader = this.i18nService.t("businessUnit");
|
||||||
|
|||||||
@@ -4,11 +4,15 @@ import { firstValueFrom } from "rxjs";
|
|||||||
|
|
||||||
import { ProviderService } from "@bitwarden/common/admin-console/abstractions/provider.service";
|
import { ProviderService } from "@bitwarden/common/admin-console/abstractions/provider.service";
|
||||||
import { ProviderStatusType } from "@bitwarden/common/admin-console/enums";
|
import { ProviderStatusType } from "@bitwarden/common/admin-console/enums";
|
||||||
|
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||||
|
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||||
|
|
||||||
export const hasConsolidatedBilling: CanActivateFn = async (route: ActivatedRouteSnapshot) => {
|
export const hasConsolidatedBilling: CanActivateFn = async (route: ActivatedRouteSnapshot) => {
|
||||||
const providerService = inject(ProviderService);
|
const providerService = inject(ProviderService);
|
||||||
|
const accountService = inject(AccountService);
|
||||||
|
|
||||||
const provider = await firstValueFrom(providerService.get$(route.params.providerId));
|
const userId = await firstValueFrom(getUserId(accountService.activeAccount$));
|
||||||
|
const provider = await firstValueFrom(providerService.get$(route.params.providerId, userId));
|
||||||
|
|
||||||
if (!provider || provider.providerStatus !== ProviderStatusType.Billable) {
|
if (!provider || provider.providerStatus !== ProviderStatusType.Billable) {
|
||||||
return createUrlTreeFromSnapshot(route, ["/providers", route.params.providerId]);
|
return createUrlTreeFromSnapshot(route, ["/providers", route.params.providerId]);
|
||||||
|
|||||||
@@ -83,8 +83,12 @@ const BANK_ACCOUNT_VERIFIED_COMMAND = new CommandDefinition<{
|
|||||||
export class ProviderPaymentDetailsComponent implements OnInit, OnDestroy {
|
export class ProviderPaymentDetailsComponent implements OnInit, OnDestroy {
|
||||||
private viewState$ = new BehaviorSubject<View | null>(null);
|
private viewState$ = new BehaviorSubject<View | null>(null);
|
||||||
|
|
||||||
private provider$ = this.activatedRoute.params.pipe(
|
private provider$ = combineLatest([
|
||||||
switchMap(({ providerId }) => this.providerService.get$(providerId)),
|
this.activatedRoute.params,
|
||||||
|
this.accountService.activeAccount$.pipe(getUserId),
|
||||||
|
]).pipe(
|
||||||
|
switchMap(([{ providerId }, userId]) => this.providerService.get$(providerId, userId)),
|
||||||
|
filter((provider) => provider != null),
|
||||||
);
|
);
|
||||||
|
|
||||||
private load$: Observable<View> = this.provider$.pipe(
|
private load$: Observable<View> = this.provider$.pipe(
|
||||||
|
|||||||
@@ -123,7 +123,9 @@ export class AddAccountCreditDialogComponent implements OnInit {
|
|||||||
this.formGroup.patchValue({
|
this.formGroup.patchValue({
|
||||||
creditAmount: 20.0,
|
creditAmount: 20.0,
|
||||||
});
|
});
|
||||||
this.provider = await this.providerService.get(this.dialogParams.providerId);
|
this.provider = await firstValueFrom(
|
||||||
|
this.providerService.get$(this.dialogParams.providerId, this.user.id),
|
||||||
|
);
|
||||||
payPalCustomField = "provider_id:" + this.provider.id;
|
payPalCustomField = "provider_id:" + this.provider.id;
|
||||||
this.payPalConfig.subject = this.provider.name;
|
this.payPalConfig.subject = this.provider.name;
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -5,8 +5,7 @@ import { ProviderData } from "../models/data/provider.data";
|
|||||||
import { Provider } from "../models/domain/provider";
|
import { Provider } from "../models/domain/provider";
|
||||||
|
|
||||||
export abstract class ProviderService {
|
export abstract class ProviderService {
|
||||||
abstract get$(id: string): Observable<Provider>;
|
abstract providers$(userId: UserId): Observable<Provider[]>;
|
||||||
abstract get(id: string): Promise<Provider>;
|
abstract get$(id: string, userId: UserId): Observable<Provider | undefined>;
|
||||||
abstract getAll(): Promise<Provider[]>;
|
abstract save(providers: { [id: string]: ProviderData }, userId: UserId): Promise<any>;
|
||||||
abstract save(providers: { [id: string]: ProviderData }, userId?: UserId): Promise<any>;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { firstValueFrom } from "rxjs";
|
import { firstValueFrom } from "rxjs";
|
||||||
|
|
||||||
import { FakeAccountService, FakeStateProvider, mockAccountServiceWith } from "../../../spec";
|
import { FakeAccountService, FakeStateProvider, mockAccountServiceWith } from "../../../spec";
|
||||||
import { FakeActiveUserState, FakeSingleUserState } from "../../../spec/fake-state";
|
import { FakeSingleUserState } from "../../../spec/fake-state";
|
||||||
import { Utils } from "../../platform/misc/utils";
|
import { Utils } from "../../platform/misc/utils";
|
||||||
import { UserId } from "../../types/guid";
|
import { UserId } from "../../types/guid";
|
||||||
import {
|
import {
|
||||||
@@ -20,11 +20,11 @@ import { PROVIDERS, ProviderService } from "./provider.service";
|
|||||||
* in state. This helper methods lets us build provider arrays in tests
|
* in state. This helper methods lets us build provider arrays in tests
|
||||||
* and easily map them to records before storing them in state.
|
* and easily map them to records before storing them in state.
|
||||||
*/
|
*/
|
||||||
function arrayToRecord(input: ProviderData[]): Record<string, ProviderData> {
|
function arrayToRecord(input: ProviderData[] | undefined): Record<string, ProviderData> | null {
|
||||||
if (input == null) {
|
if (input == null || input.length < 1) {
|
||||||
return undefined;
|
return null;
|
||||||
}
|
}
|
||||||
return Object.fromEntries(input?.map((i) => [i.id, i]));
|
return Object.fromEntries(input.map((i) => [i.id, i]));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -39,7 +39,7 @@ function arrayToRecord(input: ProviderData[]): Record<string, ProviderData> {
|
|||||||
*/
|
*/
|
||||||
function buildMockProviders(count = 1, suffix?: string): ProviderData[] {
|
function buildMockProviders(count = 1, suffix?: string): ProviderData[] {
|
||||||
if (count < 1) {
|
if (count < 1) {
|
||||||
return undefined;
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildMockProvider(id: string, name: string): ProviderData {
|
function buildMockProvider(id: string, name: string): ProviderData {
|
||||||
@@ -87,30 +87,28 @@ describe("ProviderService", () => {
|
|||||||
let fakeAccountService: FakeAccountService;
|
let fakeAccountService: FakeAccountService;
|
||||||
let fakeStateProvider: FakeStateProvider;
|
let fakeStateProvider: FakeStateProvider;
|
||||||
let fakeUserState: FakeSingleUserState<Record<string, ProviderData>>;
|
let fakeUserState: FakeSingleUserState<Record<string, ProviderData>>;
|
||||||
let fakeActiveUserState: FakeActiveUserState<Record<string, ProviderData>>;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
fakeAccountService = mockAccountServiceWith(fakeUserId);
|
fakeAccountService = mockAccountServiceWith(fakeUserId);
|
||||||
fakeStateProvider = new FakeStateProvider(fakeAccountService);
|
fakeStateProvider = new FakeStateProvider(fakeAccountService);
|
||||||
fakeUserState = fakeStateProvider.singleUser.getFake(fakeUserId, PROVIDERS);
|
fakeUserState = fakeStateProvider.singleUser.getFake(fakeUserId, PROVIDERS);
|
||||||
fakeActiveUserState = fakeStateProvider.activeUser.getFake(PROVIDERS);
|
|
||||||
|
|
||||||
providerService = new ProviderService(fakeStateProvider);
|
providerService = new ProviderService(fakeStateProvider);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("getAll()", () => {
|
describe("providers$()", () => {
|
||||||
it("Returns an array of all providers stored in state", async () => {
|
it("Returns an array of all providers stored in state", async () => {
|
||||||
const mockData: ProviderData[] = buildMockProviders(5);
|
const mockData = buildMockProviders(5);
|
||||||
fakeUserState.nextState(arrayToRecord(mockData));
|
fakeUserState.nextState(arrayToRecord(mockData));
|
||||||
const providers = await providerService.getAll();
|
const providers = await firstValueFrom(providerService.providers$(fakeUserId));
|
||||||
expect(providers).toHaveLength(5);
|
expect(providers).toHaveLength(5);
|
||||||
expect(providers).toEqual(mockData.map((x) => new Provider(x)));
|
expect(providers).toEqual(mockData.map((x) => new Provider(x)));
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Returns an empty array if no providers are found in state", async () => {
|
it("Returns an empty array if no providers are found in state", async () => {
|
||||||
const mockData: ProviderData[] = undefined;
|
let mockData;
|
||||||
fakeUserState.nextState(arrayToRecord(mockData));
|
fakeUserState.nextState(arrayToRecord(mockData));
|
||||||
const result = await providerService.getAll();
|
const result = await firstValueFrom(providerService.providers$(fakeUserId));
|
||||||
expect(result).toEqual([]);
|
expect(result).toEqual([]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -119,50 +117,38 @@ describe("ProviderService", () => {
|
|||||||
it("Returns an observable of a single provider from state that matches the specified id", async () => {
|
it("Returns an observable of a single provider from state that matches the specified id", async () => {
|
||||||
const mockData = buildMockProviders(5);
|
const mockData = buildMockProviders(5);
|
||||||
fakeUserState.nextState(arrayToRecord(mockData));
|
fakeUserState.nextState(arrayToRecord(mockData));
|
||||||
const result = providerService.get$(mockData[3].id);
|
const result = providerService.get$(mockData[3].id, fakeUserId);
|
||||||
const provider = await firstValueFrom(result);
|
const provider = await firstValueFrom(result);
|
||||||
expect(provider).toEqual(new Provider(mockData[3]));
|
expect(provider).toEqual(new Provider(mockData[3]));
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Returns an observable of undefined if the specified provider is not found", async () => {
|
it("Returns an observable of undefined if the specified provider is not found", async () => {
|
||||||
const result = providerService.get$("this-provider-does-not-exist");
|
const result = providerService.get$("this-provider-does-not-exist", fakeUserId);
|
||||||
const provider = await firstValueFrom(result);
|
const provider = await firstValueFrom(result);
|
||||||
expect(provider).toBe(undefined);
|
expect(provider).toBe(undefined);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("get()", () => {
|
|
||||||
it("Returns a single provider from state that matches the specified id", async () => {
|
|
||||||
const mockData = buildMockProviders(5);
|
|
||||||
fakeUserState.nextState(arrayToRecord(mockData));
|
|
||||||
const result = await providerService.get(mockData[3].id);
|
|
||||||
expect(result).toEqual(new Provider(mockData[3]));
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Returns undefined if the specified provider id is not found", async () => {
|
|
||||||
const result = await providerService.get("this-provider-does-not-exist");
|
|
||||||
expect(result).toBe(undefined);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("save()", () => {
|
describe("save()", () => {
|
||||||
it("replaces the entire provider list in state for the active user", async () => {
|
it("replaces the entire provider list in state for the specified user", async () => {
|
||||||
const originalData = buildMockProviders(10);
|
const originalData = buildMockProviders(10);
|
||||||
fakeUserState.nextState(arrayToRecord(originalData));
|
fakeUserState.nextState(arrayToRecord(originalData));
|
||||||
|
|
||||||
const newData = arrayToRecord(buildMockProviders(10, "newData"));
|
const newData = arrayToRecord(buildMockProviders(10, "newData"));
|
||||||
await providerService.save(newData);
|
if (newData) {
|
||||||
|
await providerService.save(newData, fakeUserId);
|
||||||
|
}
|
||||||
|
|
||||||
expect(fakeActiveUserState.nextMock).toHaveBeenCalledWith([fakeUserId, newData]);
|
expect(fakeUserState.nextMock).toHaveBeenCalledWith(newData);
|
||||||
});
|
});
|
||||||
|
|
||||||
// This is more or less a test for logouts
|
// This is more or less a test for logouts
|
||||||
it("can replace state with null", async () => {
|
it("can replace state with null", async () => {
|
||||||
const originalData = buildMockProviders(2);
|
const originalData = buildMockProviders(2);
|
||||||
fakeActiveUserState.nextState(arrayToRecord(originalData));
|
fakeUserState.nextState(arrayToRecord(originalData));
|
||||||
await providerService.save(null);
|
await providerService.save(null, fakeUserId);
|
||||||
|
|
||||||
expect(fakeActiveUserState.nextMock).toHaveBeenCalledWith([fakeUserId, null]);
|
expect(fakeUserState.nextMock).toHaveBeenCalledWith(null);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
// FIXME: Update this file to be type safe and remove this and next line
|
import { map, Observable } from "rxjs";
|
||||||
// @ts-strict-ignore
|
|
||||||
import { firstValueFrom, map, Observable, of, switchMap, take } from "rxjs";
|
|
||||||
|
|
||||||
|
import { getById } from "../../platform/misc";
|
||||||
import { PROVIDERS_DISK, StateProvider, UserKeyDefinition } from "../../platform/state";
|
import { PROVIDERS_DISK, StateProvider, UserKeyDefinition } from "../../platform/state";
|
||||||
import { UserId } from "../../types/guid";
|
import { UserId } from "../../types/guid";
|
||||||
import { ProviderService as ProviderServiceAbstraction } from "../abstractions/provider.service";
|
import { ProviderService as ProviderServiceAbstraction } from "../abstractions/provider.service";
|
||||||
@@ -13,46 +12,26 @@ export const PROVIDERS = UserKeyDefinition.record<ProviderData>(PROVIDERS_DISK,
|
|||||||
clearOn: ["logout"],
|
clearOn: ["logout"],
|
||||||
});
|
});
|
||||||
|
|
||||||
function mapToSingleProvider(providerId: string) {
|
|
||||||
return map<Provider[], Provider>((providers) => providers?.find((p) => p.id === providerId));
|
|
||||||
}
|
|
||||||
|
|
||||||
export class ProviderService implements ProviderServiceAbstraction {
|
export class ProviderService implements ProviderServiceAbstraction {
|
||||||
constructor(private stateProvider: StateProvider) {}
|
constructor(private stateProvider: StateProvider) {}
|
||||||
|
|
||||||
private providers$(userId?: UserId): Observable<Provider[] | undefined> {
|
providers$(userId: UserId): Observable<Provider[]> {
|
||||||
// FIXME: Can be replaced with `getUserStateOrDefault$` if we weren't trying to pick this.
|
return this.stateProvider
|
||||||
return (
|
.getUser(userId, PROVIDERS)
|
||||||
userId != null
|
.state$.pipe(this.mapProviderRecordToArray());
|
||||||
? this.stateProvider.getUser(userId, PROVIDERS).state$
|
|
||||||
: this.stateProvider.activeUserId$.pipe(
|
|
||||||
take(1),
|
|
||||||
switchMap((userId) =>
|
|
||||||
userId != null ? this.stateProvider.getUser(userId, PROVIDERS).state$ : of(null),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
).pipe(this.mapProviderRecordToArray());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private mapProviderRecordToArray() {
|
private mapProviderRecordToArray() {
|
||||||
return map<Record<string, ProviderData>, Provider[]>((providers) =>
|
return map<Record<string, ProviderData> | null, Provider[]>((providers) =>
|
||||||
Object.values(providers ?? {})?.map((o) => new Provider(o)),
|
Object.values(providers ?? {}).map((o) => new Provider(o)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
get$(id: string): Observable<Provider> {
|
get$(id: string, userId: UserId): Observable<Provider | undefined> {
|
||||||
return this.providers$().pipe(mapToSingleProvider(id));
|
return this.providers$(userId).pipe(getById(id));
|
||||||
}
|
}
|
||||||
|
|
||||||
async get(id: string): Promise<Provider> {
|
async save(providers: { [id: string]: ProviderData }, userId: UserId) {
|
||||||
return await firstValueFrom(this.providers$().pipe(mapToSingleProvider(id)));
|
|
||||||
}
|
|
||||||
|
|
||||||
async getAll(): Promise<Provider[]> {
|
|
||||||
return await firstValueFrom(this.providers$());
|
|
||||||
}
|
|
||||||
|
|
||||||
async save(providers: { [id: string]: ProviderData }, userId?: UserId) {
|
|
||||||
await this.stateProvider.setUserState(PROVIDERS, providers, userId);
|
await this.stateProvider.setUserState(PROVIDERS, providers, userId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user