refactor: 授权凭证优化

This commit is contained in:
meilin.huang
2024-04-10 23:17:20 +08:00
parent 40b6e603fc
commit 4ef8d27b1e
14 changed files with 360 additions and 129 deletions

View File

@@ -2,10 +2,13 @@ import EnumValue from './Enum';
// 标签关联的资源类型
export const TagResourceTypeEnum = {
AuthCert: EnumValue.of(-2, '公共凭证').setExtra({ icon: 'Ticket' }),
Tag: EnumValue.of(-1, '标签').setExtra({ icon: 'CollectionTag' }),
Machine: EnumValue.of(1, '机器').setExtra({ icon: 'Monitor' }),
Db: EnumValue.of(2, '数据库').setExtra({ icon: 'Coin' }),
Redis: EnumValue.of(3, 'redis').setExtra({ icon: 'iconfont icon-redis' }),
Mongo: EnumValue.of(4, 'mongo').setExtra({ icon: 'iconfont icon-mongo' }),
MachineAuthCert: EnumValue.of(11, '机器-授权凭证').setExtra({ icon: 'Ticket' }),
};

View File

@@ -2,26 +2,8 @@
<div class="auth-cert-edit">
<el-dialog title="凭证保存" v-model="dialogVisible" :show-close="false" width="500px" :destroy-on-close="true" :close-on-click-modal="false">
<el-form ref="acForm" :model="state.form" label-width="auto" :rules="rules">
<el-form-item prop="ciphertextType" label="密文类型" required>
<el-select
:disabled="form.id && props.resourceEdit"
v-model="form.ciphertextType"
placeholder="请选择密文类型"
@change="changeCiphertextType"
>
<el-option
v-for="item in AuthCertCiphertextTypeEnum"
:key="item.value"
:label="item.label"
:value="item.value"
v-show="!props.disableCiphertextType?.includes(item.value)"
>
</el-option>
</el-select>
</el-form-item>
<el-form-item prop="type" label="凭证类型" required>
<el-select style="width: 100%" v-model="form.type" placeholder="请选择凭证类型">
<el-select @change="changeType" v-model="form.type" placeholder="请选择凭证类型">
<el-option
v-for="item in AuthCertTypeEnum"
:key="item.value"
@@ -33,13 +15,44 @@
</el-select>
</el-form-item>
<el-form-item prop="ciphertextType" label="密文类型" required>
<el-select v-model="form.ciphertextType" placeholder="请选择密文类型" @change="changeCiphertextType">
<el-option
v-for="item in AuthCertCiphertextTypeEnum"
:key="item.value"
:label="item.label"
:value="item.value"
v-show="!props.disableCiphertextType?.includes(item.value)"
:disabled="item.value == AuthCertCiphertextTypeEnum.Public.value && form.type == AuthCertTypeEnum.Public.value"
>
</el-option>
</el-select>
</el-form-item>
<template v-if="showResourceEdit">
<el-form-item prop="type" label="资源类型" required>
<el-select :disabled="form.id" v-model="form.resourceType" placeholder="请选择资源类型">
<el-option
:key="TagResourceTypeEnum.Machine.value"
:label="TagResourceTypeEnum.Machine.label"
:value="TagResourceTypeEnum.Machine.value"
/>
<el-option :key="TagResourceTypeEnum.Db.value" :label="TagResourceTypeEnum.Db.label" :value="TagResourceTypeEnum.Db.value" />
</el-select>
</el-form-item>
<el-form-item prop="resourceCode" label="资源编号" required>
<el-input :disabled="form.id" v-model="form.resourceCode" placeholder="请输入资源编号"></el-input>
</el-form-item>
</template>
<el-form-item prop="name" label="名称" required>
<el-input :disabled="form.id" v-model="form.name"></el-input>
<el-input :disabled="form.id" v-model="form.name" placeholder="请输入凭证名 (全局唯一)"></el-input>
</el-form-item>
<template v-if="form.ciphertextType != AuthCertCiphertextTypeEnum.Public.value">
<el-form-item prop="username" label="用户名">
<el-input :disabled="form.id && props.resourceEdit" v-model="form.username"></el-input>
<el-input v-model="form.username"></el-input>
</el-form-item>
<el-form-item v-if="form.ciphertextType == AuthCertCiphertextTypeEnum.Password.value" prop="ciphertext" label="密码">
@@ -97,11 +110,12 @@
</template>
<script lang="ts" setup>
import { reactive, ref, toRefs, onMounted, watch } from 'vue';
import { reactive, ref, toRefs, onMounted, watch, computed } from 'vue';
import { AuthCertTypeEnum, AuthCertCiphertextTypeEnum } from '../tag/enums';
import EnumTag from '@/components/enumtag/EnumTag.vue';
import { resourceAuthCertApi } from '../tag/api';
import { ResourceCodePattern } from '@/common/pattern';
import { TagResourceTypeEnum } from '@/common/commonEnum';
const props = defineProps({
authCert: {
@@ -113,7 +127,7 @@ const props = defineProps({
disableType: {
type: Array,
},
// 是否为资源编辑该授权凭证
// 是否为资源编辑该授权凭证,即机器编辑等页面等
resourceEdit: {
type: Boolean,
default: true,
@@ -126,6 +140,8 @@ const DefaultForm = {
username: '',
ciphertextType: AuthCertCiphertextTypeEnum.Password.value,
type: AuthCertTypeEnum.Private.value,
resourceType: TagResourceTypeEnum.AuthCert.value,
resourceCode: '',
ciphertext: '',
extra: {} as any,
remark: '',
@@ -158,6 +174,10 @@ const state = reactive({
publicAuthCerts: [] as any,
});
const showResourceEdit = computed(() => {
return state.form.type != AuthCertTypeEnum.Public.value && !props.resourceEdit;
});
onMounted(() => {
setForm(props.authCert);
});
@@ -170,6 +190,7 @@ watch(
);
const setForm = (val: any) => {
val = { ...val };
if (!val.extra) {
val.extra = {};
}
@@ -181,8 +202,16 @@ const setForm = (val: any) => {
const { form, btnLoading } = toRefs(state);
const changeType = (val: any) => {
// 如果选择了公共凭证,则需要保证密文类型不能为公共凭证
if (val == AuthCertTypeEnum.Public.value && state.form.ciphertextType == AuthCertCiphertextTypeEnum.Public.value) {
state.form.ciphertextType = AuthCertCiphertextTypeEnum.Password.value;
}
};
const changeCiphertextType = (val: any) => {
if (val == AuthCertCiphertextTypeEnum.Public.value) {
state.form.type = AuthCertTypeEnum.Private.value;
getPublicAuthCerts();
}
};
@@ -211,6 +240,7 @@ const cancelEdit = () => {
dialogVisible.value = false;
setTimeout(() => {
state.form = { ...DefaultForm };
acForm.value?.resetFields();
}, 300);
};

View File

@@ -6,7 +6,7 @@
<el-button v-auth="'authcert:save'" class="ml0" type="primary" circle size="small" icon="Plus" @click="edit(null)"> </el-button>
</template>
<template #default="scope">
<el-button v-auth="'authcert:save'" @click="edit({ ...scope.row }, scope.$index)" type="primary" icon="edit" link></el-button>
<el-button v-auth="'authcert:save'" @click="edit(scope.row, scope.$index)" type="primary" icon="edit" link></el-button>
<el-button class="ml1" v-auth="'authcert:del'" type="danger" @click="deleteRow(scope.$index)" icon="delete" link></el-button>
<el-button

View File

@@ -274,7 +274,7 @@ const perms = {
};
const searchItems = [
getTagPathSearchItem(TagResourceTypeEnum.Machine.value),
getTagPathSearchItem(TagResourceTypeEnum.MachineAuthCert.value),
SearchItem.input('code', '编号'),
SearchItem.input('ip', 'IP'),
SearchItem.input('name', '名称'),

View File

@@ -5,17 +5,16 @@
:page-api="resourceAuthCertApi.listByQuery"
:search-items="state.searchItems"
v-model:query-form="query"
:show-selection="true"
v-model:selection-data="selectionData"
:columns="state.columns"
>
<template #tableHeader>
<el-button v-auth="'authcert:save'" type="primary" icon="plus" @click="edit(false)">添加</el-button>
<el-button v-auth="'authcert:del'" :disabled="disabledDelBtn" @click="deleteAc(selectionData)" type="danger" icon="delete">删除 </el-button>
</template>
<template #action="{ data }">
<el-button v-auth="'authcert:save'" v-if="data.type == AuthCertTypeEnum.Public.value" @click="edit(data)" type="primary" link>编辑 </el-button>
<el-button v-auth="'authcert:save'" @click="edit(data)" type="primary" link>编辑</el-button>
<el-button v-auth="'authcert:del'" @click="deleteAc(data)" type="danger" link>删除</el-button>
</template>
</page-table>
@@ -23,15 +22,15 @@
v-model:visible="editor.visible"
:auth-cert="editor.authcert"
@confirm="confirmSave"
:disable-type="state.disableAuthCertType"
:disable-ciphertext-type="state.disableAuthCertCiphertextType"
:resource-edit="false"
:disable-ciphertext-type="[AuthCertCiphertextTypeEnum.Public.value]"
:disable-type="[AuthCertTypeEnum.Private.value, AuthCertTypeEnum.PrivateDefault.value, AuthCertTypeEnum.Privileged.value]"
/>
</div>
</template>
<script lang="ts" setup>
import { toRefs, reactive, onMounted, ref, Ref, computed } from 'vue';
import { toRefs, reactive, onMounted, ref, Ref } from 'vue';
import { resourceAuthCertApi } from './api';
import { ElMessage, ElMessageBox } from 'element-plus';
import PageTable from '@/components/pagetable/PageTable.vue';
@@ -55,19 +54,18 @@ const state = reactive({
],
columns: [
TableColumn.new('name', '名称'),
TableColumn.new('type', '凭证类型').typeTag(AuthCertTypeEnum),
TableColumn.new('username', '用户名'),
TableColumn.new('ciphertextType', '密文类型').typeTag(AuthCertCiphertextTypeEnum),
TableColumn.new('type', '凭证类型').typeTag(AuthCertTypeEnum),
TableColumn.new('resourceType', '资源类型').typeTag(TagResourceTypeEnum),
TableColumn.new('resourceCode', '资源编号'),
TableColumn.new('remark', '备注'),
TableColumn.new('creator', '创建人'),
TableColumn.new('createTime', '创建时间').isTime(),
TableColumn.new('creator', '修改者'),
TableColumn.new('createTime', '修改时间').isTime(),
TableColumn.new('action', '操作').isSlot().fixedRight().setMinWidth(65).alignCenter(),
TableColumn.new('modifier', '修改者'),
TableColumn.new('updateTime', '修改时间').isTime(),
TableColumn.new('action', '操作').isSlot().fixedRight().setMinWidth(120).alignCenter(),
],
selectionData: [],
paramsDialog: {
visible: false,
config: null as any,
@@ -79,9 +77,11 @@ const state = reactive({
visible: false,
authcert: {},
},
disableAuthCertType: [] as any,
disableAuthCertCiphertextType: [] as any,
});
const { query, selectionData, editor } = toRefs(state);
const { query, editor } = toRefs(state);
onMounted(() => {});
@@ -89,13 +89,19 @@ const search = async () => {
pageTableRef.value.search();
};
const disabledDelBtn = computed(() => {
return state.selectionData.length < 1 || state.selectionData.find((item: any) => item.type != AuthCertTypeEnum.Public.value);
});
const edit = (data: any) => {
state.disableAuthCertType = [];
state.disableAuthCertCiphertextType = [];
if (data) {
state.editor.authcert = data;
// 如果数据为公共授权凭证,则不允许修改凭证类型
if (data.type == AuthCertTypeEnum.Public.value) {
state.disableAuthCertType = [AuthCertTypeEnum.Private.value, AuthCertTypeEnum.PrivateDefault.value, AuthCertTypeEnum.Privileged.value];
state.disableAuthCertCiphertextType = [AuthCertCiphertextTypeEnum.Public.value];
} else {
// 如果非公共凭证,也无法修改为公共凭证
state.disableAuthCertType = [AuthCertTypeEnum.Public.value];
}
} else {
state.editor.authcert = {
type: AuthCertTypeEnum.Public.value,
@@ -116,12 +122,12 @@ const confirmSave = async (authCert: any) => {
const deleteAc = async (data: any) => {
try {
await ElMessageBox.confirm(`确定删除该【${data.map((x: any) => x.name).join(', ')}授权凭证?`, '提示', {
await ElMessageBox.confirm(`确定删除该【${data.name}授权凭证?`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
});
await resourceAuthCertApi.delete.request({ id: data.map((x: any) => x.id).join(',') });
await resourceAuthCertApi.delete.request({ id: data.id });
ElMessage.success('删除成功');
search();
} catch (err) {

View File

@@ -78,19 +78,31 @@
</el-descriptions>
</el-tab-pane>
<el-tab-pane v-if="currentTag.type == TagResourceTypeEnum.Tag.value" :label="`机器 (${resourceCount.machine})`" :name="MachineTag">
<el-tab-pane
:disabled="currentTag.type != TagResourceTypeEnum.Tag.value"
:label="`机器 (${resourceCount.machine || 0})`"
:name="MachineTag"
>
<MachineList lazy ref="machineListRef" />
</el-tab-pane>
<el-tab-pane v-if="currentTag.type == TagResourceTypeEnum.Tag.value" :label="`数据库 (${resourceCount.db})`" :name="DbTag">
<el-tab-pane :disabled="currentTag.type != TagResourceTypeEnum.Tag.value" :label="`数据库 (${resourceCount.db || 0})`" :name="DbTag">
<DbList lazy ref="dbListRef" />
</el-tab-pane>
<el-tab-pane v-if="currentTag.type == TagResourceTypeEnum.Tag.value" :label="`Redis (${resourceCount.redis})`" :name="RedisTag">
<el-tab-pane
:disabled="currentTag.type != TagResourceTypeEnum.Tag.value"
:label="`Redis (${resourceCount.redis || 0})`"
:name="RedisTag"
>
<RedisList lazy ref="redisListRef" />
</el-tab-pane>
<el-tab-pane v-if="currentTag.type == TagResourceTypeEnum.Tag.value" :label="`Mongo (${resourceCount.mongo})`" :name="MongoTag">
<el-tab-pane
:disabled="currentTag.type != TagResourceTypeEnum.Tag.value"
:label="`Mongo (${resourceCount.mongo || 0})`"
:name="MongoTag"
>
<MongoList lazy ref="mongoListRef" />
</el-tab-pane>
</el-tabs>
@@ -233,9 +245,12 @@ watch(filterTag, (val) => {
watch(
() => state.currentTag,
(val: any) => {
tagApi.countTagResource.request({ tagPath: val.codePath }).then((res: any) => {
state.resourceCount = res;
});
if (val.type == TagResourceTypeEnum.Tag.value) {
tagApi.countTagResource.request({ tagPath: val.codePath }).then((res: any) => {
state.resourceCount = res;
});
}
setNowTabData();
}
);

View File

@@ -86,7 +86,6 @@ func (m *machineAppImpl) SaveMachine(ctx context.Context, param *SaveMachinePara
tagIds := param.TagIds
authCerts := param.AuthCerts
resourceType := tagentity.TagTypeMachine
authCertTagType := tagentity.TagTypeMachineAuthCert
oldMachine := &entity.Machine{
Ip: me.Ip,
@@ -119,11 +118,10 @@ func (m *machineAppImpl) SaveMachine(ctx context.Context, param *SaveMachinePara
return err
}
return m.resourceAuthCertApp.SaveAuthCert(ctx, &tagapp.SaveAuthCertParam{
ResourceCode: me.Code,
ResourceType: resourceType,
AuthCertTagType: authCertTagType,
AuthCerts: authCerts,
return m.resourceAuthCertApp.RelateAuthCert(ctx, &tagapp.RelateAuthCertParam{
ResourceCode: me.Code,
ResourceType: resourceType,
AuthCerts: authCerts,
})
}
@@ -152,11 +150,10 @@ func (m *machineAppImpl) SaveMachine(ctx context.Context, param *SaveMachinePara
return err
}
return m.resourceAuthCertApp.SaveAuthCert(ctx, &tagapp.SaveAuthCertParam{
ResourceCode: oldMachine.Code,
ResourceType: resourceType,
AuthCertTagType: authCertTagType,
AuthCerts: authCerts,
return m.resourceAuthCertApp.RelateAuthCert(ctx, &tagapp.RelateAuthCertParam{
ResourceCode: oldMachine.Code,
ResourceType: resourceType,
AuthCerts: authCerts,
})
}
@@ -216,7 +213,7 @@ func (m *machineAppImpl) Delete(ctx context.Context, id uint64) error {
ResourceType: resourceType,
})
}, func(ctx context.Context) error {
return m.resourceAuthCertApp.SaveAuthCert(ctx, &tagapp.SaveAuthCertParam{
return m.resourceAuthCertApp.RelateAuthCert(ctx, &tagapp.RelateAuthCertParam{
ResourceCode: machine.Code,
ResourceType: resourceType,
})

View File

@@ -6,8 +6,6 @@ import (
"mayfly-go/internal/tag/domain/entity"
"mayfly-go/pkg/biz"
"mayfly-go/pkg/req"
"mayfly-go/pkg/utils/collx"
"strings"
"github.com/may-fly/cast"
)
@@ -51,25 +49,11 @@ func (c *ResourceAuthCert) SaveAuthCert(rc *req.Ctx) {
acForm.Ciphertext = "***"
rc.ReqParam = acForm
biz.ErrIsNil(c.ResourceAuthCertApp.SavePulbicAuthCert(rc.MetaCtx, ac))
biz.ErrIsNil(c.ResourceAuthCertApp.SaveAuthCert(rc.MetaCtx, ac))
}
func (c *ResourceAuthCert) Delete(rc *req.Ctx) {
idsStr := rc.PathParam("id")
ids := strings.Split(idsStr, ",")
acIds := make([]uint64, 0)
acNames := make([]string, 0)
for _, v := range ids {
id := cast.ToUint64(v)
rac, err := c.ResourceAuthCertApp.GetById(new(entity.ResourceAuthCert), id)
biz.ErrIsNil(err, "存在错误授权凭证id")
biz.IsTrue(rac.Type == entity.AuthCertTypePublic, "只允许删除公共授权凭证")
biz.IsTrue(c.ResourceAuthCertApp.CountByCond(&entity.ResourceAuthCert{Ciphertext: rac.Name}) == 0, "[%s]该授权凭证已被关联", rac.Name)
acIds = append(acIds, id)
acNames = append(acNames, rac.Name)
}
rc.ReqParam = acNames
biz.ErrIsNil(c.ResourceAuthCertApp.DeleteByWheres(rc.MetaCtx, collx.M{"id in ?": acIds}))
id := rc.PathParamInt("id")
rc.ReqParam = id
biz.ErrIsNil(c.ResourceAuthCertApp.DeleteAuthCert(rc.MetaCtx, cast.ToUint64(id)))
}

View File

@@ -102,8 +102,14 @@ func (p *TagTree) TagResources(rc *req.Ctx) {
func (p *TagTree) CountTagResource(rc *req.Ctx) {
tagPath := rc.Query("tagPath")
accountId := rc.GetLoginAccount().Id
machienAuthCerts := p.ResourceAuthCertApp.GetAccountAuthCert(accountId, entity.TagTypeMachineAuthCert, tagPath)
machineCodes := collx.ArrayMap(machienAuthCerts, func(ac *entity.ResourceAuthCert) string {
return ac.ResourceCode
})
rc.ResData = collx.M{
"machine": len(p.TagTreeApp.GetAccountResourceCodes(accountId, consts.TagResourceTypeMachine, tagPath)),
"machine": len(collx.ArrayDeduplicate(machineCodes)),
"db": len(p.TagTreeApp.GetAccountResourceCodes(accountId, consts.TagResourceTypeDb, tagPath)),
"redis": len(p.TagTreeApp.GetAccountResourceCodes(accountId, consts.TagResourceTypeRedis, tagPath)),
"mongo": len(p.TagTreeApp.GetAccountResourceCodes(accountId, consts.TagResourceTypeMongo, tagPath)),

View File

@@ -10,14 +10,12 @@ import (
"mayfly-go/pkg/utils/collx"
)
type SaveAuthCertParam struct {
type RelateAuthCertParam struct {
ResourceCode string
// 资源标签类型
ResourceType entity.TagType
// 授权凭证类型
AuthCertTagType entity.TagType
// 空数组则为删除该资源绑定的授权凭证
AuthCerts []*entity.ResourceAuthCert
}
@@ -25,11 +23,13 @@ type SaveAuthCertParam struct {
type ResourceAuthCert interface {
base.App[*entity.ResourceAuthCert]
// SaveAuthCert 保存资源授权凭证信息,不可放于事务中
SaveAuthCert(ctx context.Context, param *SaveAuthCertParam) error
// RelateAuthCert 保存资源授权凭证信息,不可放于事务中
RelateAuthCert(ctx context.Context, param *RelateAuthCertParam) error
// SavePublicAuthCert 保存公共授权凭证信息
SavePulbicAuthCert(ctx context.Context, rac *entity.ResourceAuthCert) error
// SaveAuthCert 保存授权凭证信息
SaveAuthCert(ctx context.Context, rac *entity.ResourceAuthCert) error
DeleteAuthCert(ctx context.Context, id uint64) error
// GetAuthCert 根据授权凭证名称获取授权凭证
GetAuthCert(authCertName string) (*entity.ResourceAuthCert, error)
@@ -56,23 +56,25 @@ func (r *resourceAuthCertAppImpl) InjectResourceAuthCertRepo(resourceAuthCertRep
r.Repo = resourceAuthCertRepo
}
func (r *resourceAuthCertAppImpl) SaveAuthCert(ctx context.Context, params *SaveAuthCertParam) error {
func (r *resourceAuthCertAppImpl) RelateAuthCert(ctx context.Context, params *RelateAuthCertParam) error {
resourceCode := params.ResourceCode
resourceType := int8(params.ResourceType)
resourceAuthCerts := params.AuthCerts
authCertTagType := params.AuthCertTagType
authCertTagType := getResourceAuthCertTagType(entity.TagType(resourceType))
if authCertTagType == 0 {
return errorx.NewBiz("资源授权凭证所属标签类型不能为空")
}
if resourceCode == "" {
return errorx.NewBiz("资源授权凭证的资源编号不能为空")
}
if resourceType == 0 {
return errorx.NewBiz("资源类型不能为空")
}
// 删除授权信息
if len(resourceAuthCerts) == 0 {
logx.DebugfContext(ctx, "SaveAuthCert[%d-%s]-删除所有关联的授权凭证信息", resourceType, resourceCode)
logx.DebugfContext(ctx, "RelateAuthCert[%d-%s]-删除所有关联的授权凭证信息", resourceType, resourceCode)
if err := r.DeleteByCond(ctx, &entity.ResourceAuthCert{ResourceCode: resourceCode, ResourceType: resourceType}); err != nil {
return err
}
@@ -114,9 +116,10 @@ func (r *resourceAuthCertAppImpl) SaveAuthCert(ctx context.Context, params *Save
var oldAuthCert []*entity.ResourceAuthCert
r.ListByCond(&entity.ResourceAuthCert{ResourceCode: resourceCode, ResourceType: resourceType}, &oldAuthCert)
// 新增、删除以及不变的授权凭证名
var adds, dels, unmodifys []string
if len(oldAuthCert) == 0 {
logx.DebugfContext(ctx, "SaveAuthCert[%d-%s]-不存在已有的授权凭证信息, 为新增资源授权凭证", resourceType, resourceCode)
logx.DebugfContext(ctx, "RelateAuthCert[%d-%s]-不存在已有的授权凭证信息, 为新增资源授权凭证", resourceType, resourceCode)
adds = collx.MapKeys(name2AuthCert)
} else {
oldNames := collx.ArrayMap(oldAuthCert, func(ac *entity.ResourceAuthCert) string {
@@ -134,7 +137,7 @@ func (r *resourceAuthCertAppImpl) SaveAuthCert(ctx context.Context, params *Save
// 处理新增的授权凭证
if len(addAuthCerts) > 0 {
logx.DebugfContext(ctx, "SaveAuthCert[%d-%s]-新增授权凭证-[%v]", resourceType, resourceCode, adds)
logx.DebugfContext(ctx, "RelateAuthCert[%d-%s]-新增授权凭证-[%v]", resourceType, resourceCode, adds)
if err := r.BatchInsert(ctx, addAuthCerts); err != nil {
return err
}
@@ -150,7 +153,7 @@ func (r *resourceAuthCertAppImpl) SaveAuthCert(ctx context.Context, params *Save
if len(resourceTagIds) > 0 {
// 保存授权凭证类型的资源标签
for _, authCert := range addAuthCerts {
logx.DebugfContext(ctx, "SaveAuthCert[%d-%s]-授权凭证标签[%d-%s]关联至所属资源标签下[%v]", resourceType, resourceCode, authCertTagType, authCert.Name, resourceTagIds)
logx.DebugfContext(ctx, "RelateAuthCert[%d-%s]-授权凭证标签[%d-%s]关联至所属资源标签下[%v]", resourceType, resourceCode, authCertTagType, authCert.Name, resourceTagIds)
if err := r.tagTreeApp.SaveResource(ctx, &SaveResourceTagParam{
ResourceCode: authCert.Name,
ResourceType: authCertTagType,
@@ -164,7 +167,7 @@ func (r *resourceAuthCertAppImpl) SaveAuthCert(ctx context.Context, params *Save
}
for _, del := range dels {
logx.DebugfContext(ctx, "SaveAuthCert[%d-%s]-删除授权凭证-[%v]", resourceType, resourceCode, del)
logx.DebugfContext(ctx, "RelateAuthCert[%d-%s]-删除授权凭证-[%v]", resourceType, resourceCode, del)
if err := r.DeleteByCond(ctx, &entity.ResourceAuthCert{ResourceCode: resourceCode, ResourceType: resourceType, Name: del}); err != nil {
return err
}
@@ -177,39 +180,75 @@ func (r *resourceAuthCertAppImpl) SaveAuthCert(ctx context.Context, params *Save
}
}
for _, unmodify := range unmodifys {
unmodifyAc := name2AuthCert[unmodify]
if unmodifyAc.Id == 0 {
continue
}
logx.DebugfContext(ctx, "SaveAuthCert[%d-%s]-更新授权凭证-[%v]", resourceType, resourceCode, unmodify)
// 置空用户名不允许修改TagTree的name关联了该值修改了会造成数据不一致懒得处理该同步。要修改用户名可通过重新删除旧凭证后添加一个授权凭证或通过关联公共凭证公共凭证可修改用户名
if unmodifyAc.CiphertextType != entity.AuthCertCiphertextTypePublic {
unmodifyAc.Username = ""
}
if len(unmodifys) > 0 {
// 旧凭证名 -> 旧凭证
oldName2AuthCert := collx.ArrayToMap(oldAuthCert, func(ac *entity.ResourceAuthCert) string {
return ac.Name
})
for _, unmodify := range unmodifys {
unmodifyAc := name2AuthCert[unmodify]
if unmodifyAc.Id == 0 {
continue
}
if err := r.UpdateById(ctx, unmodifyAc); err != nil {
return err
oldAuthCert := oldName2AuthCert[unmodify]
if !unmodifyAc.HasChanged(oldAuthCert) {
logx.DebugfContext(ctx, "RelateAuthCert[%d-%s]-授权凭证[%s]未发生字段变更", resourceType, resourceCode, unmodify)
continue
}
logx.DebugfContext(ctx, "RelateAuthCert[%d-%s]-更新授权凭证-[%v]", resourceType, resourceCode, unmodify)
if oldAuthCert.Username != unmodifyAc.Username {
if err := r.updateAuthCertTagName(ctx, unmodify, authCertTagType, unmodifyAc.Username); err != nil {
logx.WarnfContext(ctx, "授权凭证[%s]修改了用户名-同步更新授权凭证标签名失败", unmodify)
}
}
if err := r.UpdateById(ctx, unmodifyAc); err != nil {
return err
}
}
}
return nil
}
func (r *resourceAuthCertAppImpl) SavePulbicAuthCert(ctx context.Context, rac *entity.ResourceAuthCert) error {
rac.Type = entity.AuthCertTypePublic
rac.CiphertextEncrypt()
rac.ResourceType = 0
rac.ResourceCode = "-"
func (r *resourceAuthCertAppImpl) SaveAuthCert(ctx context.Context, rac *entity.ResourceAuthCert) error {
if rac.Id == 0 {
if r.CountByCond(&entity.ResourceAuthCert{Name: rac.Name}) > 0 {
return errorx.NewBiz("授权凭证的名称不能重复[%s]", rac.Name)
}
return r.Insert(ctx, rac)
return r.addAuthCert(ctx, rac)
}
// 名称置空,防止被更新
rac.Name = ""
return r.UpdateById(ctx, rac)
return r.updateAuthCert(ctx, rac)
}
func (r *resourceAuthCertAppImpl) DeleteAuthCert(ctx context.Context, id uint64) error {
rac, err := r.GetById(new(entity.ResourceAuthCert), id)
if err != nil {
return errorx.NewBiz("授权凭证不存在")
}
if rac.Type == entity.AuthCertTypePublic {
if r.CountByCond(&entity.ResourceAuthCert{Ciphertext: rac.Name}) > 0 {
return errorx.NewBiz("该公共授权凭证[%s]已被关联", rac.Name)
}
// 公共授权凭证直接删除即可
return r.DeleteById(ctx, id)
}
if r.CountByCond(&entity.ResourceAuthCert{ResourceCode: rac.ResourceCode, ResourceType: rac.ResourceType}) <= 1 {
return errorx.NewBiz("资源至少需要绑定一个授权凭证,无法删除该凭证[%s]", rac.Name)
}
return r.Tx(ctx,
func(ctx context.Context) error {
// 删除对应授权凭证标签
return r.tagTreeApp.DeleteResource(ctx, &DelResourceTagParam{
ResourceCode: rac.Name,
})
},
func(ctx context.Context) error {
return r.DeleteById(ctx, id)
})
}
func (r *resourceAuthCertAppImpl) GetAuthCert(authCertName string) (*entity.ResourceAuthCert, error) {
@@ -290,6 +329,109 @@ func (r *resourceAuthCertAppImpl) FillAuthCert(authCerts []*entity.ResourceAuthC
}
}
// addAuthCert 添加授权凭证
func (r *resourceAuthCertAppImpl) addAuthCert(ctx context.Context, rac *entity.ResourceAuthCert) error {
if r.CountByCond(&entity.ResourceAuthCert{Name: rac.Name}) > 0 {
return errorx.NewBiz("授权凭证的名称不能重复[%s]", rac.Name)
}
// 公共凭证
if rac.Type == entity.AuthCertTypePublic {
rac.ResourceCode = "-"
rac.CiphertextEncrypt()
rac.Type = -2
return r.Insert(ctx, rac)
}
resourceCode := rac.ResourceCode
resourceType := rac.ResourceType
// 获取资源编号对应的资源标签信息
var resourceTags []*entity.TagTree
r.tagTreeApp.ListByCond(&entity.TagTree{Type: entity.TagType(resourceType), Code: resourceCode}, &resourceTags)
// 资源标签id相当于父tag id
resourceTagIds := collx.ArrayMap(resourceTags, func(tag *entity.TagTree) uint64 {
return tag.Id
})
if len(resourceTagIds) == 0 {
return errorx.NewBiz("资源标签不存在[%s], 请检查资源编号是否正确", resourceCode)
}
authCertTagType := getResourceAuthCertTagType(entity.TagType(resourceType))
// 如果密文类型不为公共凭证,则进行加密。公共凭证密文内容存的是明文的公共凭证名
if rac.CiphertextType != entity.AuthCertCiphertextTypePublic {
rac.CiphertextEncrypt()
}
return r.Tx(ctx, func(ctx context.Context) error {
logx.DebugfContext(ctx, "[%d-%s]-授权凭证标签[%d-%s]关联至所属资源标签下[%v]", resourceType, resourceCode, authCertTagType, rac.Name, resourceTagIds)
return r.tagTreeApp.SaveResource(ctx, &SaveResourceTagParam{
ResourceCode: rac.Name,
ResourceType: authCertTagType,
ResourceName: rac.Username,
TagIds: resourceTagIds,
})
}, func(ctx context.Context) error {
return r.Insert(ctx, rac)
})
}
// updateAuthCert 更新授权凭证
func (r *resourceAuthCertAppImpl) updateAuthCert(ctx context.Context, rac *entity.ResourceAuthCert) error {
oldRac, err := r.GetById(new(entity.ResourceAuthCert), rac.Id)
if err != nil {
return errorx.NewBiz("该授权凭证不存在")
}
if !oldRac.HasChanged(rac) {
return nil
}
if oldRac.Type == entity.AuthCertTypePublic {
// 如果旧凭证为公共凭证,则不允许修改凭证类型
if rac.Type != entity.AuthCertTypePublic {
return errorx.NewBiz("公共授权凭证不允许修改凭证类型")
}
if rac.CiphertextType == entity.AuthCertCiphertextTypePublic {
return errorx.NewBiz("公共授权凭证不允许绑定其他公共授权凭证")
}
} else {
if rac.Type == entity.AuthCertTypePublic {
return errorx.NewBiz("非公共授权凭证不允许修改为公共凭证")
}
// 修改了用户名,则需要同步更新对应授权凭证标签里的名称
if rac.Username != oldRac.Username {
if err := r.updateAuthCertTagName(ctx, oldRac.Name, getResourceAuthCertTagType(entity.TagType(oldRac.ResourceType)), rac.Username); err != nil {
return errorx.NewBiz("同步更新授权凭证标签名称失败")
}
}
}
// 防止误更新
rac.Name = ""
rac.ResourceCode = ""
rac.ResourceType = 0
return r.UpdateById(ctx, rac)
}
// updateAuthCertTagName 同步更新授权凭证的标签名,防止标签展示不一致
func (r *resourceAuthCertAppImpl) updateAuthCertTagName(ctx context.Context, authCertName string, autyCertTagType entity.TagType, newTagName string) error {
return r.tagTreeApp.UpdateByWheres(ctx, &entity.TagTree{Name: newTagName}, collx.M{"code = ?": authCertName, "type = ?": autyCertTagType})
}
// getResourceAuthCertTagType 根据资源类型,获取对应的授权凭证标签类型
func getResourceAuthCertTagType(resourceType entity.TagType) entity.TagType {
if resourceType == entity.TagTypeMachine {
return entity.TagTypeMachineAuthCert
}
if resourceType == entity.TagTypeDb {
return entity.TagTypeDbAuthCert
}
return -2
}
// 解密授权凭证信息
func (r *resourceAuthCertAppImpl) decryptAuthCert(authCert *entity.ResourceAuthCert) (*entity.ResourceAuthCert, error) {
if authCert.CiphertextType == entity.AuthCertCiphertextTypePublic {

View File

@@ -152,19 +152,30 @@ func (p *tagTreeAppImpl) GetAccountTagResources(accountId uint64, query *entity.
if len(accountTagPaths) == 0 {
accountTagPaths = tagPaths
} else {
accountTagPaths = collx.ArrayFilter[string](tagPaths, func(s string) bool {
for _, v := range accountTagPaths {
// 要过滤的权限需要在用户拥有的子标签下, accountTagPath: test/ tagPath: test/test1/ -> true
if strings.HasPrefix(v, s) {
queryPaths := collx.ArrayFilter[string](tagPaths, func(tagPath string) bool {
for _, acPath := range accountTagPaths {
// 查询条件: a/b/ 有权的a/ 查询结果应该是 a/b/
if strings.HasPrefix(tagPath, acPath) {
return true
}
}
return false
})
acPaths := collx.ArrayFilter[string](accountTagPaths, func(acPath string) bool {
for _, tagPath := range tagPaths {
// 查询条件: a/ 有权的a/b/ 查询结果应该是 a/b/
if strings.HasPrefix(acPath, tagPath) {
return true
}
}
return false
})
accountTagPaths = append(queryPaths, acPaths...)
}
}
// tagResourceQuery.CodePathLike = tagPath
tagResourceQuery.Codes = query.Codes
tagResourceQuery.CodePathLikes = accountTagPaths
p.ListByQuery(tagResourceQuery, &tagResources)

View File

@@ -97,6 +97,19 @@ func (m *ResourceAuthCert) GetExtraString(key string) string {
return cast.ToString(m.Extra[key])
}
// HasChanged 与指定授权凭证比较是否有变更
func (m *ResourceAuthCert) HasChanged(rac *ResourceAuthCert) bool {
if rac == nil {
return true
}
return m.Username != rac.Username ||
m.Ciphertext != rac.Ciphertext ||
m.CiphertextType != rac.CiphertextType ||
m.Remark != rac.Remark ||
m.Type != rac.Type ||
m.GetExtraString(ExtraKeyPassphrase) != rac.GetExtraString(ExtraKeyPassphrase)
}
// 密文类型
type AuthCertCiphertextType int8

View File

@@ -30,6 +30,8 @@ const (
TagTypeRedis TagType = TagType(consts.TagResourceTypeRedis)
TagTypeMongo TagType = TagType(consts.TagResourceTypeMongo)
// ----- (单独声明各个资源的授权凭证类型而不统一使用一个授权凭证类型是为了获取登录账号的授权凭证标签(ResourceAuthCertApp.GetAccountAuthCert)时,避免查出所有资源的授权凭证)
TagTypeMachineAuthCert TagType = 11 // 机器-授权凭证
TagTypeDbAuthCert TagType = 21 // DB-授权凭证
)

View File

@@ -0,0 +1,22 @@
package structx
import "reflect"
// DiffFields 比较两个结构体中指定字段的属性值是否发生改变,并返回改变的字段名
func DiffFields[T any](t1 T, t2 T, fieldsToCompare ...string) []string {
var changedFields []string
oldValue := reflect.ValueOf(t1)
newValue := reflect.ValueOf(t2)
for _, fieldName := range fieldsToCompare {
oldFieldValue := oldValue.FieldByName(fieldName)
newFieldValue := newValue.FieldByName(fieldName)
if !reflect.DeepEqual(oldFieldValue.Interface(), newFieldValue.Interface()) {
changedFields = append(changedFields, fieldName)
}
}
return changedFields
}