Squash commits (#4382)
Co-authored-by: Hinton <hinton@users.noreply.github.com>
This commit is contained in:
parent
049940d04b
commit
0a5f96c560
|
@ -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"
|
||||||
},
|
},
|
||||||
|
|
|
@ -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[];
|
||||||
}
|
}
|
||||||
|
|
|
@ -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">
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,5 +2,5 @@ export class SecretRequest {
|
||||||
key: string;
|
key: string;
|
||||||
value: string;
|
value: string;
|
||||||
note: string;
|
note: string;
|
||||||
projectId?: string;
|
projectIds?: string[];
|
||||||
}
|
}
|
||||||
|
|
|
@ -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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue