From f360f93cdd9485583e3789bfb8209370ae60f7eb Mon Sep 17 00:00:00 2001 From: "Jianye.Wang" Date: Thu, 14 Apr 2022 09:56:15 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8F=92=E4=BB=B6=E4=B8=8A=E6=9E=B6=EF=BC=8C?= =?UTF-8?q?=E5=88=9D=E5=A7=8B=E6=8F=90=E4=BA=A4=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 9 +- plugin.xml | 41 ++ .../integration/ControllerBridgeImpl.java | 23 + .../integration/LifecycleMonitorImpl.java | 24 + .../integration/bean/TplInfoBean.java | 54 ++ .../integration/bean/user/NamedBean.java | 25 + .../integration/bean/user/UserInfoBean.java | 45 ++ .../integration/config/IntegrateConf.java | 273 ++++++++++ .../strategy/CreateDepartmentStrategy.java | 50 ++ .../config/strategy/CreatePwdStrategy.java | 37 ++ .../strategy/user/CreateUserStrategy.java | 38 ++ .../user/impl/ContainDepRoleStrategy.java | 53 ++ .../strategy/user/impl/OnlyUserStrategy.java | 15 + .../controller/IntegratedController.java | 224 ++++++++ .../exception/JwtKeyNullException.java | 7 + .../exception/JwtSubjectFormatException.java | 14 + .../exception/ThirdAuthException.java | 14 + .../TokenDecodeExpiredException.java | 7 + .../exception/TokenNullException.java | 14 + .../integration/filter/RemoteFilter.java | 57 ++ .../integration/filter/RequestSsoFilter.java | 78 +++ .../filter/TplParaCheckFilter.java | 62 +++ .../resource/PluginConfigWebResourceImpl.java | 27 + .../resource/WebStartWebResourceImpl.java | 43 ++ .../service/IntegrateAuthService.java | 254 +++++++++ .../service/IntegrateCustomService.java | 98 ++++ .../decision/integration/utils/AESUtils.java | 68 +++ .../integration/utils/CommonUtils.java | 90 ++++ .../decision/integration/utils/Constants.java | 18 + .../decision/integration/utils/LogUtils.java | 96 ++++ .../decision/integration/utils/UserUtils.java | 417 ++++++++++++++ .../integration/web/js/button_group.item.js | 42 ++ .../decision/integration/web/js/constant.js | 17 + .../decision/integration/web/js/model.pane.js | 44 ++ .../decision/integration/web/js/pane.js | 510 ++++++++++++++++++ .../decision/integration/web/js/plugin.js | 35 ++ .../integration/web/js/protocolcheck.js | 263 +++++++++ .../decision/integration/web/js/web_start.js | 26 + 38 files changed, 3211 insertions(+), 1 deletion(-) create mode 100644 plugin.xml create mode 100644 src/main/java/com/fr/plugin/decision/integration/ControllerBridgeImpl.java create mode 100644 src/main/java/com/fr/plugin/decision/integration/LifecycleMonitorImpl.java create mode 100644 src/main/java/com/fr/plugin/decision/integration/bean/TplInfoBean.java create mode 100644 src/main/java/com/fr/plugin/decision/integration/bean/user/NamedBean.java create mode 100644 src/main/java/com/fr/plugin/decision/integration/bean/user/UserInfoBean.java create mode 100644 src/main/java/com/fr/plugin/decision/integration/config/IntegrateConf.java create mode 100644 src/main/java/com/fr/plugin/decision/integration/config/strategy/CreateDepartmentStrategy.java create mode 100644 src/main/java/com/fr/plugin/decision/integration/config/strategy/CreatePwdStrategy.java create mode 100644 src/main/java/com/fr/plugin/decision/integration/config/strategy/user/CreateUserStrategy.java create mode 100644 src/main/java/com/fr/plugin/decision/integration/config/strategy/user/impl/ContainDepRoleStrategy.java create mode 100644 src/main/java/com/fr/plugin/decision/integration/config/strategy/user/impl/OnlyUserStrategy.java create mode 100644 src/main/java/com/fr/plugin/decision/integration/controller/IntegratedController.java create mode 100644 src/main/java/com/fr/plugin/decision/integration/exception/JwtKeyNullException.java create mode 100644 src/main/java/com/fr/plugin/decision/integration/exception/JwtSubjectFormatException.java create mode 100644 src/main/java/com/fr/plugin/decision/integration/exception/ThirdAuthException.java create mode 100644 src/main/java/com/fr/plugin/decision/integration/exception/TokenDecodeExpiredException.java create mode 100644 src/main/java/com/fr/plugin/decision/integration/exception/TokenNullException.java create mode 100644 src/main/java/com/fr/plugin/decision/integration/filter/RemoteFilter.java create mode 100644 src/main/java/com/fr/plugin/decision/integration/filter/RequestSsoFilter.java create mode 100644 src/main/java/com/fr/plugin/decision/integration/filter/TplParaCheckFilter.java create mode 100644 src/main/java/com/fr/plugin/decision/integration/resource/PluginConfigWebResourceImpl.java create mode 100644 src/main/java/com/fr/plugin/decision/integration/resource/WebStartWebResourceImpl.java create mode 100644 src/main/java/com/fr/plugin/decision/integration/service/IntegrateAuthService.java create mode 100644 src/main/java/com/fr/plugin/decision/integration/service/IntegrateCustomService.java create mode 100644 src/main/java/com/fr/plugin/decision/integration/utils/AESUtils.java create mode 100644 src/main/java/com/fr/plugin/decision/integration/utils/CommonUtils.java create mode 100644 src/main/java/com/fr/plugin/decision/integration/utils/Constants.java create mode 100644 src/main/java/com/fr/plugin/decision/integration/utils/LogUtils.java create mode 100644 src/main/java/com/fr/plugin/decision/integration/utils/UserUtils.java create mode 100644 src/main/resources/com/fr/plugin/decision/integration/web/js/button_group.item.js create mode 100644 src/main/resources/com/fr/plugin/decision/integration/web/js/constant.js create mode 100644 src/main/resources/com/fr/plugin/decision/integration/web/js/model.pane.js create mode 100644 src/main/resources/com/fr/plugin/decision/integration/web/js/pane.js create mode 100644 src/main/resources/com/fr/plugin/decision/integration/web/js/plugin.js create mode 100644 src/main/resources/com/fr/plugin/decision/integration/web/js/protocolcheck.js create mode 100644 src/main/resources/com/fr/plugin/decision/integration/web/js/web_start.js diff --git a/README.md b/README.md index 87f4b36..9c06552 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,10 @@ # com.fr.plugin.decision.integration -登录集成插件 \ No newline at end of file +登录集成插件 + +# 问题记录 +Refused to execute script from ‘XXXX’ because its MIME type (‘text/html’) is not executable, and strict MIME type checking is enabled. 响应头中增加了X-Content-Type-Options:nosniff字段,导致跨域接口异常。 +解决方案:响应头中新增 content-type: application/javascript; charset=utf-8 (https://www.freesion.com/article/64191377003/) + +# 插件说明 +https://www.showdoc.com.cn/1435897295186406/7012059782236943 \ No newline at end of file diff --git a/plugin.xml b/plugin.xml new file mode 100644 index 0000000..1bfd661 --- /dev/null +++ b/plugin.xml @@ -0,0 +1,41 @@ + + + com.fr.plugin.decision.integration + + yes + no + 1.0.20 + 10.0~ + 2019-1-31 + JianYe.Wang + + + 1.0.0:调整插件结构
+ [2021-06-08]
1.0.1:优化插件配置项
+ [2021-07-07]
1.0.2:跨域登录接口新增响应头content-type规避nosniff的限制
+ [2021-08-03]
1.0.3:创建用户时部门为空
+ [2021-08-18]
1.0.4:去除角色id的传递,统一通过角色名来匹配
+ [2021-08-24]
1.0.5:角色添加逻辑异常修复
+ [2021-10-15]
1.0.8:新增URL参数单点登录校验
+ [2021-11-15]
1.0.12:去除管理员账号的权限处理
+ [2021-12-06]
1.0.13/15:添加用户类型配置参数
+ [2021-12-10]
1.0.14:移动端单点场景时重定向至携带fine_auth_token的地址
+ [2022-01-04]
1.0.16:移动端跨域登录与RequestSsoFilter移动端重定向的冲突/部门创建支持名称拼接
+ [2022-02-22]
1.0.17:将启动设计器按钮添加平台中/代码接口优化
+ [2022-03-23]
1.0.18:新增接口,单点场景可直接访问固定地址打开URL来唤醒设计器;key默认值设置为uuid
+ [2022-04-01]
1.0.19:调整WebResourceProvider接口;新增配置远程权限控制是否开启;调整用户平台类型添加逻辑
+ [2022-04-11]
1.0.20:去除自定义登录页配置选项
+ ]]> +
+ + + + + + + + + + +
\ No newline at end of file diff --git a/src/main/java/com/fr/plugin/decision/integration/ControllerBridgeImpl.java b/src/main/java/com/fr/plugin/decision/integration/ControllerBridgeImpl.java new file mode 100644 index 0000000..5ab25f6 --- /dev/null +++ b/src/main/java/com/fr/plugin/decision/integration/ControllerBridgeImpl.java @@ -0,0 +1,23 @@ +package com.fr.plugin.decision.integration; + +import com.fr.decision.fun.impl.AbstractControllerRegisterProvider; +import com.fr.plugin.context.PluginContexts; +import com.fr.plugin.decision.integration.controller.IntegratedController; +import com.fr.plugin.decision.integration.service.IntegrateAuthService; +import com.fr.plugin.transform.ExecuteFunctionRecord; +import com.fr.plugin.transform.FunctionRecorder; +import com.fr.stable.fun.Authorize; + +@Authorize(callSignKey = "com.fr.plugin.decision.integration") +@FunctionRecorder +public class ControllerBridgeImpl extends AbstractControllerRegisterProvider { + @ExecuteFunctionRecord + @Override + public Class[] getControllers() { + if (PluginContexts.currentContext().isAvailable()) { + return new Class[]{IntegratedController.class}; + } else { + return new Class[0]; + } + } +} diff --git a/src/main/java/com/fr/plugin/decision/integration/LifecycleMonitorImpl.java b/src/main/java/com/fr/plugin/decision/integration/LifecycleMonitorImpl.java new file mode 100644 index 0000000..4991bcd --- /dev/null +++ b/src/main/java/com/fr/plugin/decision/integration/LifecycleMonitorImpl.java @@ -0,0 +1,24 @@ +package com.fr.plugin.decision.integration; + +import com.fr.plugin.context.PluginContext; +import com.fr.plugin.decision.integration.config.IntegrateConf; +import com.fr.plugin.observer.inner.AbstractPluginLifecycleMonitor; + +/** + * @Author JianYe.Wang + * @Data 2022/2/22 15:54 + * @Description TODO + * @Version 10.0 + **/ +public class LifecycleMonitorImpl extends AbstractPluginLifecycleMonitor { + + @Override + public void afterRun(PluginContext pluginContext) { + IntegrateConf.getInstance(); + } + + @Override + public void beforeStop(PluginContext pluginContext) { + + } +} diff --git a/src/main/java/com/fr/plugin/decision/integration/bean/TplInfoBean.java b/src/main/java/com/fr/plugin/decision/integration/bean/TplInfoBean.java new file mode 100644 index 0000000..5ba3da7 --- /dev/null +++ b/src/main/java/com/fr/plugin/decision/integration/bean/TplInfoBean.java @@ -0,0 +1,54 @@ +package com.fr.plugin.decision.integration.bean; + +import com.fr.decision.webservice.bean.BaseBean; + +@Deprecated +public class TplInfoBean extends BaseBean { + + private String menuId; + private String title; + private String url; + private String pmenuId; + + public TplInfoBean() { + } + + public TplInfoBean(String menuId, String title, String url, String pmenuId) { + this.menuId = menuId; + this.title = title; + this.url = url; + this.pmenuId = pmenuId; + } + + public String getMenuId() { + return menuId; + } + + public void setMenuId(String menuId) { + this.menuId = menuId; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + public String getPmenuId() { + return pmenuId; + } + + public void setPmenuId(String pmenuId) { + this.pmenuId = pmenuId; + } +} diff --git a/src/main/java/com/fr/plugin/decision/integration/bean/user/NamedBean.java b/src/main/java/com/fr/plugin/decision/integration/bean/user/NamedBean.java new file mode 100644 index 0000000..1f7681d --- /dev/null +++ b/src/main/java/com/fr/plugin/decision/integration/bean/user/NamedBean.java @@ -0,0 +1,25 @@ +package com.fr.plugin.decision.integration.bean.user; + +import com.fr.decision.webservice.bean.BaseBean; + +public class NamedBean extends BaseBean { + + private String name; + private String id; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } +} diff --git a/src/main/java/com/fr/plugin/decision/integration/bean/user/UserInfoBean.java b/src/main/java/com/fr/plugin/decision/integration/bean/user/UserInfoBean.java new file mode 100644 index 0000000..ec5e54d --- /dev/null +++ b/src/main/java/com/fr/plugin/decision/integration/bean/user/UserInfoBean.java @@ -0,0 +1,45 @@ +package com.fr.plugin.decision.integration.bean.user; + +import com.fr.decision.webservice.bean.BaseBean; + +import java.util.List; + +public class UserInfoBean extends BaseBean { + + private String username; + private NamedBean department; + private List roles; + private List platformType; + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public NamedBean getDepartment() { + return department; + } + + public void setDepartment(NamedBean department) { + this.department = department; + } + + public List getRoles() { + return roles; + } + + public void setRoles(List roles) { + this.roles = roles; + } + + public List getPlatformType() { + return platformType; + } + + public void setPlatformType(List platformType) { + this.platformType = platformType; + } +} diff --git a/src/main/java/com/fr/plugin/decision/integration/config/IntegrateConf.java b/src/main/java/com/fr/plugin/decision/integration/config/IntegrateConf.java new file mode 100644 index 0000000..27f7d6a --- /dev/null +++ b/src/main/java/com/fr/plugin/decision/integration/config/IntegrateConf.java @@ -0,0 +1,273 @@ +package com.fr.plugin.decision.integration.config; + +import com.fr.cert.token.Jwts; +import com.fr.cert.token.SignatureAlgorithm; +import com.fr.config.ConfigContext; +import com.fr.config.DefaultConfiguration; +import com.fr.config.Identifier; +import com.fr.config.holder.Conf; +import com.fr.config.holder.factory.Holders; +import com.fr.config.utils.ValueReader; +import com.fr.config.utils.ValueWriter; +import com.fr.decision.base.util.UUIDUtil; +import com.fr.json.JSONObject; +import com.fr.plugin.decision.integration.config.strategy.CreateDepartmentStrategy; +import com.fr.plugin.decision.integration.config.strategy.CreatePwdStrategy; +import com.fr.plugin.decision.integration.config.strategy.user.impl.ContainDepRoleStrategy; +import com.fr.plugin.decision.integration.config.strategy.user.impl.OnlyUserStrategy; +import com.fr.plugin.decision.integration.config.strategy.user.CreateUserStrategy; +import com.fr.plugin.decision.integration.utils.AESUtils; +import com.fr.plugin.decision.integration.utils.Constants; +import com.fr.stable.StringUtils; +import com.fr.third.fasterxml.jackson.annotation.JsonIgnoreProperties; + +import java.util.Base64; +import java.util.Date; +import java.util.HashMap; + +@JsonIgnoreProperties({"id", "data", "classInfo", "nameSpace"}) +public class IntegrateConf extends DefaultConfiguration { + private static volatile IntegrateConf configuration = null; + + public static IntegrateConf getInstance() { + if (null == configuration) { + configuration = ConfigContext.getConfigInstance(IntegrateConf.class); + } + return configuration; + } + + @Identifier("jwtKey") + private Conf jwtKey = Holders.simple(UUIDUtil.generate()); + + @Identifier("createUserTurnOn") + private Conf createUserTurnOn = Holders.simple(false); + + @Identifier("userStrategy") + private Conf userStrategy = Holders.obj(new OnlyUserStrategy(), CreateUserStrategy.class); + + @Identifier("paraCheckTurnOn") + private Conf paraCheckTurnOn = Holders.simple(false); + + @Identifier("paraCheckUrl") + private Conf paraCheckUrl = Holders.simple(StringUtils.EMPTY); + + @Identifier("remoteTokenAuth") + private Conf remoteTokenAuth = Holders.simple(true); + + @Identifier("aesEncryptSubject") + private Conf aesEncryptSubject = Holders.simple(false); + + @Identifier("aesEncryptKey") + private Conf aesEncryptKey = Holders.simple(StringUtils.EMPTY); + + @Identifier("portalURLTurnOn") + private Conf portalURLTurnOn = Holders.simple(false); + + @Identifier("portalURL") + private Conf portalURL = Holders.simple(StringUtils.EMPTY); + + @Identifier("crossDomain") + private Conf crossDomain = Holders.simple(false); + + @Identifier("webStartDesigner") + private Conf webStartDesigner = Holders.simple(false); + + @Identifier("downloadUrl32") + private Conf downloadUrl32 = Holders.simple(""); + + @Identifier("downloadUrl64") + private Conf downloadUrl64 = Holders.simple(""); + + @Identifier("decisionUrl") + private Conf decisionUrl = Holders.simple(StringUtils.EMPTY); + + @Identifier("remoteFolder") + private Conf remoteFolder = Holders.simple(false); + + public String getJwtKey() { + return jwtKey.get(); + } + + public void setJwtKey(String jwtKey) { + this.jwtKey.set(jwtKey); + } + + public Boolean getCreateUserTurnOn() { + return createUserTurnOn.get(); + } + + public void setCreateUserTurnOn(Boolean createUserTurnOn) { + this.createUserTurnOn.set(createUserTurnOn); + } + + public CreateUserStrategy getUserStrategy() { + return userStrategy.get(); + } + + public void setUserStrategy(CreateUserStrategy userStrategy) { + this.userStrategy.set(userStrategy); + } + + public Boolean getParaCheckTurnOn() { + return paraCheckTurnOn.get(); + } + + public void setParaCheckTurnOn(Boolean paraCheckTurnOn) { + this.paraCheckTurnOn.set(paraCheckTurnOn); + } + + public String getParaCheckUrl() { + return paraCheckUrl.get(); + } + + public void setParaCheckUrl(String paraCheckUrl) { + this.paraCheckUrl.set(paraCheckUrl); + } + + public Boolean getRemoteTokenAuth() { + return remoteTokenAuth.get(); + } + + public void setRemoteTokenAuth(Boolean remoteTokenAuth) { + this.remoteTokenAuth.set(remoteTokenAuth); + } + + public Boolean getAesEncryptSubject() { + return aesEncryptSubject.get(); + } + + public void setAesEncryptSubject(Boolean aesEncryptSubject) { + this.aesEncryptSubject.set(aesEncryptSubject); + } + + public String getAesEncryptKey() { + return aesEncryptKey.get(); + } + + public void setAesEncryptKey(String aesEncryptKey) { + this.aesEncryptKey.set(aesEncryptKey); + } + + public Boolean getPortalURLTurnOn() { + return portalURLTurnOn.get(); + } + + public void setPortalURLTurnOn(Boolean portalURLTurnOn) { + this.portalURLTurnOn.set(portalURLTurnOn); + } + + public String getPortalURL() { + return portalURL.get(); + } + + public void setPortalURL(String portalURL) { + this.portalURL.set(portalURL); + } + + public Boolean getCrossDomain() { + return crossDomain.get(); + } + + public void setCrossDomain(Boolean crossDomain) { + this.crossDomain.set(crossDomain); + } + + public Boolean getWebStartDesigner() { + return webStartDesigner.get(); + } + + public void setWebStartDesigner(Boolean webStartDesigner) { + this.webStartDesigner.set(webStartDesigner); + } + + public String getDownloadUrl32() { + return downloadUrl32.get(); + } + + public void setDownloadUrl32(String downloadUrl32) { + this.downloadUrl32.set(downloadUrl32); + } + + public String getDownloadUrl64() { + return downloadUrl64.get(); + } + + public void setDownloadUrl64(String downloadUrl64) { + this.downloadUrl64.set(downloadUrl64); + } + + public String getDecisionUrl() { + return decisionUrl.get(); + } + + public void setDecisionUrl(String decisionUrl) { + this.decisionUrl.set(decisionUrl); + } + + public Boolean getRemoteFolder() { + return remoteFolder.get(); + } + + public void setRemoteFolder(Boolean remoteFolder) { + this.remoteFolder.set(remoteFolder); + } + + @Override + protected void initialize() { + ValueWriter.registerWriter(CreateDepartmentStrategy.class, new ValueWriter() { + @Override + public String writeObject(CreateDepartmentStrategy createDepEnum) { + return String.valueOf(createDepEnum.toInteger()); + } + }); + ValueReader.registerReader(CreateDepartmentStrategy.class, new ValueReader() { + public CreateDepartmentStrategy covert(String value) { + return CreateDepartmentStrategy.parse(Integer.parseInt(value)); + } + }); + ValueWriter.registerWriter(CreatePwdStrategy.class, new ValueWriter() { + @Override + public String writeObject(CreatePwdStrategy createDepEnum) { + return String.valueOf(createDepEnum.toInteger()); + } + }); + ValueReader.registerReader(CreatePwdStrategy.class, new ValueReader() { + public CreatePwdStrategy covert(String value) { + return CreatePwdStrategy.parse(Integer.parseInt(value)); + } + }); + } + + public HashMap getWebStartedInfo(String username) throws Exception { + HashMap info = new HashMap<>(3); + info.put("url", "fanruan://" + getDecisionUrl() + "?GWToken?" + getTokenByUserName(username)); + info.put("download64", getDownloadUrl64()); + info.put("download32", getDownloadUrl32()); + return info; + } + + public String getWebStartURL(String username, String path) throws Exception { + StringBuffer buffer = new StringBuffer(); + buffer.append(""); + return buffer.toString(); + } + + private String getTokenByUserName(String username) throws Exception { + String subject = username; + if (getUserStrategy() instanceof ContainDepRoleStrategy) { + subject = JSONObject.create().put("username", username).toString(); + } + if (getAesEncryptSubject()) { + subject = Base64.getEncoder().encodeToString(AESUtils.aesEncryptToBytes(subject, getAesEncryptKey())); + } + Date date = new Date(); + return Jwts.builder() + .setSubject(subject) + .setExpiration(new Date(date.getTime() + Constants.EXPIRED_TIME)) + .signWith(SignatureAlgorithm.HS256, getJwtKey()) + .compact(); + } +} diff --git a/src/main/java/com/fr/plugin/decision/integration/config/strategy/CreateDepartmentStrategy.java b/src/main/java/com/fr/plugin/decision/integration/config/strategy/CreateDepartmentStrategy.java new file mode 100644 index 0000000..de848d8 --- /dev/null +++ b/src/main/java/com/fr/plugin/decision/integration/config/strategy/CreateDepartmentStrategy.java @@ -0,0 +1,50 @@ +package com.fr.plugin.decision.integration.config.strategy; + +import com.fr.general.ComparatorUtils; +import com.fr.third.fasterxml.jackson.annotation.JsonCreator; +import com.fr.third.fasterxml.jackson.annotation.JsonValue; + +/** + * @Author JianYe.Wang + * @Data 10:14 + * @Description TODO + * @Version 10.0 + **/ + +/** + * "department": { + * "name": "beijing", + * "id": "02" + * } + */ +public enum CreateDepartmentStrategy { + /** + * 根据id长度, 01 011 012 + */ + DEP_ID_LENGTH(0), + /** + * 根据name拼接 中国|北京市|朝阳区 + */ + DEP_NAME_GROUP(1); + + private int type; + + CreateDepartmentStrategy(int type) { + this.type = type; + } + + @JsonValue + public int toInteger() { + return this.type; + } + + @JsonCreator + public static CreateDepartmentStrategy parse(int value) { + for (CreateDepartmentStrategy type : CreateDepartmentStrategy.values()) { + if (ComparatorUtils.equals(value, type.toInteger())) { + return type; + } + } + return null; + } +} diff --git a/src/main/java/com/fr/plugin/decision/integration/config/strategy/CreatePwdStrategy.java b/src/main/java/com/fr/plugin/decision/integration/config/strategy/CreatePwdStrategy.java new file mode 100644 index 0000000..9ee3aa4 --- /dev/null +++ b/src/main/java/com/fr/plugin/decision/integration/config/strategy/CreatePwdStrategy.java @@ -0,0 +1,37 @@ +package com.fr.plugin.decision.integration.config.strategy; + +import com.fr.general.ComparatorUtils; +import com.fr.third.fasterxml.jackson.annotation.JsonCreator; +import com.fr.third.fasterxml.jackson.annotation.JsonValue; + +/** + * @Author JianYe.Wang + * @Data 21-06-07 10:07 + * @Description TODO + * @Version 10.0 + **/ +public enum CreatePwdStrategy { + SAME_USER(0), RANDOM(1); + + private int type; + + CreatePwdStrategy(int type) { + this.type = type; + } + + @JsonValue + public int toInteger() { + return this.type; + } + + @JsonCreator + public static CreatePwdStrategy parse(int value) { + for (CreatePwdStrategy type : CreatePwdStrategy.values()) { + if (ComparatorUtils.equals(value, type.toInteger())) { + return type; + } + } + + return null; + } +} diff --git a/src/main/java/com/fr/plugin/decision/integration/config/strategy/user/CreateUserStrategy.java b/src/main/java/com/fr/plugin/decision/integration/config/strategy/user/CreateUserStrategy.java new file mode 100644 index 0000000..3b21798 --- /dev/null +++ b/src/main/java/com/fr/plugin/decision/integration/config/strategy/user/CreateUserStrategy.java @@ -0,0 +1,38 @@ +package com.fr.plugin.decision.integration.config.strategy.user; + +import com.fr.config.Identifier; +import com.fr.config.holder.Conf; +import com.fr.config.holder.factory.Holders; +import com.fr.config.utils.UniqueKey; +import com.fr.plugin.decision.integration.config.strategy.CreatePwdStrategy; +import com.fr.plugin.decision.integration.config.strategy.user.impl.ContainDepRoleStrategy; +import com.fr.plugin.decision.integration.config.strategy.user.impl.OnlyUserStrategy; +import com.fr.third.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fr.third.fasterxml.jackson.annotation.JsonSubTypes; +import com.fr.third.fasterxml.jackson.annotation.JsonTypeInfo; + +/** + * @Author JianYe.Wang + * @Data 21-6-7 10:01 + * @Description TODO + * @Version 10.0 + **/ +@JsonIgnoreProperties({"id", "data", "classInfo", "nameSpace"}) +@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "userType") +@JsonSubTypes({ + @JsonSubTypes.Type(value = OnlyUserStrategy.class, name = "onlyUser"), + @JsonSubTypes.Type(value = ContainDepRoleStrategy.class, name = "containDepRole") +}) +public abstract class CreateUserStrategy extends UniqueKey { + + @Identifier("pwdStrategy") + private Conf pwdStrategy = Holders.simple(CreatePwdStrategy.SAME_USER); + + public CreatePwdStrategy getPwdStrategy() { + return pwdStrategy.get(); + } + + public void setPwdStrategy(CreatePwdStrategy pwdStrategy) { + this.pwdStrategy.set(pwdStrategy); + } +} diff --git a/src/main/java/com/fr/plugin/decision/integration/config/strategy/user/impl/ContainDepRoleStrategy.java b/src/main/java/com/fr/plugin/decision/integration/config/strategy/user/impl/ContainDepRoleStrategy.java new file mode 100644 index 0000000..915d82f --- /dev/null +++ b/src/main/java/com/fr/plugin/decision/integration/config/strategy/user/impl/ContainDepRoleStrategy.java @@ -0,0 +1,53 @@ +package com.fr.plugin.decision.integration.config.strategy.user.impl; + +import com.fr.config.Identifier; +import com.fr.config.holder.Conf; +import com.fr.config.holder.factory.Holders; +import com.fr.plugin.decision.integration.config.strategy.CreateDepartmentStrategy; +import com.fr.plugin.decision.integration.config.strategy.user.CreateUserStrategy; +import com.fr.stable.StringUtils; +import com.fr.third.fasterxml.jackson.annotation.JsonTypeName; + +/** + * @Author JianYe.Wang + * @Data 10:13 + * @Description TODO + * @Version 10.0 + **/ +@JsonTypeName("containDepRole") +public class ContainDepRoleStrategy extends CreateUserStrategy { + + @Identifier("depStrategy") + private Conf depStrategy = Holders.simple(CreateDepartmentStrategy.DEP_ID_LENGTH); + + @Identifier("specialRootId") + private Conf specialRootId = Holders.simple(StringUtils.EMPTY); + + // 分隔符, CreateDepEnum.DEP_NAME_GROUP 会用到 + @Identifier("separator") + private Conf separator = Holders.simple(StringUtils.EMPTY); + + public CreateDepartmentStrategy getDepStrategy() { + return depStrategy.get(); + } + + public void setDepStrategy(CreateDepartmentStrategy depStrategy) { + this.depStrategy.set(depStrategy); + } + + public String getSpecialRootId() { + return specialRootId.get(); + } + + public void setSpecialRootId(String specialRootId) { + this.specialRootId.set(specialRootId); + } + + public String getSeparator() { + return separator.get(); + } + + public void setSeparator(String separator) { + this.separator.set(separator); + } +} diff --git a/src/main/java/com/fr/plugin/decision/integration/config/strategy/user/impl/OnlyUserStrategy.java b/src/main/java/com/fr/plugin/decision/integration/config/strategy/user/impl/OnlyUserStrategy.java new file mode 100644 index 0000000..a5b35b3 --- /dev/null +++ b/src/main/java/com/fr/plugin/decision/integration/config/strategy/user/impl/OnlyUserStrategy.java @@ -0,0 +1,15 @@ +package com.fr.plugin.decision.integration.config.strategy.user.impl; + +import com.fr.plugin.decision.integration.config.strategy.user.CreateUserStrategy; +import com.fr.third.fasterxml.jackson.annotation.JsonTypeName; + +/** + * @Author JianYe.Wang + * @Data 21-6-7 10:12 + * @Description TODO + * @Version 10.0 + **/ +@JsonTypeName("onlyUser") +public class OnlyUserStrategy extends CreateUserStrategy { + +} diff --git a/src/main/java/com/fr/plugin/decision/integration/controller/IntegratedController.java b/src/main/java/com/fr/plugin/decision/integration/controller/IntegratedController.java new file mode 100644 index 0000000..726fd81 --- /dev/null +++ b/src/main/java/com/fr/plugin/decision/integration/controller/IntegratedController.java @@ -0,0 +1,224 @@ +package com.fr.plugin.decision.integration.controller; + +import com.fr.base.ServerConfig; +import com.fr.decision.authority.data.User; +import com.fr.decision.webservice.CrossDomainResponse; +import com.fr.decision.webservice.Response; +import com.fr.decision.webservice.annotation.LoginStatusChecker; +import com.fr.decision.webservice.v10.login.LoginService; +import com.fr.decision.webservice.v10.login.TokenResource; +import com.fr.decision.webservice.v10.user.UserService; +import com.fr.io.utils.ResourceIOUtils; +import com.fr.log.FineLoggerFactory; +import com.fr.plugin.decision.integration.config.IntegrateConf; +import com.fr.plugin.decision.integration.service.IntegrateAuthService; +import com.fr.plugin.decision.integration.service.IntegrateCustomService; +import com.fr.plugin.decision.integration.utils.CommonUtils; +import com.fr.plugin.decision.integration.utils.Constants; +import com.fr.stable.StableUtils; +import com.fr.stable.StringUtils; +import com.fr.third.fasterxml.jackson.databind.ObjectMapper; +import com.fr.third.springframework.stereotype.Controller; +import com.fr.third.springframework.web.bind.annotation.*; +import com.fr.web.utils.WebUtils; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.Map; + +@Controller +@RequestMapping({"/third/auth"}) +@LoginStatusChecker(required = false) +public class IntegratedController { + + private ObjectMapper mapper = new ObjectMapper(); + + /** + * 建议用于后台换取 fine_auth_token + * @param request + * @param response + * @param body 请求体 + * @param third_token 请求参数 + * @return fine_auth_token + * @throws Exception + */ + @RequestMapping("/login") + @ResponseBody + public Response loginByToken(HttpServletRequest request, HttpServletResponse response, @RequestBody(required = false) Map body, @RequestParam(required = false, value = Constants.TOKEN_NAME) String third_token) throws Exception { + String token = third_token; + if (StringUtils.isBlank(third_token)) { + token = (String) body.get(Constants.TOKEN_NAME); + } + String username = IntegrateAuthService.getInstance().getUserNameFromToken(token); + return Response.ok(LoginService.getInstance().login(request, response, username)); + } + + /** + * 用于前台跨域请求 + * @param request + * @param response + * @param third_token + * @param callback + * @return + * @throws Exception + */ + @RequestMapping( + value = {"/cross/login"}, + method = {RequestMethod.GET}, + produces = "application/javascript; charset=utf-8" + ) + @ResponseBody + public String crossLoginByToken(HttpServletRequest request, + HttpServletResponse response, + @RequestParam(Constants.TOKEN_NAME) String third_token, + @RequestParam(value = "callback", required = false, defaultValue = "callback") String callback + ) throws Exception { + String username = IntegrateAuthService.getInstance().getUserNameFromToken(third_token); + return CrossDomainResponse.create().callbackFuncName(callback) + .parameter("accessToken", LoginService.getInstance().login(request, response, username)) + .parameter("url", IntegrateCustomService.getInstance().generateDefaultHomePageUrl(request)) + .parameter("status", "success") + .createCrossDomainResponse(); + } + + /** + * URL设计器启动需要用的接口 + */ + + /** + * 解析获取 token 对应的用户名 + * @param third_token + * @return + */ + @RequestMapping( + value = {"/username"} + ) + @ResponseBody + public String getUsernameFromToken(@RequestParam(Constants.TOKEN_NAME) String third_token) throws Exception { + return IntegrateAuthService.getInstance().getUserNameFromToken(third_token); + } + + /** + * 获取用户对应的token,用于web唤醒设计器传入token + */ + @RequestMapping( + value = {"/username/token"} + ) + @ResponseBody + @LoginStatusChecker + public Response getThirdTokenByUsername(HttpServletRequest request) throws Exception { + String username = LoginService.getInstance().getCurrentUserNameFromRequest(request); + return Response.ok(IntegrateConf.getInstance().getWebStartedInfo(username)); + } + + /** + * 登录状态下 + */ + @RequestMapping( + value = {"/designer/start"} + ) + @ResponseBody + //@LoginStatusChecker(tokenResource = TokenResource.COOKIE) + public String openDesignerStartUrl(HttpServletRequest request, + HttpServletResponse response, + @RequestParam(value = "path", required = false) String path) throws Exception { + String username = LoginService.getInstance().getCurrentUserNameFromRequestCookie(request); + if (StringUtils.isEmpty(username)) return StringUtils.EMPTY; + response.setContentType("text/html; charset=GB2312"); + return IntegrateConf.getInstance().getWebStartURL(username, path); + } + + /** + * 插件配置读取、保存 + */ + @RequestMapping( + value = {"/conf"}, + method = RequestMethod.GET + ) + @ResponseBody + @LoginStatusChecker + public Response getIntegrateConfig(HttpServletRequest request, HttpServletResponse response) throws Exception { + String userId = UserService.getInstance().getCurrentUserIdFromCookie(request); + if (UserService.getInstance().isAdmin(userId)) { + response.setContentType("application/json;charset=" + ServerConfig.getInstance().getServerCharset()); + mapper.writeValue(response.getOutputStream(), IntegrateConf.getInstance()); + } + + return Response.error("getIntegrateConf", "仅管理员用户可见"); + } + + @RequestMapping( + value = {"/conf"}, + method = RequestMethod.POST + ) + @ResponseBody + @LoginStatusChecker + public Response updateIntegrateConfig(HttpServletRequest request) throws IOException { + IntegrateConf conf = mapper.readValue(request.getInputStream(), IntegrateConf.class); + IntegrateAuthService.getInstance().updateIntegrateConf(conf); + return Response.success(); + } + + + /** + * 插件历史接口,做兼容使用 + */ + + @RequestMapping( + value = {"/tpl/paste"} + ) + @ResponseBody + @Deprecated + public Response pasteTemplate(@RequestParam(Constants.TOKEN_NAME) String third_token, @RequestParam("tplPath") String tplPath) throws Exception { + String username = IntegrateAuthService.getInstance().getUserNameFromToken(third_token); + if (StringUtils.isBlank(username)) + return Response.error("pasteTemplate", "TOKEN解析异常"); + + if (StringUtils.isBlank(tplPath) || !ResourceIOUtils.exist(StableUtils.pathJoin("reportlets", tplPath))) + return Response.error("pasteTemplate", "模板文件不存在"); + + ResourceIOUtils.copy( + StableUtils.pathJoin("reportlets", tplPath), + StableUtils.pathJoin("reportlets", username, tplPath.substring(tplPath.lastIndexOf("/"))) + ); + return Response.success(); + } + + @RequestMapping( + value = {"/tpl/remote/list"} + ) + @ResponseBody + @Deprecated + public Response getUserRemoteTplList(HttpServletRequest request, @RequestParam(Constants.TOKEN_NAME) String third_token) throws Exception { + String username = IntegrateAuthService.getInstance().getUserNameFromToken(third_token); + if (StringUtils.isBlank(username)) + return Response.error("UserRemoteTplList", "TOKEN解析异常"); + + User user = UserService.getInstance().getUserByUserName(username); + return Response.ok(IntegrateCustomService.getInstance().getRemoteTplListByUserId(user.getId(), WebUtils.createServletURL(request))); + } + + /** + * token 换取帆软token,主要用于后台请求 + */ + @RequestMapping({"/token"}) + @ResponseBody + @Deprecated + public String tokenExchangeFineAuthToken(HttpServletRequest request, HttpServletResponse response, @RequestBody Map body) throws Exception { + String username = IntegrateAuthService.getInstance().getUserNameFromToken((String) body.get(Constants.TOKEN_NAME)); + return LoginService.getInstance().login(request, response, username); + } + + /** + * 处理下异常返回值统一格式返回 + * @param ex + * @return + */ + @ExceptionHandler(Exception.class) + @ResponseBody + public Response exceptionRespond(Exception ex) { + FineLoggerFactory.getLogger().error(ex.getMessage(), ex); + return CommonUtils.formatException(ex); + } +} diff --git a/src/main/java/com/fr/plugin/decision/integration/exception/JwtKeyNullException.java b/src/main/java/com/fr/plugin/decision/integration/exception/JwtKeyNullException.java new file mode 100644 index 0000000..e446c88 --- /dev/null +++ b/src/main/java/com/fr/plugin/decision/integration/exception/JwtKeyNullException.java @@ -0,0 +1,7 @@ +package com.fr.plugin.decision.integration.exception; + +public class JwtKeyNullException extends ThirdAuthException { + public JwtKeyNullException() { + super("The value of jwt key is null!"); + } +} diff --git a/src/main/java/com/fr/plugin/decision/integration/exception/JwtSubjectFormatException.java b/src/main/java/com/fr/plugin/decision/integration/exception/JwtSubjectFormatException.java new file mode 100644 index 0000000..8e5eed3 --- /dev/null +++ b/src/main/java/com/fr/plugin/decision/integration/exception/JwtSubjectFormatException.java @@ -0,0 +1,14 @@ +package com.fr.plugin.decision.integration.exception; + +/** + * @Author JianYe.Wang + * @Data 2021/7/7 14:22 + * @Description TODO + * @Version 10.0 + **/ +public class JwtSubjectFormatException extends ThirdAuthException { + + public JwtSubjectFormatException() { + super("Jwt key subject format error!"); + } +} diff --git a/src/main/java/com/fr/plugin/decision/integration/exception/ThirdAuthException.java b/src/main/java/com/fr/plugin/decision/integration/exception/ThirdAuthException.java new file mode 100644 index 0000000..f7cbf63 --- /dev/null +++ b/src/main/java/com/fr/plugin/decision/integration/exception/ThirdAuthException.java @@ -0,0 +1,14 @@ +package com.fr.plugin.decision.integration.exception; + +public class ThirdAuthException extends RuntimeException { + + public static final String KEY = "FR decision integration exception: "; + + public ThirdAuthException(String message) { + super(KEY + message); + } + + public ThirdAuthException(String message, Throwable cause) { + super(KEY + message, cause); + } +} diff --git a/src/main/java/com/fr/plugin/decision/integration/exception/TokenDecodeExpiredException.java b/src/main/java/com/fr/plugin/decision/integration/exception/TokenDecodeExpiredException.java new file mode 100644 index 0000000..eea4b7b --- /dev/null +++ b/src/main/java/com/fr/plugin/decision/integration/exception/TokenDecodeExpiredException.java @@ -0,0 +1,7 @@ +package com.fr.plugin.decision.integration.exception; + +public class TokenDecodeExpiredException extends ThirdAuthException { + public TokenDecodeExpiredException() { + super("Jwt token decode result is expired!"); + } +} diff --git a/src/main/java/com/fr/plugin/decision/integration/exception/TokenNullException.java b/src/main/java/com/fr/plugin/decision/integration/exception/TokenNullException.java new file mode 100644 index 0000000..11e1806 --- /dev/null +++ b/src/main/java/com/fr/plugin/decision/integration/exception/TokenNullException.java @@ -0,0 +1,14 @@ +package com.fr.plugin.decision.integration.exception; + +/** + * @Author JianYe.Wang + * @Data 2021/11/1 9:41 + * @Description TODO + * @Version 10.0 + **/ +public class TokenNullException extends ThirdAuthException { + + public TokenNullException() { + super("Jwt token is null!"); + } +} diff --git a/src/main/java/com/fr/plugin/decision/integration/filter/RemoteFilter.java b/src/main/java/com/fr/plugin/decision/integration/filter/RemoteFilter.java new file mode 100644 index 0000000..fb3bf7c --- /dev/null +++ b/src/main/java/com/fr/plugin/decision/integration/filter/RemoteFilter.java @@ -0,0 +1,57 @@ +package com.fr.plugin.decision.integration.filter; + +import com.fr.data.NetworkHelper; +import com.fr.decision.fun.impl.AbstractEmbedRequestFilterProvider; +import com.fr.decision.webservice.Response; +import com.fr.decision.webservice.v10.remote.RemoteDesignStatusService; +import com.fr.plugin.decision.integration.config.IntegrateConf; +import com.fr.plugin.decision.integration.service.IntegrateAuthService; +import com.fr.plugin.decision.integration.utils.LogUtils; +import com.fr.security.JwtUtils; +import com.fr.security.SecurityToolbox; +import com.fr.security.encryption.transmission.TransmissionEncryptors; +import com.fr.stable.StringUtils; +import com.fr.third.fasterxml.jackson.databind.ObjectMapper; +import com.fr.web.utils.WebUtils; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +/** + * 拦截设计器远程请求, 支持Token认证 + */ +public class RemoteFilter extends AbstractEmbedRequestFilterProvider { + + private static final String PATH_TOKEN = "/remote/design/token"; + private static final String PATH_VERIFY = "/remote/design/verify"; + + @Override + public void filter(HttpServletRequest request, HttpServletResponse response) { + if (!IntegrateConf.getInstance().getRemoteTokenAuth()) return; + String username = NetworkHelper.getHTTPRequestParameter(request, "username"); + String password; + if (request.getRequestURI().endsWith(PATH_TOKEN)) { + if (StringUtils.equalsIgnoreCase("get", request.getMethod())) { + password = NetworkHelper.getHTTPRequestParameter(request, "password"); + } else { + password = SecurityToolbox.defaultDecrypt(NetworkHelper.getHTTPRequestParameter(request, "password")); + } + } else if (request.getRequestURI().endsWith(PATH_VERIFY)) { + password = TransmissionEncryptors.getInstance().decrypt(NetworkHelper.getHTTPRequestParameter(request, "password")); + } else { + return; + } + + LogUtils.info("RemoteFilter token auth success: {}", username); + ObjectMapper mapper = new ObjectMapper(); + try { + if (StringUtils.isNotBlank(IntegrateAuthService.getInstance().getUserNameFromToken(password))) { + String token = JwtUtils.createDefaultJWT(username); + RemoteDesignStatusService.loginStatusService().put(token, username, 1209600000); + WebUtils.printAsString(response, mapper.writeValueAsString(Response.ok(token))); + } + } catch (Exception e) { + LogUtils.error(e.getMessage(), e); + } + } +} diff --git a/src/main/java/com/fr/plugin/decision/integration/filter/RequestSsoFilter.java b/src/main/java/com/fr/plugin/decision/integration/filter/RequestSsoFilter.java new file mode 100644 index 0000000..6b731e3 --- /dev/null +++ b/src/main/java/com/fr/plugin/decision/integration/filter/RequestSsoFilter.java @@ -0,0 +1,78 @@ +package com.fr.plugin.decision.integration.filter; + +import com.fr.decision.fun.impl.AbstractEmbedRequestFilterProvider; +import com.fr.decision.webservice.v10.login.LoginService; +import com.fr.log.FineLoggerFactory; +import com.fr.plugin.decision.integration.config.IntegrateConf; +import com.fr.plugin.decision.integration.service.IntegrateAuthService; +import com.fr.plugin.decision.integration.utils.CommonUtils; +import com.fr.stable.StringUtils; +import com.fr.web.utils.WebUtils; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +import static org.apache.catalina.filters.CorsFilter.*; + +/** + * @Author JianYe.Wang + * @Data 2021/10/15 14:44 + * @Description TODO + * @Version 10.0 + **/ +public class RequestSsoFilter extends AbstractEmbedRequestFilterProvider { + + @Override + public void filter(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { + try { + // url 携带 token 单点 + String token = WebUtils.getHTTPRequestParameter(request, "third_token"); + if (StringUtils.isNotEmpty(token)) { + String userName = IntegrateAuthService.getInstance().getUserNameFromToken(token); + if (CommonUtils.isMobile(request) && !request.getRequestURI().endsWith("/third/auth/cross/login")) { + String frToken = doLoginFR(request, response, userName); + String originalURL = WebUtils.getOriginalURL(request).replaceAll("&?third_token=[^&]*", ""); + response.sendRedirect(originalURL + "&fine_auth_token=" + frToken); + } else { + String curUserName = LoginService.getInstance().getCurrentUserNameFromRequestCookie(request); + if (!LoginService.getInstance().isLogged(request) || !StringUtils.equals(userName, curUserName)) { + doLoginFR(request, response, userName); + } + } + } + + // 自定义登录页判断,/login 还保留 + if (IntegrateConf.getInstance().getPortalURLTurnOn() && request.getRequestURI().endsWith(WebUtils.createServletURL(request)) && !LoginService.getInstance().isLogged(request)) { + response.sendRedirect(IntegrateConf.getInstance().getPortalURL()); + return; + } + + // 跨域判断 + if (IntegrateConf.getInstance().getCrossDomain()) { + String origin = request.getHeader("Origin"); + if (StringUtils.isNotEmpty(origin)) { + response.addHeader(RESPONSE_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN, origin); + String headers = request.getHeader(REQUEST_HEADER_ACCESS_CONTROL_REQUEST_HEADERS); + if (StringUtils.isNotEmpty(headers)) + response.addHeader(RESPONSE_HEADER_ACCESS_CONTROL_ALLOW_HEADERS, headers); + response.addHeader(RESPONSE_HEADER_ACCESS_CONTROL_ALLOW_METHODS, "*"); + response.addHeader(RESPONSE_HEADER_ACCESS_CONTROL_MAX_AGE, "3600"); + + } + /*if (request.getMethod().equals("OPTIONS")) { + response.setStatus(HttpServletResponse.SC_OK); + }*/ + } + } catch (Exception e) { + FineLoggerFactory.getLogger().error(e.getMessage(), e); + } + } + + private String doLoginFR(HttpServletRequest request, HttpServletResponse response, String username) throws Exception { + String frToken = LoginService.getInstance().login(request, response, username); + request.setAttribute("fine_auth_token", frToken); + return frToken; + } +} diff --git a/src/main/java/com/fr/plugin/decision/integration/filter/TplParaCheckFilter.java b/src/main/java/com/fr/plugin/decision/integration/filter/TplParaCheckFilter.java new file mode 100644 index 0000000..189d9ac --- /dev/null +++ b/src/main/java/com/fr/plugin/decision/integration/filter/TplParaCheckFilter.java @@ -0,0 +1,62 @@ +package com.fr.plugin.decision.integration.filter; + +import com.fr.decision.fun.impl.AbstractEmbedRequestFilterProvider; +import com.fr.decision.webservice.v10.login.LoginService; +import com.fr.general.http.HttpRequest; +import com.fr.general.http.HttpToolbox; +import com.fr.json.JSONObject; +import com.fr.plugin.decision.integration.config.IntegrateConf; +import com.fr.plugin.decision.integration.utils.CommonUtils; +import com.fr.stable.ArrayUtils; +import com.fr.stable.StringUtils; +import com.fr.third.fasterxml.jackson.databind.ObjectMapper; +import com.fr.third.org.apache.http.HttpEntity; +import com.fr.third.org.apache.http.entity.StringEntity; +import com.fr.web.utils.WebUtils; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +public class TplParaCheckFilter extends AbstractEmbedRequestFilterProvider { + + private String[] reportParameterNames = new String[]{"viewlet", "viewlets", "reportlet", "reportlets"}; + + @Override + public void filter(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { + if (!needCheckParameter(request)) return; + + HashMap requestMap = new HashMap<>(); + for (String parameter : request.getParameterMap().keySet()) { + if (!ArrayUtils.contains(reportParameterNames, parameter)) + requestMap.put(parameter, request.getParameter(parameter)); + } + requestMap.put("userId", LoginService.getInstance().getCurrentUserNameFromRequestCookie(request)); + + try { + if (doCheckTplParameter(requestMap)) return; + } catch (Exception e) { + } + CommonUtils.showErrorPage(response, "请登录系统访问报表", ""); + } + + /** + * 请求是否需要校验预览参数 + **/ + private boolean needCheckParameter(HttpServletRequest request) { + return IntegrateConf.getInstance().getParaCheckTurnOn() && + //LoginService.getInstance().isLogged(request) && + StringUtils.isNotBlank(WebUtils.getReportTitleFromRequest(request)); + } + + private boolean doCheckTplParameter(Map requestMap) throws IOException { + HashMap headerMap = new HashMap(); + headerMap.put("Content-Type", "application/json"); + HttpEntity entity = new StringEntity(new ObjectMapper().writeValueAsString(requestMap), "utf-8"); + String result = HttpToolbox.executeAndParse(HttpRequest.custom().url(IntegrateConf.getInstance().getParaCheckUrl()).post(entity).headers(headerMap).build()); + return new JSONObject(result).getBoolean("data"); + } +} diff --git a/src/main/java/com/fr/plugin/decision/integration/resource/PluginConfigWebResourceImpl.java b/src/main/java/com/fr/plugin/decision/integration/resource/PluginConfigWebResourceImpl.java new file mode 100644 index 0000000..6e50e2e --- /dev/null +++ b/src/main/java/com/fr/plugin/decision/integration/resource/PluginConfigWebResourceImpl.java @@ -0,0 +1,27 @@ +package com.fr.plugin.decision.integration.resource; + +import com.fr.decision.fun.impl.AbstractWebResourceProvider; +import com.fr.decision.web.MainComponent; +import com.fr.plugin.decision.integration.config.IntegrateConf; +import com.fr.web.struct.Atom; +import com.fr.web.struct.Component; +import com.fr.web.struct.Filter; +import com.fr.web.struct.category.ScriptPath; + +public class PluginConfigWebResourceImpl extends AbstractWebResourceProvider { + + @Override + public Atom attach() { + return MainComponent.KEY; + } + + @Override + public Atom client() { + return new Component() { + @Override + public ScriptPath script() { + return ScriptPath.build("com/fr/plugin/decision/integration/web/js/plugin.js"); + } + }; + } +} diff --git a/src/main/java/com/fr/plugin/decision/integration/resource/WebStartWebResourceImpl.java b/src/main/java/com/fr/plugin/decision/integration/resource/WebStartWebResourceImpl.java new file mode 100644 index 0000000..a25c46b --- /dev/null +++ b/src/main/java/com/fr/plugin/decision/integration/resource/WebStartWebResourceImpl.java @@ -0,0 +1,43 @@ +package com.fr.plugin.decision.integration.resource; + +import com.fr.decision.fun.impl.AbstractWebResourceProvider; +import com.fr.decision.web.MainComponent; +import com.fr.plugin.decision.integration.config.IntegrateConf; +import com.fr.web.struct.Atom; +import com.fr.web.struct.Component; +import com.fr.web.struct.Filter; +import com.fr.web.struct.category.ScriptPath; + +/** + * @Author JianYe.Wang + * @Data 2022/4/1 15:22 + * @Description TODO + * @Version 10.0 + **/ +public class WebStartWebResourceImpl extends AbstractWebResourceProvider { + + @Override + public Atom attach() { + return MainComponent.KEY; + } + + @Override + public Atom client() { + return new Component() { + @Override + public ScriptPath script() { + return ScriptPath.build("com/fr/plugin/decision/integration/web/js/web_start.js"); + } + + @Override + public Filter filter() { + return new Filter() { + @Override + public boolean accept() { + return IntegrateConf.getInstance().getWebStartDesigner(); + } + }; + } + }; + } +} diff --git a/src/main/java/com/fr/plugin/decision/integration/service/IntegrateAuthService.java b/src/main/java/com/fr/plugin/decision/integration/service/IntegrateAuthService.java new file mode 100644 index 0000000..987d485 --- /dev/null +++ b/src/main/java/com/fr/plugin/decision/integration/service/IntegrateAuthService.java @@ -0,0 +1,254 @@ +package com.fr.plugin.decision.integration.service; + + +import com.fr.cert.token.Claims; +import com.fr.decision.base.util.UUIDUtil; +import com.fr.decision.privilege.TransmissionTool; +import com.fr.decision.webservice.bean.user.UserBean; +import com.fr.decision.webservice.v10.user.UserService; +import com.fr.io.utils.ResourceIOUtils; +import com.fr.plugin.decision.integration.config.strategy.CreatePwdStrategy; +import com.fr.plugin.decision.integration.bean.user.UserInfoBean; +import com.fr.plugin.decision.integration.config.IntegrateConf; +import com.fr.plugin.decision.integration.config.strategy.user.impl.OnlyUserStrategy; +import com.fr.plugin.decision.integration.exception.JwtKeyNullException; +import com.fr.plugin.decision.integration.exception.JwtSubjectFormatException; +import com.fr.plugin.decision.integration.exception.TokenDecodeExpiredException; +import com.fr.plugin.decision.integration.exception.TokenNullException; +import com.fr.plugin.decision.integration.utils.*; +import com.fr.report.ReportContext; +import com.fr.report.constant.RoleType; +import com.fr.report.data.RemoteDesignAuthority; +import com.fr.report.util.RemoteDesignAuthHelper; +import com.fr.report.util.RemoteUserInfo; +import com.fr.security.JwtUtils; +import com.fr.stable.StableUtils; +import com.fr.stable.StringUtils; +import com.fr.third.fasterxml.jackson.databind.ObjectMapper; +import com.fr.transaction.Configurations; +import com.fr.transaction.WorkerAdaptor; +import com.fr.web.service.RemoteDesignAuthorityDataService; + +import java.io.IOException; +import java.io.InputStream; +import java.util.*; + +public class IntegrateAuthService { + + private volatile static IntegrateAuthService service; + private static Properties properties = new Properties(); + private IntegrateConf config = IntegrateConf.getInstance(); + private ObjectMapper mapper = new ObjectMapper(); + + private IntegrateAuthService() { + } + + public static IntegrateAuthService getInstance() { + if (service == null) { + synchronized (IntegrateAuthService.class) { + if (service == null) { + service = new IntegrateAuthService(); + } + } + } + return service; + } + + public String getUserNameFromToken(String thirdToken) throws Exception { + if (StringUtils.isEmpty(thirdToken)) { + throw new TokenNullException(); + } + String userInfo = decodeJwtToken(thirdToken, loadJwtKey()); + if (config.getAesEncryptSubject()) { + userInfo = AESUtils.aesDecryptByBytes(Base64.getDecoder().decode(userInfo), config.getAesEncryptKey()); + } + + return addOrUpdateUserInfo(userInfo); + } + + private String loadJwtKey() { + String key = config.getJwtKey(); + if (StringUtils.isBlank(key)) { + key = properties.getProperty("key"); + } + + if (StringUtils.isBlank(key)) { + throw new JwtKeyNullException(); + } + return key; + } + + private String decodeJwtToken(String thirdToken, String key) { + Claims claims = JwtUtils.parseJWT(thirdToken, key); + Date date = claims.getExpiration(); + if (date != null && date.after(new Date())) { + return claims.getSubject(); + } + throw new TokenDecodeExpiredException(); + } + + + /** + * 校验更新/创建用户信息 + * + * @param userInfoStr + * @return username + * @throws Exception + */ + private String addOrUpdateUserInfo(String userInfoStr) throws Exception { + // 未开启创建用户时,直接返回用户名不需要创建用户信息 + if (!config.getCreateUserTurnOn()) { + if (CommonUtils.isJsonFormatStr(userInfoStr)) { + return mapper.readValue(userInfoStr, UserInfoBean.class).getUsername(); + }; + return userInfoStr; + } + + // 仅用户名 + if (config.getUserStrategy() instanceof OnlyUserStrategy) { + if (CommonUtils.isJsonFormatStr(userInfoStr)) { + throw new JwtSubjectFormatException(); + } + // 用户不存在时基于用户名创建下用户 + if (!UserUtils.existUsername(userInfoStr)) { + UserService.getInstance().addUser(buildBaseUserBean(userInfoStr)); + checkAndCreateRemoteDesignAuth(userInfoStr); + } + return userInfoStr; + } + + // 含部门职位 + UserInfoBean userInfo = mapper.readValue(userInfoStr, UserInfoBean.class); + // 管理员账号不进行处理 + if (UserUtils.isAdmin(userInfo.getUsername())) { + return userInfo.getUsername(); + } + UserBean userBean; + if (!UserUtils.existUsername(userInfo.getUsername())) { + UserService.getInstance().addUser(buildBaseUserBean(userInfo.getUsername())); + } + userBean = UserService.getInstance().getUserAccount(userInfo.getUsername()); + // 更新部门信息 + if (null != userInfo.getDepartment()) { + userBean.setDepartmentPostIds(UserUtils.updateDepTreeAndPost( + userInfo.getDepartment().getName(), + userInfo.getDepartment().getId()) + ); + UserUtils.updateUserDepartmentPost(UserUtils.getAdminUserId(), userBean); + } + + // 更新角色信息 + if (null != userInfo.getRoles()) { + ArrayList roles = new ArrayList<>(); + for (String roleName : userInfo.getRoles()) { + roles.add(UserUtils.checkAndAddRole(roleName)); + } + userBean.setRoleIds(roles.toArray(new String[0])); + UserService.getInstance().updateUserRoles(UserUtils.getAdminUserId(), userBean); + } + + // 用户平台类型 + if (null != userInfo.getPlatformType()) { + UserUtils.updateUserPlatformType(userBean, userInfo.getPlatformType()); + } + + // 更新远程设计权限和目录信息 + checkAndCreateRemoteDesignAuth(userInfo.getUsername()); + return userInfo.getUsername(); + } + + + private UserBean buildBaseUserBean(String username) { + UserBean userBean = new UserBean(UUIDUtil.generate(), username, username, true); + String pwd; + if (config.getUserStrategy().getPwdStrategy() == CreatePwdStrategy.SAME_USER) { + pwd = username; + } else { + pwd = UserUtils.getRandomPassword(8); + } + userBean.setPassword(TransmissionTool.defaultEncrypt(pwd)); + return userBean; + } + + /** + * 校验如果用户没有自己同名模板跟目录文件夹的远程设计权限,则新建添加一下 + * + * @param username + */ + private void checkAndCreateRemoteDesignAuth(String username) throws Exception { + if (!config.getRemoteFolder()) return; + RemoteUserInfo remoteUserInfo = RemoteDesignAuthHelper.getUserInfo(username); + if (remoteUserInfo == null) { + return; + } + String userRemoteFileName = formatUsername(username); + List remoteDesignAuthorities = RemoteDesignAuthorityDataService.getInstance().getAuthorities(remoteUserInfo.getUserID()); + for (RemoteDesignAuthority remoteDesignAuthority : remoteDesignAuthorities) { + if (remoteDesignAuthority.getPathType() && StringUtils.equals(userRemoteFileName, remoteDesignAuthority.getPath())) { + return; + } + } + String path = StableUtils.pathJoin("reportlets", userRemoteFileName); + if (!ResourceIOUtils.isDirectoryExist(path)) { + ResourceIOUtils.createDirectory(path); + } + + RemoteDesignAuthority authority = new RemoteDesignAuthority(); + authority.userName(username).userId(remoteUserInfo.getUserID()).path(userRemoteFileName).pathType(true).roleType(RoleType.USER); + ReportContext.getInstance().getRemoteDesignAuthorityController().add(authority); + } + + private String formatUsername(String username) { + return username.replaceAll("[\\\\/:*?\"<>|]", StringUtils.EMPTY); + } + + public void updateIntegrateConf(IntegrateConf conf) { + Configurations.update(new WorkerAdaptor(IntegrateConf.class) { + @Override + public void run() { + IntegrateConf instance = IntegrateConf.getInstance(); + instance.setDecisionUrl(conf.getDecisionUrl()); + instance.setJwtKey(conf.getJwtKey()); + instance.setRemoteTokenAuth(conf.getRemoteTokenAuth()); + instance.setAesEncryptSubject(conf.getAesEncryptSubject()); + instance.setAesEncryptKey(conf.getAesEncryptKey()); + instance.setPortalURLTurnOn(conf.getPortalURLTurnOn()); + instance.setPortalURL(conf.getPortalURL()); + instance.setCrossDomain(conf.getCrossDomain()); + instance.setRemoteFolder(conf.getRemoteFolder()); + if (conf.getCreateUserTurnOn()) { + instance.setUserStrategy(conf.getUserStrategy()); + instance.setCreateUserTurnOn(true); + } else { + instance.setCreateUserTurnOn(false); + } + + if (conf.getParaCheckTurnOn()) { + instance.setParaCheckUrl(conf.getParaCheckUrl()); + instance.setParaCheckTurnOn(true); + } else { + instance.setParaCheckTurnOn(false); + } + + if (conf.getWebStartDesigner()) { + instance.setDownloadUrl32(conf.getDownloadUrl32()); + instance.setDownloadUrl64(conf.getDownloadUrl64()); + instance.setWebStartDesigner(true); + } else { + instance.setWebStartDesigner(false); + } + } + }); + } + + static { + // 历史参数获取方式,兼容使用 + InputStream inputStream = ResourceIOUtils.read("/resources/third_auth.properties"); + if (null != inputStream) { + try { + properties.load(inputStream); + } catch (IOException ignore) {} + } + } + +} diff --git a/src/main/java/com/fr/plugin/decision/integration/service/IntegrateCustomService.java b/src/main/java/com/fr/plugin/decision/integration/service/IntegrateCustomService.java new file mode 100644 index 0000000..89b80ac --- /dev/null +++ b/src/main/java/com/fr/plugin/decision/integration/service/IntegrateCustomService.java @@ -0,0 +1,98 @@ +package com.fr.plugin.decision.integration.service; + +import com.fr.base.FRContext; +import com.fr.base.TemplateUtils; +import com.fr.data.NetworkHelper; +import com.fr.decision.webservice.v10.user.UserService; +import com.fr.file.filetree.FileNode; +import com.fr.file.filetree.FileNodes; +import com.fr.json.JSONException; +import com.fr.plugin.decision.integration.bean.TplInfoBean; +import com.fr.report.DesignAuthority; +import com.fr.workspace.WorkContext; +import com.fr.workspace.Workspace; +import com.fr.workspace.server.authority.AuthorityOperator; + +import javax.servlet.http.HttpServletRequest; +import java.util.ArrayList; + +@Deprecated +public class IntegrateCustomService { + + private static final String FRM_PATH = "/view/form?viewlet="; + private static final String CPT_PATH = "/view/report?viewlet="; + + private volatile static IntegrateCustomService service = null; + + public IntegrateCustomService() { + } + + public static IntegrateCustomService getInstance() { + if (service == null) { + synchronized (IntegrateAuthService.class) { + if (service == null) { + service = new IntegrateCustomService(); + } + } + } + return service; + } + + public String generateDefaultHomePageUrl(HttpServletRequest request) throws Exception { + String originalURL = NetworkHelper.getOriginalURL(request); + String requestURI = request.getRequestURI(); + return TemplateUtils.render(originalURL.replace(requestURI, "${fineServletURL}")); + } + + public ArrayList getRemoteTplListByUserId(String userId, String servletName) { + Workspace workspace = WorkContext.getCurrent(); + FileNodes root = FRContext.getFileNodes(); + ArrayList fileNodes = new ArrayList(); + + fileNodes.add(getNodeItem("reportlets", "", true, servletName)); + if (UserService.getInstance().isAdmin(userId)) { + getFileNode(fileNodes, root, "reportlets", servletName); + } else { + DesignAuthority auth = (workspace.get(AuthorityOperator.class)).getAuthorities(userId)[0]; + for (DesignAuthority.Item item : auth.getItems()) { + String path = item.getPath(); + boolean type = item.getType(); + fileNodes.add(getNodeItem("reportlets/" + path, "reportlets", type, servletName)); + if (type) { + getFileNode(fileNodes, root, "reportlets/" + path, servletName); + } + } + } + return fileNodes; + } + + private void getFileNode(ArrayList nodeItem, FileNodes root, String path, String servletName) throws JSONException { + FileNode[] fNodes = root.list(path, false); + for (int i = 0; i < fNodes.length; i++) { + FileNode fileNode = fNodes[i]; + String fileNodePath = fileNode.getEnvPath(); + nodeItem.add(getNodeItem(fileNodePath, path, fileNode.isDirectory(), servletName)); + if (fileNode.isDirectory()) { + getFileNode(nodeItem, root, fileNodePath, servletName); + } + } + } + + private TplInfoBean getNodeItem(String path, String pId, boolean isDirectory, String servletName) { + boolean isCpt = false; + String menuId; + if (path.contains(".cpt")) { + isCpt = true; + menuId = path.replace(".cpt", ""); + } else { + menuId = path.replace(".frm", ""); + } + if (!isDirectory) { + menuId = new StringBuffer().append(menuId).append("_f").toString(); + } + path = path.replace("reportlets/", ""); + String nodeName = menuId.replaceAll(pId + "/", ""); + + return new TplInfoBean(menuId, nodeName, isDirectory ? "" : isCpt ? servletName + CPT_PATH + path : servletName + FRM_PATH + path, pId); + } +} diff --git a/src/main/java/com/fr/plugin/decision/integration/utils/AESUtils.java b/src/main/java/com/fr/plugin/decision/integration/utils/AESUtils.java new file mode 100644 index 0000000..037f812 --- /dev/null +++ b/src/main/java/com/fr/plugin/decision/integration/utils/AESUtils.java @@ -0,0 +1,68 @@ +package com.fr.plugin.decision.integration.utils; + +import com.fr.base.Base64; + +import javax.crypto.Cipher; +import javax.crypto.KeyGenerator; +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; + +/** + * @Author JianYe.Wang + * @Data 2021/8/16 15:06 + * @Description TODO + * @Version 10.0 + **/ +public class AESUtils { + + private static final String AES_ECB_PKCS_5_PADDING = "AES/ECB/PKCS5Padding"; + private static final String HMAC_SHA_256 = "HmacSHA256"; + + /** + * AES加密 + * + * @param content 待加密的内容 + * @param encryptKey 加密密钥 + * @return 加密后的byte[] + */ + public static byte[] aesEncryptToBytes(String content, String encryptKey) throws Exception { + KeyGenerator kgen = KeyGenerator.getInstance("AES"); + kgen.init(128); + Cipher cipher = Cipher.getInstance(AES_ECB_PKCS_5_PADDING); + cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(encryptKey.getBytes(), "AES")); + return cipher.doFinal(content.getBytes("utf-8")); + } + + /** + * AES解密 + * + * @param encryptBytes 待解密的byte[] + * @param decryptKey 解密密钥 + * @return 解密后的String + */ + public static String aesDecryptByBytes(byte[] encryptBytes, String decryptKey) throws Exception { + KeyGenerator kgen = KeyGenerator.getInstance("AES"); + kgen.init(128); + + Cipher cipher = Cipher.getInstance(AES_ECB_PKCS_5_PADDING); + cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(decryptKey.getBytes(), "AES")); + byte[] decryptBytes = cipher.doFinal(encryptBytes); + + return new String(decryptBytes, "utf-8"); + } + + /** + * HmacSHA256 签名 + * + * @param content + * @param signKey + * @return base64 encode + * @throws Exception + */ + public static String hmacSHA256Sign(String content, String signKey) throws Exception { + Mac sha256Hmac = Mac.getInstance(HMAC_SHA_256); + SecretKeySpec secret_key = new SecretKeySpec(signKey.getBytes(), HMAC_SHA_256); + sha256Hmac.init(secret_key); + return Base64.encode(sha256Hmac.doFinal(content.getBytes())); + } +} \ No newline at end of file diff --git a/src/main/java/com/fr/plugin/decision/integration/utils/CommonUtils.java b/src/main/java/com/fr/plugin/decision/integration/utils/CommonUtils.java new file mode 100644 index 0000000..4ba7382 --- /dev/null +++ b/src/main/java/com/fr/plugin/decision/integration/utils/CommonUtils.java @@ -0,0 +1,90 @@ +package com.fr.plugin.decision.integration.utils; + +import com.fr.data.NetworkHelper; +import com.fr.decision.webservice.Response; +import com.fr.decision.webservice.utils.WebServiceUtils; +import com.fr.intelligence.IntelligenceException; +import com.fr.json.JSONObject; +import com.fr.log.FineLoggerFactory; +import com.fr.stable.StringUtils; +import com.fr.web.utils.WebUtils; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.PrintWriter; +import java.util.HashMap; +import java.util.Map; + +public class CommonUtils { + + public static boolean isJsonFormatStr(String str) { + if (StringUtils.isNotBlank(str)) { + str = str.trim(); + try { + new JSONObject(str); + return true; + } catch (Exception e) { + } + } + return false; + } + + public static void showErrorPage(HttpServletResponse response, String result, String solution) { + try { + PrintWriter printWriter = WebUtils.createPrintWriter(response); + Map map = new HashMap<>(); + map.put("result", result); + map.put("solution", solution); + String page = WebServiceUtils.parseWebPageResourceSafe("com/fr/web/controller/decision/entrance/resources/unavailable.html", map); + printWriter.write(page); + printWriter.flush(); + printWriter.close(); + } catch (Exception e) { + LogUtils.error(e.getLocalizedMessage(), e); + } + } + + public static boolean isMobile(HttpServletRequest req) { + String[] mobileArray = {"iPhone", "iPad", "android", "windows phone", "xiaomi"}; + String userAgent = req.getHeader("user-agent"); + // if (userAgent != null && userAgent.toUpperCase().contains("MOBILE")) { + // 华为PAD的chrome浏览器userAgent中不包含mobile + if (userAgent != null) { + for (String mobile : mobileArray) { + if (userAgent.toLowerCase().contains(mobile.toLowerCase())) { + return true; + } + } + } + //pc端企业微信 + //if (userAgent.contains("wxwork")) return true; + FineLoggerFactory.getLogger().debug("isMobile userAgent:{} is:{}", userAgent, NetworkHelper.getDevice(req).isMobile()); + return NetworkHelper.getDevice(req).isMobile(); + } + + public static Response formatException(Exception ex) { + String msg = ex.getMessage(); + if (StringUtils.isBlank(msg)) { + msg = getStackTraceInfo(ex); + } + + if (ex instanceof IntelligenceException) { + return Response.error(((IntelligenceException) ex).errorCode(), msg); + } + + return Response.error("", msg); + } + + //从异常中获取具体的堆栈信息 + private static String getStackTraceInfo(Exception ex) { + StackTraceElement[] stackElements = ex.getStackTrace(); + StringBuilder stringBuilder = new StringBuilder("\r\n"); + if (stackElements != null) { + for (StackTraceElement stackElement : stackElements) { + stringBuilder.append(stackElement.getMethodName()).append("\r\n"); + } + return stringBuilder.toString(); + } + return ex.toString(); + } +} diff --git a/src/main/java/com/fr/plugin/decision/integration/utils/Constants.java b/src/main/java/com/fr/plugin/decision/integration/utils/Constants.java new file mode 100644 index 0000000..35de271 --- /dev/null +++ b/src/main/java/com/fr/plugin/decision/integration/utils/Constants.java @@ -0,0 +1,18 @@ +package com.fr.plugin.decision.integration.utils; + +/** + * @Author JianYe.Wang + * @Data 2021/12/3 14:05 + * @Description TODO + * @Version 10.0 + **/ +public class Constants { + + public static final int HOLD = -1; + + // third token名称 + public static final String TOKEN_NAME = "third_token"; + // token的默认超期时间 + public static final long EXPIRED_TIME = 1000 * 60 * 10; + +} diff --git a/src/main/java/com/fr/plugin/decision/integration/utils/LogUtils.java b/src/main/java/com/fr/plugin/decision/integration/utils/LogUtils.java new file mode 100644 index 0000000..d33d79f --- /dev/null +++ b/src/main/java/com/fr/plugin/decision/integration/utils/LogUtils.java @@ -0,0 +1,96 @@ +package com.fr.plugin.decision.integration.utils; + +import com.fr.log.FineLoggerFactory; +import com.fr.log.FineLoggerProvider; +import com.fr.plugin.context.PluginContexts; +import com.fr.stable.StringUtils; + +public final class LogUtils { + private static String LOG_PREFIX = "[平台集成] "; + private static final String PLUGIN_VERSION; + private static final FineLoggerProvider LOGGER = FineLoggerFactory.getLogger(); + + static { + // 判断 null + String version = PluginContexts.currentContext().getMarker().getVersion(); + if (StringUtils.isNotBlank(version)) { + PLUGIN_VERSION = "[v" + version + "] "; + } else { + PLUGIN_VERSION = "[unknown version] "; + } + + LOG_PREFIX = LOG_PREFIX + PLUGIN_VERSION; + } + + public static void setPrefix(String prefix) { + if (prefix != null) { + LOG_PREFIX = prefix; + } + } + + public static boolean isDebugEnabled() { + return LOGGER.isDebugEnabled(); + } + + public static void debug(String s) { + if (isDebugEnabled()) { + LOGGER.debug(LOG_PREFIX + s); + } + } + + public static void debug(String s, Object... objects) { + if (isDebugEnabled()) { + LOGGER.debug(LOG_PREFIX + s, objects); + } + } + + public static void debug(String s, Throwable throwable) { + if (isDebugEnabled()) { + LOGGER.debug(LOG_PREFIX + s, throwable); + } + } + + public static boolean isInfoEnabled() { + return LOGGER.isInfoEnabled(); + } + + public static void info(String s) { + LOGGER.info(LOG_PREFIX + s); + } + + public static void info(String s, Object... objects) { + LOGGER.info(LOG_PREFIX + s, objects); + } + + public static void warn(String s) { + LOGGER.warn(LOG_PREFIX + s); + } + + public static void warn(String s, Object... objects) { + LOGGER.warn(LOG_PREFIX + s, objects); + } + + public static void warn(String s, Throwable throwable) { + LOGGER.warn(LOG_PREFIX + s, throwable); + } + + public static void warn(Throwable throwable, String s, Object... objects) { + LOGGER.warn(throwable, LOG_PREFIX + s, objects); + } + + public static void error(String s) { + LOGGER.error(LOG_PREFIX + s); + } + + public static void error(String s, Object... objects) { + LOGGER.error(LOG_PREFIX + s, objects); + } + + public static void error(String s, Throwable throwable) { + LOGGER.error(LOG_PREFIX + s, throwable); + } + + public static void error(Throwable throwable, String s, Object... objects) { + LOGGER.error(throwable, LOG_PREFIX + s, objects); + } +} diff --git a/src/main/java/com/fr/plugin/decision/integration/utils/UserUtils.java b/src/main/java/com/fr/plugin/decision/integration/utils/UserUtils.java new file mode 100644 index 0000000..dfdab11 --- /dev/null +++ b/src/main/java/com/fr/plugin/decision/integration/utils/UserUtils.java @@ -0,0 +1,417 @@ +package com.fr.plugin.decision.integration.utils; + +import com.finebi.cbb.utils.StringEscapeUtils; +import com.fr.decision.authority.AuthorityContext; +import com.fr.decision.authority.base.constant.type.operation.ManualOperationType; +import com.fr.decision.authority.controller.UserController; +import com.fr.decision.authority.data.*; +import com.fr.decision.authority.data.extra.user.type.PlatformUserKey; +import com.fr.decision.authority.data.extra.user.type.UserProductTypeKey; +import com.fr.decision.authority.data.personnel.DepRole; +import com.fr.decision.authority.entity.CustomRoleEntity; +import com.fr.decision.base.util.CollectionUtil; +import com.fr.decision.base.util.UUIDUtil; +import com.fr.decision.webservice.annotation.RoleCheckerType; +import com.fr.decision.webservice.bean.user.DepartmentPostBean; +import com.fr.decision.webservice.bean.user.UserBean; +import com.fr.decision.webservice.bean.user.UserUpdateBean; +import com.fr.decision.webservice.exception.user.AddUserProductTypeException; +import com.fr.decision.webservice.impl.user.type.UserProductType; +import com.fr.decision.webservice.utils.ControllerFactory; +import com.fr.decision.webservice.utils.UserSourceFactory; +import com.fr.decision.webservice.utils.WebServiceUtils; +import com.fr.decision.webservice.v10.user.DepartmentService; +import com.fr.decision.webservice.v10.user.PositionService; +import com.fr.decision.webservice.v10.user.UserService; +import com.fr.general.ComparatorUtils; +import com.fr.plugin.decision.integration.config.IntegrateConf; +import com.fr.plugin.decision.integration.config.strategy.user.impl.ContainDepRoleStrategy; +import com.fr.stable.ArrayUtils; +import com.fr.stable.StringUtils; +import com.fr.stable.db.data.BaseDataRecord; +import com.fr.stable.query.QueryFactory; +import com.fr.stable.query.condition.QueryCondition; +import com.fr.stable.query.restriction.Restriction; +import com.fr.stable.query.restriction.RestrictionFactory; + +import java.util.*; + +public class UserUtils { + + public static final String POST_DEFAULT_NAME = "职员"; + public static final String DEP_ROOT_NAME = null; + + public static boolean existUsername(String username) throws Exception { + return UserService.getInstance().getUserByUserName(username) != null; + } + + public static String getAdminUserId() throws Exception { + return UserService.getInstance().getAdminUserIdList().get(0); + } + + public static boolean isAdmin(String username) throws Exception { + return UserService.getInstance().getAdminUserNameList().contains(username); + } + + public static List updateDepTreeAndPost(String depName, String depId) throws Exception { + ArrayList result = new ArrayList<>(); + ContainDepRoleStrategy depStrategy = (ContainDepRoleStrategy) IntegrateConf.getInstance().getUserStrategy(); + switch (depStrategy.getDepStrategy()) { + case DEP_ID_LENGTH: + generateDepByIdLength(depName, depId); + break; + case DEP_NAME_GROUP: + depId = generateDepByName(depName, depStrategy.getSeparator()); + break; + } + String postId = generateDefaultPosition(depId); + result.add(WebServiceUtils.createUniquePostId(depId, postId)); + return result; + } + + /** + * 添加部门前先计算出最匹配的父节点,添加完之后在更新父节点下子节点的父信息 + * + * @param depName + * @param depId + * @return + * @throws Exception + */ + private static String generateDepByIdLength(String depName, String depId) throws Exception { + Department department = AuthorityContext.getInstance().getDepartmentController().getById(depId); + if (department == null) { + String depPId = getMostSimilarDepId(depId); + if (checkDuplicatedDepartmentName(depPId, depName)) { + depName = depName + "_" + depId; + } + addDepartment(depName, depId, depPId); + // 更新depPId下子节点的父信息 + generateUnderParentDepartment(depPId, depId); + } + return depId; + } + + /** + * 根据部门名称拼接来更新部门 + * + * @param depNameGroup + * @return 部门id + */ + private static String generateDepByName(String depNameGroup, String separator) throws Exception { + String[] depArr = depNameGroup.split(StringEscapeUtils.escapeRegexSpecialWord(separator)); + String pId = DEP_ROOT_NAME; + for (String depName : depArr) { + Department department = getDepartmentByNameAndPid(depName, pId); + if (department != null) { + pId = department.getId(); + } else { + String depId = UUIDUtil.generate(); + addDepartment(depName, depId, pId); + pId = depId; + } + } + return pId; + } + + private static boolean existDepId(String depId) throws Exception { + return AuthorityContext.getInstance().getDepartmentController().getById(depId) != null; + } + + private static Department getDepartmentByNameAndPid(String depName, String pId) throws Exception { + return AuthorityContext.getInstance().getDepartmentController().findOne( + QueryFactory.create().addRestriction(RestrictionFactory.eq(Department.COLUMN_NAME, depName)) + .addRestriction(RestrictionFactory.eq(Department.COLUMN_PARENT_ID, pId)) + ); + } + + private static boolean checkDuplicatedDepartmentName(String parentId, String depName) throws Exception { + QueryCondition condition = QueryFactory.create().addRestriction( + RestrictionFactory.and(new Restriction[]{RestrictionFactory.eq("name", depName), RestrictionFactory.eq("parentId", parentId)}) + ); + Department sameNameDep = AuthorityContext.getInstance().getDepartmentController().findOne(condition); + return sameNameDep != null; + } + + /** + * 添加部门 + * + * @param depName + * @param depId + * @param depPId + * @throws Exception + */ + private static void addDepartment(String depName, String depId, String depPId) throws Exception { + Department record = (new Department()).id(depId).name(depName).parentId(depPId).creationType(ManualOperationType.KEY).lastOperationType(ManualOperationType.KEY).enable(true); + AuthorityContext.getInstance().getDepartmentController().add(record); + } + + private static void generateUnderParentDepartment(String depPId, String depId) throws Exception { + for (DepartmentPostBean departmentPostBean : DepartmentService.getInstance().getDepartmentsUnderParentDepartment(getAdminUserId(), depPId)) { + if (ComparatorUtils.equals(depId, departmentPostBean.getId())) continue; + String mostSimilarDepId = getMostSimilarDepId(departmentPostBean.getId()); + if (!ComparatorUtils.equals(mostSimilarDepId, depPId)) { + Department record = AuthorityContext.getInstance().getDepartmentController().getById(departmentPostBean.getId()); + record.setParentId(mostSimilarDepId); + AuthorityContext.getInstance().getDepartmentController().update(record); + } + } + } + + private static List getAllDepartment() throws Exception { + return AuthorityContext.getInstance().getDepartmentController().find(QueryFactory.create()); + } + + /** + * 校验部门下是否有默认 POST_DEFAULT_NAME 职位 + * + * @param departmentId + * @return POST_DEFAULT_NAME 职位的 id + * @throws Exception + */ + private static String generateDefaultPosition(String departmentId) throws Exception { + String adminId = UserService.getInstance().getAdminUserIdList().get(0); + Post[] posts = ControllerFactory.getInstance().getPostController(adminId).getPositionsUnderParentDepartment(adminId, departmentId, null); + for (Post post : posts) { + if (ComparatorUtils.equals(post.getName(), POST_DEFAULT_NAME)) { + return post.getId(); + } + } + String positionId = getDefaultPostId(); + AuthorityContext.getInstance().getPostController().addPostToDepartment(positionId, departmentId); + return positionId; + } + + /** + * 校验是否有 POST_DEFAULT_NAME 职位,没有的话新建个 + * + * @return POST_DEFAULT_NAME 职位的 id + * @throws Exception + */ + private static String getDefaultPostId() throws Exception { + String adminId = UserService.getInstance().getAdminUserIdList().get(0); + Post[] posts = ControllerFactory.getInstance().getPostController(adminId).getPositions(adminId, null); + for (Post post : posts) { + if (ComparatorUtils.equals(post.getName(), POST_DEFAULT_NAME)) { + return post.getId(); + } + } + + return PositionService.getInstance().addPosition(POST_DEFAULT_NAME).getId(); + } + + /** + * 计算出与 depId 最相似的父节点 + * + * @param depId + * @return + * @throws Exception + */ + private static String getMostSimilarDepId(String depId) throws Exception { + int similarLevel = 0; + String mostSimilarDepId = DEP_ROOT_NAME; + String specialDepRootId = ((ContainDepRoleStrategy) IntegrateConf.getInstance().getUserStrategy()).getSpecialRootId(); + if (StringUtils.isNotBlank(specialDepRootId)) { + if (ComparatorUtils.equals(depId, specialDepRootId)) { + return mostSimilarDepId; + } else if (existDepId(specialDepRootId)) { + mostSimilarDepId = specialDepRootId; + } + } + for (Department department : getAllDepartment()) { + String currentDepId = department.getId(); + // 部门id长度大于当前id长度的直接跳过,因为部门的树结构时根据id其实是否一致判断的 + if (currentDepId.length() > depId.length() || ComparatorUtils.equals(currentDepId, depId)) continue; + int currentLevel = getDepIdSimilarityDegree(currentDepId, depId); + if (currentLevel > similarLevel) { + similarLevel = currentLevel; + mostSimilarDepId = department.getId(); + } + } + return mostSimilarDepId; + } + + private static int getDepIdSimilarityDegree(String depId1, String depId2) { + String shortId, longId; + if (depId1.length() > depId2.length()) { + shortId = depId2; + longId = depId1; + } else { + shortId = depId1; + longId = depId2; + } + + if (longId.startsWith(shortId)) { + return shortId.length(); + } + return 0; + } + + public static String checkAndAddRole(String roleName) throws Exception { + CustomRole customRole = AuthorityContext.getInstance().getCustomRoleController().findOne( + QueryFactory.create().addRestriction( + RestrictionFactory.eq(CustomRoleEntity.COLUMN_NAME, roleName)) + ); + if (null != customRole) return customRole.getId(); + String roleId = UUIDUtil.generate(); + addRole(roleName, roleId); + return roleId; + } + + public static void addRole(String roleName, String roleId) throws Exception { + String adminId = UserService.getInstance().getAdminUserIdList().get(0); + CustomRole record = (new CustomRole()).id(roleId).name(roleName); + ControllerFactory.getInstance().getCustomRoleController(adminId).addCustomRole(adminId, record); + } + + /** + * 更新用户的部门信息 + * + * @param currentUserId + * @param userBean + * @throws Exception + */ + public static void updateUserDepartmentPost(String currentUserId, UserBean userBean) throws Exception { + if (userBean.getDepartmentPostIds() != null) { + List newDepRoleList = new LinkedList(); + Iterator depRoleIt = userBean.getDepartmentPostIds().iterator(); + + while (depRoleIt.hasNext()) { + String depPostId = (String) depRoleIt.next(); + DepRole depRole = WebServiceUtils.parseUniqueDepartmentPostId(depPostId); + newDepRoleList.add(AuthorityContext.getInstance().getDepartmentController().getDepRole(depRole.getDepartmentId(), depRole.getPostId())); + } + + List oldDepRoleList = AuthorityContext.getInstance().getDepartmentController().findDepRoleByUser(userBean.getId()); + Map newIdDepRoleMap = CollectionUtil.convertToMap(newDepRoleList, BaseDataRecord::getId); + Map oldIdDepRoleMap = CollectionUtil.convertToMap(oldDepRoleList, BaseDataRecord::getId); + List newDepRoleIdList = new LinkedList(newIdDepRoleMap.keySet()); + List oldDepRoleIdList = new LinkedList(oldIdDepRoleMap.keySet()); + List addDepRoleIdList = new LinkedList(newDepRoleIdList); + List removeDepRoleIdList = new LinkedList(oldDepRoleIdList); + addDepRoleIdList.removeAll(oldDepRoleIdList); + removeDepRoleIdList.removeAll(newDepRoleIdList); + User targetUser = UserService.getInstance().getUserByUserId(userBean.getId()); + Iterator addDepRoleIt = addDepRoleIdList.iterator(); + + String removeDepRoleId; + DepRole depRole; + while (addDepRoleIt.hasNext()) { + removeDepRoleId = (String) addDepRoleIt.next(); + depRole = newIdDepRoleMap.get(removeDepRoleId); + if (RoleCheckerType.DEPARTMENT.checkAuthority(currentUserId, depRole.getDepartmentId())) { + Department department = AuthorityContext.getInstance().getDepartmentController().getById(depRole.getDepartmentId()); + Post post = AuthorityContext.getInstance().getPostController().getById(depRole.getPostId()); + UserSourceFactory.getInstance().checkSource(targetUser, department); + UserSourceFactory.getInstance().checkSource(targetUser, post); + AuthorityContext.getInstance().getUserController().addUserToDepartmentAndPost(targetUser.getId(), depRole.getDepartmentId(), depRole.getPostId()); + } + } + + addDepRoleIt = removeDepRoleIdList.iterator(); + + while (addDepRoleIt.hasNext()) { + removeDepRoleId = (String) addDepRoleIt.next(); + depRole = oldIdDepRoleMap.get(removeDepRoleId); + if (RoleCheckerType.DEPARTMENT.checkAuthority(currentUserId, depRole.getDepartmentId())) { + AuthorityContext.getInstance().getUserController().removeUserFromDepartmentAndPost(targetUser.getId(), depRole.getDepartmentId(), depRole.getPostId()); + } + } + } + } + + + /** + * 更新用户平台的类型 + * + * @param userBean + * @param platformTypeList + * @throws Exception + */ + public static void updateUserPlatformType(UserBean userBean, List platformTypeList) throws Exception { + UserController userController = AuthorityContext.getInstance().getUserController(); + // 查询用户的平台用户类型 + List extraProperties = userController.findUserExtraProperty(QueryFactory.create().addRestriction( + RestrictionFactory.eq(ExtraProperty.COLUMN_RELEATED_ID, userBean.getId()) + ).addRestriction( + RestrictionFactory.startWith(ExtraProperty.COLUMN_NAME, UserProductTypeKey.KEY.getKey()) + )); + List newIdList = new LinkedList(platformTypeList); + List oldIdList = new LinkedList(); + for (ExtraProperty property : extraProperties) { + UserProductTypeKey userProductTypeKey = (UserProductTypeKey) PlatformUserKey.fromKey(property.getName()); + oldIdList.add(UserProductType.fromKey(userProductTypeKey).toInteger()); + } + + // 校验新的用户平台类型是否冲突 + for (int i=0; i addIdList = new LinkedList(newIdList); + List removeIdList = new LinkedList(oldIdList); + addIdList.removeAll(oldIdList); + removeIdList.removeAll(newIdList); + + // 删除用户原有平台类型 + for (Integer i : removeIdList) { + userController.removeUserProductType(userBean.getId(), UserProductType.fromInteger(i).transProductKey()); + } + + // 添加新增部分的平台类型 + out: + for (Integer platformType : addIdList) { + UserProductType userProductType = UserProductType.fromInteger(platformType); + if (!userProductType.isNoLimitEditOpen()) { + continue out; + } + + if (userProductType.allowMax() > 0 && 1 > userProductType.leftRegisterTotal()) { + throw new AddUserProductTypeException(); + } + userController.addUserProductType(userBean.getId(), userProductType.transProductKey()); + } + } + + /** + * 随机生成强密码 + * + * @param len + * @return + */ + public static String getRandomPassword(int len) { + // 密码字典 + char[] str = { + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '~', '!', '@', '#', '$', '%', '^', '-', '+' + }; + StringBuffer pwd = null; + boolean flag = false; + while (!flag) { + int a = 0, b = 0, c = 0, d = 0; + pwd = new StringBuffer(); + for (int i = 0; i < len; i++) { + int rand = (int) (Math.random() * str.length); + pwd.append(str[rand]); + if (rand < 26) { + a++; + } else if (rand < 52) { + b++; + } else if (rand < 62) { + c++; + } else { + d++; + } + } + flag = (a * b * c * d != 0); + } + return pwd.toString(); + } +} diff --git a/src/main/resources/com/fr/plugin/decision/integration/web/js/button_group.item.js b/src/main/resources/com/fr/plugin/decision/integration/web/js/button_group.item.js new file mode 100644 index 0000000..5d7f867 --- /dev/null +++ b/src/main/resources/com/fr/plugin/decision/integration/web/js/button_group.item.js @@ -0,0 +1,42 @@ +!(function () { + var Item = BI.inherit(BI.Widget, { + props: { + value: "", + items: [], + changeAction: BI.emptyFn() + }, + + render: function () { + var self = this, o = this.options; + return { + type: "bi.button_group", + value: o.value + "", + layouts: [{ + type: "bi.vertical_adapt" + }], + items: BI.createItems(o.items, { + type: "bi.single_select_radio_item", + height: 16, + logic: { + dynamic: true + }, + cls: "bi-list-item-none" + }), + listeners: [{ + eventName: BI.ButtonGroup.EVENT_CHANGE, + action: function (e) { + o.changeAction && o.changeAction(e); + } + }], + ref: function (_ref) { + self.group = _ref; + } + } + }, + + getValue: function () { + return this.group.getValue()[0]; + } + }); + BI.shortcut("dec.plugin.system.integration.group", Item); +})(); \ No newline at end of file diff --git a/src/main/resources/com/fr/plugin/decision/integration/web/js/constant.js b/src/main/resources/com/fr/plugin/decision/integration/web/js/constant.js new file mode 100644 index 0000000..2b94198 --- /dev/null +++ b/src/main/resources/com/fr/plugin/decision/integration/web/js/constant.js @@ -0,0 +1,17 @@ +!(function () { + BI.constant("plugin.integration.userCreate", [{ + value: "0", text: "仅用户名", userType: "onlyUser" + }, { + value: "1", text: "包含部门职位", userType: "containDepRole" + }]); + BI.constant("plugin.integration.pwd", [{ + value: "0", text: "同用户名" + }, { + value: "1", text: "随机密码" + }]); + BI.constant("plugin.integration.dep", [{ + value: "0", text: "部门ID长度构建部门树" + }, { + value: "1", text: "部门名称拼接构建部门树" + }]); +})(); \ No newline at end of file diff --git a/src/main/resources/com/fr/plugin/decision/integration/web/js/model.pane.js b/src/main/resources/com/fr/plugin/decision/integration/web/js/model.pane.js new file mode 100644 index 0000000..7126684 --- /dev/null +++ b/src/main/resources/com/fr/plugin/decision/integration/web/js/model.pane.js @@ -0,0 +1,44 @@ +!(function () { + var user_only = BI.Constants.getConstant("plugin.integration.userCreate")[0], + user_roledep = BI.Constants.getConstant("plugin.integration.userCreate")[1]; + var Model = BI.inherit(Fix.Model, { + state: function () { + return { + config: {} + } + }, + + computed: { + userType: function () { + return this.model.config.userStrategy.userType === user_only.userType ? user_only.value : user_roledep.value; + } + }, + + actions: { + initData: function (cb) { + var self = this; + Dec.Utils.getPluginIntegrateConfig(function (res) { + if (!res.errorCode) { + self.model.config = res; + cb(); + } + }) + }, + + changeUserType: function (v) { + this.model.config.userStrategy.userType = (v === user_only.value ? user_only.userType : user_roledep.userType) + }, + + saveData: function (value) { + Dec.Utils.updatePluginIntegrateConfig(value, function (res) { + if (res.data === "success") { + BI.Msg.toast("保存成功!", {level: "success"}) + } else { + BI.Msg.toast("保存失败!", {level: "error"}) + } + }) + } + } + }); + BI.model("dec.plugin.system.integration.model", Model); +})(); \ No newline at end of file diff --git a/src/main/resources/com/fr/plugin/decision/integration/web/js/pane.js b/src/main/resources/com/fr/plugin/decision/integration/web/js/pane.js new file mode 100644 index 0000000..52c25af --- /dev/null +++ b/src/main/resources/com/fr/plugin/decision/integration/web/js/pane.js @@ -0,0 +1,510 @@ +!(function () { + var WIDGET_HEIGHT = 24; + var user_only = BI.Constants.getConstant("plugin.integration.userCreate")[0], + user_roledep = BI.Constants.getConstant("plugin.integration.userCreate")[1]; + var dep_legnth = BI.Constants.getConstant("plugin.integration.dep")[0]; + + var Pane = BI.inherit(BI.Widget, { + props: { + baseCls: "dec-plugin-system-integration" + }, + _store: function () { + return BI.Models.getModel("dec.plugin.system.integration.model"); + }, + beforeInit: function (cb) { + this.store.initData(cb); + }, + render: function () { + var self = this; + var createUserInfoPane = { + type: "bi.vertical", + lgap: 10, bgap: 10, + ref: function (_ref) { + self.createUserInfoPane = _ref; + }, + invisible: !self.model.config.createUserTurnOn, + items: [{ + type: "bi.vertical_adapt", + height: 24, + items: [{ + type: "bi.label", + textAlign: "left", + cls: "dec-font-weight-bold", + text: "创建远程连接同名目录", + title: "创建远程连接同名目录", + width: 210 + }, { + type: "dec.switch_button", + value: self.model.config.remoteFolder, + ref: function (_ref) { + self.remoteFolder = _ref; + } + }] + }, { + type: "bi.vertical_adapt", + height: WIDGET_HEIGHT, + items: [{ + type: "bi.label", + textAlign: "left", + cls: "dec-font-weight-bold", + text: "用户创建策略", + width: 210 + }, { + type: "dec.plugin.system.integration.group", + value: self.model.userType, + items: BI.Constants.getConstant("plugin.integration.userCreate"), + changeAction: function (e) { + self.depStrategyPane.setVisible(e === user_roledep.value); + self.store.changeUserType(e); + }, + ref: function (_ref) { + self.userCreate = _ref; + } + }] + }, { + type: "bi.vertical_adapt", + height: WIDGET_HEIGHT, + items: [{ + type: "bi.label", + textAlign: "left", + cls: "dec-font-weight-bold", + text: "密码策略", + width: 210 + }, { + type: "dec.plugin.system.integration.group", + value: self.model.config.userStrategy.pwdStrategy, + items: BI.Constants.getConstant("plugin.integration.pwd"), + ref: function (_ref) { + self.pwd = _ref; + } + }] + }, { + type: "bi.vertical", + bgap: 10, + invisible: self.model.userType === user_only.value, + items: [{ + type: "bi.vertical_adapt", + height: WIDGET_HEIGHT, + items: [{ + type: "bi.label", + textAlign: "left", + cls: "dec-font-weight-bold", + text: "创建部门策略", + width: 210 + }, { + type: "dec.plugin.system.integration.group", + value: self.model.config.userStrategy.depStrategy || dep_legnth.value, + items: BI.Constants.getConstant("plugin.integration.dep"), + ref: function (_ref) { + self.dep = _ref; + }, + changeAction: function (e) { + var depLen = e == 0; + self.specialRootId.setVisible(depLen); + self.separator.setVisible(!depLen); + }, + }] + }, { + type: "dec.label.editor.item", + textWidth: 210, + invisible: BI.isKey(self.model.config.userStrategy.depStrategy) && self.model.config.userStrategy.depStrategy != 0, + cls: "dec-font-weight-bold", + text: "特殊根部门ID", + editorWidth: 180, + value: self.model.config.userStrategy.specialRootId, + ref: function (_ref) { + self.specialRootId = _ref + } + }, { + type: "dec.label.editor.item", + textWidth: 210, + invisible: self.model.config.userStrategy.depStrategy != 1, + cls: "dec-font-weight-bold", + text: "分隔符", + editorWidth: 180, + value: self.model.config.userStrategy.separator, + ref: function (_ref) { + self.separator = _ref + } + }], + ref: function (_ref) { + self.depStrategyPane = _ref; + } + }] + }; + + + return { + type: "bi.vertical", + cls: "bi-card", + hgap: 10, + items: [{ + type: "dec.card.vertical", + text: "配置项", + content: { + type: "bi.vertical", + vgap: 10, + items: [{ + type: "dec.label.editor.item", + textWidth: 210, + cls: "dec-font-weight-bold", + text: "帆软平台地址", + title: "帆软平台地址", + watermark: "http(s)://ip:port/webroot/decision", + editorWidth: 300, + value: self.model.config.decisionUrl, + ref: function (_ref) { + self.decisionUrl = _ref + } + }, { + type: "dec.label.editor.item", + textWidth: 210, + cls: "dec-font-weight-bold", + text: "Jwt 密钥值", + editorWidth: 300, + value: self.model.config.jwtKey, + ref: function (_ref) { + self.jwtKey = _ref + } + }, { + type: "bi.vertical_adapt", + height: 24, + items: [{ + type: "bi.label", + textAlign: "left", + cls: "dec-font-weight-bold", + text: "认证完成创建用户", + title: "Jwt Token认证用户在平台中不存在时,自动在平台中创建用户信息", + width: 210 + }, { + type: "dec.switch_button", + value: self.model.config.createUserTurnOn, + listeners: [{ + eventName: "EVENT_CHANGE", + action: function () { + self.createUserInfoPane.setVisible(this.getValue()); + } + }], + ref: function (_ref) { + self.createUserTurnOn = _ref; + } + }] + }, createUserInfoPane, { + type: "bi.vertical_adapt", + height: 24, + items: [{ + type: "bi.label", + textAlign: "left", + cls: "dec-font-weight-bold", + text: "模板预览参数校验", + width: 210 + }, { + type: "dec.switch_button", + value: self.model.config.paraCheckTurnOn, + listeners: [{ + eventName: "EVENT_CHANGE", + action: function () { + self.paraCheckPane.setVisible(this.getValue()); + } + }], + ref: function (_ref) { + self.paraCheckTurnOn = _ref; + } + }] + }, { + type: "bi.vertical", + lgap: 10, bgap: 10, + invisible: !self.model.config.paraCheckTurnOn, + items: [{ + type: "dec.label.editor.item", + textWidth: 210, + cls: "dec-font-weight-bold", + text: "校验地址", + title: "校验地址", + editorWidth: 180, + value: self.model.config.paraCheckUrl, + ref: function (_ref) { + self.paraCheckUrl = _ref + } + }], + ref: function (_ref) { + self.paraCheckPane = _ref + } + }, { + type: "bi.vertical_adapt", + height: 24, + items: [{ + type: "bi.label", + textAlign: "left", + cls: "dec-font-weight-bold", + text: "远程设计连接开启token校验", + width: 210 + }, { + type: "dec.switch_button", + value: self.model.config.remoteTokenAuth, + ref: function (_ref) { + self.remoteTokenAuth = _ref; + } + }] + }, { + type: "bi.vertical_adapt", + height: 24, + items: [{ + type: "bi.label", + textAlign: "left", + cls: "dec-font-weight-bold", + text: "用户信息是否进行AES加密", + title: "Jwt Token中的 subject 字段是否使用 AES/ECB/PKCS5Padding 加密", + width: 210 + }, { + type: "dec.switch_button", + value: self.model.config.aesEncryptSubject, + ref: function (_ref) { + self.aesEncryptSubjectButton = _ref; + }, + listeners: [{ + eventName: "EVENT_CHANGE", + action: function () { + self.aesEncryptKeyPane.setVisible(this.getValue()); + } + }], + }] + }, { + type: "bi.vertical", + lgap: 10, bgap: 10, + invisible: !self.model.config.aesEncryptSubject, + items: [{ + type: "dec.label.editor.item", + textWidth: 210, + cls: "dec-font-weight-bold", + text: "AES加密密钥(16位)", + title: "AES加密密钥", + editorWidth: 180, + value: self.model.config.aesEncryptKey, + ref: function (_ref) { + self.aesEncryptKeyEditor = _ref + } + }], + ref: function (_ref) { + self.aesEncryptKeyPane = _ref + } + }, /*{ + type: "bi.vertical_adapt", + height: 24, + items: [{ + type: "bi.label", + textAlign: "left", + cls: "dec-font-weight-bold", + text: "自定义登录页", + title: "页面未登录时跳转至指定URL地址替代帆软登录页", + width: 210 + }, { + type: "dec.switch_button", + value: self.model.config.portalURLTurnOn, + ref: function (_ref) { + self.portalURLTurnOnBtn = _ref; + }, + listeners: [{ + eventName: "EVENT_CHANGE", + action: function () { + self.portalURLPane.setVisible(this.getValue()); + } + }], + }] + }, { + type: "bi.vertical", + lgap: 10, bgap: 10, + invisible: !self.model.config.portalURLTurnOn, + items: [{ + type: "dec.label.editor.item", + textWidth: 210, + cls: "dec-font-weight-bold", + text: "地址", + title: "自定义登录页地址", + editorWidth: 180, + value: self.model.config.portalURL, + ref: function (_ref) { + self.portalURLEditor = _ref + } + }], + ref: function (_ref) { + self.portalURLPane = _ref + } + }, */{ + type: "bi.vertical_adapt", + height: 24, + items: [{ + type: "bi.label", + textAlign: "left", + cls: "dec-font-weight-bold", + text: "跨域功能", + title: "开启后会在 response 中会添加允许的请求头", + width: 210 + }, { + type: "dec.switch_button", + value: self.model.config.crossDomain, + ref: function (_ref) { + self.crossDomainBtn = _ref; + } + }] + }] + }, + listeners: [{ + eventName: "EVENT_CHANGE", + action: function () { + self.validate() && self.store.saveData(self.getValue()); + } + }] + }, { + type: "bi.vertical_adapt", + height: 24, + items: [{ + type: "bi.label", + textAlign: "left", + cls: "dec-font-weight-bold", + text: "显示唤醒设计器", + title: "平台首页地址显示唤醒设计器功能", + width: 210 + }, { + type: "dec.switch_button", + value: self.model.config.webStartDesigner, + ref: function (_ref) { + self.webStartDesigner = _ref; + }, + listeners: [{ + eventName: "EVENT_CHANGE", + action: function () { + self.downloadURLPane.setVisible(this.getValue()); + } + }], + }] + }, { + type: "bi.vertical", + lgap: 10, vgap: 10, + invisible: !self.model.config.webStartDesigner, + items: [{ + type: "dec.label.editor.item", + textWidth: 210, + cls: "dec-font-weight-bold", + text: "64位设计器下载地址", + title: "64位设计器下载地址", + editorWidth: 500, + value: self.model.config.downloadUrl64, + ref: function (_ref) { + self.downloadUrl64 = _ref + } + }, { + type: "dec.label.editor.item", + textWidth: 210, + cls: "dec-font-weight-bold", + text: "32位设计器下载地址", + title: "32位设计器下载地址", + editorWidth: 500, + value: self.model.config.downloadUrl32, + ref: function (_ref) { + self.downloadUrl32 = _ref + } + }], + ref: function (_ref) { + self.downloadURLPane = _ref + } + }] + + } + }, + + aesValidate: function () { + var editor = this.aesEncryptKeyEditor; + return editor.getValue().length == 16 ? true : (editor.showError("AES密钥长度需为16"), false); + }, + + validate: function () { + if (!BI.isKey(this.decisionUrl.getValue())) { + BI.Msg.toast("帆软平台地址不允许为空", {level: "error"}); + return false; + } + if (this.aesEncryptSubjectButton.getValue()) { + return this.aesValidate(); + } + if (this.webStartDesigner.getValue()) { + if (!BI.isKey(this.downloadUrl32.getValue())) { + BI.Msg.toast("32位下载地址不允许为空", {level: "error"}); + return false; + } + if (!BI.isKey(this.downloadUrl64.getValue())) { + BI.Msg.toast("64位下载地址不允许为空", {level: "error"}); + return false; + } + } + return true; + }, + + getValue: function () { + var self = this; + var value = { + decisionUrl: self.decisionUrl.getValue(), + jwtKey: self.jwtKey.getValue(), + remoteTokenAuth: self.remoteTokenAuth.getValue(), + aesEncryptSubject: self.aesEncryptSubjectButton.getValue(), + aesEncryptKey: self.aesEncryptKeyEditor.getValue(), + /*portalURLTurnOn: self.portalURLTurnOnBtn.getValue(), + portalURL: self.portalURLEditor.getValue(),*/ + crossDomain: self.crossDomainBtn.getValue(), + remoteFolder: self.remoteFolder.getValue() + }; + + if (self.createUserTurnOn.getValue()) { + var userStrategy = { + pwdStrategy: self.pwd.getValue() + }; + switch (self.userCreate.getValue()) { + case user_only.value: + BI.extend(userStrategy, { + userType: user_only.userType + }); + break; + case user_roledep.value: + BI.extend(userStrategy, { + userType: user_roledep.userType, + depStrategy: self.dep.getValue(), + specialRootId: self.specialRootId.getValue(), + separator: self.separator.getValue() + }); + break; + } + BI.extend(value, { + createUserTurnOn: true, + userStrategy: userStrategy + }); + } else { + BI.extend(value, { + createUserTurnOn: false + }) + } + + if (self.paraCheckTurnOn.getValue()) { + BI.extend(value, { + paraCheckTurnOn: true, + paraCheckUrl: self.paraCheckUrl.getValue() + }) + } else { + BI.extend(value, { + paraCheckTurnOn: false + }) + } + + var webStartDesigner = self.webStartDesigner.getValue(); + value.webStartDesigner = webStartDesigner; + if (webStartDesigner) { + BI.extend(value, { + downloadUrl64: self.downloadUrl64.getValue(), + downloadUrl32: self.downloadUrl32.getValue(), + }) + } + console.log(value); + return value; + } + }); + + BI.shortcut("dec.plugin.system.integration", Pane); +})(); \ No newline at end of file diff --git a/src/main/resources/com/fr/plugin/decision/integration/web/js/plugin.js b/src/main/resources/com/fr/plugin/decision/integration/web/js/plugin.js new file mode 100644 index 0000000..3dfbf97 --- /dev/null +++ b/src/main/resources/com/fr/plugin/decision/integration/web/js/plugin.js @@ -0,0 +1,35 @@ +!(function () { + BI.Plugin.config(function (type, options) { + }, function (type, object) { + object.element.attr("shortcut", object.options.type); + }); + + Dec.Utils = Dec.Utils || {}; + BI.extend(Dec.Utils, { + getPluginIntegrateConfig: function (callback) { + Dec.reqGet("/third/auth/conf", "", callback); + }, + updatePluginIntegrateConfig: function (val, callback) { + Dec.reqPost("/third/auth/conf", val, callback); + }, + getWebStartInfo: function (callback) { + Dec.reqGet("/third/auth/username/token", "", callback); + } + }); + + BI.config("dec.constant.system.tabs", function (items) { + items.push({ + value: "integration", + text: "登录集成", + cardType: "dec.plugin.system.integration" + }); + return items; + }); + + var commonPath = Dec.fineServletURL + "/file?path=com/fr/plugin/decision/integration/web/js/"; + BI.$import(commonPath + "constant.js"); + BI.$import(commonPath + "button_group.item.js"); + BI.$import(commonPath + "model.pane.js"); + BI.$import(commonPath + "pane.js"); + BI.$import(commonPath + "protocolcheck.js"); +})(); \ No newline at end of file diff --git a/src/main/resources/com/fr/plugin/decision/integration/web/js/protocolcheck.js b/src/main/resources/com/fr/plugin/decision/integration/web/js/protocolcheck.js new file mode 100644 index 0000000..31bba67 --- /dev/null +++ b/src/main/resources/com/fr/plugin/decision/integration/web/js/protocolcheck.js @@ -0,0 +1,263 @@ +(function (f) { + if (typeof exports === "object" && typeof module !== "undefined") { + module.exports = f() + } else if (typeof define === "function" && define.amd) { + define([], f) + } else { + var g; + if (typeof window !== "undefined") { + g = window + } else if (typeof global !== "undefined") { + g = global + } else if (typeof self !== "undefined") { + g = self + } else { + g = this + } + g.protocolCheck = f() + } +})(function () { + var define, module, exports; + return (function e(t, n, r) { + function s(o, u) { + if (!n[o]) { + if (!t[o]) { + var a = typeof require == "function" && require; + if (!u && a) return a(o, !0); + if (i) return i(o, !0); + var f = new Error("Cannot find module '" + o + "'"); + throw f.code = "MODULE_NOT_FOUND", f + } + var l = n[o] = {exports: {}}; + t[o][0].call(l.exports, function (e) { + var n = t[o][1][e]; + return s(n ? n : e) + }, l, l.exports, e, t, n, r) + } + return n[o].exports + } + + var i = typeof require == "function" && require; + for (var o = 0; o < r.length; o++) s(r[o]); + return s + })({ + 1: [function (require, module, exports) { + function _registerEvent(target, eventType, cb) { + if (target.addEventListener) { + target.addEventListener(eventType, cb); + return { + remove: function () { + target.removeEventListener(eventType, cb); + } + }; + } else { + target.attachEvent(eventType, cb); + return { + remove: function () { + target.detachEvent(eventType, cb); + } + }; + } + } + + function _createHiddenIframe(target, uri) { + var iframe = document.createElement("iframe"); + iframe.src = uri; + iframe.id = "hiddenIframe"; + iframe.style.display = "none"; + target.appendChild(iframe); + + return iframe; + } + + function openUriWithHiddenFrame(uri, failCb, successCb) { + + var timeout = setTimeout(function () { + failCb(); + handler.remove(); + }, 1000); + + var iframe = document.querySelector("#hiddenIframe"); + if (!iframe) { + iframe = _createHiddenIframe(document.body, "about:blank"); + } + + var handler = _registerEvent(window, "blur", onBlur); + + function onBlur() { + clearTimeout(timeout); + handler.remove(); + successCb(); + } + + iframe.contentWindow.location.href = uri; + } + + function openUriWithTimeoutHack(uri, failCb, successCb) { + + var timeout = setTimeout(function () { + failCb(); + handler.remove(); + }, 1000); + + //handle page running in an iframe (blur must be registered with top level window) + var target = window; + while (target != target.parent) { + target = target.parent; + } + + var handler = _registerEvent(target, "blur", onBlur); + + function onBlur() { + clearTimeout(timeout); + handler.remove(); + successCb(); + } + + window.location = uri; + } + + function openUriUsingFirefox(uri, failCb, successCb) { + var iframe = document.querySelector("#hiddenIframe"); + + if (!iframe) { + iframe = _createHiddenIframe(document.body, "about:blank"); + } + + try { + iframe.contentWindow.location.href = uri; + successCb(); + } catch (e) { + if (e.name == "NS_ERROR_UNKNOWN_PROTOCOL") { + alert("Un Kown!") + failCb(); + } + } + } + + function openUriWithIE11UsingRegistry(uri, failCb, successCb) { + var shell = new ActiveXObject("WScript.shell"); + try { + var reg = shell.RegRead("HKEY_CLASSES_ROOT\\glcloud\\URL Protocol"); + if (reg) { + console.log(reg); + window.location.href = uri; + } + successCb(); + } catch (e) { + failCb(); + } + + } + + function openUriUsingIEInOlderWindows(uri, failCb, successCb) { + if (getInternetExplorerVersion() === 10) { + openUriUsingIE10InWindows7(uri, failCb, successCb); + } else if (getInternetExplorerVersion() === 9 || getInternetExplorerVersion() === 11) { + /*openUriWithHiddenFrame(uri, failCb, successCb);*/ + openUriWithIE11UsingRegistry(uri, failCb, successCb); + } else { + openUriInNewWindowHack(uri, failCb, successCb); + } + } + + function openUriUsingIE10InWindows7(uri, failCb, successCb) { + var timeout = setTimeout(failCb, 6000); + window.addEventListener("blur", function () { + clearTimeout(timeout); + successCb(); + }); + + var iframe = document.querySelector("#hiddenIframe"); + if (!iframe) { + iframe = _createHiddenIframe(document.body, "about:blank"); + } + try { + iframe.contentWindow.location.href = uri; + } catch (e) { + failCb(); + clearTimeout(timeout); + } + } + + function openUriInNewWindowHack(uri, failCb, successCb) { + var myWindow = window.open('', '', 'width=0,height=0'); + + myWindow.document.write(""); + + setTimeout(function () { + try { + myWindow.location.href; + myWindow.setTimeout("window.close()", 1000); + successCb(); + } catch (e) { + myWindow.close(); + failCb(); + } + }, 1000); + } + + function openUriWithMsLaunchUri(uri, failCb, successCb) { + navigator.msLaunchUri(uri, + successCb, + failCb + ); + } + + function checkBrowser() { + var isOpera = !!window.opera || navigator.userAgent.indexOf(' OPR/') >= 0; + return { + isOpera: isOpera, + isFirefox: typeof InstallTrigger !== 'undefined', + isSafari: Object.prototype.toString.call(window.HTMLElement).indexOf('Constructor') > 0, + isChrome: !!window.chrome && !isOpera, + isIE: /*@cc_on!@*/false || !!document.documentMode // At least IE6 + } + } + + function getInternetExplorerVersion() { + var rv = -1; + if (navigator.appName === "Microsoft Internet Explorer") { + var ua = navigator.userAgent; + var re = new RegExp("MSIE ([0-9]{1,}[\.0-9]{0,})"); + if (re.exec(ua) != null) + rv = parseFloat(RegExp.$1); + } else if (navigator.appName === "Netscape") { + var ua = navigator.userAgent; + var re = new RegExp("Trident/.*rv:([0-9]{1,}[\.0-9]{0,})"); + if (re.exec(ua) != null) { + rv = parseFloat(RegExp.$1); + } + } + return rv; + } + + module.exports = function (uri, failCb, successCb) { + function failCallback() { + failCb && failCb(); + } + + function successCallback() { + successCb && successCb(); + } + + if (navigator.msLaunchUri) { //for IE and Edge in Win 8 and Win 10 + openUriWithMsLaunchUri(uri, failCb, successCb); + } else { + var browser = checkBrowser(); + + if (browser.isFirefox) { + openUriUsingFirefox(uri, failCallback, successCallback); + } else if (browser.isChrome) { + openUriWithTimeoutHack(uri, failCallback, successCallback); + } else if (browser.isIE) { + openUriUsingIEInOlderWindows(uri, failCallback, successCallback); + } else { + //not supported, implement please + } + } + } + + }, {}] + }, {}, [1])(1) +}); diff --git a/src/main/resources/com/fr/plugin/decision/integration/web/js/web_start.js b/src/main/resources/com/fr/plugin/decision/integration/web/js/web_start.js new file mode 100644 index 0000000..a3d830f --- /dev/null +++ b/src/main/resources/com/fr/plugin/decision/integration/web/js/web_start.js @@ -0,0 +1,26 @@ +!(function () { + BI.config("dec.constant.header.items", function (items) { + return BI.concat([{ + type: "bi.button", + text: "启动设计器", + ghost: true, + css: {color: "white"}, + handler: function () { + Dec.Utils.getWebStartInfo(function (res) { + if (res.data) { + console.log(res); + window.protocolCheck(res.data.url, function () { + var agent = navigator.userAgent.toLowerCase(); + var is64 = agent.indexOf("win64") >= 0 || agent.indexOf("wow64") >= 0 || agent.indexOf("x64") >= 0; + var downloadURL = is64 ? res.data.download64 : res.data.download32; + console.log(downloadURL); + window.open(downloadURL); + }) + } else { + BI.Msg.toast(res.errorMsg, {level: "error"}) + } + }) + } + }], items); + }); +})(); \ No newline at end of file