mirror of https://gitee.com/topiam/eiam
Merge remote-tracking branch 'origin/master'
# Conflicts: # images/group-qr-code.pngpull/90/head
commit
ca213a8c05
17
README.md
17
README.md
|
@ -23,21 +23,26 @@
|
|||
用于管理企业内员工账号、权限、身份认证、应用访问,帮助整合部署在本地或云端的内部办公系统、业务系统及三方 SaaS
|
||||
系统的所有身份,实现一个账号打通所有应用的服务。
|
||||
|
||||
官网:https://topiam.cn
|
||||
|
||||
管理端演示:https://eiam-console.topiam.cn
|
||||
|
||||
用户端演示:https://eiam-portal.topiam.cn
|
||||
|
||||
付费服务支持或商务合作:
|
||||
|
||||
<img src="images/contact-qr-code.png" alt="logo" width="200px"/>
|
||||
|
||||
## 核心特性
|
||||
|
||||
+ 提供统一组织信息管理,多维度建立对应关系,实现在一个平台对企业人员、组织架构、应用信息的高效统一管理。
|
||||
+ 支持钉钉、飞书、企业微信等身份源集成能力,实现系统和企业OA平台数据联动,以用户为管理基点,结合入职、离职、调岗、兼职等人事事件,关联其相关应用权限变化而变化,保证应用访问权限的安全控制。
|
||||
+ 支持多因素认证,行为验证码、社交认证,融合认证等机制,保证用户认证安全可靠。
|
||||
+ 支持微信、钉钉、、飞书QQ等社交认证集成,使企业具有快速纳入互联网化认证能力。
|
||||
+ 支持微信、钉钉、飞书QQ等社交认证集成,使企业具有快速纳入互联网化认证能力。
|
||||
+ 支持 `SAML2`,`OAuth2`,`OIDC`,`CAS`,`JWT`,`表单代填`等认证协议及机制,实现单点登录功能,预配置大量 SaaS 应用及传统应用模板,开箱即用。
|
||||
+ 完善的安全审计,详尽记录每一次用户行为,使每一步操作有据可循,实时记录企业信息安全状况,精准识别企业异常访问和潜在威胁的源头。
|
||||
+ 提供标准`RESTAPI`、`SCIM2.0`接口轻松完成机构用户同步,提供`HTTP`、`MQ`事件通知,实现企业对于账号生命周期的精细化管理。
|
||||
|
||||
## 在线演示
|
||||
|
||||
+ 管理端:https://eiam-console.topiam.cn
|
||||
+ 用户端:https://eiam-portal.topiam.cn
|
||||
|
||||
## 系统架构
|
||||
|
||||

|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
#
|
||||
# eiam-common - Employee Identity and Access Management
|
||||
# Copyright © 2022-Present Jinan Yuanchuang Network Technology Co., Ltd. (support@topiam.cn)
|
||||
# Copyright \u00A9 2022-Present Jinan Yuanchuang Network Technology Co., Ltd. (support@topiam.cn)
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as published by
|
||||
|
@ -23,7 +23,7 @@ forget_password=\u9A8C\u8BC1\u7801${verify_code}\uFF0C\u8BE5\u9A8C\u8BC1\u7801${
|
|||
update_password=\u9A8C\u8BC1\u7801${verify_code}\uFF0C\u8BE5\u9A8C\u8BC1\u7801${expire_time}\u5206\u949F\u5185\u6709\u6548\uFF0C\u8BF7\u52FF\u6CC4\u9732\u4E8E\u4ED6\u4EBA\u3002
|
||||
login=\u9A8C\u8BC1\u7801${verify_code}\uFF0C\u8BE5\u9A8C\u8BC1\u7801${expire_time}\u5206\u949F\u5185\u6709\u6548\uFF0C\u8BF7\u52FF\u6CC4\u9732\u4E8E\u4ED6\u4EBA\u3002
|
||||
again_verify=\u9A8C\u8BC1\u7801${verify_code}\uFF0C\u8BE5\u9A8C\u8BC1\u7801${expire_time}\u5206\u949F\u5185\u6709\u6548\uFF0C\u8BF7\u52FF\u6CC4\u9732\u4E8E\u4ED6\u4EBA\u3002
|
||||
welcome_sms=\u5c0a\u656c\u7684\u7528\u6237\uff1a${username}\uff0c\u60a8\u7684 TopIAM \u8d26\u6237\u7684\u521d\u59cb\u5bc6\u7801\u662f\uff1a${password}
|
||||
welcome_sms=\u5C0A\u656C\u7684\u7528\u6237\uFF1A${username}\uFF0C\u60A8\u7684 TOPIAM \u8D26\u6237\u7684\u521D\u59CB\u5BC6\u7801\u662F\uFF1A${password}
|
||||
reset_password=\u9A8C\u8BC1\u7801${verify_code}\uFF0C\u8BE5\u9A8C\u8BC1\u7801${expire_time}\u5206\u949F\u5185\u6709\u6548\uFF0C\u8BF7\u52FF\u6CC4\u9732\u4E8E\u4ED6\u4EBA\u3002
|
||||
reset_password_success=\u7528\u6237\u540D${username}\uFF0C\u5BC6\u7801${password}\uFF0C\u8BF7\u52FF\u6CC4\u9732\u4E8E\u4ED6\u4EBA\u3002
|
||||
warning=\u5C0A\u656C\u7684\u7528\u6237\uFF1A\u4F60\u597D\uFF01\u68C0\u6D4B\u5230\u60A8\u7684\u8D26\u53F7\u201C\u7528\u6237\u540D ${username}\u201D\u5B58\u5728\u5F02\u5E38\u98CE\u9669\uFF0C\u60A8\u53EF\u81F3\u7528\u6237\u4E2D\u5FC3-\u8D26\u53F7\u8BBE\u7F6E-\u98CE\u9669\u4E8B\u4EF6\u67E5\u770B\u5F02\u5E38\u8BE6\u60C5\u3002
|
||||
|
|
|
@ -49,6 +49,7 @@
|
|||
"@ant-design/icons": "^5.3.7",
|
||||
"@ant-design/maps": "^1.0.8",
|
||||
"@ant-design/pro-components": "^2.7.1",
|
||||
"@ant-design/pro-editor": "^1.2.1",
|
||||
"ahooks": "^3.7.11",
|
||||
"antd": "^5.17.0",
|
||||
"antd-img-crop": "^4.21.0",
|
||||
|
@ -56,13 +57,13 @@
|
|||
"classnames": "^2.5.1",
|
||||
"codemirror": "^5.65.5",
|
||||
"content-security-policy-parser": "^0.6.0",
|
||||
"copy-to-clipboard": "^3.3.3",
|
||||
"crypto-js": "^4.2.0",
|
||||
"dayjs": "^1.11.11",
|
||||
"echarts": "^5.5.0",
|
||||
"fetch-jsonp": "^1.3.0",
|
||||
"form-render": "^2.4.4",
|
||||
"google-libphonenumber": "^3.2.34",
|
||||
"copy-to-clipboard": "^3.3.3",
|
||||
"js-base64": "^3.7.7",
|
||||
"js-yaml": "^4.1.0",
|
||||
"jsencrypt": "^3.3.2",
|
||||
|
|
|
@ -15,8 +15,10 @@
|
|||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { DraggablePanel } from '@ant-design/pro-editor';
|
||||
import { PageContainer } from '@ant-design/pro-components';
|
||||
import { Col, Row } from 'antd';
|
||||
import { Col, Flex, Row } from 'antd';
|
||||
import { useState } from 'react';
|
||||
import OrgTree from './components/Organization';
|
||||
import UserList from './components/User';
|
||||
|
@ -48,16 +50,19 @@ export const User = () => {
|
|||
|
||||
return (
|
||||
<PageContainer content={intl.formatMessage({ id: 'pages.account.user_list.desc' })}>
|
||||
<Row gutter={[16, 16]}>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
||||
{/* 左侧 */}
|
||||
<Col {...leftLayout} style={{ minHeight: '100%', overflow: 'auto' }}>
|
||||
<DraggablePanel
|
||||
placement="left"
|
||||
maxWidth={800}
|
||||
style={{ flex: 1, padding: 0, borderRadius: 12 }}
|
||||
>
|
||||
<OrgTree onSelect={treeOnSelect} />
|
||||
</Col>
|
||||
</DraggablePanel>
|
||||
<div style={{ width: 'auto', flex: 1, padding: '0px 6px' }}></div>
|
||||
{/* 表格 */}
|
||||
<Col {...rightLayout} style={{ minHeight: '100%', overflow: 'auto' }}>
|
||||
<UserList organization={organization} />
|
||||
</Col>
|
||||
</Row>
|
||||
<UserList organization={organization} />
|
||||
</div>
|
||||
</PageContainer>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -318,7 +318,7 @@ export const OrganizationTree = (props: {
|
|||
return (
|
||||
<div className={styles}>
|
||||
<Card
|
||||
style={{ height: 'calc(100vh - 200px)', overflow: 'auto' }}
|
||||
style={{ height: 'calc(100vh - 220px)', overflow: 'auto' }}
|
||||
bordered={false}
|
||||
className={classnames(`${prefixCls}`)}
|
||||
>
|
||||
|
|
|
@ -387,7 +387,7 @@ export default (props: UserListProps) => {
|
|||
return (
|
||||
<>
|
||||
{!organization ? (
|
||||
<Card style={{ height: 'calc(100vh - 200px)' }} bordered={false}>
|
||||
<Card style={{ height: 'calc(100vh - 220px)' }} bordered={false}>
|
||||
<Skeleton paragraph={{ rows: 10 }} active={true} />
|
||||
</Card>
|
||||
) : (
|
||||
|
@ -396,10 +396,10 @@ export default (props: UserListProps) => {
|
|||
scroll={{ x: 1200 }}
|
||||
params={{ organizationId: organization?.id, inclSubOrganization }}
|
||||
style={{
|
||||
height: 'calc(100vh - 200px)',
|
||||
height: 'calc(100vh - 220px)',
|
||||
overflow: 'auto',
|
||||
}}
|
||||
cardProps={{ style: { minHeight: 'calc(100vh - 200px)' } }}
|
||||
cardProps={{ style: { minHeight: 'calc(100vh - 220px)' } }}
|
||||
search={{
|
||||
defaultCollapsed: true,
|
||||
}}
|
||||
|
|
|
@ -26,7 +26,11 @@ import org.springframework.data.jpa.domain.Specification;
|
|||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||
|
||||
import cn.topiam.employee.audit.context.AuditContext;
|
||||
import cn.topiam.employee.audit.entity.Target;
|
||||
|
@ -245,20 +249,28 @@ public class IdentitySourceServiceImpl implements IdentitySourceService {
|
|||
*/
|
||||
@Override
|
||||
public Boolean identitySourceConfigValidator(IdentitySourceConfigValidatorParam param) {
|
||||
return switch (param.getProvider()) {
|
||||
//钉钉
|
||||
case DINGTALK -> {
|
||||
DingTalkConfig config = JSONObject.parseObject(param.getConfig().toJSONString(),
|
||||
DingTalkConfig.class);
|
||||
yield new DingTalkConfigValidator().validate(config);
|
||||
}
|
||||
case FEISHU -> {
|
||||
FeiShuConfig config = JSONObject.parseObject(param.getConfig().toJSONString(),
|
||||
FeiShuConfig.class);
|
||||
yield new FeiShuConfigValidator().validate(config);
|
||||
}
|
||||
default -> throw new TopIamException("暂未支持此提供商连接验证");
|
||||
};
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
|
||||
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
|
||||
objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
|
||||
try {
|
||||
return switch (param.getProvider()) {
|
||||
//钉钉
|
||||
case DINGTALK -> {
|
||||
DingTalkConfig config = objectMapper
|
||||
.readValue(param.getConfig().toJSONString(), DingTalkConfig.class);;
|
||||
yield new DingTalkConfigValidator().validate(config);
|
||||
}
|
||||
case FEISHU -> {
|
||||
FeiShuConfig config = objectMapper
|
||||
.readValue(param.getConfig().toJSONString(), FeiShuConfig.class);
|
||||
yield new FeiShuConfigValidator().validate(config);
|
||||
}
|
||||
default -> throw new TopIamException("暂未支持此提供商连接验证");
|
||||
};
|
||||
} catch (JsonProcessingException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -67,4 +67,7 @@ public class DingTalkConfig extends IdentitySourceConfig {
|
|||
*/
|
||||
@JsonPropertyEncrypt
|
||||
private String token;
|
||||
|
||||
public DingTalkConfig() {
|
||||
}
|
||||
}
|
||||
|
|
|
@ -63,4 +63,7 @@ public class FeiShuConfig extends IdentitySourceConfig {
|
|||
*/
|
||||
@JsonPropertyEncrypt
|
||||
private String verificationToken;
|
||||
|
||||
public FeiShuConfig() {
|
||||
}
|
||||
}
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 380 KiB |
Binary file not shown.
Before Width: | Height: | Size: 1.1 MiB After Width: | Height: | Size: 876 KiB |
Loading…
Reference in New Issue