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":{ "deleteSecrets":{
"message": "Delete Secrets" "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":{ "project":{
"message": "Project" "message": "Project"
}, },

View File

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

View File

@ -23,7 +23,43 @@
</bit-form-field> </bit-form-field>
</bit-tab> </bit-tab>
<bit-tab [label]="'serviceAccounts' | i18n"></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> </bit-tab-group>
</div> </div>
<div bitDialogFooter class="tw-flex tw-gap-2"> <div bitDialogFooter class="tw-flex tw-gap-2">

View File

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

View File

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

View File

@ -1,12 +1,14 @@
import { BaseResponse } from "@bitwarden/common/models/response/base.response"; import { BaseResponse } from "@bitwarden/common/models/response/base.response";
import { SecretProjectResponse } from "./secret-project.response";
export class SecretListItemResponse extends BaseResponse { export class SecretListItemResponse extends BaseResponse {
id: string; id: string;
organizationId: string; organizationId: string;
name: string; name: string;
creationDate: string; creationDate: string;
revisionDate: string; revisionDate: string;
projects: string[]; projects: SecretProjectResponse[];
constructor(response: any) { constructor(response: any) {
super(response); super(response);
@ -15,6 +17,8 @@ export class SecretListItemResponse extends BaseResponse {
this.name = this.getResponseProperty("Key"); this.name = this.getResponseProperty("Key");
this.creationDate = this.getResponseProperty("CreationDate"); this.creationDate = this.getResponseProperty("CreationDate");
this.revisionDate = this.getResponseProperty("RevisionDate"); 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 { BaseResponse } from "@bitwarden/common/models/response/base.response";
import { SecretProjectResponse } from "./secret-project.response";
export class SecretResponse extends BaseResponse { export class SecretResponse extends BaseResponse {
id: string; id: string;
organizationId: string; organizationId: string;
@ -8,6 +10,7 @@ export class SecretResponse extends BaseResponse {
note: string; note: string;
creationDate: string; creationDate: string;
revisionDate: string; revisionDate: string;
projects: SecretProjectResponse[];
constructor(response: any) { constructor(response: any) {
super(response); super(response);
@ -18,5 +21,9 @@ export class SecretResponse extends BaseResponse {
this.note = this.getResponseProperty("Note"); this.note = this.getResponseProperty("Note");
this.creationDate = this.getResponseProperty("CreationDate"); this.creationDate = this.getResponseProperty("CreationDate");
this.revisionDate = this.getResponseProperty("RevisionDate"); 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> { async getBySecretId(secretId: string): Promise<SecretView> {
const r = await this.apiService.send("GET", "/secrets/" + secretId, null, true, true); const r = await this.apiService.send("GET", "/secrets/" + secretId, null, true, true);
const secretResponse = new SecretResponse(r); const secretResponse = new SecretResponse(r);
return await this.createSecretView(secretResponse); return await this.createSecretView(secretResponse);
} }
@ -63,8 +64,8 @@ export class SecretService {
return await this.createSecretsListView(organizationId, results); return await this.createSecretsListView(organizationId, results);
} }
async create(organizationId: string, secretView: SecretView, projectId?: string) { async create(organizationId: string, secretView: SecretView) {
const request = await this.getSecretRequest(organizationId, secretView, projectId); const request = await this.getSecretRequest(organizationId, secretView);
const r = await this.apiService.send( const r = await this.apiService.send(
"POST", "POST",
"/organizations/" + organizationId + "/secrets", "/organizations/" + organizationId + "/secrets",
@ -106,8 +107,7 @@ export class SecretService {
private async getSecretRequest( private async getSecretRequest(
organizationId: string, organizationId: string,
secretView: SecretView, secretView: SecretView
projectId?: string
): Promise<SecretRequest> { ): Promise<SecretRequest> {
const orgKey = await this.getOrganizationKey(organizationId); const orgKey = await this.getOrganizationKey(organizationId);
const request = new SecretRequest(); const request = new SecretRequest();
@ -119,7 +119,10 @@ export class SecretService {
request.key = key.encryptedString; request.key = key.encryptedString;
request.value = value.encryptedString; request.value = value.encryptedString;
request.note = note.encryptedString; request.note = note.encryptedString;
request.projectId = projectId; request.projectIds = [];
secretView.projects?.forEach((e) => request.projectIds.push(e.id));
return request; return request;
} }
@ -141,6 +144,13 @@ export class SecretService {
secretView.value = value; secretView.value = value;
secretView.note = note; secretView.note = note;
if (secretResponse.projects != null) {
secretView.projects = await this.decryptProjectsMappedToSecrets(
orgKey,
secretResponse.projects
);
}
return secretView; return secretView;
} }
@ -150,7 +160,7 @@ export class SecretService {
): Promise<SecretListView[]> { ): Promise<SecretListView[]> {
const orgKey = await this.getOrganizationKey(organizationId); const orgKey = await this.getOrganizationKey(organizationId);
const projectsMappedToSecretsView = this.decryptProjectsMappedToSecrets( const projectsMappedToSecretsView = await this.decryptProjectsMappedToSecrets(
orgKey, orgKey,
secrets.projects secrets.projects
); );
@ -166,9 +176,12 @@ export class SecretService {
); );
secretListView.creationDate = s.creationDate; secretListView.creationDate = s.creationDate;
secretListView.revisionDate = s.revisionDate; 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; return secretListView;
}) })
); );

View File

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