import { Component, OnInit, OnDestroy } from '@angular/core'; import { ActivatedRoute, Router } from '@angular/router'; import { HttpClient } from '@angular/common/http'; import { Subscription } from 'rxjs'; import { MatDialog } from '@angular/material/dialog'; import { DialogFormConfiguration } from '../../common/entity/entity-dialog/dialog-form-configuration.interface'; import { FieldConfig } from '../../common/entity/entity-form/models/field-config.interface' import * as _ from 'lodash'; import { SystemGeneralService, WebSocketService, AppLoaderService, DialogService, StorageService } from '../../../services/'; import { EntityFormService } from '../../common/entity/entity-form/services/entity-form.service'; import { ModalService } from '../../../services/modal.service'; import { T } from '../../../translate-marker'; import { CertificateAddComponent } from './forms/certificate-add.component'; import { CertificateEditComponent } from './forms/certificate-edit.component'; import { CertificateAuthorityAddComponent } from './forms/ca-add.component'; import { CertificateAuthorityEditComponent } from './forms/ca-edit.component'; import { CertificateAuthoritySignComponent } from './forms/ca-sign.component'; import { CertificateAcmeAddComponent } from './forms/certificate-acme-add.component'; import { AcmednsFormComponent } from './forms/acmedns-form.component'; import { helptext_system_certificates } from 'app/helptext/system/certificates'; import { helptext_system_ca } from 'app/helptext/system/ca'; import { EntityUtils } from '../../common/entity/utils'; @Component({ selector: 'app-certificates-dash', templateUrl: './certificates-dash.component.html', providers: [EntityFormService] }) export class CertificatesDashComponent implements OnInit, OnDestroy { cards: any; refreshTable: Subscription; refreshForm: Subscription; message: Subscription; protected certificateAddComponent: CertificateAddComponent; protected certificateEditComponent: CertificateEditComponent; protected certificateAuthorityAddComponent: CertificateAuthorityAddComponent; protected certificateAuthorityEditComponent: CertificateAuthorityEditComponent; protected certificateAuthoritySignComponent: CertificateAuthoritySignComponent; protected acmeAddComponent: CertificateAcmeAddComponent; protected acmeDNSComponent: AcmednsFormComponent; private downloadActions: any; private unsignedCAs = []; private caId: any; constructor(private modalService: ModalService, private router: Router, private route: ActivatedRoute, 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.refreshTable = this.modalService.refreshTable$.subscribe(() => { this.getCards(); }) this.refreshForms(); this.refreshForm = this.modalService.refreshForm$.subscribe(() => { this.refreshForms(); }); this.message = this.modalService.message$.subscribe(res => { if (res['action'] === 'open' && res['component'] === 'acmeComponent') { this.openForm(this.acmeAddComponent, res['row']); } }) this.systemGeneralService.getUnsignedCertificates().subscribe( (res) => { res.forEach((item) => { this.unsignedCAs.push( { label : item.name, value : parseInt(item.id)} ); }); }) } getCards() { this.cards = [ { name: 'certificates', tableConf: { title: T('Certificates'), queryCall: 'certificate.query', deleteCall: 'certificate.delete', 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: function() { this.parent.modalService.open('slide-in-form', this.parent.certificateAddComponent); }, edit: function(row) { 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', 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: function() { this.parent.modalService.open('slide-in-form', this.parent.certificateAddComponent); }, edit: function(row) { 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: function() { this.parent.modalService.open('slide-in-form', this.parent.certificateAuthorityAddComponent); }, edit: function(row) { this.parent.modalService.open('slide-in-form', this.parent.certificateAuthorityEditComponent, row.id); } } }, { 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: function() { this.parent.modalService.open('slide-in-form', this.parent.acmeDNSComponent); }, edit: function(row) { this.parent.modalService.open('slide-in-form', this.parent.acmeDNSComponent, row.id); } } } ] } certificatesDataSourceHelper(res) { res.forEach(certificate => { if(_.isObject(certificate.issuer)) { certificate.issuer = certificate.issuer.name; } }) return res.filter(item => item.certificate !== null); } csrDataSourceHelper(res) { return res.filter(item => item.CSR !== null); } caDataSourceHelper(res) { res.forEach(row => { if (_.isObject(row.issuer)) { row.issuer = row.issuer.name; } }) return res; } refreshForms() { this.certificateAddComponent = new CertificateAddComponent( this.ws,this.dialog,this.systemGeneralService,this.modalService); 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.systemGeneralService); this.certificateAuthorityEditComponent = new CertificateAuthorityEditComponent(this.ws,this.loader, this.modalService,this.storage, this.http,this.dialogService,this.systemGeneralService); this.certificateAuthoritySignComponent = new CertificateAuthoritySignComponent(this.router,this.route, this.ws,this.systemGeneralService); this.acmeAddComponent = new CertificateAcmeAddComponent(this.router,this.route, 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() { this.downloadActions = [{ icon: 'save_alt', name: "download", onClick: (rowinner) => { 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]).subscribe( (res) => { const url = res[1]; const mimetype = 'application/x-x509-user-cert'; this.storage.streamDownloadFile(this.http, url, fileName, mimetype).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]).subscribe( (res) => { const url = res[1]; const mimetype = 'text/plain'; this.storage.streamDownloadFile(this.http, url, keyName, mimetype).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() { let csrRowActions = [...this.downloadActions]; const acmeAction = { icon: 'build', name: 'create_ACME', matTooltip: T('Create ACME Certificate'), onClick: (rowinner) => { this.modalService.open('slide-in-form', this.acmeAddComponent, rowinner.id); event.stopPropagation(); } } csrRowActions.unshift(acmeAction); return csrRowActions; } caActions() { let caRowActions = [...this.downloadActions]; const acmeAction = { icon: 'beenhere', name: 'sign_CSR', matTooltip: helptext_system_ca.list.action_sign, onClick: (rowinner) => { this.dialogService.dialogForm(this.signCSRFormConf); this.caId = rowinner.id; event.stopPropagation(); } } caRowActions.unshift(acmeAction); return caRowActions; } openForm(component, id) { 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 } ]; public 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) { 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]).subscribe(() => { entityDialog.loader.close(); self.dialogService.closeAllDialogs(); self.getCards(); }, (err) => { entityDialog.loader.close(); self.dialogService.errorReport(helptext_system_ca.error, err.reason, err.trace.formatted); }) } ngOnDestroy() { this.message.unsubscribe(); this.refreshTable.unsubscribe(); this.refreshForm.unsubscribe(); } }