perf: 支持授权给管理员查看和下载用户证书

This commit is contained in:
xiaojunnuo
2025-12-28 23:36:53 +08:00
parent f847c4a414
commit 1347355cb1
12 changed files with 213 additions and 13 deletions

View File

@@ -71,7 +71,7 @@ function createService() {
// @ts-ignore
response.config.onError(err);
}
errorCreate(`${errorMessage}: ${response.config.url}`, showErrorNotify, dataAxios);
errorCreate(`${errorMessage} (请求接口: ${response.config.url}`, showErrorNotify, dataAxios);
}
},
error => {
@@ -113,7 +113,7 @@ function createService() {
default:
break;
}
error.message += `: ${error.response?.config?.url}`;
error.message += ` 请求接口:${error.response?.config?.url}`;
errorLog(error, error?.response?.config?.showErrorNotify);
if (status === 401) {
const userStore = useUserStore();

View File

@@ -52,6 +52,7 @@ export default {
siteMonitorSetting: "Site Monitor Settings",
userSecurity: "Security Settings",
userProfile: "Account Info",
userGrant: "Grant Delegation",
suite: "Suite",
mySuite: "My Suite",
suiteBuy: "Suite Purchase",
@@ -62,6 +63,12 @@ export default {
greeting: "Hello",
profile: "Account Info",
logout: "Logout",
setting: {
grantSetting: "Grant Settings",
saveSuccess: "Save Success",
allowAdminViewCerts: "Allow Admin view and download Certs",
allowAdminViewCertsHelper: "Allow admin view and download all certificates",
},
},
dashboard: {
greeting: "Hello, {name}, welcome to 【{site}】",

View File

@@ -57,6 +57,7 @@ export default {
siteMonitorSetting: "站点监控设置",
userSecurity: "认证安全设置",
userProfile: "账号信息",
userGrant: "授权委托",
suite: "套餐",
mySuite: "我的套餐",
suiteBuy: "套餐购买",
@@ -68,6 +69,13 @@ export default {
greeting: "您好",
profile: "账号信息",
logout: "注销登录",
setting: {
grantSetting: "授权委托设置",
saveSuccess: "保存成功",
allowAdminViewCerts: "授权管理员查看和下载证书",
allowAdminViewCertsHelper: "允许管理员查看和下载我的所有证书",
},
},
dashboard: {
greeting: "您好,{name},欢迎使用 【{site}】",

View File

@@ -204,6 +204,17 @@ export const certdResources = [
isMenu: true,
},
},
{
title: "certd.userGrant",
name: "UserGrantSetting",
path: "/certd/mine/grant",
component: "/certd/mine/grant/index.vue",
meta: {
icon: "mi:user-check",
auth: true,
isMenu: true,
},
},
{
title: "certd.userProfile",
name: "UserProfile",

View File

@@ -1,6 +1,6 @@
<template>
<a-button v-if="showButton" type="primary" @click="open">
{{ $t("authentication.changePasswordButton") }}
{{ t("authentication.changePasswordButton") }}
</a-button>
</template>

View File

@@ -0,0 +1,25 @@
// @ts-ignore
import { request } from "/@/api/service";
const apiPrefix = "/user/settings";
export type UserGrantSetting = {
allowAdminViewCerts: boolean;
};
export async function GrantSettingsGet() {
const res = await request({
url: apiPrefix + "/grant/get",
method: "post",
});
if (!res) {
return {};
}
return res as UserGrantSetting;
}
export async function UserSettingSave(req: any) {
return await request({
url: apiPrefix + "/grant/save",
method: "post",
data: req,
});
}

View File

@@ -0,0 +1,67 @@
<template>
<fs-page class="page-user-settings page-grant">
<template #header>
<div class="title">{{ t("certd.user.setting.grantSetting") }}</div>
</template>
<div class="user-settings-form settings-form">
<a-form :model="formState" name="basic" :label-col="{ span: 8 }" :wrapper-col="{ span: 16 }" autocomplete="off">
<a-form-item :label="t('certd.user.setting.allowAdminViewCerts')" :name="['allowAdminViewCerts']">
<div class="flex mt-5">
<a-switch v-model:checked="formState.allowAdminViewCerts" :disabled="!settingsStore.isPlus" />
<vip-button class="ml-5" mode="button"></vip-button>
</div>
<div class="helper">{{ t("certd.user.setting.allowAdminViewCertsHelper") }}</div>
</a-form-item>
<a-form-item label=" " :colon="false" :wrapper-col="{ span: 16 }">
<loading-button type="primary" html-type="button" :click="doSave">{{ t("certd.confirm") }}</loading-button>
</a-form-item>
</a-form>
</div>
</fs-page>
</template>
<script setup lang="tsx">
import { computed, reactive, watch } from "vue";
import * as api from "./api";
import { UserGrantSetting } from "./api";
import { Modal, notification } from "ant-design-vue";
import { merge } from "lodash-es";
import { useSettingStore } from "/@/store/settings";
import { useI18n } from "/src/locales";
const { t } = useI18n();
const settingsStore = useSettingStore();
defineOptions({
name: "UserSecurity",
});
const formState = reactive<Partial<UserGrantSetting>>({
allowAdminViewCerts: false,
});
async function loadUserSettings() {
const data: any = await api.GrantSettingsGet();
merge(formState, data);
}
loadUserSettings();
const doSave = async () => {
await api.UserSettingSave({
...formState,
});
notification.success({
message: t("certd.user.setting.saveSuccess"),
});
};
</script>
<style lang="less">
.page-user-settings {
.user-settings-form {
width: 600px;
margin: 20px;
}
}
</style>

View File

@@ -2,6 +2,9 @@ import { ALL, Body, Controller, Inject, Post, Provide, Query } from "@midwayjs/c
import { Constants, CrudController } from "@certd/lib-server";
import { UserSettingsService } from "../../../modules/mine/service/user-settings-service.js";
import { UserSettingsEntity } from "../../../modules/mine/entity/user-settings.js";
import { UserGrantSetting } from "../../../modules/mine/service/models.js";
import { isPlus } from "@certd/plus-core";
import { merge } from "lodash-es";
/**
*/
@@ -65,6 +68,26 @@ export class UserSettingsController extends CrudController<UserSettingsService>
const entity = await this.service.getByKey(key, this.getUserId());
return this.ok(entity);
}
@Post("/grant/get", { summary: Constants.per.authOnly })
async grantSettingsGet() {
const userId = this.getUserId();
const setting = await this.service.getSetting<UserGrantSetting>(userId, UserGrantSetting);
return this.ok(setting);
}
@Post("/grant/save", { summary: Constants.per.authOnly })
async grantSettingsSave(@Body(ALL) bean: UserGrantSetting) {
if (!isPlus()) {
throw new Error('本功能需要开通专业版')
}
const userId = this.getUserId();
const setting = new UserGrantSetting();
merge(setting, bean);
await this.service.saveSetting(userId, setting);
return this.ok({});
}
}

View File

@@ -1,8 +1,10 @@
import {Body, Controller, Inject, Post, Provide, Query} from '@midwayjs/core';
import { Body, Controller, Inject, Post, Provide, Query } from '@midwayjs/core';
import { PipelineService } from '../../../modules/pipeline/service/pipeline-service.js';
import { BaseController, Constants } from '@certd/lib-server';
import { BaseController, Constants, PermissionException } from '@certd/lib-server';
import { StorageService } from '../../../modules/pipeline/service/storage-service.js';
import {CertReader} from "@certd/plugin-cert";
import { CertReader } from "@certd/plugin-cert";
import { UserSettingsService } from '../../../modules/mine/service/user-settings-service.js';
import { UserGrantSetting } from '../../../modules/mine/service/models.js';
@Provide()
@Controller('/api/pi/cert')
@@ -12,10 +14,32 @@ export class CertController extends BaseController {
@Inject()
storeService: StorageService;
@Inject()
userSettingsService: UserSettingsService;
@Post('/get', { summary: Constants.per.authOnly })
async getCert(@Query('id') id: number) {
const userId = this.getUserId();
await this.pipelineService.checkUserId(id, userId);
const pipleinUserId = await this.pipelineService.getPipelineUserId(id);
if (pipleinUserId !== userId) {
// 如果是管理员,检查用户是否有授权管理员查看
const isAdmin = await this.isAdmin()
if (!isAdmin) {
throw new PermissionException();
}
// 是否允许管理员查看
const setting = await this.userSettingsService.getSetting<UserGrantSetting>(pipleinUserId, UserGrantSetting, false);
if (setting?.allowAdminViewCerts !== true) {
//不允许管理员查看
throw new PermissionException("该流水线的用户还未授权管理员查看证书,请先让用户在”设置->授权委托“中打开开关");
}
}
const privateVars = await this.storeService.getPipelinePrivateVars(id);
return this.ok(privateVars.cert);
}
@@ -24,7 +48,7 @@ export class CertController extends BaseController {
@Post('/readCertDetail', { summary: Constants.per.authOnly })
async readCertDetail(@Body('crt') crt: string) {
if (!crt) {
throw new Error('crt is required');
throw new Error('crt is required');
}
const certDetail = CertReader.readCertDetail(crt)
return this.ok(certDetail);

View File

@@ -10,6 +10,8 @@ import * as fs from "fs";
import { logger } from "@certd/basic";
import { AuthService } from "../../../modules/sys/authority/service/auth-service.js";
import { In } from "typeorm";
import { UserSettingsService } from "../../../modules/mine/service/user-settings-service.js";
import { UserGrantSetting } from "../../../modules/mine/service/models.js";
/**
* 证书
@@ -30,6 +32,9 @@ export class HistoryController extends CrudController<HistoryService> {
@Inject()
sysSettingsService: SysSettingsService;
@Inject()
userSettingsService: UserSettingsService;
getService(): HistoryService {
return this.service;
}
@@ -77,7 +82,7 @@ export class HistoryController extends CrudController<HistoryService> {
@Post('/list', { summary: Constants.per.authOnly })
async list(@Body(ALL) body) {
const isAdmin = await this.authService.isAdmin(this.ctx);
const isAdmin = this.authService.isAdmin(this.ctx);
if (!isAdmin) {
body.userId = this.getUserId();
}
@@ -89,7 +94,7 @@ export class HistoryController extends CrudController<HistoryService> {
};
const withDetail = body.withDetail;
delete body.withDetail;
let select:any = null
let select: any = null
if (!withDetail) {
select = {
pipeline: true, // 后面这里改成false
@@ -193,7 +198,6 @@ export class HistoryController extends CrudController<HistoryService> {
@Post('/files', { summary: Constants.per.authOnly })
async files(@Query('pipelineId') pipelineId: number, @Query('historyId') historyId: number) {
await this.authService.checkEntityUserId(this.ctx, this.service, historyId);
const files = await this.getFiles(historyId, pipelineId);
return this.ok(files);
}
@@ -210,14 +214,24 @@ export class HistoryController extends CrudController<HistoryService> {
throw new CommonException('historyId is null');
}
if (history.userId !== this.getUserId()) {
throw new PermissionException();
// 如果是管理员,检查用户是否有授权管理员查看
const isAdmin = await this.isAdmin()
if (!isAdmin) {
throw new PermissionException();
}
// 是否允许管理员查看
const setting = await this.userSettingsService.getSetting<UserGrantSetting>(history.userId, UserGrantSetting, false);
if (setting?.allowAdminViewCerts!==true) {
//不允许管理员查看
throw new PermissionException("该流水线的用户还未授权管理员下载证书,请先让用户在”设置->授权委托“中打开开关");
}
//允许管理员查看
}
return await this.service.getFiles(history);
}
@Get('/download', { summary: Constants.per.authOnly })
async download(@Query('pipelineId') pipelineId: number, @Query('historyId') historyId: number, @Query('fileId') fileId: string) {
await this.authService.checkEntityUserId(this.ctx, this.service, historyId);
const files = await this.getFiles(historyId, pipelineId);
const file = files.find(f => f.id === fileId);
if (file == null) {

View File

@@ -37,3 +37,12 @@ export class UserEmailSetting extends BaseSettings {
list:string[] = [];
}
export class UserGrantSetting extends BaseSettings {
static __title__ = "用户授权设置";
static __key__ = "user.grant";
allowAdminViewCerts:boolean = false;
}

View File

@@ -1072,4 +1072,16 @@ export class PipelineService extends BaseService<PipelineEntity> {
});
return res?.status;
}
async getPipelineUserId(pipelineId: number) {
const res = await this.repository.findOne({
select: {
userId: true
},
where: {
id: pipelineId
}
});
return res?.userId;
}
}