dashboard/shell/edit/auth/oidc.vue

298 lines
8.2 KiB
Vue

<script>
import Loading from '@shell/components/Loading';
import CreateEditView from '@shell/mixins/create-edit-view';
import AuthConfig from '@shell/mixins/auth-config';
import CruResource from '@shell/components/CruResource';
import { LabeledInput } from '@components/Form/LabeledInput';
import { Banner } from '@components/Banner';
import AllowedPrincipals from '@shell/components/auth/AllowedPrincipals';
import FileSelector from '@shell/components/form/FileSelector';
import AuthBanner from '@shell/components/auth/AuthBanner';
import { RadioGroup } from '@components/Form/Radio';
export default {
components: {
Loading,
CruResource,
LabeledInput,
Banner,
AllowedPrincipals,
FileSelector,
AuthBanner,
RadioGroup
},
mixins: [CreateEditView, AuthConfig],
data() {
return {
customEndpoint: {
value: false,
labels: [
this.t('authConfig.oidc.customEndpoint.standard'),
this.t('authConfig.oidc.customEndpoint.custom'),
],
options: [
false,
true
]
},
keycloakUrls: {
url: null,
realm: null
}
};
},
computed: {
tArgs() {
return {
baseUrl: this.serverSetting,
provider: this.displayName,
username: this.principal.loginName || this.principal.name,
};
},
toSave() {
return {
enabled: true,
oidcConfig: this.model
};
},
},
watch: {
'keycloakUrls.url'() {
this.updateIssuerEndpoint();
},
'keycloakUrls.realm'() {
this.updateIssuerEndpoint();
},
'model.enabled'(neu) {
// Cover case where oidc gets disabled and we return to the edit screen with a reset model
if (!neu) {
this.keycloakUrls = {
url: null,
realm: null
};
this.customEndpoint.value = false;
}
},
editConfig(neu, old) {
// Cover use case where user edits existing oidc (keycloakUrls aren't persisted, so if we have issuer & authEndpoint set custom endpoints to true)
if (!old && neu) {
this.customEndpoint.value = (!this.keycloakUrls.url && !this.keycloakUrls.authEndpoint) && (!!this.model.issuer && !!this.model.authEndpoint);
}
}
},
methods: {
updateIssuerEndpoint() {
if (!this.keycloakUrls.url) {
return;
}
const url = this.keycloakUrls.url.replaceAll(' ', '');
this.model.issuer = `${ url }/auth/realms/${ this.keycloakUrls.realm || '' }`;
this.model.authEndpoint = `${ this.model.issuer || '' }/protocol/openid-connect/auth`;
},
}
};
</script>
<template>
<Loading v-if="$fetchState.pending" />
<div v-else>
<CruResource
:cancel-event="true"
:done-route="doneRoute"
:mode="mode"
:resource="model"
:subtypes="[]"
:validation-passed="true"
:finish-button-mode="model.enabled ? 'edit' : 'enable'"
:can-yaml="false"
:errors="errors"
:show-cancel="showCancel"
@error="e=>errors = e"
@finish="save"
@cancel="cancel"
>
<template v-if="model.enabled && !isEnabling && !editConfig">
<AuthBanner
:t-args="tArgs"
:disable="disable"
:edit="goToEdit"
>
<template slot="rows">
<tr><td>{{ t(`authConfig.oidc.rancherUrl`) }}: </td><td>{{ model.rancherUrl }}</td></tr>
<tr><td>{{ t(`authConfig.oidc.clientId`) }}: </td><td>{{ model.clientId }}</td></tr>
<tr><td>{{ t(`authConfig.oidc.issuer`) }}: </td><td>{{ model.issuer }}</td></tr>
<tr><td>{{ t(`authConfig.oidc.authEndpoint`) }}: </td><td>{{ model.authEndpoint }}</td></tr>
</template>
</AuthBanner>
<hr>
<AllowedPrincipals
:provider="NAME"
:auth-config="model"
:mode="mode"
/>
</template>
<template v-else>
<Banner
v-if="!model.enabled"
:label="t('authConfig.stateBanner.disabled', tArgs)"
color="warning"
/>
<h3>{{ t(`authConfig.oidc.${NAME}`) }}</h3>
<div class="row mb-20">
<div class="col span-6">
<LabeledInput
v-model="model.clientId"
:label="t(`authConfig.oidc.clientId`)"
:mode="mode"
required
/>
</div>
<div class="col span-6">
<LabeledInput
v-model="model.clientSecret"
:label="t(`authConfig.oidc.clientSecret`)"
:mode="mode"
required
/>
</div>
</div>
<div class="row mb-20">
<div class="col span-6">
<LabeledInput
v-model="model.privateKey"
:label="t(`authConfig.oidc.key.label`)"
:placeholder="t(`authConfig.oidc.key.placeholder`)"
:mode="mode"
type="multiline"
/>
<FileSelector
class="role-tertiary add mt-5"
:label="t('generic.readFromFile')"
:mode="mode"
@selected="$set(model, 'privateKey', $event)"
/>
</div>
<div class="col span-6">
<LabeledInput
v-model="model.certificate"
:label="t(`authConfig.oidc.cert.label`)"
:placeholder="t(`authConfig.oidc.cert.placeholder`)"
:mode="mode"
type="multiline"
/>
<FileSelector
class="role-tertiary add mt-5"
:label="t('generic.readFromFile')"
:mode="mode"
@selected="$set(model, 'certificate', $event)"
/>
</div>
</div>
<div class="row mb-20">
<div class="col span-6">
<RadioGroup
v-model="customEndpoint.value"
name="customEndpoint"
label-key="authConfig.oidc.customEndpoint.label"
:labels="customEndpoint.labels"
:options="customEndpoint.options"
>
<template #label>
<h4>{{ t('authConfig.oidc.customEndpoint.label') }}</h4>
</template>
</RadioGroup>
</div>
</div>
<div class="row mb-20">
<div class="col span-6">
<LabeledInput
v-model="keycloakUrls.url"
:label="t(`authConfig.oidc.keycloak.url`)"
:mode="mode"
:required="!customEndpoint.value"
:disabled="customEndpoint.value"
/>
</div>
<div class="col span-6">
<LabeledInput
v-model="keycloakUrls.realm"
:label="t(`authConfig.oidc.keycloak.realm`)"
:mode="mode"
:required="!customEndpoint.value"
:disabled="customEndpoint.value"
/>
</div>
</div>
<div class="row mb-20">
<div class="col span-6">
<LabeledInput
v-model="model.rancherUrl"
:label="t(`authConfig.oidc.rancherUrl`)"
:mode="mode"
required
:disabled="!customEndpoint.value"
/>
</div>
</div>
<div class="row mb-20">
<div class="col span-6">
<LabeledInput
v-model="model.issuer"
:label="t(`authConfig.oidc.issuer`)"
:mode="mode"
required
:disabled="!customEndpoint.value"
/>
</div>
<div class="col span-6">
<LabeledInput
v-model="model.authEndpoint"
:label="t(`authConfig.oidc.authEndpoint`)"
:mode="mode"
required
:disabled="!customEndpoint.value"
/>
</div>
</div>
</template>
<div
v-if="!model.enabled"
class="row"
>
<div class="col span-12">
<Banner
v-clean-html="t('authConfig.associatedWarning', tArgs, true)"
color="info"
/>
</div>
</div>
</CruResource>
</div>
</template>
<style lang="scss" scoped>
.banner {
display: block;
&::v-deep code {
padding: 0 3px;
margin: 0 3px;
}
}
</style>