import { HttpClient } from '@angular/common/http'; import { Component, OnInit } from '@angular/core'; import { MatDialog } from '@angular/material/dialog'; import { ActivatedRoute, Router } from '@angular/router'; import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy'; import * as _ from 'lodash'; import { helptext_system_ca } from 'app/helptext/system/ca'; import { helptext_system_certificates } from 'app/helptext/system/certificates'; import { Option } from 'app/interfaces/option.interface'; import { DialogFormConfiguration } from 'app/pages/common/entity/entity-dialog/dialog-form-configuration.interface'; import { FieldConfig } from 'app/pages/common/entity/entity-form/models/field-config.interface'; import { EntityFormService } from 'app/pages/common/entity/entity-form/services/entity-form.service'; import { AppTableAction } from 'app/pages/common/entity/table/table.component'; import { EntityUtils } from 'app/pages/common/entity/utils'; import { SystemGeneralService, WebSocketService, AppLoaderService, DialogService, StorageService, } from 'app/services'; import { ModalService } from 'app/services/modal.service'; import { T } from 'app/translate-marker'; import { AcmednsFormComponent } from './forms/acmedns-form.component'; import { CertificateAuthorityAddComponent } from './forms/ca-add.component'; import { CertificateAuthorityEditComponent } from './forms/ca-edit.component'; import { CertificateAcmeAddComponent } from './forms/certificate-acme-add.component'; import { CertificateAddComponent } from './forms/certificate-add.component'; import { CertificateEditComponent } from './forms/certificate-edit.component'; @UntilDestroy() @Component({ selector: 'app-certificates-dash', templateUrl: './certificates-dash.component.html', providers: [EntityFormService], }) export class CertificatesDashComponent implements OnInit { cards: any; protected certificateAddComponent: CertificateAddComponent; protected certificateEditComponent: CertificateEditComponent; protected certificateAuthorityAddComponent: CertificateAuthorityAddComponent; protected certificateAuthorityEditComponent: CertificateAuthorityEditComponent; protected acmeAddComponent: CertificateAcmeAddComponent; protected acmeDNSComponent: AcmednsFormComponent; private downloadActions: any; private unsignedCAs: Option[] = []; private caId: any; constructor( private modalService: ModalService, private ws: WebSocketService, private dialog: MatDialog, private systemGeneralService: SystemGeneralService, private loader: AppLoaderService, private dialogService: DialogService, private entityFormService: EntityFormService, private storage: StorageService, private http: HttpClient, ) { } ngOnInit(): void { this.getCards(); this.modalService.refreshTable$.pipe(untilDestroyed(this)).subscribe(() => { this.getCards(); }); this.refreshForms(); this.modalService.refreshForm$.pipe(untilDestroyed(this)).subscribe(() => { this.refreshForms(); }); this.modalService.message$.pipe(untilDestroyed(this)).subscribe((res: any) => { if (res['action'] === 'open' && res['component'] === 'acmeComponent') { this.openForm(this.acmeAddComponent, res['row']); } }); this.systemGeneralService.getUnsignedCertificates().pipe(untilDestroyed(this)).subscribe((res: any[]) => { res.forEach((item) => { this.unsignedCAs.push( { label: item.name, value: parseInt(item.id) }, ); }); }); } getCards(): void { this.cards = [ { name: 'certificates', tableConf: { title: T('Certificates'), queryCall: 'certificate.query', deleteCall: 'certificate.delete', deleteCallIsJob: true, complex: true, dataSourceHelper: this.certificatesDataSourceHelper, getActions: this.certificateActions.bind(this), columns: [ { name: T('Name'), prop1: 'name', name2: T('Issuer'), prop2: 'issuer', }, { name: T('From'), prop1: 'from', name2: T('Until'), prop2: 'until', }, { name: T('CN'), prop1: 'common', name2: T('SAN'), prop2: 'san', }, ], parent: this, add() { this.parent.modalService.open('slide-in-form', this.parent.certificateAddComponent); }, edit(row: any) { this.parent.modalService.open('slide-in-form', this.parent.certificateEditComponent, row.id); }, }, }, { name: 'CSRs', tableConf: { title: T('Certificate Signing Requests'), queryCall: 'certificate.query', deleteCall: 'certificate.delete', deleteCallIsJob: true, complex: true, dataSourceHelper: this.csrDataSourceHelper, getActions: this.csrActions.bind(this), columns: [ { name: T('Name'), prop1: 'name', name2: T('Issuer'), prop2: 'issuer', }, { name: T('CN'), prop1: 'common', name2: T('SAN'), prop2: 'san', }, ], parent: this, add() { this.parent.modalService.open('slide-in-form', this.parent.certificateAddComponent, 'csr'); }, edit(row: any) { this.parent.modalService.open('slide-in-form', this.parent.certificateEditComponent, row.id); }, }, }, { name: 'certificate-authorities', tableConf: { title: T('Certificate Authorities'), queryCall: 'certificateauthority.query', deleteCall: 'certificateauthority.delete', complex: true, dataSourceHelper: this.caDataSourceHelper, getActions: this.caActions.bind(this), columns: [ { name: T('Name'), prop1: 'name', name2: T('Issuer'), prop2: 'issuer', }, { name: T('From'), prop1: 'from', name2: T('Until'), prop2: 'until', }, { name: T('CN'), prop1: 'common', name2: T('SAN'), prop2: 'san', }, ], parent: this, add() { this.parent.modalService.open('slide-in-form', this.parent.certificateAuthorityAddComponent); }, edit(row: any) { this.parent.modalService.open('slide-in-form', this.parent.certificateAuthorityEditComponent, row.id); }, delete(row: any, table: any) { if (row.signed_certificates > 0) { (this.parent.dialogService as DialogService).confirm({ title: helptext_system_ca.delete_error.title, message: helptext_system_ca.delete_error.message, hideCheckBox: true, buttonMsg: helptext_system_ca.delete_error.button, hideCancel: true, }); } else { table.tableService.delete(table, row); } }, }, }, { name: 'acme-dns', tableConf: { title: T('ACME DNS-Authenticators'), queryCall: 'acme.dns.authenticator.query', deleteCall: 'acme.dns.authenticator.delete', complex: false, columns: [ { name: T('Name'), prop: 'name' }, { name: T('Authenticator'), prop: 'authenticator' }, ], parent: this, add() { this.parent.modalService.open('slide-in-form', this.parent.acmeDNSComponent); }, edit(row: any) { this.parent.modalService.open('slide-in-form', this.parent.acmeDNSComponent, row.id); }, }, }, ]; } certificatesDataSourceHelper(res: any[]): any[] { res.forEach((certificate) => { if (_.isObject(certificate.issuer)) { certificate.issuer = certificate.issuer.name; } }); return res.filter((item) => item.certificate !== null); } csrDataSourceHelper(res: any[]): any[] { return res.filter((item) => item.CSR !== null); } caDataSourceHelper(res: any[]): any[] { res.forEach((row) => { if (_.isObject(row.issuer)) { row.issuer = row.issuer.name; } }); return res; } refreshForms(): void { this.certificateAddComponent = new CertificateAddComponent( this.ws, this.dialog, this.systemGeneralService, this.modalService, this.loader, this.dialogService, ); this.certificateEditComponent = new CertificateEditComponent( this.ws, this.dialog, this.loader, this.dialogService, this.modalService, this.storage, this.http, ); this.certificateAuthorityAddComponent = new CertificateAuthorityAddComponent( this.ws, this.modalService, this.loader, this.dialogService, this.systemGeneralService, ); this.certificateAuthorityEditComponent = new CertificateAuthorityEditComponent(this.ws, this.loader, this.modalService, this.storage, this.http, this.dialogService, this.systemGeneralService); this.acmeAddComponent = new CertificateAcmeAddComponent(this.ws, this.loader, this.dialog, this.entityFormService, this.dialogService, this.modalService); this.acmeDNSComponent = new AcmednsFormComponent(this.ws, this.loader, this.dialogService, this.modalService); } certificateActions(): AppTableAction[] { this.downloadActions = [{ icon: 'save_alt', name: 'download', onClick: (rowinner: any) => { const path = rowinner.CSR ? rowinner.csr_path : rowinner.certificate_path; const fileName = rowinner.name + '.crt'; // what about for a csr? this.ws.call('core.download', ['filesystem.get', [path], fileName]).pipe(untilDestroyed(this)).subscribe( (res) => { const url = res[1]; const mimetype = 'application/x-x509-user-cert'; this.storage.streamDownloadFile(this.http, url, fileName, mimetype) .pipe(untilDestroyed(this)) .subscribe((file) => { this.storage.downloadBlob(file, fileName); }, (err) => { this.dialogService.errorReport(helptext_system_certificates.list.download_error_dialog.title, helptext_system_certificates.list.download_error_dialog.cert_message, `${err.status} - ${err.statusText}`); }); }, (err) => { new EntityUtils().handleWSError(this, err, this.dialog); }, ); const keyName = rowinner.name + '.key'; this.ws.call('core.download', ['filesystem.get', [rowinner.privatekey_path], keyName]).pipe(untilDestroyed(this)).subscribe( (res) => { const url = res[1]; const mimetype = 'text/plain'; this.storage.streamDownloadFile(this.http, url, keyName, mimetype) .pipe(untilDestroyed(this)) .subscribe((file) => { this.storage.downloadBlob(file, keyName); }, (err) => { this.dialogService.errorReport(helptext_system_certificates.list.download_error_dialog.title, helptext_system_certificates.list.download_error_dialog.key_message, `${err.status} - ${err.statusText}`); }); }, (err) => { new EntityUtils().handleWSError(this, err, this.dialog); }, ); event.stopPropagation(); }, }]; return this.downloadActions; } csrActions(): AppTableAction[] { const csrRowActions = [...this.downloadActions]; const acmeAction = { icon: 'build', name: 'create_ACME', matTooltip: T('Create ACME Certificate'), onClick: (rowinner: any) => { this.modalService.open('slide-in-form', this.acmeAddComponent, rowinner.id); event.stopPropagation(); }, }; csrRowActions.unshift(acmeAction); return csrRowActions; } caActions(): AppTableAction[] { const caRowActions = [...this.downloadActions]; const acmeAction = { icon: 'beenhere', name: 'sign_CSR', matTooltip: helptext_system_ca.list.action_sign, onClick: (rowinner: any) => { this.dialogService.dialogForm(this.signCSRFormConf); this.caId = rowinner.id; event.stopPropagation(); }, }; caRowActions.unshift(acmeAction); return caRowActions; } openForm(component: any, id: any): void { setTimeout(() => { this.modalService.open('slide-in-form', component, id); }, 200); } protected signCSRFieldConf: FieldConfig[] = [ { type: 'select', name: 'csr_cert_id', placeholder: helptext_system_ca.sign.csr_cert_id.placeholder, tooltip: helptext_system_ca.sign.csr_cert_id.tooltip, required: true, options: this.unsignedCAs, }, { type: 'input', name: 'name', placeholder: helptext_system_ca.edit.name.placeholder, tooltip: helptext_system_ca.sign.name.tooltip, }, ]; signCSRFormConf: DialogFormConfiguration = { title: helptext_system_ca.sign.fieldset_certificate, fieldConfig: this.signCSRFieldConf, method_ws: 'certificateauthority.ca_sign_csr', saveButtonText: helptext_system_ca.sign.sign, customSubmit: this.doSignCSR, parent: this, }; doSignCSR(entityDialog: any): void { const self = entityDialog.parent; const payload = { ca_id: self.caId, csr_cert_id: entityDialog.formGroup.controls.csr_cert_id.value, name: entityDialog.formGroup.controls.name.value, }; entityDialog.loader.open(); entityDialog.ws.call('certificateauthority.ca_sign_csr', [payload]).pipe(untilDestroyed(this)).subscribe(() => { entityDialog.loader.close(); self.dialogService.closeAllDialogs(); self.getCards(); }, (err: any) => { entityDialog.loader.close(); self.dialogService.errorReport(helptext_system_ca.error, err.reason, err.trace.formatted); }); } }