Squash commits (#4382)

Co-authored-by: Hinton <hinton@users.noreply.github.com>
This commit is contained in:
cd-bitwarden 2023-01-05 13:31:57 -05:00 committed by GitHub
parent 049940d04b
commit 0a5f96c560
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 148 additions and 19 deletions

View File

@ -5561,6 +5561,15 @@
"deleteSecrets":{
"message": "Delete Secrets"
},
"secretProjectAssociationDescription" :{
"message": "Select projects that the secret will be associated with. Only organization users with access to these projects will be able to see the secret."
},
"typeOrSelectProjects" :{
"message": "Type or select Projects"
},
"typeOrSelectProject" :{
"message": "Type or select Project"
},
"project":{
"message": "Project"
},

View File

@ -1,5 +1,7 @@
import { View } from "@bitwarden/common/models/view/view";
import { SecretProjectView } from "./secret-project.view";
export class SecretView implements View {
id: string;
organizationId: string;
@ -8,4 +10,5 @@ export class SecretView implements View {
note: string;
creationDate: string;
revisionDate: string;
projects: SecretProjectView[];
}

View File

@ -23,7 +23,43 @@
</bit-form-field>
</bit-tab>
<bit-tab [label]="'serviceAccounts' | i18n"></bit-tab>
<bit-tab [label]="'projects' | i18n"></bit-tab>
<bit-tab [label]="'projects' | i18n">
<bit-label class="tw-text-md">{{
"secretProjectAssociationDescription" | i18n
}}</bit-label>
<bit-form-field class="tw-mt-3">
<bit-label>{{ "project" | i18n }}</bit-label>
<select bitInput name="project" formControlName="project">
<option *ngFor="let f of projects" [value]="f.id" (change)="updateProjectList()">
{{ f.name }}
</option>
</select>
</bit-form-field>
<small class="form-text text-muted">{{ "typeOrSelectProject" | i18n }}</small>
<bit-table>
<ng-container header>
<tr>
<th bitCell>{{ "project" | i18n }}</th>
<th bitCell></th>
</tr>
</ng-container>
<ng-container body *ngIf="selectedProjects != null">
<tr bitRow *ngFor="let e of selectedProjects">
<td bitCell>{{ e.name }}</td>
<td bitCell class="tw-w-0">
<button
(click)="removeProjectAssociation(e.id)"
bitIconButton="bwi-close"
buttonType="main"
[title]="'options' | i18n"
[attr.aria-label]="'options' | i18n"
></button>
</td>
</tr>
</ng-container>
</bit-table>
</bit-tab>
</bit-tab-group>
</div>
<div bitDialogFooter class="tw-flex tw-gap-2">

View File

@ -1,11 +1,15 @@
import { DialogRef, DIALOG_DATA } from "@angular/cdk/dialog";
import { Component, Inject, OnInit } from "@angular/core";
import { FormControl, FormGroup, Validators } from "@angular/forms";
import { Subject, takeUntil } from "rxjs";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
import { ProjectListView } from "../../models/view/project-list.view";
import { SecretProjectView } from "../../models/view/secret-project.view";
import { SecretView } from "../../models/view/secret.view";
import { ProjectService } from "../../projects/project.service";
import { SecretService } from "../secret.service";
export enum OperationType {
@ -29,37 +33,87 @@ export class SecretDialogComponent implements OnInit {
name: new FormControl("", [Validators.required]),
value: new FormControl("", [Validators.required]),
notes: new FormControl(""),
project: new FormControl(""),
});
protected loading = false;
protected loading = false;
projects: ProjectListView[];
selectedProjects: SecretProjectView[] = [];
private destroy$ = new Subject<void>();
constructor(
public dialogRef: DialogRef,
@Inject(DIALOG_DATA) private data: SecretOperation,
private secretService: SecretService,
private i18nService: I18nService,
private platformUtilsService: PlatformUtilsService
private platformUtilsService: PlatformUtilsService,
private projectService: ProjectService
) {}
async ngOnInit() {
this.projects = await this.projectService.getProjects(this.data.organizationId);
if (this.data.operation === OperationType.Edit && this.data.secretId) {
await this.loadData();
} else if (this.data.operation !== OperationType.Add) {
this.dialogRef.close();
throw new Error(`The secret dialog was not called with the appropriate operation values.`);
}
this.formGroup
.get("project")
.valueChanges.pipe(takeUntil(this.destroy$))
.subscribe(() => this.updateProjectList());
}
async loadData() {
this.loading = true;
const secret: SecretView = await this.secretService.getBySecretId(this.data.secretId);
this.loading = false;
this.formGroup.setValue({ name: secret.name, value: secret.value, notes: secret.note });
this.selectedProjects = secret.projects;
this.loading = false;
this.formGroup.setValue({
name: secret.name,
value: secret.value,
notes: secret.note,
project: "",
});
}
ngOnDestroy(): void {
this.destroy$.next();
this.destroy$.complete();
}
get title() {
return this.data.operation === OperationType.Add ? "newSecret" : "editSecret";
}
async removeProjectAssociation(id: string) {
this.selectedProjects = this.selectedProjects.filter((e) => e.id != id);
this.formGroup.get("project").setValue("");
}
updateProjectList() {
const newList: SecretProjectView[] = [];
const projectId = this.formGroup.get("project").value;
if (projectId) {
const selectedProject = this.projects?.filter((p) => p.id == projectId)[0];
if (selectedProject != undefined) {
const projectSecretView = new SecretProjectView();
projectSecretView.id = selectedProject.id;
projectSecretView.name = selectedProject.name;
newList.push(projectSecretView);
}
}
this.selectedProjects = newList;
}
submit = async () => {
this.formGroup.markAllAsTouched();
@ -69,7 +123,7 @@ export class SecretDialogComponent implements OnInit {
const secretView = this.getSecretView();
if (this.data.operation === OperationType.Add) {
await this.createSecret(secretView, this.data.projectId);
await this.createSecret(secretView);
} else {
secretView.id = this.data.secretId;
await this.updateSecret(secretView);
@ -77,8 +131,8 @@ export class SecretDialogComponent implements OnInit {
this.dialogRef.close();
};
private async createSecret(secretView: SecretView, projectId?: string) {
await this.secretService.create(this.data.organizationId, secretView, projectId);
private async createSecret(secretView: SecretView) {
await this.secretService.create(this.data.organizationId, secretView);
this.platformUtilsService.showToast("success", null, this.i18nService.t("secretCreated"));
}
@ -88,11 +142,14 @@ export class SecretDialogComponent implements OnInit {
}
private getSecretView() {
const emptyProjects: SecretProjectView[] = [];
const secretView = new SecretView();
secretView.organizationId = this.data.organizationId;
secretView.name = this.formGroup.value.name;
secretView.value = this.formGroup.value.value;
secretView.note = this.formGroup.value.notes;
secretView.projects = this.selectedProjects ? this.selectedProjects : emptyProjects;
return secretView;
}
}

View File

@ -2,5 +2,5 @@ export class SecretRequest {
key: string;
value: string;
note: string;
projectId?: string;
projectIds?: string[];
}

View File

@ -1,12 +1,14 @@
import { BaseResponse } from "@bitwarden/common/models/response/base.response";
import { SecretProjectResponse } from "./secret-project.response";
export class SecretListItemResponse extends BaseResponse {
id: string;
organizationId: string;
name: string;
creationDate: string;
revisionDate: string;
projects: string[];
projects: SecretProjectResponse[];
constructor(response: any) {
super(response);
@ -15,6 +17,8 @@ export class SecretListItemResponse extends BaseResponse {
this.name = this.getResponseProperty("Key");
this.creationDate = this.getResponseProperty("CreationDate");
this.revisionDate = this.getResponseProperty("RevisionDate");
this.projects = this.getResponseProperty("projects");
const project = this.getResponseProperty("projects");
this.projects = project == null ? null : project.map((k: any) => new SecretProjectResponse(k));
}
}

View File

@ -1,5 +1,7 @@
import { BaseResponse } from "@bitwarden/common/models/response/base.response";
import { SecretProjectResponse } from "./secret-project.response";
export class SecretResponse extends BaseResponse {
id: string;
organizationId: string;
@ -8,6 +10,7 @@ export class SecretResponse extends BaseResponse {
note: string;
creationDate: string;
revisionDate: string;
projects: SecretProjectResponse[];
constructor(response: any) {
super(response);
@ -18,5 +21,9 @@ export class SecretResponse extends BaseResponse {
this.note = this.getResponseProperty("Note");
this.creationDate = this.getResponseProperty("CreationDate");
this.revisionDate = this.getResponseProperty("RevisionDate");
const projects = this.getResponseProperty("Projects");
this.projects =
projects == null ? null : projects.map((k: any) => new SecretProjectResponse(k));
}
}

View File

@ -34,6 +34,7 @@ export class SecretService {
async getBySecretId(secretId: string): Promise<SecretView> {
const r = await this.apiService.send("GET", "/secrets/" + secretId, null, true, true);
const secretResponse = new SecretResponse(r);
return await this.createSecretView(secretResponse);
}
@ -63,8 +64,8 @@ export class SecretService {
return await this.createSecretsListView(organizationId, results);
}
async create(organizationId: string, secretView: SecretView, projectId?: string) {
const request = await this.getSecretRequest(organizationId, secretView, projectId);
async create(organizationId: string, secretView: SecretView) {
const request = await this.getSecretRequest(organizationId, secretView);
const r = await this.apiService.send(
"POST",
"/organizations/" + organizationId + "/secrets",
@ -106,8 +107,7 @@ export class SecretService {
private async getSecretRequest(
organizationId: string,
secretView: SecretView,
projectId?: string
secretView: SecretView
): Promise<SecretRequest> {
const orgKey = await this.getOrganizationKey(organizationId);
const request = new SecretRequest();
@ -119,7 +119,10 @@ export class SecretService {
request.key = key.encryptedString;
request.value = value.encryptedString;
request.note = note.encryptedString;
request.projectId = projectId;
request.projectIds = [];
secretView.projects?.forEach((e) => request.projectIds.push(e.id));
return request;
}
@ -141,6 +144,13 @@ export class SecretService {
secretView.value = value;
secretView.note = note;
if (secretResponse.projects != null) {
secretView.projects = await this.decryptProjectsMappedToSecrets(
orgKey,
secretResponse.projects
);
}
return secretView;
}
@ -150,7 +160,7 @@ export class SecretService {
): Promise<SecretListView[]> {
const orgKey = await this.getOrganizationKey(organizationId);
const projectsMappedToSecretsView = this.decryptProjectsMappedToSecrets(
const projectsMappedToSecretsView = await this.decryptProjectsMappedToSecrets(
orgKey,
secrets.projects
);
@ -166,9 +176,12 @@ export class SecretService {
);
secretListView.creationDate = s.creationDate;
secretListView.revisionDate = s.revisionDate;
secretListView.projects = (await projectsMappedToSecretsView).filter((p) =>
s.projects.includes(p.id)
const projectIds = s.projects?.map((p) => p.id);
secretListView.projects = projectsMappedToSecretsView.filter((p) =>
projectIds.includes(p.id)
);
return secretListView;
})
);

View File

@ -26,7 +26,7 @@
</label>
</th>
<th bitCell colspan="2">{{ "name" | i18n }}</th>
<th bitCell>{{ "projects" | i18n }}</th>
<th bitCell>{{ "project" | i18n }}</th>
<th bitCell>{{ "lastEdited" | i18n }}</th>
<th bitCell class="tw-w-0">
<button