Files
oci-portal-dash/src/components/tenant/IdpFormModal.vue
T
Wang Defa 5464d2dfea
CI / test (push) Successful in 27s
Release / release (push) Successful in 26s
发布 0.1.0:通知渠道、告警规则与多项体验修复
2026-07-10 17:31:19 +08:00

220 lines
7.8 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup lang="ts">
import { NCheckbox, NInput, NSelect, NSwitch } from 'naive-ui'
import { computed, reactive, ref, watch } from 'vue'
import { createIdp, getIdentitySetting, type CreateIdpBody } from '@/api/tenant'
import FilePicker from '@/components/FilePicker.vue'
import FormField from '@/components/FormField.vue'
import FormModal from '@/components/FormModal.vue'
import { useToast } from '@/composables/useToast'
const props = defineProps<{ show: boolean; cfgId: number; domainId?: string }>()
const emit = defineEmits<{ 'update:show': [boolean]; created: [] }>()
const message = useToast()
const submitting = ref(false)
// 「用户需要提供主电子邮件地址」开启时 JIT 必须映射邮箱
const primaryEmailRequired = ref(false)
const nameIdFormatOptions = [
{ label: '无', value: 'saml-none' },
{ label: '电子邮件地址', value: 'saml-emailaddress' },
{ label: '持久标识符', value: 'saml-persistent' },
{ label: '临时标识符', value: 'saml-transient' },
{ label: '未指定', value: 'saml-unspecified' },
]
const idpAttrOptions = [
{ label: 'SAML 断言名称 IDNameID', value: 'nameid' },
{ label: '断言属性', value: 'attribute' },
]
const userAttrOptions = [
{ label: '用户名(userName', value: 'userName' },
{ label: '主电子邮件(emails.primary.value', value: 'emails.primary.value' },
]
// 默认值与控制台一致:名称 ID 格式「无」、NameID 映射用户名、
// JIT 开启建用户不更新、分配租户管理员组
const blank = {
name: '',
metadata: '',
description: '',
iconUrl: '',
nameIdFormat: 'saml-none',
idpUserAttr: 'nameid',
assertionAttr: '',
userStoreAttribute: 'userName',
jitEnabled: true,
jitCreate: true,
jitUpdate: false,
jitAssignAdminGroup: true,
jitMapEmail: false,
}
const form = reactive({ ...blank })
watch(
() => props.show,
(v) => {
if (!v) return
Object.assign(form, blank)
void loadIdentitySetting()
},
)
async function loadIdentitySetting() {
try {
const s = await getIdentitySetting(props.cfgId)
primaryEmailRequired.value = s.primaryEmailRequired
if (s.primaryEmailRequired) form.jitMapEmail = true
} catch {
primaryEmailRequired.value = false
}
}
// IDCS 只接受 http(s) 外链图标,data URI 会被拒(error.saml.identityprovider.invalidIconUrl
const iconUrlValid = computed(() => {
const v = form.iconUrl.trim()
return !v || /^https?:\/\//.test(v)
})
const canSubmit = computed(
() =>
!!form.name.trim() &&
form.metadata.includes('EntityDescriptor') &&
iconUrlValid.value &&
(form.idpUserAttr !== 'attribute' || !!form.assertionAttr.trim()),
)
function buildBody(): CreateIdpBody {
return {
name: form.name.trim(),
metadata: form.metadata,
description: form.description.trim() || undefined,
iconUrl: form.iconUrl.trim() || undefined,
nameIdFormat: form.nameIdFormat,
assertionAttribute: form.idpUserAttr === 'attribute' ? form.assertionAttr.trim() : undefined,
userStoreAttribute: form.userStoreAttribute,
jitEnabled: form.jitEnabled,
jitCreate: form.jitCreate,
jitUpdate: form.jitUpdate,
jitAssignAdminGroup: form.jitAssignAdminGroup,
jitMapEmail: form.jitEnabled && form.jitMapEmail,
}
}
async function submit() {
submitting.value = true
try {
const created = await createIdp(props.cfgId, buildBody(), props.domainId)
message.success(`IdP「${created.name}」已创建(禁用态),激活后显示到登录页`)
emit('update:show', false)
emit('created')
} catch (e) {
message.error(e instanceof Error ? e.message : '创建失败')
} finally {
submitting.value = false
}
}
</script>
<template>
<FormModal
:show="show"
title="创建 SAML IdP"
:width="640"
:submitting="submitting"
:submit-disabled="!canSubmit"
submit-text="创建禁用态"
@update:show="emit('update:show', $event)"
@submit="submit"
>
<FormField label="名称" required>
<NInput v-model:value="form.name" placeholder="my-idp" />
</FormField>
<FormField
label="IdP SAML metadata XML 文件"
required
hint="IdP 侧导出的 metadata XML;先在本页「下载 SP 元数据」导入 IdP 侧再创建"
:error="
form.metadata && !form.metadata.includes('EntityDescriptor')
? '文件内容不含 EntityDescriptor,请确认选择的是 SAML metadata XML'
: undefined
"
:feedback="
form.metadata.includes('EntityDescriptor')
? `已读取 ${(form.metadata.length / 1024).toFixed(1)} KB`
: undefined
"
>
<FilePicker
:key="`md-${show}`"
label="拖拽或点击上传 SAML metadata 文件"
hint="支持 .xml,文件内容仅本地读取"
accept=".xml"
@load="(text) => (form.metadata = text)"
/>
</FormField>
<div class="grid grid-cols-2 gap-3 max-md:grid-cols-1">
<FormField label="备注">
<NInput v-model:value="form.description" placeholder="可选" />
</FormField>
<FormField
label="Logo(登录页图标)"
hint="展示在登录页 IdP 按钮上;身份域只接受 http(s) 外链图片 URL,不支持上传"
:error="iconUrlValid ? undefined : '需为 http(s):// 开头的图片外链 URL'"
>
<div class="flex items-center gap-2">
<img
v-if="form.iconUrl && iconUrlValid"
:src="form.iconUrl"
alt=""
class="h-7 w-7 flex-none rounded border border-line-soft object-contain"
/>
<NInput v-model:value="form.iconUrl" placeholder="https://…/logo.png" />
</div>
</FormField>
</div>
<div class="mt-1 mb-2 border-t border-line-soft pt-3 text-[12.5px] font-semibold">
映射用户身份
</div>
<FormField label="请求的名称 ID 格式" hint="向 IdP 请求断言时期望的 NameID 格式">
<NSelect v-model:value="form.nameIdFormat" :options="nameIdFormatOptions" />
</FormField>
<div class="grid grid-cols-2 gap-3 max-md:grid-cols-1">
<FormField label="IdP 用户属性" hint="从 SAML 断言中取哪个值来识别用户">
<NSelect v-model:value="form.idpUserAttr" :options="idpAttrOptions" />
</FormField>
<FormField label="域用户属性" hint="用取到的值匹配身份域中用户的哪个属性">
<NSelect v-model:value="form.userStoreAttribute" :options="userAttrOptions" />
</FormField>
</div>
<FormField v-if="form.idpUserAttr === 'attribute'" label="断言属性名" required>
<NInput v-model:value="form.assertionAttr" placeholder="如 mail、uid" />
</FormField>
<div class="mt-1 mb-2 flex items-center justify-between border-t border-line-soft pt-3">
<span class="text-[12.5px] font-semibold">JIT 预配</span>
<NSwitch v-model:value="form.jitEnabled" size="small" />
</div>
<div v-if="form.jitEnabled" class="mb-2 flex flex-col gap-1.5">
<NCheckbox v-model:checked="form.jitCreate">首次联邦登录时自动创建用户</NCheckbox>
<NCheckbox v-model:checked="form.jitUpdate">每次登录时按断言更新既有用户</NCheckbox>
<NCheckbox v-model:checked="form.jitAssignAdminGroup">
JIT 用户加入租户管理员组
</NCheckbox>
<NCheckbox
v-model:checked="form.jitMapEmail"
:disabled="primaryEmailRequired"
>
映射邮箱属性断言 email 主电子邮件
<span v-if="primaryEmailRequired" class="text-xs text-ink-3">
域已开启用户需要提供主电子邮件地址」,必须映射
</span>
</NCheckbox>
</div>
<div class="text-xs leading-relaxed text-ink-3">
创建后为禁用态需手动激活映射与 JIT 均按上方配置写入身份域
</div>
</FormModal>
</template>