mirror of
https://github.com/bitwarden/browser
synced 2025-12-21 02:33:46 +00:00
Merge branch 'master' into feature/org-admin-refresh
This commit is contained in:
@@ -77,6 +77,7 @@ export class AddEditComponent implements OnInit, OnDestroy {
|
||||
canUseReprompt = true;
|
||||
organization: Organization;
|
||||
|
||||
protected componentName = "";
|
||||
protected destroy$ = new Subject<void>();
|
||||
protected writeableCollections: CollectionView[];
|
||||
private personalOwnershipPolicyAppliesToActiveUser: boolean;
|
||||
@@ -397,7 +398,9 @@ export class AddEditComponent implements OnInit, OnDestroy {
|
||||
this.i18nService.t("deleteItem"),
|
||||
this.i18nService.t("yes"),
|
||||
this.i18nService.t("no"),
|
||||
"warning"
|
||||
"warning",
|
||||
false,
|
||||
this.componentName != "" ? this.componentName + " .modal-content" : null
|
||||
);
|
||||
if (!confirmed) {
|
||||
return false;
|
||||
|
||||
@@ -29,6 +29,7 @@ export class AttachmentsComponent implements OnInit {
|
||||
deletePromises: { [id: string]: Promise<any> } = {};
|
||||
reuploadPromises: { [id: string]: Promise<any> } = {};
|
||||
emergencyAccessId?: string = null;
|
||||
protected componentName = "";
|
||||
|
||||
constructor(
|
||||
protected cipherService: CipherService,
|
||||
@@ -104,7 +105,9 @@ export class AttachmentsComponent implements OnInit {
|
||||
this.i18nService.t("deleteAttachment"),
|
||||
this.i18nService.t("yes"),
|
||||
this.i18nService.t("no"),
|
||||
"warning"
|
||||
"warning",
|
||||
false,
|
||||
this.componentName != "" ? this.componentName + " .modal-content" : null
|
||||
);
|
||||
if (!confirmed) {
|
||||
return;
|
||||
|
||||
@@ -18,6 +18,7 @@ export class FolderAddEditComponent implements OnInit {
|
||||
title: string;
|
||||
formPromise: Promise<any>;
|
||||
deletePromise: Promise<any>;
|
||||
protected componentName = "";
|
||||
|
||||
constructor(
|
||||
protected folderService: FolderService,
|
||||
@@ -65,7 +66,9 @@ export class FolderAddEditComponent implements OnInit {
|
||||
this.i18nService.t("deleteFolder"),
|
||||
this.i18nService.t("yes"),
|
||||
this.i18nService.t("no"),
|
||||
"warning"
|
||||
"warning",
|
||||
false,
|
||||
this.componentName != "" ? this.componentName + " .modal-content" : null
|
||||
);
|
||||
if (!confirmed) {
|
||||
return false;
|
||||
|
||||
@@ -45,6 +45,7 @@ export class AddEditComponent implements OnInit, OnDestroy {
|
||||
alertShown = false;
|
||||
showOptions = false;
|
||||
|
||||
protected componentName = "";
|
||||
private sendLinkBaseUrl: string;
|
||||
private destroy$ = new Subject<void>();
|
||||
|
||||
@@ -242,7 +243,9 @@ export class AddEditComponent implements OnInit, OnDestroy {
|
||||
this.i18nService.t("deleteSend"),
|
||||
this.i18nService.t("yes"),
|
||||
this.i18nService.t("no"),
|
||||
"warning"
|
||||
"warning",
|
||||
false,
|
||||
this.componentName != "" ? this.componentName + " .modal-content" : null
|
||||
);
|
||||
if (!confirmed) {
|
||||
return false;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { Arg, Substitute, SubstituteOf } from "@fluffy-spoon/substitute";
|
||||
import { BehaviorSubject } from "rxjs";
|
||||
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { CipherService } from "@bitwarden/common/abstractions/cipher.service";
|
||||
@@ -12,9 +11,11 @@ import { KdfType } from "@bitwarden/common/enums/kdfType";
|
||||
import { Utils } from "@bitwarden/common/misc/utils";
|
||||
import { Cipher } from "@bitwarden/common/models/domain/cipher";
|
||||
import { EncString } from "@bitwarden/common/models/domain/enc-string";
|
||||
import { Folder } from "@bitwarden/common/models/domain/folder";
|
||||
import { Login } from "@bitwarden/common/models/domain/login";
|
||||
import { CipherWithIdExport as CipherExport } from "@bitwarden/common/models/export/cipher-with-ids.export";
|
||||
import { CipherView } from "@bitwarden/common/models/view/cipher.view";
|
||||
import { FolderView } from "@bitwarden/common/models/view/folder.view";
|
||||
import { LoginView } from "@bitwarden/common/models/view/login.view";
|
||||
import { ExportService } from "@bitwarden/common/services/export.service";
|
||||
|
||||
@@ -32,6 +33,10 @@ const UserCipherDomains = [
|
||||
generateCipherDomain(true),
|
||||
];
|
||||
|
||||
const UserFolderViews = [generateFolderView(), generateFolderView()];
|
||||
|
||||
const UserFolders = [generateFolder(), generateFolder()];
|
||||
|
||||
function generateCipherView(deleted: boolean) {
|
||||
return BuildTestObject(
|
||||
{
|
||||
@@ -72,6 +77,26 @@ function generateCipherDomain(deleted: boolean) {
|
||||
);
|
||||
}
|
||||
|
||||
function generateFolderView() {
|
||||
return BuildTestObject(
|
||||
{
|
||||
id: GetUniqueString("id"),
|
||||
name: GetUniqueString("name"),
|
||||
revisionDate: new Date(),
|
||||
},
|
||||
FolderView
|
||||
);
|
||||
}
|
||||
|
||||
function generateFolder() {
|
||||
const actual = Folder.fromJSON({
|
||||
revisionDate: new Date("2022-08-04T01:06:40.441Z").toISOString(),
|
||||
name: "name",
|
||||
id: "id",
|
||||
});
|
||||
return actual;
|
||||
}
|
||||
|
||||
function expectEqualCiphers(ciphers: CipherView[] | Cipher[], jsonResult: string) {
|
||||
const actual = JSON.stringify(JSON.parse(jsonResult).items);
|
||||
const items: CipherExport[] = [];
|
||||
@@ -84,6 +109,34 @@ function expectEqualCiphers(ciphers: CipherView[] | Cipher[], jsonResult: string
|
||||
expect(actual).toEqual(JSON.stringify(items));
|
||||
}
|
||||
|
||||
function expectEqualFolderViews(folderviews: FolderView[] | Folder[], jsonResult: string) {
|
||||
const actual = JSON.stringify(JSON.parse(jsonResult).folders);
|
||||
const folders: FolderResponse[] = [];
|
||||
folderviews.forEach((c) => {
|
||||
const folder = new FolderResponse();
|
||||
folder.id = c.id;
|
||||
folder.name = c.name.toString();
|
||||
folders.push(folder);
|
||||
});
|
||||
|
||||
expect(actual.length).toBeGreaterThan(0);
|
||||
expect(actual).toEqual(JSON.stringify(folders));
|
||||
}
|
||||
|
||||
function expectEqualFolders(folders: Folder[], jsonResult: string) {
|
||||
const actual = JSON.stringify(JSON.parse(jsonResult).folders);
|
||||
const items: Folder[] = [];
|
||||
folders.forEach((c) => {
|
||||
const item = new Folder();
|
||||
item.id = c.id;
|
||||
item.name = c.name;
|
||||
items.push(item);
|
||||
});
|
||||
|
||||
expect(actual.length).toBeGreaterThan(0);
|
||||
expect(actual).toEqual(JSON.stringify(items));
|
||||
}
|
||||
|
||||
describe("ExportService", () => {
|
||||
let exportService: ExportService;
|
||||
let apiService: SubstituteOf<ApiService>;
|
||||
@@ -99,8 +152,8 @@ describe("ExportService", () => {
|
||||
folderService = Substitute.for<FolderService>();
|
||||
cryptoService = Substitute.for<CryptoService>();
|
||||
|
||||
folderService.folderViews$.returns(new BehaviorSubject([]));
|
||||
folderService.folders$.returns(new BehaviorSubject([]));
|
||||
folderService.getAllDecryptedFromState().resolves(UserFolderViews);
|
||||
folderService.getAllFromState().resolves(UserFolders);
|
||||
|
||||
exportService = new ExportService(
|
||||
folderService,
|
||||
@@ -208,4 +261,25 @@ describe("ExportService", () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("exported unencrypted object contains folders", async () => {
|
||||
cipherService.getAllDecrypted().resolves(UserCipherViews.slice(0, 1));
|
||||
await folderService.getAllDecryptedFromState();
|
||||
const actual = await exportService.getExport("json");
|
||||
|
||||
expectEqualFolderViews(UserFolderViews, actual);
|
||||
});
|
||||
|
||||
it("exported encrypted json contains folders", async () => {
|
||||
cipherService.getAll().resolves(UserCipherDomains.slice(0, 1));
|
||||
await folderService.getAllFromState();
|
||||
const actual = await exportService.getExport("encrypted_json");
|
||||
|
||||
expectEqualFolders(UserFolders, actual);
|
||||
});
|
||||
});
|
||||
|
||||
export class FolderResponse {
|
||||
id: string = null;
|
||||
name: string = null;
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ export abstract class FolderService {
|
||||
clearCache: () => Promise<void>;
|
||||
encrypt: (model: FolderView, key?: SymmetricCryptoKey) => Promise<Folder>;
|
||||
get: (id: string) => Promise<Folder>;
|
||||
getAllFromState: () => Promise<Folder[]>;
|
||||
/**
|
||||
* @deprecated Only use in CLI!
|
||||
*/
|
||||
|
||||
@@ -34,7 +34,8 @@ export abstract class PlatformUtilsService {
|
||||
confirmText?: string,
|
||||
cancelText?: string,
|
||||
type?: string,
|
||||
bodyIsHtml?: boolean
|
||||
bodyIsHtml?: boolean,
|
||||
target?: string
|
||||
) => Promise<boolean>;
|
||||
isDev: () => boolean;
|
||||
isSelfHost: () => boolean;
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import * as papa from "papaparse";
|
||||
import { firstValueFrom } from "rxjs";
|
||||
|
||||
import { ApiService } from "../abstractions/api.service";
|
||||
import { CipherService } from "../abstractions/cipher.service";
|
||||
@@ -116,7 +115,7 @@ export class ExportService implements ExportServiceAbstraction {
|
||||
const promises = [];
|
||||
|
||||
promises.push(
|
||||
firstValueFrom(this.folderService.folderViews$).then((folders) => {
|
||||
this.folderService.getAllDecryptedFromState().then((folders) => {
|
||||
decFolders = folders;
|
||||
})
|
||||
);
|
||||
@@ -192,7 +191,7 @@ export class ExportService implements ExportServiceAbstraction {
|
||||
const promises = [];
|
||||
|
||||
promises.push(
|
||||
firstValueFrom(this.folderService.folders$).then((f) => {
|
||||
this.folderService.getAllFromState().then((f) => {
|
||||
folders = f;
|
||||
})
|
||||
);
|
||||
|
||||
@@ -64,6 +64,18 @@ export class FolderService implements InternalFolderServiceAbstraction {
|
||||
return folders.find((folder) => folder.id === id);
|
||||
}
|
||||
|
||||
async getAllFromState(): Promise<Folder[]> {
|
||||
const folders = await this.stateService.getEncryptedFolders();
|
||||
const response: Folder[] = [];
|
||||
for (const id in folders) {
|
||||
// eslint-disable-next-line
|
||||
if (folders.hasOwnProperty(id)) {
|
||||
response.push(new Folder(folders[id]));
|
||||
}
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated For the CLI only
|
||||
* @param id id of the folder
|
||||
|
||||
@@ -18,6 +18,8 @@ export class BitActionDirective implements OnDestroy {
|
||||
private destroy$ = new Subject<void>();
|
||||
private _loading$ = new BehaviorSubject<boolean>(false);
|
||||
|
||||
disabled = false;
|
||||
|
||||
@Input("bitAction") protected handler: FunctionReturningAwaitable;
|
||||
|
||||
readonly loading$ = this._loading$.asObservable();
|
||||
@@ -39,7 +41,7 @@ export class BitActionDirective implements OnDestroy {
|
||||
|
||||
@HostListener("click")
|
||||
protected async onClick() {
|
||||
if (!this.handler) {
|
||||
if (!this.handler || this.loading || this.disabled || this.buttonComponent.disabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -13,8 +13,10 @@ import { BitSubmitDirective } from "./bit-submit.directive";
|
||||
* - Activates the button loading effect while the form is processing an async submit action.
|
||||
* - Disables the button while a `bitAction` directive on another button is being processed.
|
||||
*
|
||||
* When attached to a standalone button with `bitAction` directive:
|
||||
* - Disables the form while the `bitAction` directive is processing an async submit action.
|
||||
* When attached to a button with `bitAction` directive inside of a form:
|
||||
* - Disables the button while the `bitSubmit` directive is processing an async submit action.
|
||||
* - Disables the button while a `bitAction` directive on another button is being processed.
|
||||
* - Disables form submission while the `bitAction` directive is processing an async action.
|
||||
*/
|
||||
@Directive({
|
||||
selector: "button[bitFormButton]",
|
||||
@@ -47,6 +49,10 @@ export class BitFormButtonDirective implements OnDestroy {
|
||||
actionDirective.loading$.pipe(takeUntil(this.destroy$)).subscribe((disabled) => {
|
||||
submitDirective.disabled = disabled;
|
||||
});
|
||||
|
||||
submitDirective.disabled$.pipe(takeUntil(this.destroy$)).subscribe((disabled) => {
|
||||
actionDirective.disabled = disabled;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,14 +16,16 @@ Adding async actions to submit buttons requires the following 3 steps
|
||||
### 1. Add a handler to your `Component`
|
||||
|
||||
A handler is a function that returns a promise or an observable. Functions that return `void` are also supported which is
|
||||
useful for aborting an action.
|
||||
useful because `return;` can be used to abort an action.
|
||||
|
||||
**NOTE:**
|
||||
**NOTE:** Defining the handlers as arrow-functions assigned to variables is mandatory if the handler needs access to the parent
|
||||
component using the variable `this`.
|
||||
|
||||
- Defining the handlers as arrow-functions assigned to variables is mandatory if the handler needs access to the parent
|
||||
component using the variable `this`.
|
||||
- `formGroup.invalid` will always return `true` after the first `await` operation, event if the form is not actually
|
||||
invalid. This is due to the form getting disabled by the `bitSubmit` directive while waiting for the async action to complete.
|
||||
**NOTE:** `formGroup.invalid` will always return `true` after the first `await` operation, event if the form is not actually
|
||||
invalid. This is due to the form getting disabled by the `bitSubmit` directive while waiting for the async action to complete.
|
||||
|
||||
**NOTE:** Handlers do not need to check if any previous requests have finished because the directives have built in protection against
|
||||
users attempting to trigger new actions before the previous ones have finished.
|
||||
|
||||
```ts
|
||||
@Component({...})
|
||||
@@ -52,6 +54,8 @@ Add the `bitSubmit` directive and supply the handler defined in step 1.
|
||||
**NOTE:** The `directive` is defined using the input syntax: `[input]="handler"`.
|
||||
This is different from how submit handlers are usually defined with the output syntax `(ngSubmit)="handler()"`.
|
||||
|
||||
**NOTE:** `[bitSubmit]` is used instead of `(ngSubmit)`. Using both is not supported.
|
||||
|
||||
```html
|
||||
<form [formGroup]="formGroup" [bitSubmit]="submit">...</form>
|
||||
```
|
||||
@@ -60,6 +64,8 @@ This is different from how submit handlers are usually defined with the output s
|
||||
|
||||
Add both `bitButton` and `bitFormButton` directives to the button.
|
||||
|
||||
**NOTE:** A summary of what each directive does can be found inside the source code.
|
||||
|
||||
```html
|
||||
<button type="submit" bitButton bitFormButton>{{ "submit" | i18n }}</button>
|
||||
```
|
||||
@@ -76,21 +82,22 @@ useful for aborting an action.
|
||||
**NOTE:** Defining the handlers as arrow-functions assigned to variables is mandatory if the handler needs access to the parent
|
||||
component using the variable `this`.
|
||||
|
||||
**NOTE:** Handlers do not need to check if any previous requests have finished because the directives have built in protection against
|
||||
users attempting to trigger new actions before the previous ones have finished.
|
||||
|
||||
```ts
|
||||
@Component({...})
|
||||
class Component {
|
||||
formGroup = this.formBuilder.group({...});
|
||||
|
||||
submit = async () => {
|
||||
// not relevant for this example
|
||||
// contents of this handler are not relevant for this example
|
||||
// as this handler will not be trigger by standalone buttons using
|
||||
// `bitAction`
|
||||
}
|
||||
|
||||
// action can also return Observable instead of Promise
|
||||
handler = async () => {
|
||||
if (/* perform guard check */) {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.apiService.post(/* ... */);
|
||||
};
|
||||
}
|
||||
@@ -98,7 +105,7 @@ class Component {
|
||||
|
||||
### 2. Add directive to the `form` element
|
||||
|
||||
The `bitSubmit` directive is required beacuse of its coordinating role.
|
||||
The `bitSubmit` directive is required beacuse of its coordinating role inside of a form.
|
||||
|
||||
```html
|
||||
<form [formGroup]="formGroup" [bitSubmit]="submit">...</form>
|
||||
@@ -108,6 +115,8 @@ The `bitSubmit` directive is required beacuse of its coordinating role.
|
||||
|
||||
Add `bitButton`, `bitFormButton`, `bitAction` directives to the button. Make sure to supply a handler.
|
||||
|
||||
**NOTE:** A summary of what each directive does can be found inside the source code.
|
||||
|
||||
```html
|
||||
<button type="button" bitFormButton bitButton [bitAction]="handler">Do action</button>
|
||||
<button type="button" bitFormButton bitIconButton="bwi-star" [bitAction]="handler"></button>
|
||||
|
||||
@@ -14,21 +14,20 @@ Adding async actions to standalone buttons requires the following 2 steps
|
||||
### 1. Add a handler to your `Component`
|
||||
|
||||
A handler is a function that returns a promise or an observable. Functions that return `void` are also supported which is
|
||||
useful for aborting an action.
|
||||
useful because `return;` can be used to abort an action.
|
||||
|
||||
**NOTE:** Defining the handlers as arrow-functions assigned to variables is mandatory if the handler needs access to the parent
|
||||
component using the variable `this`.
|
||||
|
||||
**NOTE:** Handlers do not need to check if any previous requests have finished because the directives have built in protection against
|
||||
users attempting to trigger new actions before the previous ones have finished.
|
||||
|
||||
#### Example using promises
|
||||
|
||||
```ts
|
||||
@Component({...})
|
||||
class PromiseExampleComponent {
|
||||
handler = async () => {
|
||||
if (/* perform guard check */) {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.apiService.post(/* ... */);
|
||||
};
|
||||
}
|
||||
@@ -40,10 +39,6 @@ class PromiseExampleComponent {
|
||||
@Component({...})
|
||||
class Component {
|
||||
handler = () => {
|
||||
if (/* perform guard check */) {
|
||||
return;
|
||||
}
|
||||
|
||||
return this.apiService.post$(/* ... */);
|
||||
};
|
||||
}
|
||||
@@ -56,6 +51,8 @@ Add the `bitAction` directive and supply the handler defined in step 1.
|
||||
**NOTE:** The `directive` is defined using the input syntax: `[input]="handler"`.
|
||||
This is different from how click handlers are usually defined with the output syntax `(click)="handler()"`.
|
||||
|
||||
**NOTE:** `[bitAction]` is used instead of `(click)`. Using both is not supported.
|
||||
|
||||
```html
|
||||
<button bitButton [bitAction]="handler">Do action</button>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user