commit 49c43ed78221ecc895cbe8c6fd9fd389877830f3 Author: pioneer Date: Tue Nov 8 16:31:55 2022 +0800 open diff --git a/README.md b/README.md new file mode 100644 index 0000000..7fc0432 --- /dev/null +++ b/README.md @@ -0,0 +1,6 @@ +# open-JSD-9896 + +JSD-9896 sso单点认证\ +免责说明:该源码为第三方爱好者提供,不保证源码和方案的可靠性,也不提供任何形式的源码教学指导和协助!\ +仅作为开发者学习参考使用!禁止用于任何商业用途!\ +为保护开发者隐私,开发者信息已隐去!若原开发者希望公开自己的信息,可联系【pioneer】处理。 \ No newline at end of file diff --git a/doc/JSD-9896-需求确认书V1.docx b/doc/JSD-9896-需求确认书V1.docx new file mode 100644 index 0000000..0c66767 Binary files /dev/null and b/doc/JSD-9896-需求确认书V1.docx differ diff --git a/doc/JSD-9896配置使用文档.docx b/doc/JSD-9896配置使用文档.docx new file mode 100644 index 0000000..6dbac1a Binary files /dev/null and b/doc/JSD-9896配置使用文档.docx differ diff --git a/lib/finekit-10.0.jar b/lib/finekit-10.0.jar new file mode 100644 index 0000000..f4482fc Binary files /dev/null and b/lib/finekit-10.0.jar differ diff --git a/plugin.xml b/plugin.xml new file mode 100644 index 0000000..aed1fee --- /dev/null +++ b/plugin.xml @@ -0,0 +1,24 @@ + + + com.fr.plugin.ihsf.rpc + + yes + 1.0 + 10.0 + 2018-07-31 + fr.open + + + com.fr.plugin.ihsf + + com.fanruan.api + + + + + + + + + + \ No newline at end of file diff --git a/src/main/java/com/fr/plugin/ihsf/LocaleFinder.java b/src/main/java/com/fr/plugin/ihsf/LocaleFinder.java new file mode 100644 index 0000000..60a3541 --- /dev/null +++ b/src/main/java/com/fr/plugin/ihsf/LocaleFinder.java @@ -0,0 +1,43 @@ + /* + * Copyright (C), 2018-2020 + * Project: starter + * FileName: LocaleFinder + * Author: xx + * Date: 2020/8/31 22:19 + */ + package com.fr.plugin.ihsf; + + import com.fanruan.api.i18n.I18nKit; + import com.fr.intelli.record.Focus; + import com.fr.intelli.record.Original; + import com.fr.plugin.context.PluginContexts; + import com.fr.record.analyzer.EnableMetrics; + import com.fr.stable.fun.Authorize; + import com.fr.stable.fun.impl.AbstractLocaleFinder; + + /** + *
+ * + * + * @author xx + * @since 1.0.0 + */ + @Authorize(callSignKey = LocaleFinder.PLUGIN_ID) + @EnableMetrics + public class LocaleFinder extends AbstractLocaleFinder { + public static final String PLUGIN_ID = "com.fr.plugin.ihsf.rpc"; + + @Override + @Focus(id = PLUGIN_ID, text = "Plugin-ihsf", source = Original.PLUGIN) + public String find() { + if (PluginContexts.currentContext() == null || !PluginContexts.currentContext().isAvailable()) { + throw new RuntimeException(I18nKit.getLocText("Plugin-ihsf_Licence_Expired")); + } + return "com/fr/plugin/ihsf/locale/lang"; + } + + @Override + public int currentAPILevel() { + return CURRENT_LEVEL; + } + } \ No newline at end of file diff --git a/src/main/java/com/fr/plugin/ihsf/PluginMonitor.java b/src/main/java/com/fr/plugin/ihsf/PluginMonitor.java new file mode 100644 index 0000000..36acc46 --- /dev/null +++ b/src/main/java/com/fr/plugin/ihsf/PluginMonitor.java @@ -0,0 +1,35 @@ + /* + * Copyright (C), 2018-2021 + * Project: starter + * FileName: PluginMonitor + * Author: xx + * Date: 2021/3/30 15:10 + */ + package com.fr.plugin.ihsf; + + import com.fr.plugin.context.PluginContext; + import com.fr.plugin.ihsf.config.IhsfConfig; + import com.fr.plugin.observer.inner.AbstractPluginLifecycleMonitor; + + + /** + *
+ * + * + * @author xx + * @since 1.0.0 + */ + public class PluginMonitor extends AbstractPluginLifecycleMonitor { + + public PluginMonitor() { + } + + @Override + public void afterRun(PluginContext pluginContext) { + IhsfConfig.getInstance(); + } + + @Override + public void beforeStop(PluginContext pluginContext) { + } + } \ No newline at end of file diff --git a/src/main/java/com/fr/plugin/ihsf/config/IhsfConfig.java b/src/main/java/com/fr/plugin/ihsf/config/IhsfConfig.java new file mode 100644 index 0000000..b5ad552 --- /dev/null +++ b/src/main/java/com/fr/plugin/ihsf/config/IhsfConfig.java @@ -0,0 +1,73 @@ + /* + * Copyright (C), 2018-2021 + * Project: starter + * FileName: IhcaConfig + * Author: xx + * Date: 2021/3/30 9:38 + */ + package com.fr.plugin.ihsf.config; + + import com.fanruan.api.util.StringKit; + import com.fr.config.*; + import com.fr.config.holder.Conf; + import com.fr.config.holder.factory.Holders; + + /** + *
+ * + * + * @author xx + * @since 1.0.0 + */ + @Visualization(category = "Plugin-ihsf_Group") + public class IhsfConfig extends DefaultConfiguration { + public static final String AUTH_URI = "http://xx/sso/oauth/token"; + private static volatile IhsfConfig config = null; + @Identifier(value = "uriBase", name = "Plugin-ihsf_Config_UriBase", description = "Plugin-ihsf_Config_UriBase_Description", status = Status.SHOW) + private final Conf uriBase = Holders.simple(AUTH_URI); + @Identifier(value = "clientId", name = "Plugin-ihsf_Config_ClientId", description = "Plugin-ihsf_Config_ClientId_Description", status = Status.SHOW) + private final Conf clientId = Holders.simple(StringKit.EMPTY); + @Identifier(value = "clientSecret", name = "Plugin-ihsf_Config_ClientSecret", description = "Plugin-ihsf_Config_ClientSecret_Description", status = Status.SHOW) + private final Conf clientSecret = Holders.simple(StringKit.EMPTY); + @Identifier(value = "grantType", name = "Plugin-ihsf_Config_GrantType", description = "Plugin-ihsf_Config_GrantType_Description", status = Status.SHOW) + private final Conf grantType = Holders.simple("password"); + + public static IhsfConfig getInstance() { + if (config == null) { + config = ConfigContext.getConfigInstance(IhsfConfig.class); + } + return config; + } + + public String getUriBase() { + return uriBase.get(); + } + + public void setUriBase(String uriBase) { + this.uriBase.set(uriBase); + } + + public String getClientId() { + return clientId.get(); + } + + public void setClientId(String clientId) { + this.clientId.set(clientId); + } + + public String getClientSecret() { + return clientSecret.get(); + } + + public void setClientSecret(String clientSecret) { + this.clientSecret.set(clientSecret); + } + + public String getGrantType() { + return grantType.get(); + } + + public void setGrantType(String grantType) { + this.grantType.set(grantType); + } + } \ No newline at end of file diff --git a/src/main/java/com/fr/plugin/ihsf/kit/RemoteDesignServiceKit.java b/src/main/java/com/fr/plugin/ihsf/kit/RemoteDesignServiceKit.java new file mode 100644 index 0000000..7d32c8f --- /dev/null +++ b/src/main/java/com/fr/plugin/ihsf/kit/RemoteDesignServiceKit.java @@ -0,0 +1,213 @@ + /* + * Copyright (C), 2018-2022 + * Project: starter + * FileName: RemoteDesignServiceKit + * Author: xx + * Date: 2022/4/28 12:05 + */ + package com.fr.plugin.ihsf.kit; + + import com.fanruan.api.log.LogKit; + import com.fanruan.api.net.http.HttpKit; + import com.fr.data.NetworkHelper; + import com.fr.decision.authority.data.User; + import com.fr.decision.config.LoginLockConfig; + import com.fr.decision.config.PasswordStrategyConfig; + import com.fr.decision.webservice.exception.login.UserPasswordNeedUpdateException; + import com.fr.decision.webservice.exception.login.UserPwdErrorException; + import com.fr.decision.webservice.utils.ControllerFactory; + import com.fr.decision.webservice.utils.WebServiceUtils; + import com.fr.decision.webservice.utils.controller.AuthenticController; + import com.fr.decision.webservice.v10.login.lock.LoginLockService; + import com.fr.decision.webservice.v10.password.strategy.PasswordStrategyService; + import com.fr.decision.webservice.v10.remote.RemoteDesignStatusService; + import com.fr.decision.webservice.v10.remote.RpcValidator; + import com.fr.decision.webservice.v10.user.UserService; + import com.fr.exception.RemoteDesignPermissionDeniedException; + import com.fr.general.ComparatorUtils; + import com.fr.general.GeneralUtils; + import com.fr.general.IOUtils; + import com.fr.intelli.record.FocusPoint; + import com.fr.intelli.record.MetricRegistry; + import com.fr.intelli.record.Original; + import com.fr.json.JSONObject; + import com.fr.locale.InterProviderFactory; + import com.fr.plugin.ihsf.config.IhsfConfig; + import com.fr.report.util.RemoteDesignAuthHelper; + import com.fr.report.util.RemoteUserInfo; + import com.fr.security.JwtUtils; + import com.fr.security.SecurityConfig; + import com.fr.security.encryption.SystemEncryptionManager; + import com.fr.security.encryption.mode.EncryptionMode; + import com.fr.security.encryption.storage.StorageEncryptors; + import com.fr.security.encryption.transmission.TransmissionEncryptors; + import com.fr.security.encryption.transmission.impl.SM4TransmissionEncryption; + import com.fr.stable.StringUtils; + import com.fr.stable.web.Device; + import com.fr.web.service.RemoteDesignAuthorityDataService; + import com.fr.workspace.WorkContext; + + import javax.servlet.http.HttpServletRequest; + import javax.servlet.http.HttpServletResponse; + import java.io.IOException; + import java.util.Date; + import java.util.HashMap; + import java.util.Map; + + /** + *
+ * + * + * @author xx + * @since 1.0.0 + */ + public class RemoteDesignServiceKit { + private static final String REMOTE_DESIGN_ID = "function_com.fr.remote.design"; + private static final String REMOTE_DESIGN = InterProviderFactory.getFrontProvider().getLocText("Fine-Dec_Focus_Point_Remote_Design"); + private static volatile RemoteDesignServiceKit instance = null; + + public RemoteDesignServiceKit() { + try { + WorkContext.registerValidator(new RpcValidator()); + } catch (Exception e) { + LogKit.error(e.getMessage(), e); + } + + } + + public static RemoteDesignServiceKit getInstance() { + if (instance == null) { + synchronized (RemoteDesignServiceKit.class) { + if (instance == null) { + instance = new RemoteDesignServiceKit(); + } + } + } + return instance; + } + + public String getRemoteToken(String username, String password, String compatibleParameters0) throws Exception { + if (StringUtils.isEmpty(compatibleParameters0) && !ComparatorUtils.equals(StorageEncryptors.getInstance().getCurrentEncryptionMode(), EncryptionMode.RSA)) { + throw new RemoteDesignPermissionDeniedException(); + } else { + //SSO接口鉴权 + if (doSSOAuthenticate(username, password)) { + return this.generateToken(username); + } + boolean auth = RemoteDesignAuthHelper.doAuthenticate(username, password); + if (auth) { + return this.generateToken(username); + } else { + User user = UserService.getInstance().getUserByUserName(username); + if (user != null && RemoteDesignAuthorityDataService.getInstance().hasAuthority(user.getId())) { + return this.generateToken(username); + } else { + throw new RemoteDesignPermissionDeniedException(); + } + } + } + } + + /** + * SSO接口鉴权 + * + * @param username + * @param password + * @return + */ + private boolean doSSOAuthenticate(String username, String password) { + IhsfConfig config = IhsfConfig.getInstance(); + Map params = new HashMap<>(); + params.put("client_id", config.getClientId()); + params.put("client_secret", config.getClientSecret()); + params.put("grant_type", config.getGrantType()); + params.put("username", username); + params.put("password", password); + LogKit.info("ihsf-RemoteDesignServiceKit-doAuthenticate-params:{}", params); + try { + String response = HttpKit.post(config.getUriBase(), params); + LogKit.info("ihsf-RemoteDesignServiceKit-doAuthenticate-response:{}", response); + JSONObject result = new JSONObject(response); + return result.has("access_token"); + } catch (IOException e) { + LogKit.error(e.getMessage(), e); + return false; + } + } + + private String generateToken(String var1) throws Exception { + String var2 = JwtUtils.createDefaultJWT(var1); + RemoteDesignStatusService.loginStatusService().put(var2, var1, 1209600000); + return var2; + } + + public String saferGetRemoteToken(HttpServletRequest request) throws Exception { + String username = NetworkHelper.getHTTPRequestParameter(request, "username"); + String password = TransmissionEncryptors.getInstance().decrypt(NetworkHelper.getHTTPRequestParameter(request, "password")); + String ipInfo = WebServiceUtils.getIpInfoFromRequest(request); + User user = UserService.getInstance().getUserByUserName(username); + if (user == null) { + throw new UserPwdErrorException(); + } else { + String token = JwtUtils.createDefaultJWT(username); + if (this.doSSOAuthenticate(username, password)) { + RemoteDesignStatusService.loginStatusService().put(token, username, 1209600000); + return token; + } + AuthenticController var6 = ControllerFactory.getInstance().getAuthenticController(user.getId()); + this.doUserAuthentication(var6, user, password, NetworkHelper.getDevice(request), ipInfo); + if (var6.passwordChangeable(user)) { + this.checkUpdateInitPassword(user); + PasswordStrategyService.getInstance().checkPasswordStrength(password, username, token); + PasswordStrategyService.getInstance().checkPasswordNeedUpdate(user, token); + } + + RemoteUserInfo var8 = RemoteDesignAuthHelper.getUserInfo(username); + if (!var8.isRoot() && !RemoteDesignAuthorityDataService.getInstance().hasAuthority(user.getId())) { + throw new RemoteDesignPermissionDeniedException(); + } else { + RemoteDesignStatusService.loginStatusService().put(token, username, 1209600000); + return token; + } + } + } + + private void doUserAuthentication(AuthenticController var1, User var2, String var3, Device var4, String var5) throws Exception { + String var6 = LoginLockConfig.getInstance().getLockObject(); + String var7 = ComparatorUtils.equals(var6, "ip") ? var5 : var2.getDisplayName(); + var1.dealWithLoginLock(var2, var4, var6, var7); + if (!var1.doAuthentication(var2, var3, var4)) { + var1.dealWithPasswordError(var2, var4, var6, var7); + throw new UserPwdErrorException(); + } else { + LoginLockService.getInstance().unlockObject(var2.getId(), var6, var7); + } + } + + private void checkUpdateInitPassword(User var1) { + Date var2 = var1.getPasswordChangeTime(); + if (PasswordStrategyConfig.getInstance().isUpdateInitPassword() && var2 == null) { + throw new UserPasswordNeedUpdateException(); + } + } + + public void recordConnection(String var1, String var2) throws Exception { + HashMap var3 = new HashMap(); + var3.put("designerJarVersion", StringUtils.alwaysNotNull(var1)); + var3.put("serverJarVersion", GeneralUtils.readBuildNO()); + MetricRegistry.getMetric().submit(FocusPoint.newBuilder().id("function_com.fr.remote.design").text(REMOTE_DESIGN).source(Original.EMBED).username(var2).body(var3).build()); + } + + public Map checkInfo() { + HashMap var1 = new HashMap(); + var1.put("frontSeed", SecurityConfig.getInstance().getFrontSeed()); + var1.put("frontSM4Key", SM4TransmissionEncryption.getInstance().getTransmissionKey()); + var1.put("transmissionEncryption", SystemEncryptionManager.getInstance().getTransmissionEncryption().getType()); + return var1; + } + + public void onMessage(HttpServletRequest var1, HttpServletResponse var2) throws Exception { + byte[] var3 = IOUtils.inputStream2Bytes(var1.getInputStream()); + var2.getOutputStream().write(WorkContext.handleMessage(var3)); + } + } \ No newline at end of file diff --git a/src/main/java/com/fr/plugin/ihsf/request/RemoteFilter.java b/src/main/java/com/fr/plugin/ihsf/request/RemoteFilter.java new file mode 100644 index 0000000..146f703 --- /dev/null +++ b/src/main/java/com/fr/plugin/ihsf/request/RemoteFilter.java @@ -0,0 +1,150 @@ + /* + * Copyright (C), 2018-2021 + * Project: starter + * FileName: RemoteFilter + * Author: xx + * Date: 2021/3/30 22:09 + */ + package com.fr.plugin.ihsf.request; + + import com.fanruan.api.i18n.I18nKit; + import com.fanruan.api.log.LogKit; + import com.fanruan.api.util.StringKit; + import com.fr.data.NetworkHelper; + import com.fr.decision.fun.impl.AbstractGlobalRequestFilterProvider; + import com.fr.decision.webservice.Response; + import com.fr.intelli.record.Focus; + import com.fr.intelli.record.Original; + import com.fr.intelligence.IntelligenceException; + import com.fr.intelligence.IntelligenceRuntimeException; + import com.fr.json.JSONObject; + import com.fr.plugin.context.PluginContexts; + import com.fr.plugin.ihsf.kit.RemoteDesignServiceKit; + import com.fr.record.analyzer.EnableMetrics; + import com.fr.security.SecurityToolbox; + import com.fr.stable.fun.Authorize; + import com.fr.web.utils.WebUtils; + + import javax.servlet.FilterChain; + import javax.servlet.FilterConfig; + import javax.servlet.http.HttpServletRequest; + import javax.servlet.http.HttpServletResponse; + + import static com.fr.plugin.ihsf.LocaleFinder.PLUGIN_ID; + + /** + *
+ * + * + * @author xx + * @since 1.0.0 + */ + @Authorize(callSignKey = PLUGIN_ID) + @EnableMetrics + public class RemoteFilter extends AbstractGlobalRequestFilterProvider { + + /** + * 过滤器名称 + * + * @return + */ + @Override + public String filterName() { + return "ihsfFilter"; + } + + /** + * 过滤规则 + * + * @return + */ + @Override + @Focus(id = PLUGIN_ID, text = "Plugin-ihsf", source = Original.PLUGIN) + public String[] urlPatterns() { + if (PluginContexts.currentContext() == null || !PluginContexts.currentContext().isAvailable()) { + LogKit.error(I18nKit.getLocText("Plugin-ihsf_Licence_Expired")); + return new String[]{}; + } + return new String[]{"/decision/remote/design/token", "/decision/remote/design/verify"}; + } + + /** + * 过滤器初始化 + * + * @param filterConfig + */ + @Override + public void init(FilterConfig filterConfig) { + super.init(filterConfig); + } + + /** + * 过滤器处理 + * + * @param request + * @param response + * @param filterChain + */ + @Override + public void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) { + try { + String pathInfo = (request.getPathInfo() != null) ? request.getPathInfo() : StringKit.EMPTY; + if (StringKit.equals(pathInfo, "/remote/design/token") + && StringKit.equalsIgnoreCase(request.getMethod(), "get")) { + getRemoteToken(request, response); + } else if (StringKit.equals(pathInfo, "/remote/design/token") + && StringKit.equalsIgnoreCase(request.getMethod(), "post")) { + safeGetRemoteToken(request, response); + } else if (StringKit.equals(pathInfo, "/remote/design/verify") + && StringKit.equalsIgnoreCase(request.getMethod(), "post")) { + saferGetRemoteToken(request, response); + } else { + filterChain.doFilter(request, response); + } + } catch (Exception e) { + LogKit.error(e.getMessage(), e); + } + } + + private void getRemoteToken(HttpServletRequest request, HttpServletResponse response) throws Exception { + LogKit.info("ihsf-RemoteFilter-getRemoteToken"); + String username = NetworkHelper.getHTTPRequestParameter(request, "username"); + String password = NetworkHelper.getHTTPRequestParameter(request, "password"); + String compatibleParameters0 = NetworkHelper.getHTTPRequestParameter(request, "compatibleParameters0"); + Response result; + try { + result = Response.ok(RemoteDesignServiceKit.getInstance().getRemoteToken(username, password, compatibleParameters0)); + WebUtils.printAsString(response, JSONObject.mapFrom(result).encode()); + } catch (IntelligenceRuntimeException | IntelligenceException exception) { + result = Response.error((exception).errorCode(), exception.getMessage()); + WebUtils.printAsString(response, JSONObject.mapFrom(result).encode()); + } + } + + private void safeGetRemoteToken(HttpServletRequest request, HttpServletResponse response) throws Exception { + LogKit.info("ihsf-RemoteFilter-safeGetRemoteToken"); + String username = NetworkHelper.getHTTPRequestParameter(request, "username"); + String password = SecurityToolbox.defaultDecrypt(NetworkHelper.getHTTPRequestParameter(request, "password")); + String compatibleParameters0 = NetworkHelper.getHTTPRequestParameter(request, "compatibleParameters0"); + Response result; + try { + result = Response.ok(RemoteDesignServiceKit.getInstance().getRemoteToken(username, password, compatibleParameters0)); + WebUtils.printAsString(response, JSONObject.mapFrom(result).encode()); + } catch (IntelligenceRuntimeException | IntelligenceException exception) { + result = Response.error((exception).errorCode(), exception.getMessage()); + WebUtils.printAsString(response, JSONObject.mapFrom(result).encode()); + } + } + + private void saferGetRemoteToken(HttpServletRequest request, HttpServletResponse response) throws Exception { + LogKit.info("ihsf-RemoteFilter-saferGetRemoteToken"); + Response result; + try { + result = Response.ok(RemoteDesignServiceKit.getInstance().saferGetRemoteToken(request)); + WebUtils.printAsString(response, JSONObject.mapFrom(result).encode()); + } catch (IntelligenceRuntimeException | IntelligenceException exception) { + result = Response.error((exception).errorCode(), exception.getMessage()); + WebUtils.printAsString(response, JSONObject.mapFrom(result).encode()); + } + } + } \ No newline at end of file diff --git a/src/main/resources/com/fr/plugin/ihsf/locale/lang.properties b/src/main/resources/com/fr/plugin/ihsf/locale/lang.properties new file mode 100644 index 0000000..f17b38c --- /dev/null +++ b/src/main/resources/com/fr/plugin/ihsf/locale/lang.properties @@ -0,0 +1,11 @@ +Plugin-ihsf=RPC Plugin +Plugin-ihsf_Group=RPC Plugin +Plugin-ihsf_Config_UriBase=Uri Base +Plugin-ihsf_Config_UriBase_Description=Uri Base +Plugin-ihsf_Config_ClientId=Client Id +Plugin-ihsf_Config_ClientId_Description=Client Id +Plugin-ihsf_Config_ClientSecret=Client Secret +Plugin-ihsf_Config_ClientSecret_Description=Client Secret +Plugin-ihsf_Config_GrantType=Grant Type +Plugin-ihsf_Config_GrantType_Description=Grant Type +Plugin-ihsf_Licence_Expired=RPC Plugin Licence Expired \ No newline at end of file diff --git a/src/main/resources/com/fr/plugin/ihsf/locale/lang_zh_CN.properties b/src/main/resources/com/fr/plugin/ihsf/locale/lang_zh_CN.properties new file mode 100644 index 0000000..eeda3f1 --- /dev/null +++ b/src/main/resources/com/fr/plugin/ihsf/locale/lang_zh_CN.properties @@ -0,0 +1,11 @@ +Plugin-ihsf=\u8BBE\u8BA1\u5668\u8FDC\u7A0B\u767B\u9646\u5B9A\u5236\u63D2\u4EF6 +Plugin-ihsf_Group=\u8BBE\u8BA1\u5668\u8FDC\u7A0B\u767B\u9646\u5B9A\u5236\u63D2\u4EF6 +Plugin-ihsf_Config_UriBase=\u63A5\u53E3URL +Plugin-ihsf_Config_UriBase_Description=\u63A5\u53E3URL +Plugin-ihsf_Config_ClientId=\u5E94\u7528\u6CE8\u518CID +Plugin-ihsf_Config_ClientId_Description=\u5E94\u7528\u6CE8\u518CID +Plugin-ihsf_Config_ClientSecret=\u5E94\u7528\u6CE8\u518C\u5BC6\u7801 +Plugin-ihsf_Config_ClientSecret_Description=\u5E94\u7528\u6CE8\u518C\u5BC6\u7801 +Plugin-ihsf_Config_GrantType=\u6388\u6743\u7C7B\u578B +Plugin-ihsf_Config_GrantType_Description=\u6388\u6743\u7C7B\u578B +Plugin-ihsf_Licence_Expired=\u8BBE\u8BA1\u5668\u8FDC\u7A0B\u767B\u9646\u5B9A\u5236\u63D2\u4EF6\u8BB8\u53EF\u8FC7\u671F \ No newline at end of file