diff --git a/README.md b/README.md index 752cece..0c57013 100644 --- a/README.md +++ b/README.md @@ -53,20 +53,21 @@ ### 功能列表 -| 功能 | DID | -|:-------------------------|:---:| -| Jetbrains全产品支持 | ✅ | -| Jetbrains全插件支持 | ✅ | -| 插件库全自动订阅官网更新 | ✅ | -| 公私钥/证书, 自动生成管理 | ✅ | -| power.conf文件自动配置 | ✅ | -| ja-netfilter.zip自动打包 | ✅ | -| 自定义License Show | ✅ | -| 支持实时搜索 | ✅ | -| 插件默认按名称排序 | ✅ | -| 支持local/jar/dockerfile运行 | ✅ | -| 单码全家桶激活支持 | ✅ | -| …… | ☑️ | +| 功能 | DID | +|:---------------------------|:---:| +| Jetbrains全产品支持 | ✅ | +| Jetbrains全插件支持 | ✅ | +| 插件库全自动订阅官网更新 | ✅ | +| 公私钥/证书, 自动生成管理 | ✅ | +| power.conf文件自动配置 | ✅ | +| ja-netfilter.zip自动打包 | ✅ | +| 自定义License Show | ✅ | +| 支持实时搜索 | ✅ | +| 插件默认按名称排序 | ✅ | +| 支持local/jar/dockerfile运行 | ✅ | +| 单码全家桶激活支持 | ✅ | +| Jetbrains License Server支持 | ✅ | +| …… | ☑️ | ## 运行教程 @@ -139,7 +140,7 @@ - **点击** `编辑自定义虚拟机选型` - **键入** 如下配置 ``` --javaagent:you-path/ja-netfilter.jar +-javaagent:you-path/ja-netfilter.jar=jetbrains --add-opens=java.base/jdk.internal.org.objectweb.asm=ALL-UNNAMED --add-opens=java.base/jdk.internal.org.objectweb.asm.tree=ALL-UNNAMED ``` @@ -157,7 +158,7 @@ - **点击** `编辑JVM选项` - **键入** 如下配置 ``` --javaagent:you-path/ja-netfilter.jar +-javaagent:you-path/ja-netfilter.jar=jetbrains --add-opens=java.base/jdk.internal.org.objectweb.asm=ALL-UNNAMED --add-opens=java.base/jdk.internal.org.objectweb.asm.tree=ALL-UNNAMED ``` diff --git a/src/main/java/com/jetbrains/help/JetbrainsHelpApplication.java b/src/main/java/com/jetbrains/help/JetbrainsHelpApplication.java index 8235b84..3ab084e 100644 --- a/src/main/java/com/jetbrains/help/JetbrainsHelpApplication.java +++ b/src/main/java/com/jetbrains/help/JetbrainsHelpApplication.java @@ -1,9 +1,11 @@ package com.jetbrains.help; import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.io.IoUtil; import cn.hutool.core.net.Ipv4Util; import cn.hutool.core.text.CharSequenceUtil; import cn.hutool.core.util.StrUtil; +import cn.hutool.crypto.SecureUtil; import cn.hutool.extra.spring.SpringUtil; import com.jetbrains.help.context.*; import lombok.SneakyThrows; @@ -17,6 +19,7 @@ import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.scheduling.annotation.Scheduled; import java.net.InetAddress; +import java.security.cert.Certificate; import java.util.Collection; import java.util.List; @@ -37,7 +40,6 @@ public class JetbrainsHelpApplication { PluginsContextHolder.init(); CertificateContextHolder.init(); AgentContextHolder.init(); - InetAddress localHost = InetAddress.getLocalHost(); String address = CharSequenceUtil.format("http://{}:{}", localHost.getHostAddress(), SpringUtil.getProperty("server.port")); String runSuccessWarn = "\n====================================================================================\n" + diff --git a/src/main/java/com/jetbrains/help/context/AgentContextHolder.java b/src/main/java/com/jetbrains/help/context/AgentContextHolder.java index 166f303..11fbcfd 100644 --- a/src/main/java/com/jetbrains/help/context/AgentContextHolder.java +++ b/src/main/java/com/jetbrains/help/context/AgentContextHolder.java @@ -26,7 +26,7 @@ public class AgentContextHolder { private static final String JA_NETFILTER_FILE_PATH = "external/agent/ja-netfilter"; - private static final String POWER_CONF_FILE_NAME = JA_NETFILTER_FILE_PATH + "/config/power.conf"; + private static final String POWER_CONF_FILE_NAME = JA_NETFILTER_FILE_PATH + "/config-jetbrains/power.conf"; private static File jaNetfilterFile; @@ -35,7 +35,7 @@ public class AgentContextHolder { public static void init() { log.info("Agent context init loading..."); jaNetfilterZipFile = FileTools.getFileOrCreat(JA_NETFILTER_FILE_PATH + ".zip"); - if (!FileTools.fileExists(JA_NETFILTER_FILE_PATH)) { + if (FileTools.fileNotExists(JA_NETFILTER_FILE_PATH)) { unzipJaNetfilter(); if (!powerConfHasInit()) { log.info("Agent config init loading..."); @@ -75,14 +75,27 @@ public class AgentContextHolder { @SneakyThrows private static String generatePowerConfigRule() { - X509Certificate crt = (X509Certificate) KeyUtil.readX509Certificate(IoUtil.toStream(CertificateContextHolder.crtFile())); - RSAPublicKey publicKey = (RSAPublicKey) PemUtil.readPemPublicKey(IoUtil.toStream(CertificateContextHolder.publicKeyFile())); - RSAPublicKey rootPublicKey = (RSAPublicKey) PemUtil.readPemPublicKey(IoUtil.toStream(CertificateContextHolder.rootKeyFile())); + StringBuilder result = new StringBuilder(); + X509Certificate crt = (X509Certificate) KeyUtil.readX509Certificate(IoUtil.toStream(CertificateContextHolder.getCrtFile())); + X509Certificate licenseCrt = (X509Certificate) KeyUtil.readX509Certificate(IoUtil.toStream(CertificateContextHolder.getLicenseCrtFile())); + RSAPublicKey publicKey = (RSAPublicKey) PemUtil.readPemPublicKey(IoUtil.toStream(CertificateContextHolder.getPublicKeyFile())); + RSAPublicKey rootPublicKey = (RSAPublicKey) PemUtil.readPemPublicKey(IoUtil.toStream(CertificateContextHolder.getRootKeyFile())); + RSAPublicKey rootLicensePublicKey = (RSAPublicKey) PemUtil.readPemPublicKey(IoUtil.toStream(CertificateContextHolder.getRootLicenseKeyFile())); BigInteger x = new BigInteger(1, crt.getSignature()); - BigInteger y = BigInteger.valueOf(65537L); + BigInteger y = rootPublicKey.getPublicExponent(); BigInteger z = rootPublicKey.getModulus(); BigInteger r = x.modPow(publicKey.getPublicExponent(), publicKey.getModulus()); - return CharSequenceUtil.format("EQUAL,{},{},{}->{}", x, y, z, r); + result.append("; Activation Code").append("\n") + .append(CharSequenceUtil.format("EQUAL,{},{},{}->{}", x, y, z, r)) + .append("\n"); + x = new BigInteger(1, licenseCrt.getSignature()); + z = rootLicensePublicKey.getModulus(); + y = rootLicensePublicKey.getPublicExponent(); + r = x.modPow(publicKey.getPublicExponent(), publicKey.getModulus()); + result.append("; License Server").append("\n") + .append(CharSequenceUtil.format("EQUAL,{},{},{}->{}", x, y, z, r)) + .append("\n"); + return result.toString(); } private static String generatePowerConfigStr(String ruleValue) { diff --git a/src/main/java/com/jetbrains/help/context/CertificateContextHolder.java b/src/main/java/com/jetbrains/help/context/CertificateContextHolder.java index 154c24c..1f77a52 100644 --- a/src/main/java/com/jetbrains/help/context/CertificateContextHolder.java +++ b/src/main/java/com/jetbrains/help/context/CertificateContextHolder.java @@ -3,10 +3,12 @@ package com.jetbrains.help.context; import cn.hutool.core.date.DateField; import cn.hutool.core.date.DateUtil; import cn.hutool.core.io.FileUtil; +import cn.hutool.core.io.IoUtil; import cn.hutool.crypto.PemUtil; import cn.hutool.crypto.SecureUtil; import com.jetbrains.help.util.FileTools; import lombok.AccessLevel; +import lombok.Getter; import lombok.NoArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.bouncycastle.asn1.x500.X500Name; @@ -27,29 +29,47 @@ import java.security.cert.Certificate; import java.security.cert.CertificateEncodingException; import java.security.cert.CertificateException; +/** + * 源证书来源 + */ @Slf4j @NoArgsConstructor(access = AccessLevel.PRIVATE) public class CertificateContextHolder { private static final String ROOT_KEY_FILE_NAME = "external/certificate/root.key"; + private static final String ROOT_LICENSE_KEY_FILE_NAME = "external/certificate/license-root.key"; private static final String PRIVATE_KEY_FILE_NAME = "external/certificate/private.key"; private static final String PUBLIC_KEY_FILE_NAME = "external/certificate/public.key"; private static final String CET_FILE_NAME = "external/certificate/ca.crt"; + private static final String LICENSE_CET_FILE_NAME = "external/certificate/license-ca.crt"; + public static final String SERVER_UID = "cikaros.top"; + @Getter private static File rootKeyFile; + @Getter + private static File rootLicenseKeyFile; + + @Getter private static File privateKeyFile; + @Getter private static File publicKeyFile; + @Getter private static File crtFile; + @Getter + private static File licenseCrtFile; + public static void init() { log.info("certificate context init loading..."); rootKeyFile = FileTools.getFileOrCreat(ROOT_KEY_FILE_NAME); - if (!FileTools.fileExists(PRIVATE_KEY_FILE_NAME) - || !FileTools.fileExists(PUBLIC_KEY_FILE_NAME) - || !FileTools.fileExists(CET_FILE_NAME)) { + rootLicenseKeyFile = FileTools.getFileOrCreat(ROOT_LICENSE_KEY_FILE_NAME); + if (FileTools.fileNotExists(PRIVATE_KEY_FILE_NAME) + || FileTools.fileNotExists(PUBLIC_KEY_FILE_NAME) + || FileTools.fileNotExists(CET_FILE_NAME) + || FileTools.fileNotExists(LICENSE_CET_FILE_NAME)) { log.info("certificate context generate loading..."); generateCertificate(); log.info("certificate context generate success!"); @@ -57,27 +77,11 @@ public class CertificateContextHolder { privateKeyFile = FileTools.getFileOrCreat(PRIVATE_KEY_FILE_NAME); publicKeyFile = FileTools.getFileOrCreat(PUBLIC_KEY_FILE_NAME); crtFile = FileTools.getFileOrCreat(CET_FILE_NAME); + licenseCrtFile = FileTools.getFileOrCreat(LICENSE_CET_FILE_NAME); } log.info("certificate context init success !"); } - - public static File rootKeyFile() { - return CertificateContextHolder.rootKeyFile; - } - - public static File privateKeyFile() { - return CertificateContextHolder.privateKeyFile; - } - - public static File publicKeyFile() { - return CertificateContextHolder.publicKeyFile; - } - - public static File crtFile() { - return CertificateContextHolder.crtFile; - } - public static void generateCertificate() { KeyPair keyPair = SecureUtil.generateKeyPair("RSA", 4096); PrivateKey privateKey = keyPair.getPrivate(); @@ -86,16 +90,8 @@ public class CertificateContextHolder { PemUtil.writePemObject("PRIVATE KEY", privateKey.getEncoded(), FileUtil.getWriter(privateKeyFile, StandardCharsets.UTF_8, false)); publicKeyFile = FileTools.getFileOrCreat(PUBLIC_KEY_FILE_NAME); PemUtil.writePemObject("PUBLIC KEY", publicKey.getEncoded(), FileUtil.getWriter(publicKeyFile, StandardCharsets.UTF_8, false)); - JcaX509v3CertificateBuilder certificateBuilder = new JcaX509v3CertificateBuilder( - new X500Name("CN=JetProfile CA"), - BigInteger.valueOf(System.currentTimeMillis()), - DateUtil.yesterday(), - DateUtil.date().offset(DateField.YEAR, 100), - new X500Name("CN=Jetbrains-Help"), - SubjectPublicKeyInfo.getInstance(publicKey.getEncoded())); try { - ContentSigner signer = new JcaContentSignerBuilder("SHA256withRSA").build(privateKey); - Certificate certificate = new JcaX509CertificateConverter().setProvider("BC").getCertificate(certificateBuilder.build(signer)); + Certificate certificate = generateCertificate(keyPair,"JetProfile CA","Jetbrains-Help"); crtFile = FileTools.getFileOrCreat(CET_FILE_NAME); PemUtil.writePemObject("CERTIFICATE", certificate.getEncoded(), FileUtil.getWriter(crtFile, StandardCharsets.UTF_8, false)); } catch (OperatorCreationException e) { @@ -105,7 +101,33 @@ public class CertificateContextHolder { } catch (CertificateException e) { throw new IllegalArgumentException("The certificate read exception", e); } + //TODO 创建License Servers CA + try { + Certificate certificate = generateCertificate(keyPair,"License Servers CA",String.format("%s.lsrv.jetbrains.com", SERVER_UID)); + licenseCrtFile = FileTools.getFileOrCreat(LICENSE_CET_FILE_NAME); + PemUtil.writePemObject("CERTIFICATE", certificate.getEncoded(), FileUtil.getWriter(licenseCrtFile, StandardCharsets.UTF_8, false)); + } catch (OperatorCreationException e) { + throw new IllegalArgumentException("Certificate operator creation exception", e); + } catch (CertificateEncodingException e) { + throw new IllegalArgumentException("The certificate encoding exception", e); + } catch (CertificateException e) { + throw new IllegalArgumentException("The certificate read exception", e); + } } + private static Certificate generateCertificate(KeyPair keyPair, String issuer, String subject) throws OperatorCreationException, CertificateException { + PrivateKey privateKey = keyPair.getPrivate(); + PublicKey publicKey = keyPair.getPublic(); + JcaX509v3CertificateBuilder certificateBuilder = new JcaX509v3CertificateBuilder( + new X500Name(String.format("CN=%s", issuer)), + BigInteger.valueOf(System.currentTimeMillis()), + DateUtil.yesterday(), + DateUtil.date().offset(DateField.YEAR, 100), + new X500Name(String.format("CN=%s", subject)), + SubjectPublicKeyInfo.getInstance(publicKey.getEncoded())); + ContentSigner signer = new JcaContentSignerBuilder("SHA256withRSA").build(privateKey); + return new JcaX509CertificateConverter().setProvider("BC").getCertificate(certificateBuilder.build(signer)); + + } } diff --git a/src/main/java/com/jetbrains/help/context/LicenseContextHolder.java b/src/main/java/com/jetbrains/help/context/LicenseContextHolder.java index c1c69e2..defae9e 100644 --- a/src/main/java/com/jetbrains/help/context/LicenseContextHolder.java +++ b/src/main/java/com/jetbrains/help/context/LicenseContextHolder.java @@ -1,20 +1,24 @@ package com.jetbrains.help.context; import cn.hutool.core.codec.Base64; -import cn.hutool.core.collection.CollUtil; import cn.hutool.core.io.IoUtil; import cn.hutool.core.text.CharSequenceUtil; import cn.hutool.core.util.IdUtil; +import cn.hutool.core.util.XmlUtil; import cn.hutool.crypto.PemUtil; import cn.hutool.crypto.SecureUtil; import cn.hutool.crypto.SignUtil; import cn.hutool.crypto.asymmetric.Sign; import cn.hutool.json.JSONUtil; import lombok.AccessLevel; +import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.w3c.dom.Document; +import org.w3c.dom.Node; +import java.math.BigInteger; import java.security.PrivateKey; import java.security.PublicKey; import java.security.cert.Certificate; @@ -22,7 +26,7 @@ import java.security.cert.CertificateEncodingException; import java.util.List; import java.util.Set; -import static cn.hutool.crypto.asymmetric.SignAlgorithm.SHA1withRSA; +import static cn.hutool.crypto.asymmetric.SignAlgorithm.*; @Slf4j @NoArgsConstructor(access = AccessLevel.PRIVATE) @@ -43,9 +47,9 @@ public class LicenseContextHolder { .setProducts(products); String licensePartJson = JSONUtil.toJsonStr(licensePart); String licensePartBase64 = Base64.encode(licensePartJson); - PrivateKey privateKey = PemUtil.readPemPrivateKey(IoUtil.toStream(CertificateContextHolder.privateKeyFile())); - PublicKey publicKey = PemUtil.readPemPublicKey(IoUtil.toStream(CertificateContextHolder.publicKeyFile())); - Certificate certificate = SecureUtil.readX509Certificate(IoUtil.toStream(CertificateContextHolder.crtFile())); + PrivateKey privateKey = PemUtil.readPemPrivateKey(IoUtil.toStream(CertificateContextHolder.getPrivateKeyFile())); + PublicKey publicKey = PemUtil.readPemPublicKey(IoUtil.toStream(CertificateContextHolder.getPublicKeyFile())); + Certificate certificate = SecureUtil.readX509Certificate(IoUtil.toStream(CertificateContextHolder.getCrtFile())); Sign sign = SignUtil.sign(SHA1withRSA, privateKey.getEncoded(), publicKey.getEncoded()); String signatureBase64 = Base64.encode(sign.sign(licensePartJson)); String certBase64; @@ -74,4 +78,147 @@ public class LicenseContextHolder { private String paidUpTo; } + public static String obtainTicket(ObtainTicketRequest request) { + PrivateKey privateKey = PemUtil.readPemPrivateKey(IoUtil.toStream(CertificateContextHolder.getPrivateKeyFile())); + PublicKey publicKey = PemUtil.readPemPublicKey(IoUtil.toStream(CertificateContextHolder.getPublicKeyFile())); + Certificate cert = SecureUtil.readX509Certificate(IoUtil.toStream(CertificateContextHolder.getCrtFile())); + Certificate licenseCert = SecureUtil.readX509Certificate(IoUtil.toStream(CertificateContextHolder.getLicenseCrtFile())); + ObtainTicketResponse response = obtainTicket(privateKey, publicKey, cert, licenseCert, request); + Document document = XmlUtil.beanToXml(response); + String result = XmlUtil.toStr(document, "UTF-8", true, true); + //!important 必须去除XML中所有的换行和空格,即紧凑模式 + result = result.replaceAll("[\n ]", ""); + Sign sign = SignUtil.sign(SHA1withRSA, privateKey.getEncoded(), publicKey.getEncoded()); + String sign64 = Base64.encode(sign.sign(result)); + String licenseCertBase64; + try { + licenseCertBase64 = Base64.encode(licenseCert.getEncoded()); + } catch (CertificateEncodingException e) { + throw new IllegalArgumentException("Certificate extraction failed", e); + } + // + String signRes = String.format("", sign64, licenseCertBase64); + return signRes + "\n" + result; + } + + public static ObtainTicketResponse obtainTicket(PrivateKey privateKey, PublicKey publicKey, Certificate cert, Certificate licenseCert, ObtainTicketRequest request) { + String certBase64; + String licenseCertBase64; + try { + certBase64 = Base64.encode(cert.getEncoded()); + licenseCertBase64 = Base64.encode(licenseCert.getEncoded()); + } catch (CertificateEncodingException e) { + throw new IllegalArgumentException("Certificate extraction failed", e); + } + + long time = System.currentTimeMillis(); + long leaseTime = time + (1000L * 60 * 60 * 24 * 365); + //{失效时间戳}:{serverUid} + String serverLease = String.format("%d:%s", leaseTime, CertificateContextHolder.SERVER_UID); + + Sign sign = SignUtil.sign(SHA1withRSA, privateKey.getEncoded(), publicKey.getEncoded()); + String signatureBase64 = Base64.encode(sign.sign(String.format("%d:%s", time, request.getMachineId()))); + //{时间戳}:{客户端提交的machineId}:SHA1withRSA:{对({时间戳}:{客户端提交的machineId})的签名}:{license Server CA} + String confirmationStamp = String.format("%d:%s:SHA1withRSA:%s:%s", + time, request.getMachineId(), signatureBase64, licenseCertBase64); + + sign = SignUtil.sign(SHA512withRSA, privateKey.getEncoded(), publicKey.getEncoded()); + byte[] bytes = sign.sign(serverLease); + signatureBase64 = Base64.encode(bytes); + //SHA512withRSA-{对「serverLease」的值进行签名}-{JetProfile CA} + String leaseSignature = String.format("SHA512withRSA-%s-%s", signatureBase64, certBase64); +// String leaseSignature = new BigInteger(1,bytes).toString(); + return ObtainTicketResponse.builder() + .action(Action.NONE) + .responseCode(ResponseCode.OK) + .message(ResponseCode.OK.name()) + .ticketId("20") + .ticketProperties(String.format("licensee=%s\tlicenseType=4\tmetadata=0120230914PSAX000005", request.getUserName())) + .validationPeriod(71829000L) + .validationDeadlinePeriod(-1) + .prolongationPeriod(63462000L)//有效期 + .salt(request.getSalt()) + .serverUid(CertificateContextHolder.SERVER_UID) + .serverLease(serverLease) + .confirmationStamp(confirmationStamp) + .leaseSignature(leaseSignature) + .build(); + } + + public enum Action { + NONE, + } + + public enum ResponseCode { + OK + } + + /** + * 获取认证票据 + * + * @author Cikaros + * @date 2023/11/1 + */ + @Data + @Builder + public static class ObtainTicketResponse { + @Builder.Default + private Action action = Action.NONE; + private String confirmationStamp; + private String leaseSignature; + @Builder.Default + private String message = ""; + @Builder.Default + private Long prolongationPeriod = 1800000L; + @Builder.Default + private ResponseCode responseCode = ResponseCode.OK; + private String salt; + private String serverLease; + private String serverUid; + @Builder.Default + private String ticketId = "1"; + private String ticketProperties; + @Builder.Default + private Integer validationDeadlinePeriod = -1; + @Builder.Default + private Long validationPeriod = 1800000L; + + } + + /** + * @author Cikaros + * @date 2023/10/20 + */ + @Data + public static class ObtainTicketRequest { + + private String ideProductCode; + + private Boolean empty; + + private String buildDate; + + private String buildNumber; + + private Integer clientVersion; + + private String hostName; + + private String machineId; + + private String productCode; + + private String productFamilyId; + + private String salt; + + private Boolean secure; + + private String userName; + + private String version; + + private Long versionNumber; + + } } diff --git a/src/main/java/com/jetbrains/help/controller/ObtainTicketApiController.java b/src/main/java/com/jetbrains/help/controller/ObtainTicketApiController.java new file mode 100644 index 0000000..d553583 --- /dev/null +++ b/src/main/java/com/jetbrains/help/controller/ObtainTicketApiController.java @@ -0,0 +1,22 @@ +package com.jetbrains.help.controller; + +import com.jetbrains.help.context.LicenseContextHolder; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseBody; + +@Controller +@RequestMapping("/rpc") +public class ObtainTicketApiController { + + @GetMapping(value = "/obtainTicket.action", produces = {"application/xml;charset=utf-8"}) + @ResponseBody + public ResponseEntity obtainTicket(LicenseContextHolder.ObtainTicketRequest request) { + return ResponseEntity.ok() + .contentType(MediaType.APPLICATION_XML) + .body(LicenseContextHolder.obtainTicket(request)); + } +} diff --git a/src/main/java/com/jetbrains/help/route/IndexController.java b/src/main/java/com/jetbrains/help/route/IndexController.java index 984b6f2..438356b 100644 --- a/src/main/java/com/jetbrains/help/route/IndexController.java +++ b/src/main/java/com/jetbrains/help/route/IndexController.java @@ -4,15 +4,19 @@ import cn.hutool.core.io.FileUtil; import cn.hutool.core.io.IoUtil; import cn.hutool.core.text.CharSequenceUtil; import cn.hutool.core.util.StrUtil; +import cn.hutool.crypto.digest.DigestUtil; import com.jetbrains.help.JetbrainsHelpApplication; import com.jetbrains.help.context.AgentContextHolder; import com.jetbrains.help.context.PluginsContextHolder; import com.jetbrains.help.context.ProductsContextHolder; import com.jetbrains.help.properties.JetbrainsHelpProperties; +import jakarta.servlet.http.HttpServletRequest; import lombok.AllArgsConstructor; import lombok.RequiredArgsConstructor; +import org.springframework.cglib.core.Local; import org.springframework.core.io.InputStreamResource; import org.springframework.core.io.Resource; +import org.springframework.http.HttpRequest; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; @@ -20,11 +24,13 @@ import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody; +import org.thymeleaf.util.DateUtils; import java.io.File; -import java.util.List; +import java.util.*; import static org.springframework.http.HttpHeaders.CONTENT_DISPOSITION; +import static org.springframework.http.MediaType.APPLICATION_JSON; import static org.springframework.http.MediaType.APPLICATION_OCTET_STREAM; @Controller @@ -34,15 +40,37 @@ public class IndexController { private final JetbrainsHelpProperties jetbrainsHelpProperties; @GetMapping - public String index(Model model) { + public String index(HttpServletRequest request,Model model) { + String basePath = (request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + request.getContextPath()); List productCacheList = ProductsContextHolder.productCacheList(); List pluginCacheList = PluginsContextHolder.pluginCacheList(); model.addAttribute("products", productCacheList); model.addAttribute("plugins", pluginCacheList); model.addAttribute("defaults", jetbrainsHelpProperties); + model.addAttribute("basePath", basePath); return "index"; } + @GetMapping("/scoop/ja-netfilter") + @ResponseBody + public ResponseEntity> scoopInstall(HttpServletRequest request) { + String basePath = (request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + request.getContextPath()); + Map json = new HashMap<>(); + String version = DateUtils.format(new Date(),"yyyyMMdd", Locale.getDefault()); + json.put("version", version); + json.put("description", "JetBrains' dragon slayer"); + json.put("homepage", "https://cikaros.top"); + json.put("license", "MIT"); + json.put("url", String.format("%s/ja-netfilter#dl.zip", basePath)); + json.put("extract_to", Arrays.asList("", "config-jetbrains", "plugins-jetbrains", "scripts", "vmoptions")); + json.put("hash", DigestUtil.sha256Hex(AgentContextHolder.jaNetfilterZipFile())); + json.put("post_install", "cscript $dir/scripts/install-current-user.vbs"); + json.put("pre_uninstall", "cscript $dir/scripts/uninstall-current-user.vbs"); + return ResponseEntity.ok() + .contentType(APPLICATION_JSON) + .body(json); + } + @GetMapping("search") public String index(@RequestParam(required = false) String search, Model model) { List productCacheList = ProductsContextHolder.productCacheList(); @@ -70,4 +98,5 @@ public class IndexController { .contentType(APPLICATION_OCTET_STREAM) .body(new InputStreamResource(FileUtil.getInputStream(jaNetfilterZipFile))); } + } diff --git a/src/main/java/com/jetbrains/help/util/FileTools.java b/src/main/java/com/jetbrains/help/util/FileTools.java index 5b78a16..8d7db4a 100644 --- a/src/main/java/com/jetbrains/help/util/FileTools.java +++ b/src/main/java/com/jetbrains/help/util/FileTools.java @@ -3,12 +3,10 @@ package com.jetbrains.help.util; import cn.hutool.core.io.FileUtil; import cn.hutool.core.text.CharSequenceUtil; import cn.hutool.core.util.ObjectUtil; -import cn.hutool.core.util.StrUtil; import org.springframework.boot.system.ApplicationHome; import org.springframework.core.io.ClassPathResource; import java.io.File; -import java.io.IOException; public interface FileTools { @@ -19,6 +17,10 @@ public interface FileTools { return getFile(path).exists(); } + static boolean fileNotExists(String path) { + return !fileExists(path); + } + static File getFile(String path) { File homeDir = application.getDir(); File source = application.getSource(); diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index d2b4e74..4db737b 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -6,6 +6,6 @@ spring: server: port: 10768 help: - default-license-name: 光云 - default-assignee-name: 藏柏 - default-expiry-date: 2111-11-11 \ No newline at end of file + default-license-name: cikaros.top + default-assignee-name: Cikaros + default-expiry-date: 2099-12-31 \ No newline at end of file diff --git a/src/main/resources/external/agent/ja-netfilter.zip b/src/main/resources/external/agent/ja-netfilter.zip index f259b7e..2ecb6da 100644 Binary files a/src/main/resources/external/agent/ja-netfilter.zip and b/src/main/resources/external/agent/ja-netfilter.zip differ diff --git a/src/main/resources/external/certificate/license-root.key b/src/main/resources/external/certificate/license-root.key new file mode 100644 index 0000000..56ad02a --- /dev/null +++ b/src/main/resources/external/certificate/license-root.key @@ -0,0 +1,31 @@ +-----BEGIN CERTIFICATE----- +MIIFTDCCAzSgAwIBAgIJAMCrW9HV+hjZMA0GCSqGSIb3DQEBCwUAMB0xGzAZBgNV +BAMMEkxpY2Vuc2UgU2VydmVycyBDQTAgFw0xNjEwMTIxNDMwNTRaGA8yMTE2MTIy +NzE0MzA1NFowHTEbMBkGA1UEAwwSTGljZW5zZSBTZXJ2ZXJzIENBMIICIjANBgkq +hkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAoT7LvHj3JKK2pgc5f02z+xEiJDcvlBi6 +fIwrg/504UaMx3xWXAE5CEPelFty+QPRJnTNnSxqKQQmg2s/5tMJpL9lzGwXaV7a +rrcsEDbzV4el5mIXUnk77Bm/QVv48s63iQqUjVmvjQt9SWG2J7+h6X3ICRvF1sQB +yeat/cO7tkpz1aXXbvbAws7/3dXLTgAZTAmBXWNEZHVUTcwSg2IziYxL8HRFOH0+ +GMBhHqa0ySmF1UTnTV4atIXrvjpABsoUvGxw+qOO2qnwe6ENEFWFz1a7pryVOHXg +P+4JyPkI1hdAhAqT2kOKbTHvlXDMUaxAPlriOVw+vaIjIVlNHpBGhqTj1aqfJpLj +qfDFcuqQSI4O1W5tVPRNFrjr74nDwLDZnOF+oSy4E1/WhL85FfP3IeQAIHdswNMJ +y+RdkPZCfXzSUhBKRtiM+yjpIn5RBY+8z+9yeGocoxPf7l0or3YF4GUpud202zgy +Y3sJqEsZksB750M0hx+vMMC9GD5nkzm9BykJS25hZOSsRNhX9InPWYYIi6mFm8QA +2Dnv8wxAwt2tDNgqa0v/N8OxHglPcK/VO9kXrUBtwCIfZigO//N3hqzfRNbTv/ZO +k9lArqGtcu1hSa78U4fuu7lIHi+u5rgXbB6HMVT3g5GQ1L9xxT1xad76k2EGEi3F +9B+tSrvru70CAwEAAaOBjDCBiTAdBgNVHQ4EFgQUpsRiEz+uvh6TsQqurtwXMd4J +8VEwTQYDVR0jBEYwRIAUpsRiEz+uvh6TsQqurtwXMd4J8VGhIaQfMB0xGzAZBgNV +BAMMEkxpY2Vuc2UgU2VydmVycyBDQYIJAMCrW9HV+hjZMAwGA1UdEwQFMAMBAf8w +CwYDVR0PBAQDAgEGMA0GCSqGSIb3DQEBCwUAA4ICAQCJ9+GQWvBS3zsgPB+1PCVc +oG6FY87N6nb3ZgNTHrUMNYdo7FDeol2DSB4wh/6rsP9Z4FqVlpGkckB+QHCvqU+d +rYPe6QWHIb1kE8ftTnwapj/ZaBtF80NWUfYBER/9c6To5moW63O7q6cmKgaGk6zv +St2IhwNdTX0Q5cib9ytE4XROeVwPUn6RdU/+AVqSOspSMc1WQxkPVGRF7HPCoGhd +vqebbYhpahiMWfClEuv1I37gJaRtsoNpx3f/jleoC/vDvXjAznfO497YTf/GgSM2 +LCnVtpPQQ2vQbOfTjaBYO2MpibQlYpbkbjkd5ZcO5U5PGrQpPFrWcylz7eUC3c05 +UVeygGIthsA/0hMCioYz4UjWTgi9NQLbhVkfmVQ5lCVxTotyBzoubh3FBz+wq2Qt +iElsBrCMR7UwmIu79UYzmLGt3/gBdHxaImrT9SQ8uqzP5eit54LlGbvGekVdAL5l +DFwPcSB1IKauXZvi1DwFGPeemcSAndy+Uoqw5XGRqE6jBxS7XVI7/4BSMDDRBz1u +a+JMGZXS8yyYT+7HdsybfsZLvkVmc9zVSDI7/MjVPdk6h0sLn+vuPC1bIi5edoNy +PdiG2uPH5eDO6INcisyPpLS4yFKliaO4Jjap7yzLU9pbItoWgCAYa2NpxuxHJ0tB +7tlDFnvaRnQukqSG+VqNWg== +-----END CERTIFICATE----- \ No newline at end of file diff --git a/src/main/resources/templates/index.html b/src/main/resources/templates/index.html index 827ed1c..386ed0b 100644 --- a/src/main/resources/templates/index.html +++ b/src/main/resources/templates/index.html @@ -13,7 +13,8 @@

🇨🇳 下载 ja-netfilter.zip , 然后配置 你的JetBrains IDE(anything)'s IDE.vmoptions 配置文件!
- 🇨🇳 当然你也可以 重新定制激活授权 用以自定义你的激活信息!
+ 🇨🇳 当然你也可以 重新定制激活授权 用以自定义你的激活信息!
+ 🇨🇳 也可使用Scoop进行安装:
🇨🇳 请注意,此页面仅由 个人所有!