Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Hide download and email if the certificate has been signed by scep request #6778

Merged
merged 9 commits into from
Jan 19, 2022
1 change: 1 addition & 0 deletions db/pf-schema-X.Y.sql
Original file line number Diff line number Diff line change
Expand Up @@ -1455,6 +1455,7 @@ CREATE TABLE `pki_certs` (
`serial_number` varchar(255) DEFAULT NULL,
`dns_names` varchar(255) DEFAULT NULL,
`ip_addresses` varchar(255) DEFAULT NULL,
`scep` BOOLEAN DEFAULT FALSE,
UNIQUE KEY `cn` (`cn`),
KEY `profile_name` (`profile_name`),
KEY `valid_until` (`valid_until`),
Expand Down
7 changes: 7 additions & 0 deletions db/upgrade-X.X-X.Y.sql
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,13 @@ DELIMITER ;
call ValidateVersion;
DROP PROCEDURE IF EXISTS ValidateVersion;

\! echo "altering pki_certs"
ALTER TABLE pki_certs
ADD COLUMN IF NOT EXISTS `scep` BOOLEAN DEFAULT FALSE AFTER ip_addresses;

\! echo "set pki_certs.scep to true if private key is empty"
UPDATE TABLE pki_certs
nqb marked this conversation as resolved.
Show resolved Hide resolved
SET `scep`=1 WHERE `key` = "";

\! echo "Incrementing PacketFence schema version...";
INSERT IGNORE INTO pf_version (id, version, created_at) VALUES (@VERSION_INT, CONCAT_WS('.', @MAJOR_VERSION, @MINOR_VERSION), NOW());
Expand Down
5 changes: 3 additions & 2 deletions go/caddy/pfpki/models/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ type (
SerialNumber string `json:"serial_number,omitempty"`
DNSNames string `json:"dns_names,omitempty"`
IPAddresses string `json:"ip_addresses,omitempty"`
Scep *bool `json:"scep,omitempty" gorm:"default:false"`
}

// RevokedCert struct
Expand Down Expand Up @@ -519,8 +520,8 @@ func (c CA) Put(cn string, crt *x509.Certificate, options ...string) error {
if CaDB := c.DB.First(&ca, profiledb[0].CaID).Find(&ca); CaDB.Error != nil {
c.DB.First(&ca)
}

if err := c.DB.Create(&Cert{Cn: cn, Ca: ca, CaName: ca.Cn, ProfileName: profiledb[0].Name, SerialNumber: crt.SerialNumber.String(), Mail: attributeMap["emailAddress"], StreetAddress: attributeMap["streetAddress"], Organisation: attributeMap["O"], OrganisationalUnit: attributeMap["OU"], Country: attributeMap["C"], State: attributeMap["ST"], Locality: attributeMap["L"], PostalCode: attributeMap["emailAddress"], Profile: profiledb[0], Key: "", Cert: publicKey.String(), ValidUntil: crt.NotAfter}).Error; err != nil {
notFalse := true
if err := c.DB.Create(&Cert{Cn: cn, Ca: ca, CaName: ca.Cn, ProfileName: profiledb[0].Name, SerialNumber: crt.SerialNumber.String(), Mail: attributeMap["emailAddress"], StreetAddress: attributeMap["streetAddress"], Organisation: attributeMap["O"], OrganisationalUnit: attributeMap["OU"], Country: attributeMap["C"], State: attributeMap["ST"], Locality: attributeMap["L"], PostalCode: attributeMap["emailAddress"], Profile: profiledb[0], Key: "", Cert: publicKey.String(), ValidUntil: crt.NotAfter, Scep: &notFalse}).Error; err != nil {
return errors.New(dbError)
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<template>
<b-button v-if="!isClone && !isNew"
size="sm" variant="outline-primary" class="text-nowrap"
:disabled="disabled"
@click.stop.prevent="onClipboard"
>{{ $t('Copy Certificate') }}</b-button>
</template>
<script>
const props = {
id : {
type: [String, Number]
},
isClone: {
type: Boolean
},
isNew: {
type: Boolean
},
disabled: {
type: Boolean
}
}

import { toRefs } from '@vue/composition-api'
import i18n from '@/utils/locale'

const setup = (props, context) => {

const {
id
} = toRefs(props)

const { root: { $store } = {} } = context

const onClipboard = () => {
$store.dispatch('$_pkis/getCert', id.value).then(cert => {
try {
navigator.clipboard.writeText(cert.cert).then(() => {
$store.dispatch('notification/info', { message: i18n.t('<code>{cn}</code> certificate copied to clipboard', cert) })
}).catch(() => {
$store.dispatch('notification/danger', { message: i18n.t('Could not copy <code>{cn}</code> certificate to clipboard.', cert) })
})
} catch (e) {
$store.dispatch('notification/danger', { message: i18n.t('Clipboard not supported.') })
}
})
}

return {
onClipboard
}
}

// @vue/component
export default {
name: 'button-certificate-copy',
inheritAttrs: false,
props,
setup
}
</script>
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<template>
<b-button-group v-if="!isClone && !isNew">
<b-button-group v-if="!isClone && !isNew && !isScep">
<b-button size="sm" variant="outline-primary" :disabled="disabled || isLoading" @click="onShowModal">{{ $t('Download') }}</b-button>
<b-modal v-model="isShowModal"
size="lg" centered cancel-disabled>
Expand Down Expand Up @@ -67,7 +67,7 @@ const schema = yup.object({
password: yup.string().required(i18n.t('Password required.')).min(8)
})

import { computed, ref, toRefs } from '@vue/composition-api'
import { computed, ref, toRefs, watch } from '@vue/composition-api'
import { useDebouncedWatchHandler } from '@/composables/useDebounce'
import StoreModule from '../../_store'

Expand All @@ -82,6 +82,21 @@ const setup = (props, context) => {
if (!$store.state.$_pkis)
$store.registerModule('$_pkis', StoreModule)

const cert = ref({})
watch(id, () => {
if(!id.value) {
cert.value = {}
}
else {
$store.dispatch('$_pkis/getCert', id.value)
.then(_cert => cert.value = _cert)
}
}, { immediate: true })
const isScep = computed(() => {
const { scep } = cert.value
return scep
})

const isLoading = computed(() => $store.getters['$_pkis/isLoading'])
const rootRef = ref(null)
const clipboard = ref(false)
Expand All @@ -92,31 +107,29 @@ const setup = (props, context) => {
const onHideModal = () => { isShowModal.value = false }
const isValid = useDebouncedWatchHandler([form, isShowModal], () => (!rootRef.value || rootRef.value.$el.querySelectorAll('.is-invalid').length === 0))
const onDownload = () => {
$store.dispatch('$_pkis/getCert', id.value).then(cert => {
const { ca_id, profile_id } = cert
$store.dispatch('$_pkis/getProfile', profile_id).then(profile => {
$store.dispatch('$_pkis/getCa', ca_id).then(ca => {
const filename = `${ca.cn}-${profile.name}-${cert.cn}.p12`
const { password } = form.value || {}
$store.dispatch('$_pkis/downloadCert', { id: id.value, password }).then(arrayBuffer => {
if (clipboard.value) {
navigator.clipboard.writeText(password).then(() => {
$store.dispatch('notification/info', { message: i18n.t('Certificate password copied to clipboard') })
})
}
const blob = new Blob([arrayBuffer], { type: 'application/x-pkcs12' })
if (window.navigator.msSaveOrOpenBlob) {
window.navigator.msSaveBlob(blob, filename)
} else {
let elem = window.document.createElement('a')
elem.href = window.URL.createObjectURL(blob)
elem.download = filename
document.body.appendChild(elem)
elem.click()
document.body.removeChild(elem)
}
onHideModal()
})
const { ca_id, profile_id } = cert.value
$store.dispatch('$_pkis/getProfile', profile_id).then(profile => {
$store.dispatch('$_pkis/getCa', ca_id).then(ca => {
const filename = `${ca.cn}-${profile.name}-${cert.cn}.p12`
const { password } = form.value || {}
$store.dispatch('$_pkis/downloadCert', { id: id.value, password }).then(arrayBuffer => {
if (clipboard.value) {
navigator.clipboard.writeText(password).then(() => {
$store.dispatch('notification/info', { message: i18n.t('Certificate password copied to clipboard') })
})
}
const blob = new Blob([arrayBuffer], { type: 'application/x-pkcs12' })
if (window.navigator.msSaveOrOpenBlob) {
window.navigator.msSaveBlob(blob, filename)
} else {
let elem = window.document.createElement('a')
elem.href = window.URL.createObjectURL(blob)
elem.download = filename
document.body.appendChild(elem)
elem.click()
document.body.removeChild(elem)
}
onHideModal()
})
})
})
Expand All @@ -128,6 +141,7 @@ const setup = (props, context) => {
clipboard,
form,
schema: ref(schema),
isScep,
isValid,
isShowModal,
onShowModal,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<template>
<b-button v-if="!isClone && !isNew"
<b-button v-if="!isClone && !isNew && !isScep"
size="sm" variant="outline-primary" :disabled="disabled || isLoading" @click.stop.prevent="onEmail">{{ $t('Email') }}</b-button>
</template>
<script>
Expand Down Expand Up @@ -28,7 +28,7 @@ const props = {
}
}

import { computed, toRefs } from '@vue/composition-api'
import { computed, ref, toRefs, watch } from '@vue/composition-api'
import i18n from '@/utils/locale'
import StoreModule from '../../_store'

Expand All @@ -43,20 +43,34 @@ const setup = (props, context) => {
if (!$store.state.$_pkis)
$store.registerModule('$_pkis', StoreModule)

const cert = ref({})
watch(id, () => {
if(!id.value) {
cert.value = {}
}
else {
$store.dispatch('$_pkis/getCert', id.value)
.then(_cert => cert.value = _cert)
}
}, { immediate: true })
const isScep = computed(() => {
const { scep } = cert.value
return scep
})

const isLoading = computed(() => $store.getters['$_pkis/isLoading'])
const onEmail = () => {
$store.dispatch('$_pkis/getCert', id.value).then(cert => {
const { ID, cn, mail } = cert
$store.dispatch('$_pkis/emailCert', ID).then(() => {
$store.dispatch('notification/info', { message: i18n.t('Certificate <code>{cn}</code> emailed to <code>{mail}</code>.', { cn, mail }) })
}).catch(e => {
$store.dispatch('notification/danger', { message: i18n.t('Could not email certificate <code>{cn}</code> to <code>{mail}</code>: ', { cn, mail }) + e })
})
const { ID, cn, mail } = cert.value
$store.dispatch('$_pkis/emailCert', ID).then(() => {
$store.dispatch('notification/info', { message: i18n.t('Certificate <code>{cn}</code> emailed to <code>{mail}</code>.', { cn, mail }) })
}).catch(e => {
$store.dispatch('notification/danger', { message: i18n.t('Could not email certificate <code>{cn}</code> to <code>{mail}</code>: ', { cn, mail }) + e })
})
}

return {
isLoading,
isScep,
onEmail
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,9 +77,14 @@
:disabled="!isServiceAlive"
@click.stop.prevent="goToClone({ id: item.ID, ...item })"
>{{ $t('Clone') }}</b-button>
<button-certificate-download :disabled="!isServiceAlive" :id="item.ID" class="my-1 mr-1" />
<button-certificate-email :disabled="!isServiceAlive" :id="item.ID" class="my-1 mr-1" />
<button-certificate-revoke :disabled="!isServiceAlive" :id="item.ID" class="my-1 mr-1" @change="reSearch" />
<button-certificate-copy
:disabled="!isServiceAlive" :id="item.ID" class="my-1 mr-1" />
<button-certificate-download v-if="!item.scep"
:disabled="!isServiceAlive" :id="item.ID" class="my-1 mr-1" />
<button-certificate-email v-if="!item.scep"
:disabled="!isServiceAlive" :id="item.ID" class="my-1 mr-1" />
<button-certificate-revoke
:disabled="!isServiceAlive" :id="item.ID" class="my-1 mr-1" @change="reSearch" />
</span>
</template>
<template #cell(ca_name)="{ item }">
Expand All @@ -88,6 +93,9 @@
<template #cell(profile_name)="{ item }">
<router-link :is="(isServiceAlive) ? 'router-link' : 'span'" :to="{ name: 'pkiProfile', params: { id: item.profile_id } }">{{ item.profile_name }}</router-link>
</template>
<template #cell(scep)="{ item }">
<icon name="circle" :class="{ 'text-success': !!item.scep, 'text-danger': !item.scep }" />
</template>
</b-table>
<b-container fluid v-if="selected.length"
class="mt-3 p-0">
Expand All @@ -110,6 +118,7 @@ import {
BaseTableEmpty
} from '@/components/new/'
import {
ButtonCertificateCopy,
ButtonCertificateDownload,
ButtonCertificateEmail,
ButtonCertificateRevoke
Expand All @@ -121,6 +130,7 @@ const components = {
BaseSearch,
BaseSearchInputColumns,
BaseTableEmpty,
ButtonCertificateCopy,
ButtonCertificateDownload,
ButtonCertificateEmail,
ButtonCertificateRevoke
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {
BaseView,
ButtonCertificateCopy,
ButtonCertificateDownload,
ButtonCertificateEmail,
ButtonCertificateRevoke,
Expand Down Expand Up @@ -34,6 +35,7 @@ import { BButtonGroup } from 'bootstrap-vue'
const render = renderHOCWithScopedSlots(BaseView, { components, props, setup }, {
buttonsAppend: (h, props) => {
return h(BButtonGroup, {}, [
h(ButtonCertificateCopy, { props }),
h(ButtonCertificateDownload, { props }),
h(ButtonCertificateEmail, { props }),
h(ButtonCertificateRevoke, { props })
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
import {
BaseFormGroupChosenOneProfile
} from '../../_components/'
import ButtonCertificateCopy from './ButtonCertificateCopy'
import ButtonCertificateDownload from './ButtonCertificateDownload'
import ButtonCertificateEmail from './ButtonCertificateEmail'
import ButtonCertificateRevoke from './ButtonCertificateRevoke'
Expand All @@ -31,6 +32,7 @@ export {
BaseFormGroupInput as FormGroupStreetAddress,
BaseFormGroupInput as FormGroupPostalCode,

ButtonCertificateCopy,
ButtonCertificateDownload,
ButtonCertificateEmail,
ButtonCertificateRevoke,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,13 @@ export const useSearch = makeSearch('pkiCerts', {
sortable: true,
visible: true
},
{
key: 'scep',
label: 'SCEP', // i18n defer
required: true,
sortable: true,
visible: true
},
{
key: 'buttons',
class: 'text-right p-0',
Expand Down