Joaquín Reñé
2025-10-07 146a0fb8b0e90f9196e569152f649baf60d6cc8f
#4410 - Comments on classes
57 files modified
changed files
securis/src/main/java/net/curisit/securis/AuthFilter.java patch | view | blame | history
securis/src/main/java/net/curisit/securis/DefaultExceptionHandler.java patch | view | blame | history
securis/src/main/java/net/curisit/securis/DevFilter.java patch | view | blame | history
securis/src/main/java/net/curisit/securis/FreeLicenseGenerator.java patch | view | blame | history
securis/src/main/java/net/curisit/securis/GzipFilter.java patch | view | blame | history
securis/src/main/java/net/curisit/securis/LicenseGenerator.java patch | view | blame | history
securis/src/main/java/net/curisit/securis/RestServicesApplication.java patch | view | blame | history
securis/src/main/java/net/curisit/securis/beans/User.java patch | view | blame | history
securis/src/main/java/net/curisit/securis/db/Application.java patch | view | blame | history
securis/src/main/java/net/curisit/securis/db/ApplicationMetadata.java patch | view | blame | history
securis/src/main/java/net/curisit/securis/db/BlockedRequest.java patch | view | blame | history
securis/src/main/java/net/curisit/securis/db/License.java patch | view | blame | history
securis/src/main/java/net/curisit/securis/db/LicenseHistory.java patch | view | blame | history
securis/src/main/java/net/curisit/securis/db/LicenseStatus.java patch | view | blame | history
securis/src/main/java/net/curisit/securis/db/LicenseType.java patch | view | blame | history
securis/src/main/java/net/curisit/securis/db/LicenseTypeMetadata.java patch | view | blame | history
securis/src/main/java/net/curisit/securis/db/Organization.java patch | view | blame | history
securis/src/main/java/net/curisit/securis/db/Pack.java patch | view | blame | history
securis/src/main/java/net/curisit/securis/db/PackMetadata.java patch | view | blame | history
securis/src/main/java/net/curisit/securis/db/PackStatus.java patch | view | blame | history
securis/src/main/java/net/curisit/securis/db/Settings.java patch | view | blame | history
securis/src/main/java/net/curisit/securis/db/User.java patch | view | blame | history
securis/src/main/java/net/curisit/securis/db/common/CodedEnum.java patch | view | blame | history
securis/src/main/java/net/curisit/securis/db/common/CreationTimestampEntity.java patch | view | blame | history
securis/src/main/java/net/curisit/securis/db/common/LicenseStatusType.java patch | view | blame | history
securis/src/main/java/net/curisit/securis/db/common/Metadata.java patch | view | blame | history
securis/src/main/java/net/curisit/securis/db/common/ModificationTimestampEntity.java patch | view | blame | history
securis/src/main/java/net/curisit/securis/db/common/PackStatusType.java patch | view | blame | history
securis/src/main/java/net/curisit/securis/db/common/PersistentEnumUserType.java patch | view | blame | history
securis/src/main/java/net/curisit/securis/db/common/SystemParams.java patch | view | blame | history
securis/src/main/java/net/curisit/securis/db/listeners/CreationTimestampListener.java patch | view | blame | history
securis/src/main/java/net/curisit/securis/db/listeners/ModificationTimestampListener.java patch | view | blame | history
securis/src/main/java/net/curisit/securis/ioc/EnsureTransaction.java patch | view | blame | history
securis/src/main/java/net/curisit/securis/ioc/EntityManagerProvider.java patch | view | blame | history
securis/src/main/java/net/curisit/securis/ioc/RequestsInterceptor.java patch | view | blame | history
securis/src/main/java/net/curisit/securis/ioc/RequestsModule.java patch | view | blame | history
securis/src/main/java/net/curisit/securis/ioc/SecurisModule.java patch | view | blame | history
securis/src/main/java/net/curisit/securis/security/BasicSecurityContext.java patch | view | blame | history
securis/src/main/java/net/curisit/securis/security/Securable.java patch | view | blame | history
securis/src/main/java/net/curisit/securis/services/ApiResource.java patch | view | blame | history
securis/src/main/java/net/curisit/securis/services/ApplicationResource.java patch | view | blame | history
securis/src/main/java/net/curisit/securis/services/BasicServices.java patch | view | blame | history
securis/src/main/java/net/curisit/securis/services/LicenseResource.java patch | view | blame | history
securis/src/main/java/net/curisit/securis/services/LicenseTypeResource.java patch | view | blame | history
securis/src/main/java/net/curisit/securis/services/OrganizationResource.java patch | view | blame | history
securis/src/main/java/net/curisit/securis/services/PackResource.java patch | view | blame | history
securis/src/main/java/net/curisit/securis/services/UserResource.java patch | view | blame | history
securis/src/main/java/net/curisit/securis/services/exception/SeCurisServiceException.java patch | view | blame | history
securis/src/main/java/net/curisit/securis/services/helpers/LicenseHelper.java patch | view | blame | history
securis/src/main/java/net/curisit/securis/services/helpers/MetadataHelper.java patch | view | blame | history
securis/src/main/java/net/curisit/securis/services/helpers/UserHelper.java patch | view | blame | history
securis/src/main/java/net/curisit/securis/utils/CacheTTL.java patch | view | blame | history
securis/src/main/java/net/curisit/securis/utils/Config.java patch | view | blame | history
securis/src/main/java/net/curisit/securis/utils/EmailManager.java patch | view | blame | history
securis/src/main/java/net/curisit/securis/utils/GZipServletResponseWrapper.java patch | view | blame | history
securis/src/main/java/net/curisit/securis/utils/TokenHelper.java patch | view | blame | history
securis/src/main/resources/META-INF/persistence.xml patch | view | blame | history
securis/src/main/java/net/curisit/securis/AuthFilter.java
....@@ -1,3 +1,6 @@
1
+/*
2
+ * Copyright @ 2013 CurisTEC, S.A.S. All Rights Reserved.
3
+ */
14 package net.curisit.securis;
25
36 import java.io.IOException;
....@@ -17,16 +20,63 @@
1720 import org.apache.logging.log4j.LogManager;
1821 import org.apache.logging.log4j.Logger;
1922
23
+/**
24
+* AuthFilter
25
+* <p>
26
+* Simple authentication/role wrapper for development and lightweight scenarios.
27
+* If a request parameter <code>user</code> or a session attribute <code>user</code>
28
+* is present, this filter wraps the current request with a custom {@link Principal}
29
+* and an ad-hoc role. The role assignment is temporary and follows the rule:
30
+* <ul>
31
+* <li>user == "advance" → role "advance"</li>
32
+* <li>otherwise → role "normal"</li>
33
+* </ul>
34
+* If no user is present, the request continues unmodified.
35
+*
36
+* <p><b>Security note:</b> This filter trusts a user name coming from a request parameter,
37
+* which must not be used in production. Replace with a proper authentication mechanism
38
+* (e.g., JWT, container security, SSO) and derive roles from authoritative claims.
39
+*
40
+* @author JRA
41
+* Last reviewed by JRA on Oct 6, 2025.
42
+*/
2043 @ApplicationScoped
2144 @WebFilter(urlPatterns = "/*")
2245 public class AuthFilter implements Filter {
2346
2447 private static final Logger LOG = LogManager.getLogger(AuthFilter.class);
2548
49
+ // ---------------------------------------------------------------------
50
+ // Lifecycle
51
+ // ---------------------------------------------------------------------
52
+
53
+ /**
54
+ * init<p>
55
+ * Filter initialization hook (unused).
56
+ */
2657 @Override
2758 public void init(FilterConfig fc) throws ServletException {
2859 }
2960
61
+ // ---------------------------------------------------------------------
62
+ // Filtering
63
+ // ---------------------------------------------------------------------
64
+
65
+
66
+ /**
67
+ * doFilter
68
+ * <p>
69
+ * If a user is detected (request param or session attribute), wrap the request to:
70
+ * <ul>
71
+ * <li>Expose a {@link Principal} with the provided username.</li>
72
+ * <li>Report a single role through {@link HttpServletRequest#isUserInRole(String)}.</li>
73
+ * </ul>
74
+ * Otherwise, pass-through.
75
+ *
76
+ * @param sr incoming request
77
+ * @param sr1 outgoing response
78
+ * @param fc filter chain
79
+ */
3080 @Override
3181 public void doFilter(ServletRequest sr, ServletResponse sr1, FilterChain fc) throws IOException, ServletException {
3282 HttpServletRequest req = (HttpServletRequest) sr;
....@@ -46,21 +96,46 @@
4696
4797 }
4898
99
+ /**
100
+ * destroy<p>
101
+ * Filter destruction hook (unused).
102
+ */
49103 @Override
50104 public void destroy() {
51105 }
52106
107
+ // ---------------------------------------------------------------------
108
+ // Wrapper
109
+ // ---------------------------------------------------------------------
110
+
111
+ /**
112
+ * UserRoleRequestWrapper
113
+ * <p>
114
+ * Wrapper that overrides role checks and the user principal when a synthetic user is present.
115
+ */
53116 private class UserRoleRequestWrapper extends HttpServletRequestWrapper {
54117
55118 private String role;
56119 private String user;
57120
121
+ /**
122
+ * Constructor
123
+ * <p>
124
+ * @param role single role to expose via {@link #isUserInRole(String)}
125
+ * @param user user name to expose via {@link #getUserPrincipal()}
126
+ * @param request original request to wrap
127
+ */
58128 public UserRoleRequestWrapper(String role, String user, HttpServletRequest request) {
59129 super(request);
60130 this.role = role;
61131 this.user = user;
62132 }
63133
134
+ /**
135
+ * isUserInRole
136
+ * <p>
137
+ * Returns {@code true} if the requested role equals the configured synthetic role.
138
+ */
64139 @Override
65140 public boolean isUserInRole(String role) {
66141 LOG.info("isUserRole METHOD: {}, {}", role, this.role);
....@@ -70,6 +145,11 @@
70145 return this.role.equals(role);
71146 }
72147
148
+ /**
149
+ * getUserPrincipal
150
+ * <p>
151
+ * Returns a minimal {@link Principal} with the configured user name; delegates otherwise.
152
+ */
73153 @Override
74154 public Principal getUserPrincipal() {
75155 if (this.user == null) {
securis/src/main/java/net/curisit/securis/DefaultExceptionHandler.java
....@@ -1,3 +1,6 @@
1
+/*
2
+ * Copyright @ 2013 CurisTEC, S.A.S. All Rights Reserved.
3
+ */
14 package net.curisit.securis;
25
36 import jakarta.persistence.EntityManager;
....@@ -17,18 +20,49 @@
1720 import net.curisit.securis.services.exception.SeCurisServiceException;
1821 import net.curisit.securis.services.exception.SeCurisServiceException.ErrorCodes;
1922
23
+/**
24
+* DefaultExceptionHandler
25
+* <p>
26
+* JAX-RS {@link ExceptionMapper} that normalizes error responses across the API.
27
+* It also makes a best-effort to rollback and close a request-scoped {@link EntityManager}
28
+* if still open.
29
+*
30
+* <p>Response strategy:
31
+* <ul>
32
+* <li>{@link ForbiddenException} → 401 UNAUTHORIZED with app-specific error headers.</li>
33
+* <li>{@link SeCurisServiceException} → 418 (custom) with app error headers.</li>
34
+* <li>Other exceptions → 500 with generic message and request context logging.</li>
35
+* </ul>
36
+*
37
+* Headers:
38
+* <ul>
39
+* <li>{@code X-SECURIS-ERROR-MSG}</li>
40
+* <li>{@code X-SECURIS-ERROR-CODE}</li>
41
+* </ul>
42
+*
43
+* @author JRA
44
+* Last reviewed by JRA on Oct 6, 2025.
45
+*/
2046 @Provider
2147 public class DefaultExceptionHandler implements ExceptionMapper<Exception> {
48
+
2249 private static final Logger LOG = LogManager.getLogger(DefaultExceptionHandler.class);
23
-
50
+
51
+ /** Default status code used for application-defined errors. */
2452 public static final int DEFAULT_APP_ERROR_STATUS_CODE = 418;
53
+
54
+ /** Header name carrying a human-readable error message. */
2555 public static final String ERROR_MESSAGE_HEADER = "X-SECURIS-ERROR-MSG";
56
+
57
+ /** Header name carrying a symbolic application error code. */
2658 public static final String ERROR_CODE_MESSAGE_HEADER = "X-SECURIS-ERROR-CODE";
2759
60
+ /** Default constructor (logs instantiation). */
2861 public DefaultExceptionHandler() {
2962 LOG.info("Creating DefaultExceptionHandler ");
3063 }
3164
65
+ // Context objects injected by the runtime
3266 @Context
3367 HttpServletRequest request;
3468 @Context
....@@ -36,6 +70,12 @@
3670 @Context
3771 EntityManager em;
3872
73
+ /**
74
+ * toResponse
75
+ * <p>
76
+ * Map a thrown exception to an HTTP {@link Response}, releasing the {@link EntityManager}
77
+ * if present.
78
+ */
3979 @Override
4080 public Response toResponse(Exception e) {
4181 releaseEntityManager();
....@@ -57,6 +97,11 @@
5797 return Response.serverError().header(ERROR_MESSAGE_HEADER, "Unexpected error: " + e.toString()).type(MediaType.APPLICATION_JSON).build();
5898 }
5999
100
+ /**
101
+ * releaseEntityManager
102
+ * <p>
103
+ * Best-effort cleanup: rollback active transaction (if joined) and close the {@link EntityManager}.
104
+ */
60105 private void releaseEntityManager() {
61106 try {
62107 if (em != null && em.isOpen()) {
securis/src/main/java/net/curisit/securis/DevFilter.java
....@@ -1,3 +1,6 @@
1
+/*
2
+ * Copyright @ 2013 CurisTEC, S.A.S. All Rights Reserved.
3
+ */
14 package net.curisit.securis;
25
36 import java.io.IOException;
....@@ -16,6 +19,19 @@
1619 import org.apache.logging.log4j.LogManager;
1720 import org.apache.logging.log4j.Logger;
1821
22
+/**
23
+* DevFilter
24
+* <p>
25
+* Development-time CORS helper. Adds permissive CORS headers to allow front-end
26
+* resources (e.g. JS served from a different origin) to call the API.
27
+* Short-circuits <code>OPTIONS</code> preflight requests.
28
+*
29
+* <p><b>Security note:</b> This configuration is intentionally permissive and should be
30
+* restricted for production.
31
+*
32
+* @author JRA
33
+ * Last reviewed by JRA on Oct 5, 2025.
34
+*/
1935 @ApplicationScoped
2036 @WebFilter(urlPatterns = "/*")
2137 public class DevFilter implements Filter {
....@@ -23,10 +39,19 @@
2339 @SuppressWarnings("unused")
2440 private static final Logger log = LogManager.getLogger(DevFilter.class);
2541
42
+ /**
43
+ * init<p>
44
+ * Filter init hook (unused).
45
+ */
2646 @Override
2747 public void init(FilterConfig fc) throws ServletException {
2848 }
2949
50
+ /**
51
+ * doFilter
52
+ * <p>
53
+ * Add CORS headers and pass through non-OPTIONS methods to the next filter.
54
+ */
3055 @Override
3156 public void doFilter(ServletRequest sreq, ServletResponse sres, FilterChain fc) throws IOException, ServletException {
3257 HttpServletRequest req = (HttpServletRequest) sreq;
....@@ -44,6 +69,10 @@
4469 }
4570 }
4671
72
+ /**
73
+ * destroy<p>
74
+ * Filter destroy hook (unused).
75
+ */
4776 @Override
4877 public void destroy() {
4978 }
securis/src/main/java/net/curisit/securis/FreeLicenseGenerator.java
....@@ -1,3 +1,6 @@
1
+/*
2
+ * Copyright @ 2013 CurisTEC, S.A.S. All Rights Reserved.
3
+ */
14 package net.curisit.securis;
25
36 import java.util.Date;
....@@ -9,10 +12,31 @@
912 import net.curisit.securis.beans.SignedLicenseBean;
1013 import net.curisit.securis.utils.JsonUtils;
1114
15
+/**
16
+* FreeLicenseGenerator
17
+* <p>
18
+* Helper to generate a signed FREE license (no expiration) for a given app and code.
19
+*
20
+* @author JRA
21
+ * Last reviewed by JRA on Oct 5, 2025.
22
+*/
1223 public class FreeLicenseGenerator {
1324
25
+ /** Constant license type code for FREE licenses. */
1426 public static final String FREE_LICENSE_TYPE = "FREE";
1527
28
+ /**
29
+ * generateLicense
30
+ * <p>
31
+ * Build and sign a FREE license using the default generator. Uses a <code>Date(-1)</code>
32
+ * sentinel as "no expiration".
33
+ *
34
+ * @param appName application name
35
+ * @param licCode license code
36
+ * @param metadata additional metadata to embed
37
+ * @return signed license bean wrapper
38
+ * @throws SeCurisException on generation/signature errors
39
+ */
1640 public static SignedLicenseBean generateLicense(String appName, String licCode, Map<String, Object> metadata) throws SeCurisException {
1741 SignedLicenseBean sl = null;
1842 RequestBean rb = new RequestBean();
....@@ -24,6 +48,13 @@
2448 return sl;
2549 }
2650
51
+
52
+ /**
53
+ * Demo main
54
+ *
55
+ * @param args
56
+ * @throws SeCurisException
57
+ */
2758 public static void main(String[] args) throws SeCurisException {
2859 Map<String, Object> metadata = new HashMap<>();
2960 metadata.put("max_docs", 2000);
securis/src/main/java/net/curisit/securis/GzipFilter.java
....@@ -1,3 +1,6 @@
1
+/*
2
+ * Copyright @ 2013 CurisTEC, S.A.S. All Rights Reserved.
3
+ */
14 package net.curisit.securis;
25
36 import java.io.IOException;
....@@ -18,6 +21,12 @@
1821
1922 import net.curisit.securis.utils.GZipServletResponseWrapper;
2023
24
+/**
25
+* GzipFilter
26
+* <p>
27
+* Servlet filter that compresses <code>*.js</code> responses with GZIP when the client
28
+* advertises <code>Accept-Encoding: gzip</code>.
29
+*/
2130 @ApplicationScoped
2231 @WebFilter(urlPatterns = "*.js")
2332 public class GzipFilter implements Filter {
....@@ -25,10 +34,16 @@
2534 @SuppressWarnings("unused")
2635 private static final Logger LOG = LogManager.getLogger(GzipFilter.class);
2736
37
+ /** init<p>Filter init hook (unused). */
2838 @Override
2939 public void init(FilterConfig fc) throws ServletException {
3040 }
3141
42
+ /**
43
+ * doFilter
44
+ * <p>
45
+ * Wrap the response with a GZIP-compressing wrapper if supported by the client.
46
+ */
3247 @Override
3348 public void doFilter(ServletRequest sreq, ServletResponse sres, FilterChain chain) throws IOException, ServletException {
3449 HttpServletRequest httpRequest = (HttpServletRequest) sreq;
....@@ -44,12 +59,18 @@
4459 }
4560 }
4661
62
+ /**
63
+ * acceptsGZipEncoding
64
+ * <p>
65
+ * @return {@code true} when request header contains "gzip" in <code>Accept-Encoding</code>.
66
+ */
4767 private boolean acceptsGZipEncoding(HttpServletRequest httpRequest) {
4868 String acceptEncoding = httpRequest.getHeader("Accept-Encoding");
4969
5070 return acceptEncoding != null && acceptEncoding.indexOf("gzip") != -1;
5171 }
5272
73
+ /** destroy<p>Filter destroy hook (unused). */
5374 @Override
5475 public void destroy() {
5576 }
securis/src/main/java/net/curisit/securis/LicenseGenerator.java
....@@ -1,3 +1,6 @@
1
+/*
2
+ * Copyright @ 2013 CurisTEC, S.A.S. All Rights Reserved.
3
+ */
14 package net.curisit.securis;
25
36 import java.io.File;
....@@ -24,23 +27,35 @@
2427 import org.apache.logging.log4j.LogManager;
2528 import org.apache.logging.log4j.Logger;
2629
30
+import jakarta.inject.Singleton;
31
+
2732 /**
28
- * License generator and signer
29
- *
30
- * @author roberto <roberto.sanchez@curisit.net>
31
- */
32
-@javax.inject.Singleton
33
+* LicenseGenerator
34
+* <p>
35
+* Factory for building and signing {@link LicenseBean} instances. Uses a process-wide
36
+* singleton and expects a PKCS#8 private key at:
37
+* <code>~/.SeCuris/keys/securis_private_key.pkcs8</code>.
38
+*
39
+* @author JRA
40
+* Last reviewed by JRA on Oct 5, 2025.
41
+*/
42
+@Singleton
3343 public class LicenseGenerator {
3444
3545 private static final Logger LOG = LogManager.getLogger(LicenseGenerator.class);
3646
3747 private static LicenseGenerator singleton = new LicenseGenerator();
3848
49
+ /**
50
+ * getInstance<p>
51
+ * Singleton accessor.
52
+ */
3953 public static LicenseGenerator getInstance() {
4054 return singleton;
4155 }
4256
4357 /**
58
+ * generateLicense<p>
4459 * Generate a license bean with the specified data
4560 *
4661 * @param req
....@@ -66,12 +81,14 @@
6681 }
6782
6883 /**
69
- * Generate a license file using a {@link LicenseBean}
70
- *
71
- * @param license
72
- * @param file
73
- * @throws SeCurisException
74
- */
84
+ * save
85
+ * <p>
86
+ * Persist a pretty-printed JSON representation of the signed license to disk.
87
+ *
88
+ * @param license source license
89
+ * @param file target file path
90
+ * @throws SeCurisException if serialization or IO fails
91
+ */
7592 public void save(LicenseBean license, File file) throws SeCurisException {
7693 SignedLicenseBean signedLic = new SignedLicenseBean(license);
7794 byte[] json;
....@@ -91,15 +108,14 @@
91108 }
92109
93110 /**
94
- *
95
- * @param licBean
96
- * @return
97
- * @throws NoSuchAlgorithmException
98
- * @throws IOException
99
- * @throws InvalidKeySpecException
100
- * @throws InvalidKeyException
101
- * @throws SignatureException
102
- */
111
+ * sign
112
+ * <p>
113
+ * Compute a Base64 signature for the given license and set it into the bean.
114
+ *
115
+ * @param licBean license to sign (in-place)
116
+ * @return Base64 signature string
117
+ * @throws SeCurisException if the signature process fails
118
+ */
103119 public String sign(LicenseBean licBean) throws SeCurisException {
104120 SignatureHelper sh = SignatureHelper.getInstance();
105121
....@@ -114,16 +130,8 @@
114130 byte[] signatureData = signature.sign();
115131 licBean.setSignature(Base64.encodeBase64String(signatureData));
116132 return licBean.getSignature();
117
- } catch (NoSuchAlgorithmException e) {
118
- LOG.error("Error signing license for " + licBean, e);
119
- } catch (InvalidKeyException e) {
120
- LOG.error("Error signing license for " + licBean, e);
121
- } catch (InvalidKeySpecException e) {
122
- LOG.error("Error signing license for " + licBean, e);
123
- } catch (IOException e) {
124
- LOG.error("Error signing license for " + licBean, e);
125
- } catch (SignatureException e) {
126
- LOG.error("Error signing license for " + licBean, e);
133
+ } catch (NoSuchAlgorithmException | InvalidKeyException | InvalidKeySpecException | IOException | SignatureException e) {
134
+ LOG.error("Error signing license for {}", licBean, e);
127135 }
128136 throw new SeCurisException("License could not be generated");
129137 }
securis/src/main/java/net/curisit/securis/RestServicesApplication.java
....@@ -1,3 +1,6 @@
1
+/*
2
+ * Copyright @ 2013 CurisTEC, S.A.S. All Rights Reserved.
3
+ */
14 package net.curisit.securis;
25
36 import java.util.HashSet;
....@@ -19,11 +22,25 @@
1922 import org.apache.logging.log4j.LogManager;
2023 import org.apache.logging.log4j.Logger;
2124
25
+/**
26
+* RestServicesApplication
27
+* <p>
28
+* JAX-RS application configuring the REST resource classes and interceptors.
29
+* Declares base path <code>/</code>.
30
+*
31
+* @author JRA
32
+* Last reviewed by JRA on Oct 5, 2025.
33
+*/
2234 @ApplicationPath("/")
2335 public class RestServicesApplication extends Application {
2436
2537 private static final Logger LOG = LogManager.getLogger(RestServicesApplication.class);
2638
39
+ /**
40
+ * getClasses
41
+ * <p>
42
+ * @return set of REST endpoints and filters to be registered by the runtime
43
+ */
2744 @Override
2845 public Set<Class<?>> getClasses() {
2946 Set<Class<?>> classes = new HashSet<>();
securis/src/main/java/net/curisit/securis/beans/User.java
....@@ -1,5 +1,16 @@
1
+/*
2
+ * Copyright @ 2013 CurisTEC, S.A.S. All Rights Reserved.
3
+ */
14 package net.curisit.securis.beans;
25
6
+/**
7
+* User
8
+* <p>
9
+* Placeholder bean for a system user. Intentionally empty in this snapshot.
10
+* Extend with fields (username, roles, etc.) and proper JSON/JPA annotations as needed.
11
+*
12
+* Note: Kept as-is to preserve current behavior.
13
+*/
314 public class User {
415
516 }
securis/src/main/java/net/curisit/securis/db/Application.java
....@@ -1,3 +1,6 @@
1
+/*
2
+ * Copyright @ 2013 CurisTEC, S.A.S. All Rights Reserved.
3
+ */
14 package net.curisit.securis.db;
25
36 import java.io.Serializable;
....@@ -30,9 +33,25 @@
3033 import com.fasterxml.jackson.annotation.JsonProperty;
3134
3235 /**
33
- * Entity implementation class for Entity: application
34
- *
35
- */
36
+* Application
37
+* <p>
38
+* JPA entity that represents an application registered in the licensing server.
39
+* Includes descriptive fields and relationships to <code>LicenseType</code>,
40
+* <code>ApplicationMetadata</code> and <code>User</code>.
41
+*
42
+* Mapping details:
43
+* <ul>
44
+* <li>Table: <code>application</code></li>
45
+* <li>Named queries: <code>list-applications</code>, <code>list-applications-by_ids</code></li>
46
+* <li>Relationships:
47
+* <ul>
48
+* <li><code>@OneToMany</code> <b>licenseTypes</b> (mappedBy="application")</li>
49
+* <li><code>@OneToMany</code> <b>metadata</b> with cascade PERSIST/REMOVE/REFRESH</li>
50
+* <li><code>@ManyToMany</code> <b>users</b> via join table <code>user_application</code></li>
51
+* </ul>
52
+* </li>
53
+* </ul>
54
+*/
3655 @JsonAutoDetect
3756 @JsonInclude(Include.NON_NULL)
3857 @JsonIgnoreProperties(ignoreUnknown = true)
....@@ -46,32 +65,57 @@
4665
4766 private static final long serialVersionUID = 1L;
4867
68
+ // ------------------------------------------------------------------
69
+ // Columns
70
+ // ------------------------------------------------------------------
71
+
72
+
73
+ /** Surrogate primary key. */
4974 @Id
5075 @GeneratedValue
5176 private Integer id;
5277
78
+ /** Unique short code for the application (business identifier). */
5379 private String code;
80
+
81
+ /** Human-friendly application name. */
5482 private String name;
83
+
84
+ /** Optional description. */
5585 private String description;
5686
87
+ /** Default license file name suggested for downloads/exports. */
5788 @Column(name = "license_filename")
5889 @JsonProperty("license_filename")
5990 private String licenseFilename;
6091
92
+ /** Creation timestamp (server-side). */
6193 @Column(name = "creation_timestamp")
6294 @JsonProperty("creation_timestamp")
6395 private Date creationTimestamp;
6496
65
- // We don't include the referenced entities to limit the size of each row at
66
- // // the listing
97
+ // ----------------------- Relationships ---------------------------
98
+
99
+
100
+ /**
101
+ * License types attached to this application (ignored in default JSON to keep listings small).
102
+ *
103
+ * We don't include the referenced entities to limit the size of each row at the listing
104
+ */
67105 @JsonIgnore
68106 @OneToMany(fetch = FetchType.LAZY, mappedBy = "application")
69107 private Set<LicenseType> licenseTypes;
70108
109
+ /**
110
+ * Metadata key/value entries for this application.
111
+ */
71112 @OneToMany(fetch = FetchType.LAZY, cascade = { CascadeType.PERSIST, CascadeType.REMOVE, CascadeType.REFRESH }, mappedBy = "application")
72113 @JsonManagedReference
73114 private Set<ApplicationMetadata> metadata;
74115
116
+ /**
117
+ * Users that have access/relationship with this application (ignored in JSON listings).
118
+ */
75119 @JsonIgnore
76120 // We don't include the users to limit the size of each row a the listing
77121 @ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.REMOVE)
....@@ -80,73 +124,180 @@
80124 inverseJoinColumns = { @JoinColumn(name = "username", referencedColumnName = "username") })
81125 private Set<User> users;
82126
127
+ // ------------------------------------------------------------------
128
+ // Getters & setters
129
+ // ------------------------------------------------------------------
130
+
131
+ /**
132
+ * getId<p>
133
+ * Return the primary key.
134
+ *
135
+ * @return id
136
+ */
83137 public Integer getId() {
84138 return id;
85139 }
86140
141
+ /**
142
+ * setId<p>
143
+ * Set the primary key
144
+ *
145
+ * @param id
146
+ */
87147 public void setId(Integer id) {
88148 this.id = id;
89149 }
90150
151
+ /**
152
+ * getName<p>
153
+ * Get the name
154
+ *
155
+ * @return name
156
+ */
91157 public String getName() {
92158 return name;
93159 }
94160
161
+ /**
162
+ * setName<p>
163
+ * Set the name
164
+ *
165
+ * @param name
166
+ */
95167 public void setName(String name) {
96168 this.name = name;
97169 }
98170
171
+ /**
172
+ * getDescription<p>
173
+ * Get the description
174
+ *
175
+ * @return description
176
+ */
99177 public String getDescription() {
100178 return description;
101179 }
102180
181
+ /**
182
+ * setDescription<p>
183
+ * Set the description
184
+ *
185
+ * @param description
186
+ */
103187 public void setDescription(String description) {
104188 this.description = description;
105189 }
106190
191
+ /**
192
+ * getCreationTimestamp<p>
193
+ * Get the creation timestamp
194
+ *
195
+ * @return creationTimestamp
196
+ */
107197 public Date getCreationTimestamp() {
108198 return creationTimestamp;
109199 }
110200
201
+ /**
202
+ * setCreationTimestamp<p>
203
+ * Set the creation timestamp
204
+ *
205
+ * @param creationTimestamp
206
+ */
111207 public void setCreationTimestamp(Date creationTimestamp) {
112208 this.creationTimestamp = creationTimestamp;
113209 }
114210
211
+ /**
212
+ * getApplicationMetadata<p>
213
+ * Set the application metadata
214
+ *
215
+ * @return appMetadata
216
+ */
115217 @JsonProperty("metadata")
116218 public Set<ApplicationMetadata> getApplicationMetadata() {
117219 return metadata;
118220 }
119221
222
+ /**
223
+ * setApplicationMetadata<p>
224
+ * Set the application metadata
225
+ *
226
+ * @param metadata
227
+ */
120228 @JsonProperty("metadata")
121229 public void setApplicationMetadata(Set<ApplicationMetadata> metadata) {
122230 this.metadata = metadata;
123231 }
124232
233
+ /**
234
+ * getLicenseFilename<p>
235
+ * Get the license file name
236
+ *
237
+ * @return licenseFilename
238
+ */
125239 public String getLicenseFilename() {
126240 return licenseFilename;
127241 }
128242
243
+ /**
244
+ * setLicenseFilename<p>
245
+ * Set the license file name
246
+ *
247
+ * @param licenseFilename
248
+ */
129249 public void setLicenseFilename(String licenseFilename) {
130250 this.licenseFilename = licenseFilename;
131251 }
132252
253
+ /**
254
+ * getLicenseTypes<p>
255
+ * Get the license types
256
+ *
257
+ * @return licenseTypes
258
+ */
133259 public Set<LicenseType> getLicenseTypes() {
134260 LOG.info("Getting list license types!!!!");
135261 return licenseTypes;
136262 }
137263
264
+ /**
265
+ * setLicenseTypes<p>
266
+ * Set the license types
267
+ *
268
+ * @param licenseTypes
269
+ */
138270 public void setLicenseTypes(Set<LicenseType> licenseTypes) {
139271 this.licenseTypes = licenseTypes;
140272 }
141273
274
+ /**
275
+ * getCode<p>
276
+ * Get the application code
277
+ *
278
+ * @return code
279
+ */
142280 public String getCode() {
143281 return code;
144282 }
145283
284
+ /**
285
+ * setCode<p>
286
+ * Set the application code
287
+ *
288
+ * @param code
289
+ */
146290 public void setCode(String code) {
147291 this.code = code;
148292 }
149293
294
+ /**
295
+ * equals<p>
296
+ * Compares the current object with the given object
297
+ *
298
+ * @param object
299
+ * @return isEquals
300
+ */
150301 @Override
151302 public boolean equals(Object obj) {
152303 if (!(obj instanceof Application))
....@@ -155,6 +306,12 @@
155306 return id.equals(other.id);
156307 }
157308
309
+ /**
310
+ * hashCode<p>
311
+ * Get the object hashCode
312
+ *
313
+ * @param hashCode
314
+ */
158315 @Override
159316 public int hashCode() {
160317
securis/src/main/java/net/curisit/securis/db/ApplicationMetadata.java
....@@ -1,3 +1,6 @@
1
+/*
2
+* Copyright @ 2013 CurisTEC, S.A.S. All Rights Reserved.
3
+*/
14 package net.curisit.securis.db;
25
36 import java.io.Serializable;
....@@ -26,97 +29,179 @@
2629 import net.curisit.securis.db.common.Metadata;
2730
2831 /**
29
- * Entity implementation class for Entity: application_metadata
30
- *
31
- */
32
+* ApplicationMetadata
33
+* <p>
34
+* Single metadata entry (key/value/mandatory) attached to an {@link Application}.
35
+* Uses a composite PK: (application_id, key).
36
+* <p>
37
+* Mapping details:
38
+* - Table: application_metadata
39
+* - PK: application_id + key (two @Id fields).
40
+* - application: @ManyToOne with @JsonBackReference to avoid JSON cycles.
41
+* - creation_timestamp exposed as "creation_timestamp".
42
+*
43
+* @author JRA
44
+* Last reviewed by JRA on Oct 7, 2025.
45
+*/
3246 @JsonAutoDetect
3347 @JsonInclude(Include.NON_NULL)
3448 @Entity
3549 @Table(name = "application_metadata")
3650 @JsonIgnoreProperties(ignoreUnknown = true)
37
-@NamedQueries({ @NamedQuery(name = "list-application-metadata", query = "SELECT a FROM ApplicationMetadata a where a.application.id = :applicationId") })
51
+@NamedQueries({
52
+ @NamedQuery(name = "list-application-metadata",
53
+ query = "SELECT a FROM ApplicationMetadata a where a.application.id = :applicationId")
54
+})
3855 public class ApplicationMetadata implements Serializable, Metadata {
3956
40
- private static final Logger LOG = LogManager.getLogger(ApplicationMetadata.class);
57
+ private static final Logger LOG = LogManager.getLogger(ApplicationMetadata.class);
4158
42
- private static final long serialVersionUID = 1L;
59
+ private static final long serialVersionUID = 1L;
4360
44
- @Id
45
- @ManyToOne
46
- @JoinColumn(name = "application_id")
47
- @JsonBackReference
48
- private Application application;
61
+ /** Part of PK: owning application. */
62
+ @Id
63
+ @ManyToOne
64
+ @JoinColumn(name = "application_id")
65
+ @JsonBackReference
66
+ private Application application;
4967
50
- @Id
51
- @Column(name = "\"key\"")
52
- private String key;
68
+ /** Part of PK: metadata key (quoted column name). */
69
+ @Id
70
+ @Column(name = "\"key\"")
71
+ private String key;
5372
54
- private String value;
73
+ /** Arbitrary metadata value. */
74
+ private String value;
5575
56
- private boolean mandatory;
76
+ /** Whether this key is required for the parent application. */
77
+ private boolean mandatory;
5778
58
- @Column(name = "creation_timestamp")
59
- @JsonProperty("creation_timestamp")
60
- private Date creationTimestamp;
79
+ /** Server-side creation timestamp. */
80
+ @Column(name = "creation_timestamp")
81
+ @JsonProperty("creation_timestamp")
82
+ private Date creationTimestamp;
6183
62
- public String getKey() {
63
- return key;
64
- }
84
+ // ---------------------------------------------------------------------
85
+ // Getters & setters
86
+ // ---------------------------------------------------------------------
6587
66
- public void setKey(String key) {
67
- this.key = key;
68
- }
88
+ /**
89
+ * getKey<p>
90
+ * Get the metadata key (PK part).
91
+ *
92
+ * @return key
93
+ */
94
+ public String getKey() { return key; }
6995
70
- public Application getApplication() {
71
- LOG.info("Getting application from app metadata: {}", application);
72
- return application;
73
- }
96
+ /**
97
+ * setKey<p>
98
+ * Set the metadata key (PK part).
99
+ *
100
+ * @param key
101
+ */
102
+ public void setKey(String key) { this.key = key; }
74103
75
- public void setApplication(Application application) {
76
- this.application = application;
77
- }
104
+ /**
105
+ * getApplication<p>
106
+ * Get the owning application.
107
+ *
108
+ * @return application
109
+ */
110
+ public Application getApplication() {
111
+ LOG.info("Getting application from app metadata: {}", application);
112
+ return application;
113
+ }
78114
79
- public Date getCreationTimestamp() {
80
- return creationTimestamp;
81
- }
115
+ /**
116
+ * setApplication<p>
117
+ * Set the owning application (PK part).
118
+ *
119
+ * @param application
120
+ */
121
+ public void setApplication(Application application) { this.application = application; }
82122
83
- public void setCreationTimestamp(Date creationTimestamp) {
84
- this.creationTimestamp = creationTimestamp;
85
- }
123
+ /**
124
+ * getCreationTimestamp<p>
125
+ * Get the creation timestamp.
126
+ *
127
+ * @return creationTimestamp
128
+ */
129
+ public Date getCreationTimestamp() { return creationTimestamp; }
86130
87
- public String getValue() {
88
- return value;
89
- }
131
+ /**
132
+ * setCreationTimestamp<p>
133
+ * Set the creation timestamp.
134
+ *
135
+ * @param creationTimestamp
136
+ */
137
+ public void setCreationTimestamp(Date creationTimestamp) { this.creationTimestamp = creationTimestamp; }
90138
91
- public void setValue(String value) {
92
- this.value = value;
93
- }
139
+ /**
140
+ * getValue<p>
141
+ * Get the metadata value.
142
+ *
143
+ * @return value
144
+ */
145
+ public String getValue() { return value; }
94146
95
- public boolean isMandatory() {
96
- return mandatory;
97
- }
147
+ /**
148
+ * setValue<p>
149
+ * Set the metadata value.
150
+ *
151
+ * @param value
152
+ */
153
+ public void setValue(String value) { this.value = value; }
98154
99
- public void setMandatory(boolean mandatory) {
100
- this.mandatory = mandatory;
101
- }
155
+ /**
156
+ * isMandatory<p>
157
+ * Whether this entry is required.
158
+ *
159
+ * @return mandatory
160
+ */
161
+ public boolean isMandatory() { return mandatory; }
102162
103
- @Override
104
- public String toString() {
163
+ /**
164
+ * setMandatory<p>
165
+ * Mark this entry as required or optional.
166
+ *
167
+ * @param mandatory
168
+ */
169
+ public void setMandatory(boolean mandatory) { this.mandatory = mandatory; }
105170
106
- return String.format("AppMd (%s: %s)", this.key, value);
107
- }
171
+ // ---------------------------------------------------------------------
172
+ // Object methods
173
+ // ---------------------------------------------------------------------
108174
109
- @Override
110
- public boolean equals(Object obj) {
111
- if (!(obj instanceof ApplicationMetadata))
112
- return false;
113
- ApplicationMetadata other = (ApplicationMetadata) obj;
114
- return Objects.equals(key, other.key) && Objects.equals(application, other.application);
115
- }
175
+ /**
176
+ * toString<p>
177
+ * Get the string describing the current object
178
+ *
179
+ * @return object string
180
+ */
181
+ @Override
182
+ public String toString() { return String.format("AppMd (%s: %s)", this.key, value); }
116183
117
- @Override
118
- public int hashCode() {
119
- return Objects.hash(key, application);
120
- }
184
+ /**
185
+ * equals<p>
186
+ * Compare the current object with the given object
187
+ *
188
+ * @param object
189
+ * @return isEquals
190
+ */
191
+ @Override
192
+ public boolean equals(Object obj) {
193
+ if (!(obj instanceof ApplicationMetadata)) return false;
194
+ ApplicationMetadata other = (ApplicationMetadata) obj;
195
+ return Objects.equals(key, other.key) && Objects.equals(application, other.application);
196
+ }
121197
198
+ /**
199
+ * hashCode<p>
200
+ * Get the object hashCode
201
+ *
202
+ * @return hashCode
203
+ */
204
+ @Override
205
+ public int hashCode() { return Objects.hash(key, application); }
122206 }
207
+
securis/src/main/java/net/curisit/securis/db/BlockedRequest.java
....@@ -1,3 +1,6 @@
1
+/*
2
+* Copyright @ 2013 CurisTEC, S.A.S. All Rights Reserved.
3
+*/
14 package net.curisit.securis.db;
25
36 import java.io.Serializable;
....@@ -21,9 +24,20 @@
2124 import com.fasterxml.jackson.annotation.JsonProperty;
2225
2326 /**
24
- * Entity implementation class for Entity: pack
25
- *
26
- */
27
+* BlockedRequest
28
+* <p>
29
+* Persistent record marking a request (by hash) as blocked.
30
+* Primary key is the SHA-256 of the original request_data.
31
+* Useful to avoid replay/duplicate processing.
32
+*
33
+* Mapping details:
34
+* - Table: blocked_request
35
+* - PK: hash (SHA-256(request_data))
36
+* - Optional relation 'blockedBy' for auditing.
37
+*
38
+* @author JRA
39
+* Last reviewed by JRA on Oct 7, 2025.
40
+*/
2741 @JsonAutoDetect
2842 @JsonInclude(Include.NON_NULL)
2943 @Entity
....@@ -33,69 +47,126 @@
3347
3448 private static final long serialVersionUID = 1L;
3549
50
+ /** Unique SHA-256 hash of {@link #requestData}. */
3651 @Id
3752 private String hash;
3853
54
+ /** Original request payload. */
3955 @Column(name = "request_data")
4056 @JsonProperty("request_data")
4157 private String requestData;
4258
59
+ /** Server-side creation timestamp. */
4360 @Column(name = "creation_timestamp")
4461 @JsonProperty("creation_timestamp")
4562 private Date creationTimestamp;
4663
64
+ /** User who blocked this request (optional, auditing). */
4765 @JsonIgnore
4866 @ManyToOne
4967 @JoinColumn(name = "blocked_by")
5068 private User blockedBy;
5169
52
- public Date getCreationTimestamp() {
53
- return creationTimestamp;
54
- }
70
+ // ---------------------------------------------------------------------
71
+ // Getters & setters
72
+ // ---------------------------------------------------------------------
5573
56
- public void setCreationTimestamp(Date creationTimestamp) {
57
- this.creationTimestamp = creationTimestamp;
58
- }
74
+ /**
75
+ * getCreationTimestamp<p>
76
+ * Get the creation timestamp.
77
+ *
78
+ * @return creationTimestamp
79
+ */
80
+ public Date getCreationTimestamp() { return creationTimestamp; }
5981
82
+ /**
83
+ * setCreationTimestamp<p>
84
+ * Set the creation timestamp.
85
+ *
86
+ * @param creationTimestamp
87
+ */
88
+ public void setCreationTimestamp(Date creationTimestamp) { this.creationTimestamp = creationTimestamp; }
89
+
90
+ /**
91
+ * equals<p>
92
+ * Identity based on primary key (hash).
93
+ */
6094 @Override
6195 public boolean equals(Object obj) {
62
- if (!(obj instanceof BlockedRequest))
63
- return false;
96
+ if (!(obj instanceof BlockedRequest)) return false;
6497 BlockedRequest other = (BlockedRequest) obj;
65
- return (hash == null && other.hash == null) || hash.equals(other.hash);
98
+ return (hash == null && other.hash == null) || (hash != null && hash.equals(other.hash));
6699 }
67100
101
+ /**
102
+ * hashCode<p>
103
+ * Hash based on primary key (hash).
104
+ */
68105 @Override
69
- public int hashCode() {
106
+ public int hashCode() { return (hash == null ? 0 : hash.hashCode()); }
70107
71
- return (hash == null ? 0 : hash.hashCode());
72
- }
108
+ /**
109
+ * getRequestData<p>
110
+ * Get the original serialized request data.
111
+ *
112
+ * @return requestData
113
+ */
114
+ public String getRequestData() { return requestData; }
73115
74
- public String getRequestData() {
75
- return requestData;
76
- }
77
-
116
+ /**
117
+ * setRequestData<p>
118
+ * Set the original request data and recompute the PK hash immediately.
119
+ * Hash is computed as SHA-256 over the request string.
120
+ *
121
+ * @param requestData
122
+ */
78123 public void setRequestData(String requestData) {
79124 this.requestData = requestData;
80125 this.hash = generateHash(this.requestData);
81126 }
82127
83
- public User getBlockedBy() {
84
- return blockedBy;
85
- }
128
+ /**
129
+ * getBlockedBy<p>
130
+ * Return the user who blocked this request (if any).
131
+ *
132
+ * @return blockedBy
133
+ */
134
+ public User getBlockedBy() { return blockedBy; }
86135
87
- public void setBlockedBy(User blockedBy) {
88
- this.blockedBy = blockedBy;
89
- }
136
+ /**
137
+ * setBlockedBy<p>
138
+ * Set the user who blocked this request.
139
+ *
140
+ * @param blockedBy
141
+ */
142
+ public void setBlockedBy(User blockedBy) { this.blockedBy = blockedBy; }
90143
144
+ // ---------------------------------------------------------------------
145
+ // Static helpers
146
+ // ---------------------------------------------------------------------
147
+
148
+ /**
149
+ * generateHash<p>
150
+ * Compute the SHA-256 hex string for the given request data.
151
+ *
152
+ * @param reqData
153
+ * @return sha256(reqData) or null if reqData is null
154
+ */
91155 public static String generateHash(String reqData) {
92156 return (reqData != null ? Utils.sha256(reqData) : null);
93157 }
94158
159
+ /**
160
+ * isRequestBlocked<p>
161
+ * Check if a request payload is blocked by looking up its hash as the PK.
162
+ *
163
+ * @param requestData original payload
164
+ * @param em JPA entity manager
165
+ * @return true if an entry exists with the same hash
166
+ */
95167 public static boolean isRequestBlocked(String requestData, EntityManager em) {
96168 String hash = generateHash(requestData);
97169 BlockedRequest br = em.find(BlockedRequest.class, hash);
98170 return br != null;
99171 }
100
-
101172 }
securis/src/main/java/net/curisit/securis/db/License.java
....@@ -1,3 +1,6 @@
1
+/*
2
+* Copyright @ 2013 CurisTEC, S.A.S. All Rights Reserved.
3
+*/
14 package net.curisit.securis.db;
25
36 import java.io.Serializable;
....@@ -41,438 +44,677 @@
4144 import net.curisit.securis.services.exception.SeCurisServiceException;
4245
4346 /**
44
- * Entity implementation class for Entity: license
45
- *
46
- */
47
+* License
48
+* <p>
49
+* Main license entity. Contains identity, ownership, timestamps and payload fields.
50
+* Includes convenience JSON properties for related IDs/names.
51
+*
52
+* Mapping details:
53
+* - Table: license
54
+* - Listeners: CreationTimestampListener, ModificationTimestampListener
55
+* - Named queries: license-by-code, license-by-activation-code, last-code-suffix-used-in-pack, ...
56
+* - Status column uses custom Hibernate type: net.curisit.securis.db.common.LicenseStatusType
57
+*
58
+* @author JRA
59
+* Last reviewed by JRA on Oct 5, 2025.
60
+*/
4761 @JsonAutoDetect
4862 @JsonInclude(Include.NON_NULL)
4963 @Entity
5064 @EntityListeners({ CreationTimestampListener.class, ModificationTimestampListener.class })
5165 @Table(name = "license")
5266 @JsonIgnoreProperties(ignoreUnknown = true)
53
-@NamedQueries({ @NamedQuery(name = "license-by-code", query = "SELECT l FROM License l where l.code = :code"),
54
- @NamedQuery(name = "license-by-activation-code", query = "SELECT l FROM License l where l.activationCode = :activationCode"),
55
- @NamedQuery(name = "last-code-suffix-used-in-pack", query = "SELECT max(l.codeSuffix) FROM License l where l.pack.id = :packId"),
56
- @NamedQuery(name = "list-licenses-by-pack", query = "SELECT l FROM License l where l.pack.id = :packId"),
57
- @NamedQuery(name = "list-licenses-by-req-data", query = "SELECT l FROM License l where l.reqDataHash = :hash"),
58
- @NamedQuery(name = "list-active-licenses-by-req-data", query = "SELECT l FROM License l where l.reqDataHash = :hash and l.status in ('AC', 'PA')"),
59
- @NamedQuery(name = "list-valid-licenses-by-req-data", query = "SELECT l FROM License l where l.reqDataHash = :hash and l.status in ('RE', 'AC', 'PA')")
60
-
67
+@NamedQueries({
68
+ @NamedQuery(name = "license-by-code", query = "SELECT l FROM License l where l.code = :code"),
69
+ @NamedQuery(name = "license-by-activation-code", query = "SELECT l FROM License l where l.activationCode = :activationCode"),
70
+ @NamedQuery(name = "last-code-suffix-used-in-pack", query = "SELECT max(l.codeSuffix) FROM License l where l.pack.id = :packId"),
71
+ @NamedQuery(name = "list-licenses-by-pack", query = "SELECT l FROM License l where l.pack.id = :packId"),
72
+ @NamedQuery(name = "list-licenses-by-req-data", query = "SELECT l FROM License l where l.reqDataHash = :hash"),
73
+ @NamedQuery(name = "list-active-licenses-by-req-data", query = "SELECT l FROM License l where l.reqDataHash = :hash and l.status in ('AC', 'PA')"),
74
+ @NamedQuery(name = "list-valid-licenses-by-req-data", query = "SELECT l FROM License l where l.reqDataHash = :hash and l.status in ('RE', 'AC', 'PA')")
6175 })
6276 public class License implements CreationTimestampEntity, ModificationTimestampEntity, Serializable {
6377
64
- private static final long serialVersionUID = 2700310404904877227L;
78
+ private static final long serialVersionUID = 2700310404904877227L;
6579
66
- private static final Logger LOG = LogManager.getLogger(License.class);
80
+ private static final Logger LOG = LogManager.getLogger(License.class);
6781
68
- @Id
69
- @GeneratedValue
70
- private Integer id;
82
+ // ------------------------------------------------------------------
83
+ // Columns & relations
84
+ // ------------------------------------------------------------------
7185
72
- private String code;
86
+ @Id
87
+ @GeneratedValue
88
+ private Integer id;
7389
74
- @Column(name = "metadata_obsolete")
75
- @JsonProperty("metadata_obsolete")
76
- private Boolean metadataObsolete;
90
+ private String code;
7791
78
- @Column(name = "activation_code")
79
- @JsonProperty("activation_code")
80
- private String activationCode;
92
+ @Column(name = "metadata_obsolete")
93
+ @JsonProperty("metadata_obsolete")
94
+ private Boolean metadataObsolete;
8195
82
- @Column(name = "code_suffix")
83
- @JsonProperty("code_suffix")
84
- private Integer codeSuffix;
96
+ @Column(name = "activation_code")
97
+ @JsonProperty("activation_code")
98
+ private String activationCode;
8599
86
- @JsonIgnore
87
- @ManyToOne
88
- @JoinColumn(name = "pack_id")
89
- private Pack pack;
100
+ @Column(name = "code_suffix")
101
+ @JsonProperty("code_suffix")
102
+ private Integer codeSuffix;
90103
91
- @JsonIgnore
92
- @ManyToOne
93
- @JoinColumn(name = "created_by")
94
- private User createdBy;
104
+ @JsonIgnore
105
+ @ManyToOne
106
+ @JoinColumn(name = "pack_id")
107
+ private Pack pack;
95108
96
- @JsonIgnore
97
- @ManyToOne
98
- @JoinColumn(name = "cancelled_by")
99
- private User cancelledBy;
109
+ @JsonIgnore
110
+ @ManyToOne
111
+ @JoinColumn(name = "created_by")
112
+ private User createdBy;
100113
101
- @Type(type = "net.curisit.securis.db.common.LicenseStatusType")
102
- private LicenseStatus status;
114
+ @JsonIgnore
115
+ @ManyToOne
116
+ @JoinColumn(name = "cancelled_by")
117
+ private User cancelledBy;
103118
104
- @Column(name = "full_name")
105
- @JsonProperty("full_name")
106
- private String fullName;
119
+ @Type(type = "net.curisit.securis.db.common.LicenseStatusType")
120
+ private LicenseStatus status;
107121
108
- private String email;
122
+ @Column(name = "full_name")
123
+ @JsonProperty("full_name")
124
+ private String fullName;
109125
110
- @Column(name = "request_data")
111
- @JsonProperty("request_data")
112
- private String requestData;
126
+ private String email;
113127
114
- /**
115
- * request data hash is automatically set when we use
116
- * {@link License#setRequestData(String)} method
117
- */
118
- @Column(name = "request_data_hash")
119
- @JsonIgnore
120
- private String reqDataHash;
128
+ @Column(name = "request_data")
129
+ @JsonProperty("request_data")
130
+ private String requestData;
121131
122
- @Column(name = "license_data")
123
- @JsonProperty("license_data")
124
- @JsonIgnore
125
- // The license data is sent to user as a separate file, It doesn't need to
126
- // be included as License attribute on browser
127
- private String licenseData;
132
+ /**
133
+ * Request data hash (not serialized). Automatically updated by setRequestData().
134
+ */
135
+ @Column(name = "request_data_hash")
136
+ @JsonIgnore
137
+ private String reqDataHash;
128138
129
- @Column(name = "creation_timestamp")
130
- @JsonProperty("creation_timestamp")
131
- private Date creationTimestamp;
139
+ @Column(name = "license_data")
140
+ @JsonProperty("license_data")
141
+ @JsonIgnore
142
+ // License data is delivered separately (e.g., file download). Not sent in list views.
143
+ private String licenseData;
132144
133
- @Column(name = "modification_timestamp")
134
- @JsonProperty("modification_timestamp")
135
- private Date modificationTimestamp;
145
+ @Column(name = "creation_timestamp")
146
+ @JsonProperty("creation_timestamp")
147
+ private Date creationTimestamp;
136148
137
- @Column(name = "last_access_timestamp")
138
- @JsonProperty("last_access_timestamp")
139
- private Date lastAccessTimestamp;
149
+ @Column(name = "modification_timestamp")
150
+ @JsonProperty("modification_timestamp")
151
+ private Date modificationTimestamp;
140152
141
- @Column(name = "expiration_date")
142
- @JsonProperty("expiration_date")
143
- private Date expirationDate;
153
+ @Column(name = "last_access_timestamp")
154
+ @JsonProperty("last_access_timestamp")
155
+ private Date lastAccessTimestamp;
144156
145
- private String comments;
157
+ @Column(name = "expiration_date")
158
+ @JsonProperty("expiration_date")
159
+ private Date expirationDate;
146160
147
- @OneToMany(fetch = FetchType.LAZY, mappedBy = "license")
148
- @JsonIgnore
149
- private List<LicenseHistory> history;
161
+ private String comments;
150162
151
- public Integer getId() {
152
- return id;
153
- }
163
+ @OneToMany(fetch = FetchType.LAZY, mappedBy = "license")
164
+ @JsonIgnore
165
+ private List<LicenseHistory> history;
154166
155
- public String getCode() {
156
- return code;
157
- }
167
+ // ------------------------------------------------------------------
168
+ // Basic accessors
169
+ // ------------------------------------------------------------------
158170
159
- public void setCode(String code) {
160
- this.code = code;
161
- }
171
+ /**
172
+ * getId<p>
173
+ * Return primary key.
174
+ *
175
+ * @return id
176
+ */
177
+ public Integer getId() { return id; }
162178
163
- @Override
164
- public Date getCreationTimestamp() {
165
- return creationTimestamp;
166
- }
179
+ /**
180
+ * getCode<p>
181
+ * Return human-readable license code.
182
+ *
183
+ * @return code
184
+ */
185
+ public String getCode() { return code; }
167186
168
- @Override
169
- public void setCreationTimestamp(Date creationTimestamp) {
170
- this.creationTimestamp = creationTimestamp;
171
- }
187
+ /**
188
+ * setCode<p>
189
+ * Set human-readable license code.
190
+ *
191
+ * @param code
192
+ */
193
+ public void setCode(String code) { this.code = code; }
172194
173
- public User getCreatedBy() {
174
- return createdBy;
175
- }
195
+ /**
196
+ * getCreationTimestamp<p>
197
+ * Required by CreationTimestampEntity.
198
+ *
199
+ * @return creationTimestamp
200
+ */
201
+ @Override
202
+ public Date getCreationTimestamp() { return creationTimestamp; }
176203
177
- public void setCreatedBy(User createdBy) {
178
- this.createdBy = createdBy;
179
- }
204
+ /**
205
+ * setCreationTimestamp<p>
206
+ * Set creation timestamp.
207
+ *
208
+ * @param creationTimestamp
209
+ */
210
+ @Override
211
+ public void setCreationTimestamp(Date creationTimestamp) { this.creationTimestamp = creationTimestamp; }
180212
181
- public Pack getPack() {
182
- return pack;
183
- }
213
+ /**
214
+ * getCreatedBy<p>
215
+ * Return creator user (entity).
216
+ *
217
+ * @return user
218
+ */
219
+ public User getCreatedBy() { return createdBy; }
184220
185
- public void setPack(Pack pack) {
186
- this.pack = pack;
187
- }
221
+ /**
222
+ * setCreatedBy<p>
223
+ * Set creator user (entity).
224
+ *
225
+ * @param user
226
+ */
227
+ public void setCreatedBy(User createdBy) { this.createdBy = createdBy; }
188228
189
- @JsonProperty("created_by_id")
190
- public String getCreatedById() {
191
- return createdBy == null ? null : createdBy.getUsername();
192
- }
229
+ /**
230
+ * getPack<p>
231
+ * Return owning pack.
232
+ *
233
+ * @return pack
234
+ */
235
+ public Pack getPack() { return pack; }
193236
194
- @JsonProperty("created_by_id")
195
- public void setCreatedById(String username) {
196
- if (username == null) {
197
- createdBy = null;
198
- } else {
199
- createdBy = new User();
200
- createdBy.setUsername(username);
201
- }
202
- }
237
+ /**
238
+ * setPack<p>
239
+ * Set owning pack.
240
+ *
241
+ * @param pack
242
+ */
243
+ public void setPack(Pack pack) { this.pack = pack; }
203244
204
- @JsonProperty("cancelled_by_id")
205
- public String getCancelledById() {
206
- return cancelledBy == null ? null : cancelledBy.getUsername();
207
- }
245
+ /**
246
+ * getCreatedById<p>
247
+ * Expose creator username as JSON.
248
+ *
249
+ * @return username
250
+ */
251
+ @JsonProperty("created_by_id")
252
+ public String getCreatedById() { return createdBy == null ? null : createdBy.getUsername(); }
208253
209
- @JsonProperty("cancelled_by_id")
210
- public void setCancelledById(String username) {
211
- if (username == null) {
212
- cancelledBy = null;
213
- } else {
214
- cancelledBy = new User();
215
- cancelledBy.setUsername(username);
216
- }
217
- }
254
+ /**
255
+ * setCreatedById<p>
256
+ * Setter by username for JSON binding.
257
+ *
258
+ * @param username
259
+ */
260
+ @JsonProperty("created_by_id")
261
+ public void setCreatedById(String username) {
262
+ if (username == null) {
263
+ createdBy = null;
264
+ } else {
265
+ createdBy = new User();
266
+ createdBy.setUsername(username);
267
+ }
268
+ }
218269
219
- @JsonProperty("pack_code")
220
- public String getPackCode() {
221
- return pack == null ? null : pack.getCode();
222
- }
270
+ /**
271
+ * getCancelledById<p>
272
+ * Expose cancelling user username as JSON.
273
+ *
274
+ * @return username
275
+ */
276
+ @JsonProperty("cancelled_by_id")
277
+ public String getCancelledById() { return cancelledBy == null ? null : cancelledBy.getUsername(); }
223278
224
- @JsonProperty("pack_id")
225
- public Integer getPackId() {
226
- return pack == null ? null : pack.getId();
227
- }
279
+ /**
280
+ * setCancelledById<p>
281
+ * Setter by username for JSON binding.
282
+ *
283
+ * @param username
284
+ */
285
+ @JsonProperty("cancelled_by_id")
286
+ public void setCancelledById(String username) {
287
+ if (username == null) {
288
+ cancelledBy = null;
289
+ } else {
290
+ cancelledBy = new User();
291
+ cancelledBy.setUsername(username);
292
+ }
293
+ }
228294
229
- @JsonProperty("pack_id")
230
- public void setPackId(Integer idPack) {
231
- if (idPack == null) {
232
- pack = null;
233
- } else {
234
- pack = new Pack();
235
- pack.setId(idPack);
236
- }
237
- }
295
+ /**
296
+ * getPackCode<p>
297
+ * Expose pack code for convenience.
298
+ *
299
+ * @return packCode
300
+ */
301
+ @JsonProperty("pack_code")
302
+ public String getPackCode() { return pack == null ? null : pack.getCode(); }
238303
239
- public LicenseStatus getStatus() {
240
- return status;
241
- }
304
+ /**
305
+ * getPackId<p>
306
+ * Expose pack id for convenience.
307
+ *
308
+ * @return packId
309
+ */
310
+ @JsonProperty("pack_id")
311
+ public Integer getPackId() { return pack == null ? null : pack.getId(); }
242312
243
- public void setStatus(LicenseStatus status) {
244
- this.status = status;
245
- }
313
+ /**
314
+ * setPackId<p>
315
+ * Setter by id for JSON binding (creates a shallow Pack).
316
+ *
317
+ * @param packId
318
+ */
319
+ @JsonProperty("pack_id")
320
+ public void setPackId(Integer idPack) {
321
+ if (idPack == null) {
322
+ pack = null;
323
+ } else {
324
+ pack = new Pack();
325
+ pack.setId(idPack);
326
+ }
327
+ }
246328
247
- @Override
248
- public Date getModificationTimestamp() {
249
- return modificationTimestamp;
250
- }
329
+ /**
330
+ * getStatus<p>
331
+ * Return license status.
332
+ *
333
+ * @return licenseStatus
334
+ */
335
+ public LicenseStatus getStatus() { return status; }
251336
252
- @Override
253
- public void setModificationTimestamp(Date modificationTimestamp) {
254
- this.modificationTimestamp = modificationTimestamp;
255
- }
337
+ /**
338
+ * setStatus<p>
339
+ * Set license status.
340
+ *
341
+ * @param status
342
+ */
343
+ public void setStatus(LicenseStatus status) { this.status = status; }
256344
257
- public String getFullName() {
258
- return fullName;
259
- }
345
+ /**
346
+ * getModificationTimestamp<p>
347
+ * Required by ModificationTimestampEntity.
348
+ *
349
+ * @return modificationTimestamp
350
+ */
351
+ @Override
352
+ public Date getModificationTimestamp() { return modificationTimestamp; }
260353
261
- public void setFullName(String fullName) {
262
- this.fullName = fullName;
263
- }
354
+ /**
355
+ * setModificationTimestamp<p>
356
+ * Set modification timestamp.
357
+ *
358
+ * @param modificationTimestamp
359
+ */
360
+ @Override
361
+ public void setModificationTimestamp(Date modificationTimestamp) { this.modificationTimestamp = modificationTimestamp; }
264362
265
- public String getEmail() {
266
- return email;
267
- }
363
+ /**
364
+ * getFullName<p>
365
+ * Return license holder full name.
366
+ *
367
+ * @return name
368
+ */
369
+ public String getFullName() { return fullName; }
268370
269
- public void setEmail(String email) {
270
- this.email = email;
271
- }
371
+ /**
372
+ * setFullName<p>
373
+ * Set license holder full name.
374
+ *
375
+ * @param name
376
+ */
377
+ public void setFullName(String fullName) { this.fullName = fullName; }
272378
273
- public void setId(Integer id) {
274
- this.id = id;
275
- }
379
+ /**
380
+ * getEmail<p>
381
+ * Return email address.
382
+ *
383
+ * @return email
384
+ */
385
+ public String getEmail() { return email; }
276386
277
- public User getCancelledBy() {
278
- return cancelledBy;
279
- }
387
+ /**
388
+ * setEmail<p>
389
+ * Set email address.
390
+ *
391
+ * @param email
392
+ */
393
+ public void setEmail(String email) { this.email = email; }
394
+
395
+ /**
396
+ * setId<p>
397
+ * Set primary key (rarely used).
398
+ *
399
+ * @param id
400
+ */
401
+ public void setId(Integer id) { this.id = id; }
402
+
403
+ /**
404
+ * getCancelledBy<p>
405
+ * Return cancelling user (entity).
406
+ *
407
+ * @param user
408
+ */
409
+ public User getCancelledBy() { return cancelledBy; }
410
+
411
+ /**
412
+ * setCancelledBy<p>
413
+ * Set cancelling user (entity).
414
+ *
415
+ * @param cancelledBy
416
+ */
417
+ public void setCancelledBy(User cancelledBy) { this.cancelledBy = cancelledBy; }
280418
281
- public void setCancelledBy(User cancelledBy) {
282
- this.cancelledBy = cancelledBy;
283
- }
419
+ /**
420
+ * getLastAccessTimestamp<p>
421
+ * Return last access timestamp.
422
+ *
423
+ * @return lastAccessTimestamp
424
+ */
425
+ public Date getLastAccessTimestamp() { return lastAccessTimestamp; }
284426
285
- public Date getLastAccessTimestamp() {
286
- return lastAccessTimestamp;
287
- }
427
+ /**
428
+ * setLastAccessTimestamp<p>
429
+ * Set last access timestamp.
430
+ *
431
+ * @param lastAccessTimestamp
432
+ */
433
+ public void setLastAccessTimestamp(Date lastAccessTimestamp) { this.lastAccessTimestamp = lastAccessTimestamp; }
288434
289
- public void setLastAccessTimestamp(Date lastAccessTimestamp) {
290
- this.lastAccessTimestamp = lastAccessTimestamp;
291
- }
435
+ /**
436
+ * getRequestData<p>
437
+ * Return raw request data.
438
+ *
439
+ * @return requestData
440
+ */
441
+ public String getRequestData() { return requestData; }
292442
293
- public String getRequestData() {
294
- return requestData;
295
- }
443
+ /**
444
+ * setRequestData<p>
445
+ * Set raw request data and recompute {@link #reqDataHash} immediately using
446
+ * the same hashing strategy as BlockedRequest (SHA-256).
447
+ *
448
+ * @param requestData
449
+ */
450
+ public void setRequestData(String requestData) {
451
+ this.requestData = requestData;
452
+ this.reqDataHash = BlockedRequest.generateHash(this.requestData);
453
+ }
296454
297
- public void setRequestData(String requestData) {
298
- this.requestData = requestData;
299
- this.reqDataHash = BlockedRequest.generateHash(this.requestData);
300
- }
455
+ /**
456
+ * getLicenseData<p>
457
+ * Return opaque license data (not serialized in lists).
458
+ *
459
+ * @return licenseData
460
+ */
461
+ public String getLicenseData() { return licenseData; }
301462
302
- public String getLicenseData() {
303
- return licenseData;
304
- }
463
+ /**
464
+ * setLicenseData<p>
465
+ * Set opaque license data (large content kept server-side).
466
+ *
467
+ * @param licenseDate
468
+ */
469
+ public void setLicenseData(String licenseData) { this.licenseData = licenseData; }
305470
306
- public void setLicenseData(String licenseData) {
307
- this.licenseData = licenseData;
308
- }
471
+ /**
472
+ * getComments<p>
473
+ * Return optional comments.
474
+ *
475
+ * @return comments
476
+ */
477
+ public String getComments() { return comments; }
309478
310
- public String getComments() {
311
- return comments;
312
- }
479
+ /**
480
+ * setComments<p>
481
+ * Set optional comments.
482
+ *
483
+ * @param comments
484
+ */
485
+ public void setComments(String comments) { this.comments = comments; }
313486
314
- public void setComments(String comments) {
315
- this.comments = comments;
316
- }
487
+ /**
488
+ * getHistory<p>
489
+ * Return change history entries (lazy).
490
+ *
491
+ * @return history
492
+ */
493
+ public List<LicenseHistory> getHistory() { return history; }
317494
318
- public List<LicenseHistory> getHistory() {
319
- return history;
320
- }
495
+ /**
496
+ * setHistory<p>
497
+ * Set change history entries.
498
+ *
499
+ * @param history
500
+ */
501
+ public void setHistory(List<LicenseHistory> history) { this.history = history; }
321502
322
- public void setHistory(List<LicenseHistory> history) {
323
- this.history = history;
324
- }
503
+ /**
504
+ * getExpirationDate<p>
505
+ * Return expiration date (nullable).
506
+ *
507
+ * @return expirationDate
508
+ */
509
+ public Date getExpirationDate() { return expirationDate; }
325510
326
- public Date getExpirationDate() {
327
- return expirationDate;
328
- }
511
+ /**
512
+ * setExpirationDate<p>
513
+ * Set expiration date (nullable).
514
+ *
515
+ * @param expirationDate
516
+ */
517
+ public void setExpirationDate(Date expirationDate) { this.expirationDate = expirationDate; }
329518
330
- public void setExpirationDate(Date expirationDate) {
331
- this.expirationDate = expirationDate;
332
- }
519
+ /**
520
+ * getReqDataHash<p>
521
+ * Return cached hash of request data (not exposed in JSON).
522
+ *
523
+ * @return reqDataHash
524
+ */
525
+ public String getReqDataHash() { return reqDataHash; }
333526
334
- public String getReqDataHash() {
335
- return reqDataHash;
336
- }
527
+ /**
528
+ * getCodeSuffix<p>
529
+ * Return numeric suffix of the code.
530
+ *
531
+ * @return codeSuffix
532
+ */
533
+ public Integer getCodeSuffix() { return codeSuffix; }
337534
338
- public static class Action {
339
- public static final int CREATE = 1;
340
- public static final int REQUEST = 2;
341
- public static final int ACTIVATION = 3;
342
- public static final int SEND = 4;
343
- public static final int DOWNLOAD = 5;
344
- public static final int CANCEL = 6;
345
- public static final int DELETE = 7;
346
- public static final int BLOCK = 8;
347
- public static final int UNBLOCK = 9;
348
- }
535
+ /**
536
+ * setCodeSuffix<p>
537
+ * Set numeric suffix of the code.
538
+ *
539
+ * @param codeSuffix
540
+ */
541
+ public void setCodeSuffix(Integer codeSuffix) { this.codeSuffix = codeSuffix; }
349542
350
- public static class Status {
543
+ /**
544
+ * getActivationCode<p>
545
+ * Return activation code.
546
+ *
547
+ * @return activationCode
548
+ */
549
+ public String getActivationCode() { return activationCode; }
351550
352
- private static final Map<Integer, List<LicenseStatus>> transitions = Utils.createMap( //
353
- Action.REQUEST, Arrays.asList(LicenseStatus.CREATED, LicenseStatus.REQUESTED), //
354
- Action.ACTIVATION, Arrays.asList(LicenseStatus.CREATED, LicenseStatus.REQUESTED, LicenseStatus.PRE_ACTIVE, LicenseStatus.EXPIRED), //
355
- Action.SEND, Arrays.asList(LicenseStatus.ACTIVE, LicenseStatus.PRE_ACTIVE), //
356
- Action.DOWNLOAD, Arrays.asList(LicenseStatus.ACTIVE, LicenseStatus.PRE_ACTIVE), //
357
- Action.CANCEL, Arrays.asList(LicenseStatus.ACTIVE, LicenseStatus.PRE_ACTIVE, LicenseStatus.REQUESTED, LicenseStatus.EXPIRED), //
358
- Action.DELETE, Arrays.asList(LicenseStatus.CANCELLED, LicenseStatus.CREATED, LicenseStatus.BLOCKED), //
359
- Action.UNBLOCK, Arrays.asList(LicenseStatus.BLOCKED), //
360
- Action.BLOCK, Arrays.asList(LicenseStatus.CANCELLED) //
361
- );
551
+ /**
552
+ * setActivationCode<p>
553
+ * Set activation code.
554
+ *
555
+ * @param activationCode
556
+ */
557
+ public void setActivationCode(String activationCode) { this.activationCode = activationCode; }
362558
363
- /**
364
- * It checks if a given action is valid for the License, passing the
365
- * action and the current license status
366
- *
367
- * @param oldStatus
368
- * @param newStatus
369
- * @return
370
- */
371
- public static boolean isActionValid(Integer action, LicenseStatus currentStatus) {
372
- List<LicenseStatus> validStatuses = transitions.get(action);
373
- LOG.info("Action {} is valid ? => {} current: {} OK? {}", action, validStatuses, currentStatus, validStatuses.contains(currentStatus));
374
- return validStatuses != null && validStatuses.contains(currentStatus);
375
- }
376
- }
559
+ /**
560
+ * isMetadataObsolete<p>
561
+ * Convenience Boolean → primitive with null-safe false.
562
+ *
563
+ * @return isMetadataObsolete
564
+ */
565
+ public boolean isMetadataObsolete() { return metadataObsolete != null && metadataObsolete; }
377566
378
- /**
379
- * Return licenses with status: REquested, ACtive, Pre-Active for a given
380
- * request data
381
- *
382
- * @param requestData
383
- * @param em
384
- * @return
385
- * @throws SeCurisServiceException
386
- */
387
- public static License findValidLicenseByRequestData(String requestData, EntityManager em) throws SeCurisServiceException {
388
- TypedQuery<License> query = em.createNamedQuery("list-valid-licenses-by-req-data", License.class);
389
- query.setParameter("hash", BlockedRequest.generateHash(requestData));
390
- try {
391
- List<License> list = query.getResultList();
392
- if (list.size() == 0) {
393
- return null;
394
- }
395
- if (list.size() > 1) {
396
- LOG.error("There are more than 1 active or requested license for request data: {}\nHash: {}", requestData, BlockedRequest.generateHash(requestData));
397
- }
398
- return list.get(0);
399
- } catch (NoResultException e) {
400
- // There is no license for request data
401
- return null;
402
- }
403
- }
567
+ /**
568
+ * setMetadataObsolete<p>
569
+ * Set metadata obsolete flag (nullable wrapper).
570
+ *
571
+ * @param obsolete
572
+ */
573
+ public void setMetadataObsolete(Boolean obsolete) { this.metadataObsolete = obsolete; }
404574
405
- /**
406
- * Return licenses with status: REquested, ACtive, Pre-Active for a given
407
- * request data
408
- *
409
- * @param requestData
410
- * @param em
411
- * @return
412
- * @throws SeCurisServiceException
413
- */
414
- public static License findLicenseByActivationCode(String activationCode, EntityManager em) throws SeCurisServiceException {
415
- TypedQuery<License> query = em.createNamedQuery("license-by-activation-code", License.class);
416
- query.setParameter("activationCode", activationCode);
417
- try {
418
- return query.getSingleResult();
419
- } catch (NoResultException e) {
420
- // There is no license for request data
421
- return null;
422
- }
423
- }
575
+ // ------------------------------------------------------------------
576
+ // Status transitions helpers
577
+ // ------------------------------------------------------------------
424578
425
- public static License findActiveLicenseByRequestData(String requestData, EntityManager em) throws SeCurisServiceException {
426
- TypedQuery<License> query = em.createNamedQuery("list-active-licenses-by-req-data", License.class);
427
- query.setParameter("hash", BlockedRequest.generateHash(requestData));
428
- try {
429
- List<License> list = query.getResultList();
430
- if (list.size() == 0) {
431
- return null;
432
- }
433
- if (list.size() > 1) {
434
- LOG.error("There are more than 1 active license for request data: {}\nHash: {}", requestData, BlockedRequest.generateHash(requestData));
435
- }
436
- return list.get(0);
437
- } catch (NoResultException e) {
438
- // There is no license for request data
439
- return null;
440
- }
441
- }
579
+ /**
580
+ * Action<p>
581
+ * Actions to take with the license
582
+ */
583
+ public static class Action {
584
+ public static final int CREATE = 1;
585
+ public static final int REQUEST = 2;
586
+ public static final int ACTIVATION = 3;
587
+ public static final int SEND = 4;
588
+ public static final int DOWNLOAD = 5;
589
+ public static final int CANCEL = 6;
590
+ public static final int DELETE = 7;
591
+ public static final int BLOCK = 8;
592
+ public static final int UNBLOCK = 9;
593
+ }
442594
443
- public static License findLicenseByCode(String code, EntityManager em) throws SeCurisServiceException {
444
- TypedQuery<License> query = em.createNamedQuery("license-by-code", License.class);
445
- query.setParameter("code", code);
446
- try {
447
- return query.getSingleResult();
448
- } catch (NoResultException e) {
449
- // There is no license for request data
450
- return null;
451
- }
452
- }
595
+ /**
596
+ * Status<p>
597
+ * Status of the requested license
598
+ */
599
+ public static class Status {
453600
454
- public Integer getCodeSuffix() {
455
- return codeSuffix;
456
- }
601
+ private static final Map<Integer, List<LicenseStatus>> transitions = Utils.createMap(
602
+ Action.REQUEST, Arrays.asList(LicenseStatus.CREATED, LicenseStatus.REQUESTED),
603
+ Action.ACTIVATION,Arrays.asList(LicenseStatus.CREATED, LicenseStatus.REQUESTED, LicenseStatus.PRE_ACTIVE, LicenseStatus.EXPIRED),
604
+ Action.SEND, Arrays.asList(LicenseStatus.ACTIVE, LicenseStatus.PRE_ACTIVE),
605
+ Action.DOWNLOAD, Arrays.asList(LicenseStatus.ACTIVE, LicenseStatus.PRE_ACTIVE),
606
+ Action.CANCEL, Arrays.asList(LicenseStatus.ACTIVE, LicenseStatus.PRE_ACTIVE, LicenseStatus.REQUESTED, LicenseStatus.EXPIRED),
607
+ Action.DELETE, Arrays.asList(LicenseStatus.CANCELLED, LicenseStatus.CREATED, LicenseStatus.BLOCKED),
608
+ Action.UNBLOCK, Arrays.asList(LicenseStatus.BLOCKED),
609
+ Action.BLOCK, Arrays.asList(LicenseStatus.CANCELLED)
610
+ );
457611
458
- public void setCodeSuffix(Integer codeSuffix) {
459
- this.codeSuffix = codeSuffix;
460
- }
612
+ /**
613
+ * isActionValid<p>
614
+ * Check whether an action is valid given the current license status.
615
+ *
616
+ * @param action action constant from {@link Action}
617
+ * @param currentStatus current license status
618
+ * @return true if allowed
619
+ */
620
+ public static boolean isActionValid(Integer action, LicenseStatus currentStatus) {
621
+ List<LicenseStatus> validStatuses = transitions.get(action);
622
+ LOG.info("Action {} is valid ? => {} current: {} OK? {}", action, validStatuses, currentStatus,
623
+ validStatuses != null && validStatuses.contains(currentStatus));
624
+ return validStatuses != null && validStatuses.contains(currentStatus);
625
+ }
626
+ }
461627
462
- public String getActivationCode() {
463
- return activationCode;
464
- }
628
+ // ------------------------------------------------------------------
629
+ // Repository helpers (static queries)
630
+ // ------------------------------------------------------------------
465631
466
- public void setActivationCode(String activationCode) {
467
- this.activationCode = activationCode;
468
- }
632
+ /**
633
+ * findValidLicenseByRequestData<p>
634
+ * Return the first license in statuses RE/AC/PA for the given request data hash.
635
+ *
636
+ * @param requestData raw request data
637
+ * @param em entity manager
638
+ * @return matching license or null
639
+ * @throws SeCurisServiceException
640
+ */
641
+ public static License findValidLicenseByRequestData(String requestData, EntityManager em) throws SeCurisServiceException {
642
+ TypedQuery<License> query = em.createNamedQuery("list-valid-licenses-by-req-data", License.class);
643
+ query.setParameter("hash", BlockedRequest.generateHash(requestData));
644
+ try {
645
+ List<License> list = query.getResultList();
646
+ if (list.size() == 0) return null;
647
+ if (list.size() > 1) {
648
+ LOG.error("There are more than 1 active or requested license for request data: {}\nHash: {}",
649
+ requestData, BlockedRequest.generateHash(requestData));
650
+ }
651
+ return list.get(0);
652
+ } catch (NoResultException e) {
653
+ return null;
654
+ }
655
+ }
469656
470
- public boolean isMetadataObsolete() {
471
- return metadataObsolete != null && metadataObsolete;
472
- }
657
+ /**
658
+ * findLicenseByActivationCode<p>
659
+ * Retrieve a license by its activation code.
660
+ *
661
+ * @param activationCode
662
+ * @param entityManager
663
+ * @return license
664
+ * @throws SeCurisServiceException
665
+ */
666
+ public static License findLicenseByActivationCode(String activationCode, EntityManager em) throws SeCurisServiceException {
667
+ TypedQuery<License> query = em.createNamedQuery("license-by-activation-code", License.class);
668
+ query.setParameter("activationCode", activationCode);
669
+ try {
670
+ return query.getSingleResult();
671
+ } catch (NoResultException e) {
672
+ return null;
673
+ }
674
+ }
473675
474
- public void setMetadataObsolete(Boolean obsolete) {
475
- this.metadataObsolete = obsolete;
476
- }
676
+ /**
677
+ * findActiveLicenseByRequestData<p>
678
+ * Return the first AC/PA license for a given requestData.
679
+ *
680
+ * @param requestData
681
+ * @param entityManager
682
+ * @return license
683
+ * @throws SeCurisServiceException
684
+ */
685
+ public static License findActiveLicenseByRequestData(String requestData, EntityManager em) throws SeCurisServiceException {
686
+ TypedQuery<License> query = em.createNamedQuery("list-active-licenses-by-req-data", License.class);
687
+ query.setParameter("hash", BlockedRequest.generateHash(requestData));
688
+ try {
689
+ List<License> list = query.getResultList();
690
+ if (list.size() == 0) return null;
691
+ if (list.size() > 1) {
692
+ LOG.error("There are more than 1 active license for request data: {}\nHash: {}",
693
+ requestData, BlockedRequest.generateHash(requestData));
694
+ }
695
+ return list.get(0);
696
+ } catch (NoResultException e) {
697
+ return null;
698
+ }
699
+ }
477700
701
+ /**
702
+ * findLicenseByCode<p>
703
+ * Retrieve a license by human-readable code.
704
+ *
705
+ * @param code
706
+ * @param entityManager
707
+ * @return license
708
+ * @throws SeCurisServiceException
709
+ */
710
+ public static License findLicenseByCode(String code, EntityManager em) throws SeCurisServiceException {
711
+ TypedQuery<License> query = em.createNamedQuery("license-by-code", License.class);
712
+ query.setParameter("code", code);
713
+ try {
714
+ return query.getSingleResult();
715
+ } catch (NoResultException e) {
716
+ return null;
717
+ }
718
+ }
478719 }
720
+
securis/src/main/java/net/curisit/securis/db/LicenseHistory.java
....@@ -1,3 +1,6 @@
1
+/*
2
+* Copyright @ 2013 CurisTEC, S.A.S. All Rights Reserved.
3
+*/
14 package net.curisit.securis.db;
25
36 import java.io.Serializable;
....@@ -21,16 +24,26 @@
2124 import com.fasterxml.jackson.annotation.JsonProperty;
2225
2326 /**
24
- * Entity implementation class for Entity: license
25
- *
26
- */
27
+* LicenseHistory
28
+* <p>
29
+* Audit trail entries for a given license (who/what/when).
30
+*
31
+* Mapping details:
32
+* - Table: license_history
33
+* - Many-to-one to License and User (ignored in JSON).
34
+* - NamedQuery: list-license-history by license id.
35
+*
36
+* @author JRA
37
+* Last reviewed by JRA on Oct 5, 2025.
38
+*/
2739 @JsonAutoDetect
2840 @JsonInclude(Include.NON_NULL)
2941 @Entity
3042 @Table(name = "license_history")
3143 @JsonIgnoreProperties(ignoreUnknown = true)
3244 @NamedQueries({
33
- @NamedQuery(name = "list-license-history", query = "SELECT lh FROM LicenseHistory lh where lh.license.id = :licId")
45
+ @NamedQuery(name = "list-license-history",
46
+ query = "SELECT lh FROM LicenseHistory lh where lh.license.id = :licId")
3447 })
3548 public class LicenseHistory implements Serializable {
3649
....@@ -57,59 +70,117 @@
5770 @JsonProperty("creation_timestamp")
5871 private Date creationTimestamp;
5972
60
- public int getId() {
61
- return id;
62
- }
73
+ // ---------------- Getters & setters ----------------
6374
64
- public License getLicense() {
65
- return license;
66
- }
75
+ /**
76
+ * getId<p>
77
+ * Return primary key.
78
+ *
79
+ * @return id
80
+ */
81
+ public int getId() { return id; }
6782
68
- public void setLicense(License license) {
69
- this.license = license;
70
- }
83
+ /**
84
+ * getLicense<p>
85
+ * Return parent license (entity).
86
+ *
87
+ * @return license
88
+ */
89
+ public License getLicense() { return license; }
7190
72
- public User getUser() {
73
- return user;
74
- }
91
+ /**
92
+ * setLicense<p>
93
+ * Set parent license (entity).
94
+ *
95
+ * @return license
96
+ */
97
+ public void setLicense(License license) { this.license = license; }
7598
99
+ /**
100
+ * getUser<p>
101
+ * Return actor user (entity).
102
+ *
103
+ * @return user
104
+ */
105
+ public User getUser() { return user; }
106
+
107
+ /**
108
+ * getUsername<p>
109
+ * Expose username for JSON.
110
+ *
111
+ * @return username
112
+ */
76113 @JsonProperty("username")
77
- public String getUsername() {
78
- return user == null ? null : user.getUsername();
79
- }
114
+ public String getUsername() { return user == null ? null : user.getUsername(); }
80115
81
- public void setUser(User user) {
82
- this.user = user;
83
- }
116
+ /**
117
+ * setUser<p>
118
+ * Set actor user (entity).
119
+ *
120
+ * @param user
121
+ */
122
+ public void setUser(User user) { this.user = user; }
84123
85
- public String getAction() {
86
- return action;
87
- }
124
+ /**
125
+ * getAction<p>
126
+ * Return action key (e.g., "activate").
127
+ *
128
+ * @return action
129
+ */
130
+ public String getAction() { return action; }
88131
89
- public void setAction(String action) {
90
- this.action = action;
91
- }
132
+ /**
133
+ * setAction<p>
134
+ * Set action key.
135
+ *
136
+ * @param action
137
+ */
138
+ public void setAction(String action) { this.action = action; }
92139
93
- public String getComments() {
94
- return comments;
95
- }
140
+ /**
141
+ * getComments<p>
142
+ * Return optional comments.
143
+ *
144
+ * @return comments
145
+ */
146
+ public String getComments() { return comments; }
96147
97
- public void setComments(String comments) {
98
- this.comments = comments;
99
- }
148
+ /**
149
+ * setComments<p>
150
+ * Set optional comments.
151
+ *
152
+ * @param comments
153
+ */
154
+ public void setComments(String comments) { this.comments = comments; }
100155
101
- public void setId(int id) {
102
- this.id = id;
103
- }
156
+ /**
157
+ * setId<p>
158
+ * Set primary key (normally framework-managed)
159
+ *
160
+ * @param id.
161
+ */
162
+ public void setId(int id) { this.id = id; }
104163
105
- public Date getCreationTimestamp() {
106
- return creationTimestamp;
107
- }
164
+ /**
165
+ * getCreationTimestamp<p>
166
+ * Return timestamp.
167
+ *
168
+ * @return creationTimestamp
169
+ */
170
+ public Date getCreationTimestamp() { return creationTimestamp; }
108171
109
- public void setCreationTimestamp(Date creationTimestamp) {
110
- this.creationTimestamp = creationTimestamp;
111
- }
172
+ /**
173
+ * setCreationTimestamp<p>
174
+ * Set timestamp.
175
+ *
176
+ * @param creationTimestamp
177
+ */
178
+ public void setCreationTimestamp(Date creationTimestamp) { this.creationTimestamp = creationTimestamp; }
112179
180
+ /**
181
+ * Actions<p>
182
+ * Catalog of action names.
183
+ */
113184 public static class Actions {
114185 public static final String CREATE = "creation";
115186 public static final String ADD_REQUEST = "request";
....@@ -125,3 +196,4 @@
125196 public static final String DELETE = "delete";
126197 }
127198 }
199
+
securis/src/main/java/net/curisit/securis/db/LicenseStatus.java
....@@ -1,3 +1,6 @@
1
+/*
2
+* Copyright @ 2013 CurisTEC, S.A.S. All Rights Reserved.
3
+*/
14 package net.curisit.securis.db;
25
36 import net.curisit.securis.db.common.CodedEnum;
....@@ -6,42 +9,64 @@
69 import com.fasterxml.jackson.annotation.JsonValue;
710
811 /**
9
- * Contains the possible license statuses. For further details:
10
- * https://redmine.curistec.com/projects/securis/wiki/LicensesServerManagement
11
- *
12
- * @author rob
13
- */
12
+* LicenseStatus
13
+* <p>
14
+* Enumerates the possible license states. JSON code/value is the short code (CR, RE, AC, ...).
15
+* See: https://redmine.curistec.com/projects/securis/wiki/LicensesServerManagement
16
+*
17
+* @author JRA
18
+* Last reviewed by JRA on Oct 5, 2025.
19
+*/
1420 public enum LicenseStatus implements CodedEnum {
1521 CREATED("CR"), REQUESTED("RE"), ACTIVE("AC"), PRE_ACTIVE("PA"), EXPIRED("EX"), CANCELLED("CA"), BLOCKED("BL");
1622
1723 private final String code;
1824
19
- LicenseStatus(String code) {
20
- this.code = code;
21
- }
25
+ /**
26
+ * LicenseStatus<p>
27
+ * Constructor
28
+ *
29
+ * @param code
30
+ */
31
+ LicenseStatus(String code) { this.code = code; }
2232
23
- @Override
24
- public String getCode() {
25
- return code;
26
- }
33
+ /**
34
+ * getCode<p>
35
+ * Return the short code used in DB/JSON.
36
+ *
37
+ * @return code
38
+ */
39
+ @Override public String getCode() { return code; }
2740
41
+ /**
42
+ * valueFromCode<p>
43
+ * Factory from code string (null on unknown).
44
+ *
45
+ * @param code
46
+ */
2847 @JsonCreator
2948 public static LicenseStatus valueFromCode(String code) {
3049 for (LicenseStatus ps : LicenseStatus.values()) {
31
- if (ps.code.equals(code)) {
32
- return ps;
33
- }
50
+ if (ps.code.equals(code)) return ps;
3451 }
3552 return null;
3653 }
3754
55
+ /**
56
+ * getName<p>
57
+ * Expose the code as JSON value.
58
+ *
59
+ * @return name
60
+ */
3861 @JsonValue
39
- public String getName() {
40
- return this.code;
41
- }
62
+ public String getName() { return this.code; }
4263
64
+ /**
65
+ * toString<p>
66
+ * Get the string describing the current object
67
+ *
68
+ * @return object string
69
+ */
4370 @Override
44
- public String toString() {
45
- return code;
46
- }
71
+ public String toString() { return code; }
4772 }
securis/src/main/java/net/curisit/securis/db/LicenseType.java
....@@ -1,3 +1,6 @@
1
+/*
2
+* Copyright @ 2013 CurisTEC, S.A.S. All Rights Reserved.
3
+*/
14 package net.curisit.securis.db;
25
36 import java.io.Serializable;
....@@ -29,135 +32,236 @@
2932 import com.fasterxml.jackson.annotation.JsonProperty;
3033
3134 /**
32
- * Entity implementation class for Entity: license_type
33
- *
34
- */
35
+* LicenseType
36
+* <p>
37
+* Describes a license category within an application. Owns metadata entries.
38
+*
39
+* Mapping details:
40
+* - Table: license_type
41
+* - Many-to-one to Application (lazy).
42
+* - One-to-many metadata with cascade PERSIST/REMOVE/REFRESH.
43
+* - Named queries for listing and filtering by application(s).
44
+*
45
+* @author JRA
46
+* Last reviewed by JRA on Oct 5, 2025.
47
+*/
3548 @JsonAutoDetect
3649 @JsonInclude(Include.NON_NULL)
3750 @JsonIgnoreProperties(ignoreUnknown = true)
3851 @Entity
3952 @Table(name = "license_type")
40
-@NamedQueries({ @NamedQuery(name = "list-license_types", query = "SELECT lt FROM LicenseType lt"),
41
- @NamedQuery(name = "list-license_types-by_apps-id", query = "SELECT lt FROM LicenseType lt where lt.application.id in :list_ids"),
42
- @NamedQuery(name = "list-application-license_types", query = "SELECT lt FROM LicenseType lt where lt.application.id = :appId") })
53
+@NamedQueries({
54
+ @NamedQuery(name = "list-license_types", query = "SELECT lt FROM LicenseType lt"),
55
+ @NamedQuery(name = "list-license_types-by_apps-id", query = "SELECT lt FROM LicenseType lt where lt.application.id in :list_ids"),
56
+ @NamedQuery(name = "list-application-license_types", query = "SELECT lt FROM LicenseType lt where lt.application.id = :appId")
57
+})
4358 public class LicenseType implements Serializable {
4459
45
- @SuppressWarnings("unused")
46
- private static final Logger LOG = LogManager.getLogger(LicenseType.class);
47
- private static final long serialVersionUID = 1L;
60
+ @SuppressWarnings("unused")
61
+ private static final Logger LOG = LogManager.getLogger(LicenseType.class);
62
+ private static final long serialVersionUID = 1L;
4863
49
- @Id
50
- @GeneratedValue
51
- private Integer id;
64
+ @Id
65
+ @GeneratedValue
66
+ private Integer id;
5267
53
- private String code;
54
- private String name;
55
- private String description;
68
+ private String code;
69
+ private String name;
70
+ private String description;
5671
57
- @Column(name = "creation_timestamp")
58
- @JsonProperty("creation_timestamp")
59
- private Date creationTimestamp;
72
+ @Column(name = "creation_timestamp")
73
+ @JsonProperty("creation_timestamp")
74
+ private Date creationTimestamp;
6075
61
- @JsonIgnore
62
- @ManyToOne(fetch = FetchType.LAZY)
63
- @JoinColumn(name = "application_id")
64
- private Application application;
76
+ @JsonIgnore
77
+ @ManyToOne(fetch = FetchType.LAZY)
78
+ @JoinColumn(name = "application_id")
79
+ private Application application;
6580
66
- @OneToMany(fetch = FetchType.LAZY, cascade = { CascadeType.PERSIST, CascadeType.REMOVE, CascadeType.REFRESH }, mappedBy = "licenseType")
67
- @JsonManagedReference
68
- private Set<LicenseTypeMetadata> metadata;
81
+ @OneToMany(fetch = FetchType.LAZY, cascade = { CascadeType.PERSIST, CascadeType.REMOVE, CascadeType.REFRESH }, mappedBy = "licenseType")
82
+ @JsonManagedReference
83
+ private Set<LicenseTypeMetadata> metadata;
6984
70
- public Set<LicenseTypeMetadata> getMetadata() {
71
- return metadata;
72
- }
85
+ // ---------------- Getters & setters ----------------
7386
74
- public void setMetadata(Set<LicenseTypeMetadata> metadata) {
75
- this.metadata = metadata;
76
- }
87
+ /**
88
+ * getMetadata<p>
89
+ * Return associated metadata entries.
90
+ *
91
+ * @return metadata
92
+ */
93
+ public Set<LicenseTypeMetadata> getMetadata() { return metadata; }
7794
78
- public Integer getId() {
79
- return id;
80
- }
95
+ /**
96
+ * setMetadata<p>
97
+ * Set associated metadata entries.
98
+ *
99
+ * @param metadata
100
+ */
101
+ public void setMetadata(Set<LicenseTypeMetadata> metadata) { this.metadata = metadata; }
81102
82
- public void setId(Integer id) {
83
- this.id = id;
84
- }
103
+ /**
104
+ * getId<p>
105
+ * Return primary key.
106
+ *
107
+ * @return id
108
+ */
109
+ public Integer getId() { return id; }
85110
86
- public String getName() {
87
- return name;
88
- }
111
+ /**
112
+ * setId<p>
113
+ * Set primary key.
114
+ *
115
+ * @param id
116
+ */
117
+ public void setId(Integer id) { this.id = id; }
89118
90
- public void setName(String name) {
91
- this.name = name;
92
- }
119
+ /**
120
+ * getName<p>
121
+ * Return display name.
122
+ *
123
+ * @return name
124
+ */
125
+ public String getName() { return name; }
93126
94
- public String getDescription() {
95
- return description;
96
- }
127
+ /**
128
+ * setName<p>
129
+ * Set display name.
130
+ *
131
+ * @param name
132
+ */
133
+ public void setName(String name) { this.name = name; }
97134
98
- public void setDescription(String description) {
99
- this.description = description;
100
- }
135
+ /**
136
+ * getDescription<p>
137
+ * Return description.
138
+ *
139
+ * @return description
140
+ */
141
+ public String getDescription() { return description; }
101142
102
- public String getCode() {
103
- return code;
104
- }
143
+ /**
144
+ * setDescription<p>
145
+ * Set description.
146
+ *
147
+ * @param description
148
+ */
149
+ public void setDescription(String description) { this.description = description; }
105150
106
- public void setCode(String code) {
107
- this.code = code;
108
- }
151
+ /**
152
+ * getCode<p>
153
+ * Return short code.
154
+ *
155
+ * @return code
156
+ */
157
+ public String getCode() { return code; }
109158
110
- public Application getApplication() {
111
- return application;
112
- }
159
+ /**
160
+ * setCode<p>
161
+ * Set short code.
162
+ *
163
+ * @param code
164
+ */
165
+ public void setCode(String code) { this.code = code; }
113166
114
- @JsonProperty("application_name")
115
- public String getApplicationName() {
116
- return application == null ? null : application.getName();
117
- }
167
+ /**
168
+ * getApplication<p>
169
+ * Return owning application (entity).
170
+ *
171
+ * @return application
172
+ */
173
+ public Application getApplication() { return application; }
118174
119
- @JsonProperty("application_id")
120
- public Integer getApplicationId() {
121
- return application == null ? null : application.getId();
122
- }
175
+ /**
176
+ * getApplicationName<p>
177
+ * Expose app name for JSON.
178
+ *
179
+ * @return appName
180
+ */
181
+ @JsonProperty("application_name")
182
+ public String getApplicationName() { return application == null ? null : application.getName(); }
123183
124
- @JsonProperty("application_id")
125
- public void setApplicationId(Integer appId) {
126
- if (appId == null) {
127
- application = null;
128
- } else {
129
- application = new Application();
130
- application.setId(appId);
131
- }
132
- }
184
+ /**
185
+ * getApplicationId<p>
186
+ * Expose app id for JSON.
187
+ *
188
+ * @return appId
189
+ */
190
+ @JsonProperty("application_id")
191
+ public Integer getApplicationId() { return application == null ? null : application.getId(); }
133192
134
- public void setApplication(Application application) {
135
- this.application = application;
136
- }
193
+ /**
194
+ * setApplicationId<p>
195
+ * Setter by id for JSON binding (creates shallow Application).
196
+ *
197
+ * @param appId
198
+ */
199
+ @JsonProperty("application_id")
200
+ public void setApplicationId(Integer appId) {
201
+ if (appId == null) {
202
+ application = null;
203
+ } else {
204
+ application = new Application();
205
+ application.setId(appId);
206
+ }
207
+ }
137208
138
- public Date getCreationTimestamp() {
139
- return creationTimestamp;
140
- }
209
+ /**
210
+ * setApplication<p>
211
+ * Set owning application (entity).
212
+ *
213
+ * @param application
214
+ */
215
+ public void setApplication(Application application) { this.application = application; }
141216
142
- public void setCreationTimestamp(Date creationTimestamp) {
143
- this.creationTimestamp = creationTimestamp;
144
- }
217
+ /**
218
+ * getCreationTimestamp<p>
219
+ * Return creation timestamp.
220
+ *
221
+ * @return creationTimestamp
222
+ */
223
+ public Date getCreationTimestamp() { return creationTimestamp; }
145224
146
- @Override
147
- public boolean equals(Object obj) {
148
- if (!(obj instanceof LicenseType))
149
- return false;
150
- LicenseType other = (LicenseType) obj;
151
- return id.equals(other.id);
152
- }
225
+ /**
226
+ * setCreationTimestamp<p>
227
+ * Set creation timestamp.
228
+ *
229
+ * @param creationTimestamp
230
+ */
231
+ public void setCreationTimestamp(Date creationTimestamp) { this.creationTimestamp = creationTimestamp; }
153232
154
- @Override
155
- public int hashCode() {
156
- return (id == null ? 0 : id.hashCode());
157
- }
233
+ // ---------------- Object methods ----------------
158234
159
- @Override
160
- public String toString() {
161
- return String.format("LT: ID: %d, code: %s", id, code);
162
- }
235
+ /**
236
+ * equals<p>
237
+ * Compare the current object with the given object
238
+ *
239
+ * @param object
240
+ * @return isEquals
241
+ */
242
+ @Override
243
+ public boolean equals(Object obj) {
244
+ if (!(obj instanceof LicenseType)) return false;
245
+ LicenseType other = (LicenseType) obj;
246
+ return id != null && id.equals(other.id);
247
+ }
248
+
249
+ /**
250
+ * hashCode<p>
251
+ * Get the object hashCode
252
+ *
253
+ * @return hashCode
254
+ */
255
+ @Override
256
+ public int hashCode() { return (id == null ? 0 : id.hashCode()); }
257
+
258
+ /**
259
+ * toString<p>
260
+ * Get the string describing the current object
261
+ *
262
+ * @return object string
263
+ */
264
+ @Override
265
+ public String toString() { return String.format("LT: ID: %d, code: %s", id, code); }
163266 }
267
+
securis/src/main/java/net/curisit/securis/db/LicenseTypeMetadata.java
....@@ -1,3 +1,6 @@
1
+/*
2
+* Copyright @ 2013 CurisTEC, S.A.S. All Rights Reserved.
3
+*/
14 package net.curisit.securis.db;
25
36 import java.io.Serializable;
....@@ -21,80 +24,148 @@
2124 import net.curisit.securis.db.common.Metadata;
2225
2326 /**
24
- * Entity implementation class for Entity: licensetype_metadata
25
- *
26
- */
27
+* LicenseTypeMetadata
28
+* <p>
29
+* Single metadata entry attached to a {@link LicenseType}.
30
+* Composite PK: (license_type_id, key).
31
+*
32
+* Mapping details:
33
+* - Table: licensetype_metadata
34
+* - @JsonBackReference to avoid recursion with LicenseType.metadata.
35
+* - NamedQuery: list-licensetype-metadata by license type id.
36
+*
37
+* @author JRA
38
+* Last reviewed by JRA on Oct 5, 2025.
39
+*/
2740 @JsonAutoDetect
2841 @JsonInclude(Include.NON_NULL)
2942 @Entity
3043 @Table(name = "licensetype_metadata")
3144 @JsonIgnoreProperties(ignoreUnknown = true)
32
-@NamedQueries({ @NamedQuery(name = "list-licensetype-metadata", query = "SELECT a FROM LicenseTypeMetadata a where a.licenseType.id = :licenseTypeId") })
45
+@NamedQueries({
46
+ @NamedQuery(name = "list-licensetype-metadata",
47
+ query = "SELECT a FROM LicenseTypeMetadata a where a.licenseType.id = :licenseTypeId")
48
+})
3349 public class LicenseTypeMetadata implements Serializable, Metadata {
3450
35
- private static final long serialVersionUID = 1L;
51
+ private static final long serialVersionUID = 1L;
3652
37
- @Id
38
- @ManyToOne
39
- @JsonBackReference
40
- @JoinColumn(name = "license_type_id")
41
- private LicenseType licenseType;
53
+ /** PK part: owning license type. */
54
+ @Id
55
+ @ManyToOne
56
+ @JsonBackReference
57
+ @JoinColumn(name = "license_type_id")
58
+ private LicenseType licenseType;
4259
43
- @Id
44
- @Column(name = "\"key\"")
45
- private String key;
60
+ /** PK part: metadata key (quoted). */
61
+ @Id
62
+ @Column(name = "\"key\"")
63
+ private String key;
4664
47
- private String value;
65
+ /** Metadata value. */
66
+ private String value;
4867
49
- private boolean mandatory;
68
+ /** Whether this key is mandatory for the license type. */
69
+ private boolean mandatory;
5070
51
- public LicenseType getLicenseType() {
52
- return licenseType;
53
- }
71
+ // ---------------- Getters & setters ----------------
5472
55
- public void setLicenseType(LicenseType licenseType) {
56
- this.licenseType = licenseType;
57
- }
73
+ /**
74
+ * getLicenseType<p>
75
+ * Return owning license type.
76
+ *
77
+ * @return licenseType
78
+ */
79
+ public LicenseType getLicenseType() { return licenseType; }
5880
59
- public String getValue() {
60
- return value;
61
- }
81
+ /**
82
+ * setLicenseType<p>
83
+ * Set owning license type.
84
+ *
85
+ * @param licenseType
86
+ */
87
+ public void setLicenseType(LicenseType licenseType) { this.licenseType = licenseType; }
6288
63
- public void setValue(String value) {
64
- this.value = value;
65
- }
89
+ /**
90
+ * getValue<p>
91
+ * Return metadata value.
92
+ *
93
+ * @return value
94
+ */
95
+ public String getValue() { return value; }
6696
67
- public String getKey() {
68
- return key;
69
- }
97
+ /**
98
+ * setValue<p>
99
+ * Set metadata value.
100
+ *
101
+ * @param value
102
+ */
103
+ public void setValue(String value) { this.value = value; }
70104
71
- public void setKey(String key) {
72
- this.key = key;
73
- }
105
+ /**
106
+ * getKey<p>
107
+ * Return metadata key (PK part).
108
+ *
109
+ * @return key
110
+ */
111
+ public String getKey() { return key; }
74112
75
- public boolean isMandatory() {
76
- return mandatory;
77
- }
113
+ /**
114
+ * setKey<p>
115
+ * Set metadata key (PK part).
116
+ *
117
+ * @param key
118
+ */
119
+ public void setKey(String key) { this.key = key; }
78120
79
- public void setMandatory(boolean mandatory) {
80
- this.mandatory = mandatory;
81
- }
121
+ /**
122
+ * isMandatory<p>
123
+ * Return whether this entry is required.
124
+ *
125
+ * @return isMandatory
126
+ */
127
+ public boolean isMandatory() { return mandatory; }
82128
83
- @Override
84
- public boolean equals(Object obj) {
85
- if (!(obj instanceof LicenseTypeMetadata))
86
- return false;
87
- LicenseTypeMetadata other = (LicenseTypeMetadata) obj;
88
- return Objects.equals(key, other.key) && Objects.equals(licenseType, other.licenseType);
89
- }
129
+ /**
130
+ * setMandatory<p>
131
+ * Set whether this entry is required.
132
+ *
133
+ * @param mandatory
134
+ */
135
+ public void setMandatory(boolean mandatory) { this.mandatory = mandatory; }
90136
91
- @Override
92
- public int hashCode() {
93
- return Objects.hash(key, licenseType);
94
- }
137
+ // ---------------- Object methods ----------------
95138
96
- @Override
97
- public String toString() {
98
- return String.format("LTMD (%s: %s)", key, value);
99
- }
139
+ /**
140
+ * equals<p>
141
+ * Compare the current object with the given object
142
+ *
143
+ * @param object
144
+ * @return isEquals
145
+ */
146
+ @Override
147
+ public boolean equals(Object obj) {
148
+ if (!(obj instanceof LicenseTypeMetadata)) return false;
149
+ LicenseTypeMetadata other = (LicenseTypeMetadata) obj;
150
+ return Objects.equals(key, other.key) && Objects.equals(licenseType, other.licenseType);
151
+ }
152
+
153
+ /**
154
+ * hashCode<p>
155
+ * Get the object hashCode
156
+ *
157
+ * @return hashCode
158
+ */
159
+ @Override
160
+ public int hashCode() { return Objects.hash(key, licenseType); }
161
+
162
+ /**
163
+ * toString<p>
164
+ * Get the string describing the current object
165
+ *
166
+ * @return object string
167
+ */
168
+ @Override
169
+ public String toString() { return String.format("LTMD (%s: %s)", key, value); }
100170 }
171
+
securis/src/main/java/net/curisit/securis/db/Organization.java
....@@ -1,3 +1,6 @@
1
+/*
2
+* Copyright @ 2013 CurisTEC, S.A.S. All Rights Reserved.
3
+*/
14 package net.curisit.securis.db;
25
36 import java.io.Serializable;
....@@ -32,164 +35,253 @@
3235 import com.fasterxml.jackson.annotation.JsonProperty;
3336
3437 /**
35
- * Entity implementation class for Entity: organization
36
- *
37
- */
38
+* Organization
39
+* <p>
40
+* Represents a customer/tenant organization. Manages parent/children hierarchy
41
+* and user membership.
42
+*
43
+* Mapping details:
44
+* - Table: organization
45
+* - ManyToMany users via user_organization (ignored in default JSON).
46
+* - Self-referencing parent/children relation.
47
+* - Named queries for listing, filtering by ids, and children discovery.
48
+*
49
+* @author JRA
50
+* Last reviewed by JRA on Oct 5, 2025.
51
+*/
3852 @JsonAutoDetect
3953 @JsonInclude(Include.NON_NULL)
4054 @JsonIgnoreProperties(ignoreUnknown = true)
4155 @Entity
4256 @Table(name = "organization")
43
-@NamedQueries({ @NamedQuery(name = "list-organizations", query = "SELECT o FROM Organization o"),
44
- @NamedQuery(name = "list-organizations-by-ids", query = "SELECT o FROM Organization o where id in :list_ids"),
45
- @NamedQuery(name = "find-children-org", query = "SELECT o FROM Organization o where o.parentOrganization = :parentOrganization") })
57
+@NamedQueries({
58
+ @NamedQuery(name = "list-organizations", query = "SELECT o FROM Organization o"),
59
+ @NamedQuery(name = "list-organizations-by-ids", query = "SELECT o FROM Organization o where id in :list_ids"),
60
+ @NamedQuery(name = "find-children-org", query = "SELECT o FROM Organization o where o.parentOrganization = :parentOrganization")
61
+})
4662 public class Organization implements Serializable {
4763
48
- @SuppressWarnings("unused")
49
- private static final Logger LOG = LogManager.getLogger(Organization.class);
64
+ @SuppressWarnings("unused")
65
+ private static final Logger LOG = LogManager.getLogger(Organization.class);
5066
51
- private static final long serialVersionUID = 1L;
67
+ private static final long serialVersionUID = 1L;
5268
53
- @Id
54
- @GeneratedValue
55
- private Integer id;
69
+ @Id
70
+ @GeneratedValue
71
+ private Integer id;
5672
57
- private String code;
58
- private String name;
59
- private String description;
73
+ private String code;
74
+ private String name;
75
+ private String description;
6076
61
- @Column(name = "creation_timestamp")
62
- @JsonProperty("creation_timestamp")
63
- private Date creationTimestamp;
77
+ @Column(name = "creation_timestamp")
78
+ @JsonProperty("creation_timestamp")
79
+ private Date creationTimestamp;
6480
65
- @JsonIgnore
66
- // We don't include the users to limit the size of each row a the listing
67
- @ManyToMany(cascade = CascadeType.REMOVE)
68
- @JoinTable(name = "user_organization", //
69
- joinColumns = { @JoinColumn(name = "organization_id", referencedColumnName = "id") }, //
70
- inverseJoinColumns = { @JoinColumn(name = "username", referencedColumnName = "username") })
71
- private Set<User> users;
81
+ @JsonIgnore
82
+ @ManyToMany(cascade = CascadeType.REMOVE)
83
+ @JoinTable(name = "user_organization",
84
+ joinColumns = { @JoinColumn(name = "organization_id", referencedColumnName = "id") },
85
+ inverseJoinColumns = { @JoinColumn(name = "username", referencedColumnName = "username") })
86
+ private Set<User> users;
7287
73
- @JsonIgnore
74
- // We don't include the users to limit the size of each row a the listing
75
- @ManyToOne
76
- @JoinColumn(name = "org_parent_id")
77
- private Organization parentOrganization;
88
+ @JsonIgnore
89
+ @ManyToOne
90
+ @JoinColumn(name = "org_parent_id")
91
+ private Organization parentOrganization;
7892
79
- @JsonIgnore
80
- // We don't include the users to limit the size of each row a the listing
81
- @OneToMany(fetch = FetchType.LAZY, mappedBy = "parentOrganization")
82
- private Set<Organization> childOrganizations;
93
+ @JsonIgnore
94
+ @OneToMany(fetch = FetchType.LAZY, mappedBy = "parentOrganization")
95
+ private Set<Organization> childOrganizations;
8396
84
- public Integer getId() {
85
- return id;
86
- }
97
+ // ---------------- Getters & setters ----------------
8798
88
- public void setId(Integer id) {
89
- this.id = id;
90
- }
99
+ /**
100
+ * getId<p>
101
+ * Return primary key.
102
+ *
103
+ * @return id
104
+ */
105
+ public Integer getId() { return id; }
91106
92
- public String getName() {
93
- return name;
94
- }
107
+ /**
108
+ * setId<p>
109
+ * Set primary key.
110
+ *
111
+ * @param id
112
+ */
113
+ public void setId(Integer id) { this.id = id; }
95114
96
- public void setName(String name) {
97
- this.name = name;
98
- }
115
+ /**
116
+ * getName<p>
117
+ * Return display name.
118
+ *
119
+ * @return name
120
+ */
121
+ public String getName() { return name; }
99122
100
- public String getDescription() {
101
- return description;
102
- }
123
+ /**
124
+ * setName<p>
125
+ * Set display name.
126
+ *
127
+ * @param name
128
+ */
129
+ public void setName(String name) { this.name = name; }
103130
104
- public void setDescription(String description) {
105
- this.description = description;
106
- }
131
+ /**
132
+ * getDescription<p>
133
+ * Return optional description.
134
+ *
135
+ * @return description
136
+ */
137
+ public String getDescription() { return description; }
107138
108
- public String getCode() {
109
- return code;
110
- }
139
+ /**
140
+ * setDescription<p>
141
+ * Set optional description.
142
+ *
143
+ * @param description
144
+ */
145
+ public void setDescription(String description) { this.description = description; }
111146
112
- public void setCode(String code) {
113
- this.code = code;
114
- }
147
+ /**
148
+ * getCode<p>
149
+ * Return short code.
150
+ *
151
+ * @return code
152
+ */
153
+ public String getCode() { return code; }
115154
116
- public Date getCreationTimestamp() {
117
- return creationTimestamp;
118
- }
155
+ /**
156
+ * setCode<p>
157
+ * Set short code.
158
+ *
159
+ * @param code
160
+ */
161
+ public void setCode(String code) { this.code = code; }
119162
120
- public void setCreationTimestamp(Date creationTimestamp) {
121
- this.creationTimestamp = creationTimestamp;
122
- }
163
+ /**
164
+ * getCreationTimestamp<p>
165
+ * Return creation timestamp.
166
+ *
167
+ * @return creationTimeStamp
168
+ */
169
+ public Date getCreationTimestamp() { return creationTimestamp; }
123170
124
- public Set<User> getUsers() {
125
- return users;
126
- }
171
+ /**
172
+ * setCreationTimestamp<p>
173
+ * Set creation timestamp.
174
+ *
175
+ * @param creationTimestamp
176
+ */
177
+ public void setCreationTimestamp(Date creationTimestamp) { this.creationTimestamp = creationTimestamp; }
127178
128
- public void setUsers(Set<User> users) {
129
- this.users = users;
130
- }
179
+ /**
180
+ * getUsers<p>
181
+ * Return member users (entity set).
182
+ *
183
+ * @return users
184
+ */
185
+ public Set<User> getUsers() { return users; }
131186
132
- public Organization getParentOrganization() {
133
- return parentOrganization;
134
- }
187
+ /**
188
+ * setUsers<p>
189
+ * Set member users.
190
+ *
191
+ * @param users
192
+ */
193
+ public void setUsers(Set<User> users) { this.users = users; }
135194
136
- public void setParentOrganization(Organization parentOrganization) {
137
- this.parentOrganization = parentOrganization;
138
- }
195
+ /**
196
+ * getParentOrganization<p>
197
+ * Return parent org (entity).
198
+ *
199
+ * @return parentOrganization
200
+ */
201
+ public Organization getParentOrganization() { return parentOrganization; }
139202
140
- // Roberto: Following methods are necessary to include in the REST list
141
- // response
142
- // information about the referenced entities.
143
- @JsonProperty("org_parent_id")
144
- public void setParentOrgId(Integer orgId) {
145
- if (orgId != null) {
146
- parentOrganization = new Organization();
147
- parentOrganization.setId(orgId);
148
- } else {
149
- parentOrganization = null;
150
- }
151
- }
203
+ /**
204
+ * setParentOrganization<p>
205
+ * Set parent org (entity).
206
+ *
207
+ * @param parentOrganization
208
+ */
209
+ public void setParentOrganization(Organization parentOrganization) { this.parentOrganization = parentOrganization; }
152210
153
- @JsonProperty("org_parent_id")
154
- public Integer getParentOrgId() {
155
- return parentOrganization == null ? null : parentOrganization.getId();
156
- }
211
+ // JSON helpers for parent organization
157212
158
- @JsonProperty("org_parent_name")
159
- public String getParentOrgName() {
160
- return parentOrganization == null ? null : parentOrganization.getName();
161
- }
213
+ /**
214
+ * setParentOrgId<p>
215
+ * Setter by id (creates shallow Organization).
216
+ *
217
+ * @param orgId
218
+ */
219
+ @JsonProperty("org_parent_id")
220
+ public void setParentOrgId(Integer orgId) {
221
+ if (orgId != null) {
222
+ parentOrganization = new Organization();
223
+ parentOrganization.setId(orgId);
224
+ } else {
225
+ parentOrganization = null;
226
+ }
227
+ }
162228
163
- @JsonProperty("users_ids")
164
- public void setUsersIds(List<String> usersIds) {
165
- users = new HashSet<>();
166
- if (usersIds != null) {
167
- for (String userid : usersIds) {
168
- User u = new User();
169
- u.setUsername(userid);
170
- users.add(u);
171
- }
172
- }
173
- }
229
+ /**
230
+ * getParentOrgId<p>
231
+ * Expose parent org id.
232
+ *
233
+ * @return parentOrgId
234
+ */
235
+ @JsonProperty("org_parent_id")
236
+ public Integer getParentOrgId() { return parentOrganization == null ? null : parentOrganization.getId(); }
174237
175
- @JsonProperty("users_ids")
176
- public Set<String> getUsersIds() {
177
- if (users == null) {
178
- return null;
179
- }
180
- Set<String> ids = new HashSet<>();
181
- for (User user : users) {
182
- ids.add(user.getUsername());
183
- }
184
- return ids;
185
- }
238
+ /**
239
+ * getParentOrgName<p>
240
+ * Expose parent org name.
241
+ *
242
+ * @return parentOrgName
243
+ */
244
+ @JsonProperty("org_parent_name")
245
+ public String getParentOrgName() { return parentOrganization == null ? null : parentOrganization.getName(); }
186246
187
- public Set<Organization> getChildOrganizations() {
188
- return childOrganizations;
189
- }
247
+ // JSON helpers for users
190248
191
- public void setChildOrganizations(Set<Organization> childOrganizations) {
192
- this.childOrganizations = childOrganizations;
193
- }
249
+ /**
250
+ * setUsersIds<p>
251
+ * Replace users set from a list of usernames.
252
+ *
253
+ * @param userId list
254
+ */
255
+ @JsonProperty("users_ids")
256
+ public void setUsersIds(List<String> usersIds) {
257
+ users = new HashSet<>();
258
+ if (usersIds != null) {
259
+ for (String userid : usersIds) {
260
+ User u = new User();
261
+ u.setUsername(userid);
262
+ users.add(u);
263
+ }
264
+ }
265
+ }
194266
267
+ /**
268
+ * getUsersIds<p>
269
+ * Expose member usernames.
270
+ *
271
+ * @return userId list
272
+ */
273
+ @JsonProperty("users_ids")
274
+ public Set<String> getUsersIds() {
275
+ if (users == null) return null;
276
+ Set<String> ids = new HashSet<>();
277
+ for (User user : users) { ids.add(user.getUsername()); }
278
+ return ids;
279
+ }
280
+
281
+ /** getChildOrganizations<p>Return children (entity set). */
282
+ public Set<Organization> getChildOrganizations() { return childOrganizations; }
283
+
284
+ /** setChildOrganizations<p>Set children (entity set). */
285
+ public void setChildOrganizations(Set<Organization> childOrganizations) { this.childOrganizations = childOrganizations; }
195286 }
287
+
securis/src/main/java/net/curisit/securis/db/Pack.java
....@@ -1,3 +1,6 @@
1
+/*
2
+* Copyright @ 2013 CurisTEC, S.A.S. All Rights Reserved.
3
+*/
14 package net.curisit.securis.db;
25
36 import java.io.Serializable;
....@@ -32,379 +35,606 @@
3235 import net.curisit.integrity.commons.Utils;
3336
3437 /**
35
- * Entity implementation class for Entity: pack
36
- *
37
- */
38
+* Pack
39
+* <p>
40
+* Group/bundle of licenses for an organization and application (via LicenseType).
41
+* Tracks capacity, availability, status, and validity windows.
42
+*
43
+* Mapping details:
44
+* - Table: pack
45
+* - ManyToOne to Organization, LicenseType, User (createdBy)
46
+* - OneToMany licenses (lazy)
47
+* - Custom type: net.curisit.securis.db.common.PackStatusType
48
+* - Named queries for listing and filtering by org/app.
49
+*
50
+* @author JRA
51
+* Last reviewed by JRA on Oct 5, 2025.
52
+*/
3853 @JsonAutoDetect
3954 @JsonInclude(Include.NON_NULL)
4055 @Entity
4156 @Table(name = "pack")
4257 @JsonIgnoreProperties(ignoreUnknown = true)
43
-@NamedQueries({ @NamedQuery(name = "list-packs", query = "SELECT pa FROM Pack pa"), //
44
- @NamedQuery(name = "pack-by-code", query = "SELECT pa FROM Pack pa where pa.code = :code"), //
45
- @NamedQuery(name = "list-packs-by-lic-type", query = "SELECT pa FROM Pack pa where pa.licenseType.id = :lt_id"), //
46
- @NamedQuery(name = "list-packs-by-orgs-apps", query = "SELECT pa FROM Pack pa where pa.organization.id in :list_ids_org and pa.licenseType.application.id in :list_ids_app "), //
47
- @NamedQuery(name = "list-packs-by-apps", query = "SELECT pa FROM Pack pa where pa.licenseType.application.id in :list_ids_app ") })
58
+@NamedQueries({
59
+ @NamedQuery(name = "list-packs", query = "SELECT pa FROM Pack pa"),
60
+ @NamedQuery(name = "pack-by-code", query = "SELECT pa FROM Pack pa where pa.code = :code"),
61
+ @NamedQuery(name = "list-packs-by-lic-type", query = "SELECT pa FROM Pack pa where pa.licenseType.id = :lt_id"),
62
+ @NamedQuery(name = "list-packs-by-orgs-apps", query = "SELECT pa FROM Pack pa where pa.organization.id in :list_ids_org and pa.licenseType.application.id in :list_ids_app "),
63
+ @NamedQuery(name = "list-packs-by-apps", query = "SELECT pa FROM Pack pa where pa.licenseType.application.id in :list_ids_app ")
64
+})
4865 public class Pack implements Serializable {
4966
50
- private static final long serialVersionUID = 1L;
67
+ private static final long serialVersionUID = 1L;
5168
52
- @Id
53
- @GeneratedValue
54
- private Integer id;
69
+ @Id
70
+ @GeneratedValue
71
+ private Integer id;
5572
56
- private String code;
73
+ private String code;
74
+ private String comments;
75
+ private Boolean frozen;
5776
58
- private String comments;
77
+ @Column(name = "creation_timestamp")
78
+ @JsonProperty("creation_timestamp")
79
+ private Date creationTimestamp;
5980
60
- private Boolean frozen;
81
+ @JsonIgnore
82
+ @ManyToOne
83
+ @JoinColumn(name = "organization_id")
84
+ private Organization organization;
6185
62
- @Column(name = "creation_timestamp")
63
- @JsonProperty("creation_timestamp")
64
- private Date creationTimestamp;
86
+ @JsonIgnore
87
+ @ManyToOne
88
+ @JoinColumn(name = "license_type_id")
89
+ private LicenseType licenseType;
6590
66
- @JsonIgnore
67
- @ManyToOne
68
- @JoinColumn(name = "organization_id")
69
- private Organization organization;
91
+ @JsonIgnore
92
+ @ManyToOne
93
+ @JoinColumn(name = "created_by")
94
+ private User createdBy;
7095
71
- @JsonIgnore
72
- @ManyToOne
73
- @JoinColumn(name = "license_type_id")
74
- private LicenseType licenseType;
96
+ @JsonIgnore
97
+ @OneToMany(fetch = FetchType.LAZY, cascade = { CascadeType.PERSIST, CascadeType.REMOVE, CascadeType.REFRESH }, mappedBy = "pack")
98
+ private Set<License> licenses;
7599
76
- @JsonIgnore
77
- @ManyToOne
78
- @JoinColumn(name = "created_by")
79
- private User createdBy;
100
+ @Column(name = "num_licenses")
101
+ @JsonProperty("num_licenses")
102
+ private int numLicenses;
80103
81
- @JsonIgnore
82
- @OneToMany(fetch = FetchType.LAZY, cascade = { CascadeType.PERSIST, CascadeType.REMOVE, CascadeType.REFRESH }, mappedBy = "pack")
83
- private Set<License> licenses;
104
+ @Column(name = "init_valid_date")
105
+ @JsonProperty("init_valid_date")
106
+ private Date initValidDate;
84107
85
- @Column(name = "num_licenses")
86
- @JsonProperty("num_licenses")
87
- private int numLicenses;
108
+ @Column(name = "end_valid_date")
109
+ @JsonProperty("end_valid_date")
110
+ private Date endValidDate;
88111
89
- @Column(name = "init_valid_date")
90
- @JsonProperty("init_valid_date")
91
- private Date initValidDate;
112
+ @Type(type = "net.curisit.securis.db.common.PackStatusType")
113
+ private PackStatus status;
92114
93
- @Column(name = "end_valid_date")
94
- @JsonProperty("end_valid_date")
95
- private Date endValidDate;
115
+ @Column(name = "license_preactivation")
116
+ @JsonProperty("license_preactivation")
117
+ private boolean licensePreactivation;
96118
97
- @Type(type = "net.curisit.securis.db.common.PackStatusType")
98
- private PackStatus status;
119
+ @Column(name = "preactivation_valid_period")
120
+ @JsonProperty("preactivation_valid_period")
121
+ private Integer preactivationValidPeriod;
99122
100
- @Column(name = "license_preactivation")
101
- @JsonProperty("license_preactivation")
102
- private boolean licensePreactivation;
123
+ @Column(name = "renew_valid_period")
124
+ @JsonProperty("renew_valid_period")
125
+ private Integer renewValidPeriod;
103126
104
- @Column(name = "preactivation_valid_period")
105
- @JsonProperty("preactivation_valid_period")
106
- private Integer preactivationValidPeriod;
127
+ @OneToMany(fetch = FetchType.LAZY, cascade = { CascadeType.PERSIST, CascadeType.REMOVE, CascadeType.REFRESH }, mappedBy = "pack")
128
+ private Set<PackMetadata> metadata;
107129
108
- @Column(name = "renew_valid_period")
109
- @JsonProperty("renew_valid_period")
110
- private Integer renewValidPeriod;
130
+ // ---------------- Getters & setters ----------------
111131
112
- @OneToMany(fetch = FetchType.LAZY, cascade = { CascadeType.PERSIST, CascadeType.REMOVE, CascadeType.REFRESH }, mappedBy = "pack")
113
- private Set<PackMetadata> metadata;
132
+ /**
133
+ * getId<p>
134
+ * Return primary key.
135
+ *
136
+ * @return id
137
+ */
138
+ public Integer getId() { return id; }
114139
115
- public Integer getId() {
116
- return id;
117
- }
140
+ /**
141
+ * setId<p>
142
+ * Set primary key.
143
+ *
144
+ * @param id
145
+ */
146
+ public void setId(Integer id) { this.id = id; }
118147
119
- public void setId(Integer id) {
120
- this.id = id;
121
- }
148
+ /**
149
+ * getCode<p>
150
+ * Return pack code.
151
+ *
152
+ * @return packCode
153
+ */
154
+ public String getCode() { return code; }
122155
123
- public String getCode() {
124
- return code;
125
- }
156
+ /**
157
+ * setCode<p>
158
+ * Set pack code.
159
+ *
160
+ * @param packCode
161
+ */
162
+ public void setCode(String code) { this.code = code; }
126163
127
- public void setCode(String code) {
128
- this.code = code;
129
- }
164
+ /**
165
+ * getCreationTimestamp<p>
166
+ * Return creation timestamp.
167
+ *
168
+ * @return creationTimestamp
169
+ */
170
+ public Date getCreationTimestamp() { return creationTimestamp; }
130171
131
- public Date getCreationTimestamp() {
132
- return creationTimestamp;
133
- }
172
+ /**
173
+ * setCreationTimestamp<p>
174
+ * Set creation timestamp.
175
+ *
176
+ * @param creationTimestamp
177
+ */
178
+ public void setCreationTimestamp(Date creationTimestamp) { this.creationTimestamp = creationTimestamp; }
134179
135
- public void setCreationTimestamp(Date creationTimestamp) {
136
- this.creationTimestamp = creationTimestamp;
137
- }
180
+ /**
181
+ * getOrganization<p>
182
+ * Return owning organization (entity).
183
+ *
184
+ * @return organization
185
+ */
186
+ public Organization getOrganization() { return organization; }
138187
139
- public Organization getOrganization() {
140
- return organization;
141
- }
188
+ /**
189
+ * setOrganization<p>
190
+ * Set owning organization (entity).
191
+ *
192
+ * @param organization
193
+ */
194
+ public void setOrganization(Organization organization) { this.organization = organization; }
142195
143
- public void setOrganization(Organization organization) {
144
- this.organization = organization;
145
- }
196
+ /**
197
+ * getLicenseType<p>
198
+ * Return license type (entity).
199
+ *
200
+ * @return licenseType
201
+ */
202
+ public LicenseType getLicenseType() { return licenseType; }
146203
147
- public LicenseType getLicenseType() {
148
- return licenseType;
149
- }
204
+ /**
205
+ * setLicenseType<p>
206
+ * Set license type (entity).
207
+ *
208
+ * @param licenseType
209
+ */
210
+ public void setLicenseType(LicenseType licenseType) { this.licenseType = licenseType; }
150211
151
- public void setLicenseType(LicenseType licenseType) {
152
- this.licenseType = licenseType;
153
- }
212
+ /**
213
+ * getCreatedBy<p>
214
+ * Return creator (entity).
215
+ *
216
+ * @return user
217
+ */
218
+ public User getCreatedBy() { return createdBy; }
154219
155
- public User getCreatedBy() {
156
- return createdBy;
157
- }
220
+ /**
221
+ * setCreatedBy<p>
222
+ * Set creator (entity).
223
+ *
224
+ * @param user
225
+ */
226
+ public void setCreatedBy(User createdBy) { this.createdBy = createdBy; }
158227
159
- public void setCreatedBy(User createdBy) {
160
- this.createdBy = createdBy;
161
- }
228
+ /**
229
+ * getNumLicenses<p>
230
+ * Return capacity (licenses).
231
+ *
232
+ * @return numLicenses
233
+ * Number of licenses
234
+ */
235
+ public int getNumLicenses() { return numLicenses; }
162236
163
- public int getNumLicenses() {
164
- return numLicenses;
165
- }
237
+ /**
238
+ * setNumLicenses<p>
239
+ * Set capacity (licenses).
240
+ *
241
+ * @param numLicenses
242
+ * Number of licenses
243
+ */
244
+ public void setNumLicenses(int numLicenses) { this.numLicenses = numLicenses; }
166245
167
- public void setNumLicenses(int numLicenses) {
168
- this.numLicenses = numLicenses;
169
- }
246
+ /**
247
+ * getNumActivations<p>
248
+ * Count ACTIVE/PRE_ACTIVE licenses in this pack.
249
+ *
250
+ * @return numActivations
251
+ * number of activated licenses
252
+ */
253
+ @JsonProperty("num_activations")
254
+ public int getNumActivations() {
255
+ if (licenses == null) return 0;
256
+ int num = 0;
257
+ for (License lic : licenses) {
258
+ if (lic.getStatus() == LicenseStatus.ACTIVE || lic.getStatus() == LicenseStatus.PRE_ACTIVE) num++;
259
+ }
260
+ return num;
261
+ }
170262
171
- @JsonProperty("num_activations")
172
- public int getNumActivations() {
173
- if (licenses == null) {
174
- return 0;
175
- }
176
- int num = 0;
177
- for (License lic : licenses) {
178
- if (lic.getStatus() == LicenseStatus.ACTIVE || lic.getStatus() == LicenseStatus.PRE_ACTIVE) {
179
- num++;
180
- }
181
- }
182
- return num;
183
- }
263
+ /**
264
+ * getNumCreations<p>
265
+ * Count all created licenses (including waiting for activation). Ignores CANCELLED.
266
+ *
267
+ * @return numCreations
268
+ * number of created licenses
269
+ */
270
+ @JsonProperty("num_creations")
271
+ public int getNumCreations() {
272
+ if (licenses == null) return 0;
273
+ int num = 0;
274
+ for (License lic : licenses) {
275
+ if (lic.getStatus() != LicenseStatus.CANCELLED) num++;
276
+ }
277
+ return num;
278
+ }
184279
185
- /**
186
- * Counts all created licenses, It counts active licenses and licenses
187
- * waiting for activation This number will be used to control the max number
188
- * of licenses created. Ignore canceled licenses.
189
- *
190
- * @return
191
- */
192
- @JsonProperty("num_creations")
193
- public int getNumCreations() {
194
- if (licenses == null) {
195
- return 0;
196
- }
197
- int num = 0;
198
- for (License lic : licenses) {
199
- if (lic.getStatus() != LicenseStatus.CANCELLED) {
200
- num++;
201
- }
202
- }
203
- return num;
204
- }
280
+ /**
281
+ * getNumAvailables<p>
282
+ * Number of available licenses in this pack: capacity - activations.
283
+ *
284
+ * @return numAvailable
285
+ * Number of available licenses
286
+ */
287
+ @JsonProperty("num_available")
288
+ public int getNumAvailables() { return numLicenses - getNumActivations(); }
205289
206
- /**
207
- * Number of available licenses in this pack
208
- *
209
- * @return
210
- */
211
- @JsonProperty("num_available")
212
- public int getNumAvailables() {
213
- return numLicenses - getNumActivations();
214
- }
290
+ /**
291
+ * getOrgName<p>
292
+ * Expose organization name.
293
+ *
294
+ * @return orgName
295
+ */
296
+ @JsonProperty("organization_name")
297
+ public String getOrgName() { return organization == null ? null : organization.getName(); }
215298
216
- @JsonProperty("organization_name")
217
- public String getOrgName() {
218
- return organization == null ? null : organization.getName();
219
- }
299
+ /**
300
+ * getAppName<p>
301
+ * Expose application name via license type.
302
+ *
303
+ * @return appName
304
+ */
305
+ @JsonProperty("application_name")
306
+ public String getAppName() {
307
+ if (licenseType == null) return null;
308
+ Application app = licenseType.getApplication();
309
+ return app == null ? null : app.getName();
310
+ }
220311
221
- @JsonProperty("application_name")
222
- public String getAppName() {
223
- if (licenseType == null) {
224
- return null;
225
- }
226
- Application app = licenseType.getApplication();
227
- return app == null ? null : app.getName();
228
- }
312
+ /**
313
+ * getOrgId<p>
314
+ * Expose organization id.
315
+ *
316
+ * @return orgId
317
+ */
318
+ @JsonProperty("organization_id")
319
+ public Integer getOrgId() { return organization == null ? null : organization.getId(); }
229320
230
- @JsonProperty("organization_id")
231
- public Integer getOrgId() {
232
- return organization == null ? null : organization.getId();
233
- }
321
+ /**
322
+ * setOrgId<p>
323
+ * Setter by id for JSON binding (creates shallow Organization).
324
+ *
325
+ * @param orgId
326
+ */
327
+ @JsonProperty("organization_id")
328
+ public void setOrgId(Integer idOrg) {
329
+ if (idOrg == null) {
330
+ organization = null;
331
+ } else {
332
+ organization = new Organization();
333
+ organization.setId(idOrg);
334
+ }
335
+ }
234336
235
- @JsonProperty("organization_id")
236
- public void setOrgId(Integer idOrg) {
237
- if (idOrg == null) {
238
- organization = null;
239
- } else {
240
- organization = new Organization();
241
- organization.setId(idOrg);
242
- }
243
- }
337
+ /**
338
+ * setLicTypeId<p>
339
+ * Setter by id for JSON binding (creates shallow LicenseType).
340
+ *
341
+ * @param licTypeId
342
+ */
343
+ @JsonProperty("license_type_id")
344
+ public void setLicTypeId(Integer idLT) {
345
+ if (idLT == null) {
346
+ licenseType = null;
347
+ } else {
348
+ licenseType = new LicenseType();
349
+ licenseType.setId(idLT);
350
+ }
351
+ }
244352
245
- @JsonProperty("license_type_id")
246
- public void setLicTypeId(Integer idLT) {
247
- if (idLT == null) {
248
- licenseType = null;
249
- } else {
250
- licenseType = new LicenseType();
251
- licenseType.setId(idLT);
252
- }
253
- }
353
+ /**
354
+ * getLicTypeId<p>
355
+ * Expose license type id.
356
+ *
357
+ * @return licTypeId
358
+ */
359
+ @JsonProperty("license_type_id")
360
+ public Integer getLicTypeId() { return licenseType == null ? null : licenseType.getId(); }
254361
255
- @JsonProperty("license_type_id")
256
- public Integer getLicTypeId() {
257
- return licenseType == null ? null : licenseType.getId();
258
- }
362
+ /**
363
+ * getCreatedById<p>
364
+ * Expose creator username.
365
+ *
366
+ * @return username
367
+ */
368
+ @JsonProperty("created_by_id")
369
+ public String getCreatedById() { return createdBy == null ? null : createdBy.getUsername(); }
259370
260
- @JsonProperty("created_by_id")
261
- public String getCreatedById() {
262
- return createdBy == null ? null : createdBy.getUsername();
263
- }
371
+ /**
372
+ * setCreatedById<p>
373
+ * Setter by username (creates shallow User).
374
+ *
375
+ * @param username
376
+ */
377
+ @JsonProperty("created_by_id")
378
+ public void setCreatedById(String username) {
379
+ createdBy = new User();
380
+ createdBy.setUsername(username);
381
+ }
264382
265
- @JsonProperty("created_by_id")
266
- public void setCreatedById(String username) {
267
- createdBy = new User();
268
- createdBy.setUsername(username);
269
- }
383
+ /**
384
+ * getCreatedByname<p>
385
+ * Expose creator full display name.
386
+ *
387
+ * @return userName
388
+ */
389
+ @JsonProperty("created_by_name")
390
+ public String getCreatedByname() {
391
+ return createdBy == null ? null
392
+ : String.format("%s %s (%s)", createdBy.getFirstName(),
393
+ createdBy.getLastName() != null ? createdBy.getLastName() : "",
394
+ createdBy.getUsername());
395
+ }
270396
271
- @JsonProperty("created_by_name")
272
- public String getCreatedByname() {
273
- return createdBy == null ? null
274
- : String.format("%s %s (%s)", createdBy.getFirstName(), createdBy.getLastName() != null ? createdBy.getLastName() : "", createdBy.getUsername());
275
- }
397
+ /**
398
+ * getLicenseTypeCode<p>
399
+ * Expose license type code.
400
+ *
401
+ * @return licenseTypeCode
402
+ */
403
+ @JsonProperty("licensetype_code")
404
+ public String getLicenseTypeCode() { return licenseType == null ? null : licenseType.getCode(); }
276405
277
- @JsonProperty("licensetype_code")
278
- public String getLicenseTypeCode() {
279
- return licenseType == null ? null : licenseType.getCode();
280
- }
406
+ /**
407
+ * getComments<p>
408
+ * Return comments.
409
+ *
410
+ * @return comments
411
+ */
412
+ public String getComments() { return comments; }
281413
282
- public String getComments() {
283
- return comments;
284
- }
414
+ /**
415
+ * setComments<p>
416
+ * Set comments.
417
+ *
418
+ * @param comments
419
+ */
420
+ public void setComments(String comments) { this.comments = comments; }
285421
286
- public void setComments(String comments) {
287
- this.comments = comments;
288
- }
422
+ /**
423
+ * isLicensePreactivation<p>
424
+ * Whether licenses are pre-activated.
425
+ *
426
+ * @return isLicensePreactivation
427
+ */
428
+ public boolean isLicensePreactivation() { return licensePreactivation; }
289429
290
- public boolean isLicensePreactivation() {
291
- return licensePreactivation;
292
- }
430
+ /**
431
+ * setLicensePreactivation<p>
432
+ * Set pre-activation flag.
433
+ *
434
+ * @param licensePreactivation
435
+ */
436
+ public void setLicensePreactivation(boolean licensePreactivation) { this.licensePreactivation = licensePreactivation; }
293437
294
- public void setLicensePreactivation(boolean licensePreactivation) {
295
- this.licensePreactivation = licensePreactivation;
296
- }
438
+ /**
439
+ * getMetadata<p>
440
+ * Return pack metadata entries.
441
+ *
442
+ * @return metadata
443
+ */
444
+ public Set<PackMetadata> getMetadata() { return metadata; }
297445
298
- public Set<PackMetadata> getMetadata() {
299
- return metadata;
300
- }
446
+ /**
447
+ * setMetadata<p>
448
+ * Set pack metadata entries.
449
+ *
450
+ * @param metadata
451
+ */
452
+ public void setMetadata(Set<PackMetadata> metadata) { this.metadata = metadata; }
301453
302
- public void setMetadata(Set<PackMetadata> metadata) {
303
- this.metadata = metadata;
304
- }
454
+ /**
455
+ * getStatus<p>
456
+ * Return pack status.
457
+ *
458
+ * @return packStatus
459
+ */
460
+ public PackStatus getStatus() { return status; }
305461
306
- public PackStatus getStatus() {
307
- return status;
308
- }
462
+ /**
463
+ * setStatus<p>
464
+ * Set pack status.
465
+ *
466
+ * @param packStatus
467
+ */
468
+ public void setStatus(PackStatus status) { this.status = status; }
309469
310
- public void setStatus(PackStatus status) {
311
- this.status = status;
312
- }
470
+ /**
471
+ * getInitValidDate<p>
472
+ * Return start of validity window.
473
+ *
474
+ * @return initValidDate
475
+ */
476
+ public Date getInitValidDate() { return initValidDate; }
313477
314
- public Date getInitValidDate() {
315
- return initValidDate;
316
- }
478
+ /**
479
+ * setInitValidDate<p>
480
+ * Set start of validity window.
481
+ *
482
+ * @param initValidDate
483
+ */
484
+ public void setInitValidDate(Date initValidDate) { this.initValidDate = initValidDate; }
317485
318
- public void setInitValidDate(Date initValidDate) {
319
- this.initValidDate = initValidDate;
320
- }
486
+ /**
487
+ * getEndValidDate<p>
488
+ * Return end of validity window.
489
+ *
490
+ * @return endValidDate
491
+ */
492
+ public Date getEndValidDate() { return endValidDate; }
321493
322
- public Date getEndValidDate() {
323
- return endValidDate;
324
- }
494
+ /**
495
+ * setEndValidDate<p>
496
+ * Set end of validity window.
497
+ *
498
+ * @param endValidDate
499
+ */
500
+ public void setEndValidDate(Date endValidDate) { this.endValidDate = endValidDate; }
325501
326
- public void setEndValidDate(Date endValidDate) {
327
- this.endValidDate = endValidDate;
328
- }
502
+ /**
503
+ * getLicenses<p>
504
+ * Return contained licenses (entity set).
505
+ *
506
+ * @return licenses
507
+ */
508
+ public Set<License> getLicenses() { return licenses; }
329509
330
- public Set<License> getLicenses() {
331
- return licenses;
332
- }
510
+ /**
511
+ * setLicenses<p>
512
+ * Set contained licenses (entity set).
513
+ *
514
+ * @param licenses
515
+ */
516
+ public void setLicenses(Set<License> licenses) { this.licenses = licenses; }
333517
334
- public void setLicenses(Set<License> licenses) {
335
- this.licenses = licenses;
336
- }
518
+ /**
519
+ * getPreactivationValidPeriod<p>
520
+ * Return preactivation validity (days).
521
+ *
522
+ * @return preactivationValidPeriod
523
+ */
524
+ public Integer getPreactivationValidPeriod() { return preactivationValidPeriod; }
337525
338
- public Integer getPreactivationValidPeriod() {
339
- return preactivationValidPeriod;
340
- }
526
+ /**
527
+ * setPreactivationValidPeriod<p>
528
+ * Set preactivation validity (days).
529
+ *
530
+ * @param preactivationValidPeriod
531
+ */
532
+ public void setPreactivationValidPeriod(Integer preactivationValidPeriod) { this.preactivationValidPeriod = preactivationValidPeriod; }
341533
342
- public void setPreactivationValidPeriod(Integer preactivationValidPeriod) {
343
- this.preactivationValidPeriod = preactivationValidPeriod;
344
- }
534
+ /**
535
+ * getRenewValidPeriod<p>
536
+ * Return renewal validity (days).
537
+ *
538
+ * @return renewValidPeriod
539
+ */
540
+ public Integer getRenewValidPeriod() { return renewValidPeriod; }
345541
346
- public Integer getRenewValidPeriod() {
347
- return renewValidPeriod;
348
- }
542
+ /**
543
+ * setRenewValidPeriod<p>
544
+ * Set renewal validity (days).
545
+ *
546
+ * @param renewValidPeriod
547
+ */
548
+ public void setRenewValidPeriod(Integer renewValidPeriod) { this.renewValidPeriod = renewValidPeriod; }
349549
350
- public void setRenewValidPeriod(Integer renewValidPeriod) {
351
- this.renewValidPeriod = renewValidPeriod;
352
- }
550
+ // ---------------- Object methods ----------------
353551
354
- @Override
355
- public boolean equals(Object obj) {
356
- if (!(obj instanceof Application))
357
- return false;
358
- return id.equals(Pack.class.cast(obj).id);
359
- }
552
+ /**
553
+ * equals<p>
554
+ * Compare the current object with the given object
555
+ *
556
+ * @param object
557
+ * @return isEquals
558
+ */
559
+ @Override
560
+ public boolean equals(Object obj) {
561
+ if (!(obj instanceof Application)) return false;
562
+ return id != null && id.equals(Pack.class.cast(obj).id);
563
+ }
360564
361
- @Override
362
- public int hashCode() {
363
- return (id == null ? 0 : id.hashCode());
364
- }
565
+ /**
566
+ * hashCode<p>
567
+ * Get the object hashCode
568
+ *
569
+ * @return hashCode
570
+ */
571
+ @Override
572
+ public int hashCode() { return (id == null ? 0 : id.hashCode()); }
365573
366
- @Override
367
- public String toString() {
368
- return String.format("Pack: ID: %d, code: %s", id, code);
369
- }
574
+ /**
575
+ * toString<p>
576
+ * Get the string describing the current object
577
+ *
578
+ * @return object string
579
+ */
580
+ @Override
581
+ public String toString() { return String.format("Pack: ID: %d, code: %s", id, code); }
370582
371
- public boolean isFrozen() {
372
- return frozen != null && frozen;
373
- }
583
+ /**
584
+ * isFrozen<p>
585
+ * Null-safe boolean getter.
586
+ *
587
+ * @return isFrozen
588
+ */
589
+ public boolean isFrozen() { return frozen != null && frozen; }
374590
375
- public void setFrozen(Boolean frozen) {
376
- this.frozen = frozen;
377
- }
591
+ /**
592
+ * setFrozen<p>
593
+ * Set frozen flag (nullable wrapper).
594
+ *
595
+ * @param frozen
596
+ */
597
+ public void setFrozen(Boolean frozen) { this.frozen = frozen; }
378598
379
- public static class Action {
380
- public static final int CREATE = 1;
381
- public static final int ACTIVATION = 2;
382
- public static final int PUT_ONHOLD = 3;
383
- public static final int CANCEL = 4;
384
- public static final int DELETE = 5;
385
- }
599
+ // ---------------- Status transitions ----------------
386600
387
- public static class Status {
601
+ /**
602
+ * Action<p>
603
+ * Available actions for the Pack
604
+ */
605
+ public static class Action {
606
+ public static final int CREATE = 1;
607
+ public static final int ACTIVATION = 2;
608
+ public static final int PUT_ONHOLD = 3;
609
+ public static final int CANCEL = 4;
610
+ public static final int DELETE = 5;
611
+ }
388612
389
- private static final Map<Integer, List<PackStatus>> transitions = Utils.createMap( //
390
- Action.ACTIVATION, Arrays.asList(PackStatus.CREATED, PackStatus.ON_HOLD, PackStatus.EXPIRED), //
391
- Action.PUT_ONHOLD, Arrays.asList(PackStatus.ACTIVE), //
392
- Action.CANCEL, Arrays.asList(PackStatus.ACTIVE, PackStatus.ON_HOLD, PackStatus.EXPIRED), //
393
- Action.DELETE, Arrays.asList(PackStatus.CANCELLED, PackStatus.CREATED) //
394
- );
613
+ /**
614
+ * Status<p>
615
+ * Pack status
616
+ */
617
+ public static class Status {
395618
396
- /**
397
- * It checks if a given action is valid for the License, passing the
398
- * action and the current license status
399
- *
400
- * @param oldStatus
401
- * @param newStatus
402
- * @return
403
- */
404
- public static boolean isActionValid(Integer action, PackStatus currentStatus) {
405
- List<PackStatus> validStatuses = transitions.get(action);
619
+ private static final Map<Integer, List<PackStatus>> transitions = Utils.createMap(
620
+ Action.ACTIVATION, Arrays.asList(PackStatus.CREATED, PackStatus.ON_HOLD, PackStatus.EXPIRED),
621
+ Action.PUT_ONHOLD, Arrays.asList(PackStatus.ACTIVE),
622
+ Action.CANCEL, Arrays.asList(PackStatus.ACTIVE, PackStatus.ON_HOLD, PackStatus.EXPIRED),
623
+ Action.DELETE, Arrays.asList(PackStatus.CANCELLED, PackStatus.CREATED)
624
+ );
406625
407
- return validStatuses != null && validStatuses.contains(currentStatus);
408
- }
409
- }
626
+ /**
627
+ * isActionValid<p>
628
+ * Validate whether an action is allowed given the current pack status.
629
+ *
630
+ * @param action action constant
631
+ * @param currentStatus current pack status
632
+ * @return true if allowed
633
+ */
634
+ public static boolean isActionValid(Integer action, PackStatus currentStatus) {
635
+ List<PackStatus> validStatuses = transitions.get(action);
636
+ return validStatuses != null && validStatuses.contains(currentStatus);
637
+ }
638
+ }
410639 }
640
+
securis/src/main/java/net/curisit/securis/db/PackMetadata.java
....@@ -1,3 +1,6 @@
1
+/*
2
+* Copyright @ 2013 CurisTEC, S.A.S. All Rights Reserved.
3
+*/
14 package net.curisit.securis.db;
25
36 import java.io.Serializable;
....@@ -22,106 +25,201 @@
2225 import net.curisit.securis.db.common.Metadata;
2326
2427 /**
25
- * Entity implementation class for Entity: pack_metadata
26
- *
27
- */
28
+* PackMetadata
29
+* <p>
30
+* Single metadata entry (key/value/flags) attached to a {@link Pack}.
31
+* Uses composite PK: (pack_id, key).
32
+*
33
+* Mapping details:
34
+* - Table: pack_metadata
35
+* - PK: pack_id + key (two @Id fields)
36
+* - 'pack' is @JsonIgnore to reduce payload size in list views
37
+* - NamedQuery: list-pack-metadata by pack id
38
+*
39
+* Flags:
40
+* - readonly: UI hint to prevent edits
41
+* - mandatory: requires value on pack creation/updates
42
+*
43
+* @author JRA
44
+* Last reviewed by JRA on Oct 5, 2025.
45
+*/
2846 @JsonAutoDetect
2947 @JsonInclude(Include.NON_NULL)
3048 @Entity
3149 @Table(name = "pack_metadata")
3250 @JsonIgnoreProperties(ignoreUnknown = true)
33
-@NamedQueries({ @NamedQuery(name = "list-pack-metadata", query = "SELECT a FROM PackMetadata a where a.pack.id = :packId") })
51
+@NamedQueries({
52
+ @NamedQuery(name = "list-pack-metadata",
53
+ query = "SELECT a FROM PackMetadata a where a.pack.id = :packId")
54
+})
3455 public class PackMetadata implements Serializable, Metadata {
3556
36
- private static final long serialVersionUID = 1L;
57
+ private static final long serialVersionUID = 1L;
3758
38
- @Id
39
- @JsonIgnore
40
- @ManyToOne
41
- @JoinColumn(name = "pack_id")
42
- private Pack pack;
59
+ /** PK part: owning pack (ignored in JSON). */
60
+ @Id
61
+ @JsonIgnore
62
+ @ManyToOne
63
+ @JoinColumn(name = "pack_id")
64
+ private Pack pack;
4365
44
- @Id
45
- @Column(name = "\"key\"")
46
- private String key;
66
+ /** PK part: metadata key (quoted column name). */
67
+ @Id
68
+ @Column(name = "\"key\"")
69
+ private String key;
4770
48
- private String value;
71
+ /** Metadata value. */
72
+ private String value;
4973
50
- private boolean readonly;
74
+ /** Whether this field can be edited by clients. */
75
+ private boolean readonly;
5176
52
- private boolean mandatory;
77
+ /** Whether this field is required. */
78
+ private boolean mandatory;
5379
54
- @JsonProperty("pack_id")
55
- public Integer getPackId() {
56
- return pack == null ? null : pack.getId();
57
- }
80
+ // -------- JSON helpers to expose pack id --------
5881
59
- @JsonProperty("pack_id")
60
- public void setLicenseTypeId(Integer idPack) {
61
- if (idPack == null) {
62
- pack = null;
63
- } else {
64
- pack = new Pack();
65
- pack.setId(idPack);
66
- }
67
- }
82
+ /**
83
+ * getPackId<p>
84
+ * Expose pack id as JSON scalar.
85
+ *
86
+ * @return packId
87
+ */
88
+ @JsonProperty("pack_id")
89
+ public Integer getPackId() {
90
+ return pack == null ? null : pack.getId();
91
+ }
6892
69
- public Pack getPack() {
70
- return pack;
71
- }
93
+ /**
94
+ * setLicenseTypeId<p>
95
+ * Setter by id (creates shallow Pack).
96
+ *
97
+ * @param packId
98
+ */
99
+ @JsonProperty("pack_id")
100
+ public void setLicenseTypeId(Integer idPack) {
101
+ if (idPack == null) {
102
+ pack = null;
103
+ } else {
104
+ pack = new Pack();
105
+ pack.setId(idPack);
106
+ }
107
+ }
72108
73
- public void setPack(Pack pack) {
74
- this.pack = pack;
75
- }
109
+ // -------- Getters & setters --------
76110
77
- public String getValue() {
78
- return value;
79
- }
111
+ /**
112
+ * getPack<p>
113
+ * Return owning pack (entity).
114
+ *
115
+ * @return pack
116
+ */
117
+ public Pack getPack() { return pack; }
80118
81
- public void setValue(String value) {
82
- this.value = value;
83
- }
119
+ /**
120
+ * setPack<p>
121
+ * Set owning pack (entity).
122
+ *
123
+ * @param pack
124
+ */
125
+ public void setPack(Pack pack) { this.pack = pack; }
84126
85
- public String getKey() {
86
- return key;
87
- }
127
+ /**
128
+ * getValue<p>
129
+ * Return metadata value.
130
+ *
131
+ * @return metadataValue
132
+ */
133
+ public String getValue() { return value; }
88134
89
- public void setKey(String key) {
90
- this.key = key;
91
- }
135
+ /**
136
+ * setValue<p>
137
+ * Set the metadata value.
138
+ *
139
+ * @param metadataValue
140
+ */
141
+ public void setValue(String value) { this.value = value; }
92142
93
- public boolean isReadonly() {
94
- return readonly;
95
- }
143
+ /**
144
+ * getKey<p>
145
+ * Return metadata key (PK part).
146
+ *
147
+ * @return key
148
+ */
149
+ public String getKey() { return key; }
96150
97
- public void setReadonly(boolean readonly) {
98
- this.readonly = readonly;
99
- }
151
+ /**
152
+ * setKey<p>
153
+ * Set metadata key (PK part).
154
+ *
155
+ * @param key
156
+ */
157
+ public void setKey(String key) { this.key = key; }
100158
101
- public boolean isMandatory() {
102
- return mandatory;
103
- }
159
+ /**
160
+ * isReadonly<p>
161
+ * Return read-only flag.
162
+ *
163
+ * @return isReadonly
164
+ */
165
+ public boolean isReadonly() { return readonly; }
104166
105
- public void setMandatory(boolean mandatory) {
106
- this.mandatory = mandatory;
107
- }
167
+ /**
168
+ * setReadonly<p>
169
+ * Set read-only flag.
170
+ *
171
+ * @param readonly
172
+ */
173
+ public void setReadonly(boolean readonly) { this.readonly = readonly; }
108174
109
- @Override
110
- public String toString() {
111
- return String.format("PackMD (%s: %s)", key, value);
112
- }
175
+ /**
176
+ * isMandatory<p>
177
+ * Return mandatory flag.
178
+ *
179
+ * @return isMandatory
180
+ */
181
+ public boolean isMandatory() { return mandatory; }
113182
114
- @Override
115
- public boolean equals(Object obj) {
116
- if (!(obj instanceof PackMetadata))
117
- return false;
118
- PackMetadata other = (PackMetadata) obj;
119
- return Objects.equals(key, other.key) && Objects.equals(pack, other.pack);
120
- }
183
+ /**
184
+ * setMandatory<p>
185
+ * Set mandatory flag.
186
+ *
187
+ * @param mandatory
188
+ */
189
+ public void setMandatory(boolean mandatory) { this.mandatory = mandatory; }
121190
122
- @Override
123
- public int hashCode() {
124
- return Objects.hash(key, pack);
125
- }
191
+ // -------- Object methods --------
126192
193
+ /**
194
+ * toString<p>
195
+ * Get the string describing the current object
196
+ *
197
+ * @return object string
198
+ */
199
+ @Override
200
+ public String toString() { return String.format("PackMD (%s: %s)", key, value); }
201
+
202
+ /**
203
+ * equals<p>
204
+ * Compare the current object with the given object
205
+ *
206
+ * @param object
207
+ * @return isEquals
208
+ */
209
+ @Override
210
+ public boolean equals(Object obj) {
211
+ if (!(obj instanceof PackMetadata)) return false;
212
+ PackMetadata other = (PackMetadata) obj;
213
+ return Objects.equals(key, other.key) && Objects.equals(pack, other.pack);
214
+ }
215
+
216
+ /**
217
+ * hashCode<p>
218
+ * Get the object hashCode
219
+ *
220
+ * @return hashCode
221
+ */
222
+ @Override
223
+ public int hashCode() { return Objects.hash(key, pack); }
127224 }
225
+
securis/src/main/java/net/curisit/securis/db/PackStatus.java
....@@ -1,3 +1,6 @@
1
+/*
2
+* Copyright @ 2013 CurisTEC, S.A.S. All Rights Reserved.
3
+*/
14 package net.curisit.securis.db;
25
36 import net.curisit.securis.db.common.CodedEnum;
....@@ -6,38 +9,58 @@
69 import com.fasterxml.jackson.annotation.JsonValue;
710
811 /**
9
- * Contains the possible pack statuses. For further details:
10
- * https://redmine.curistec.com/projects/securis/wiki/LicensesServerManagement
11
- *
12
- * @author rob
13
- */
12
+* PackStatus
13
+* <p>
14
+* Enumerates possible pack lifecycle statuses. JSON representation is the short code.
15
+* See: https://redmine.curistec.com/projects/securis/wiki/LicensesServerManagement
16
+*
17
+* @author JRA
18
+* Last reviewed by JRA on Oct 5, 2025.
19
+*/
1420 public enum PackStatus implements CodedEnum {
21
+
22
+ // Available status for the pack
1523 CREATED("CR"), ACTIVE("AC"), ON_HOLD("OH"), EXPIRED("EX"), CANCELLED("CA");
1624
1725 private final String code;
1826
19
- PackStatus(String code) {
20
- this.code = code;
21
- }
27
+ /**
28
+ * PackStatus<p>
29
+ * Constructor
30
+ *
31
+ * @param code
32
+ */
33
+ PackStatus(String code) { this.code = code; }
2234
23
- @Override
24
- public String getCode() {
25
- return code;
26
- }
35
+ /**
36
+ * getCode<p>
37
+ * Short code stored in DB / used in JSON.
38
+ *
39
+ * @return packCode
40
+ */
41
+ @Override public String getCode() { return code; }
2742
43
+ /**
44
+ * valueFromCode<p>
45
+ * Factory method from short code.
46
+ *
47
+ * @param packCode
48
+ * @return packStatus
49
+ */
2850 @JsonCreator
2951 public static PackStatus valueFromCode(String code) {
3052 for (PackStatus ps : PackStatus.values()) {
31
- if (ps.code.equals(code)) {
32
- return ps;
33
- }
53
+ if (ps.code.equals(code)) return ps;
3454 }
3555 return null;
3656 }
3757
58
+ /**
59
+ * getName<p>
60
+ * Expose short code as JSON value.
61
+ *
62
+ * @return name
63
+ */
3864 @JsonValue
39
- public String getName() {
40
- return this.code;
41
- }
42
-
65
+ public String getName() { return this.code; }
4366 }
securis/src/main/java/net/curisit/securis/db/Settings.java
....@@ -1,3 +1,6 @@
1
+/*
2
+* Copyright @ 2013 CurisTEC, S.A.S. All Rights Reserved.
3
+*/
14 package net.curisit.securis.db;
25
36 import java.io.Serializable;
....@@ -20,63 +23,103 @@
2023 import com.fasterxml.jackson.annotation.JsonProperty;
2124
2225 /**
23
- * Entity implementation class for Entity: settings settings is a table that has
24
- * rows with 3 columns: "key", "value", "timestamp"
25
- *
26
- */
27
-@Entity()
28
-@EntityListeners({
29
- ModificationTimestampListener.class
30
-})
26
+* Settings
27
+* <p>
28
+* Simple key/value store with last modification timestamp.
29
+* Table rows have columns: "key", "value", "modification_timestamp".
30
+*
31
+* Mapping details:
32
+* - Table: settings
33
+* - Listeners: {@link ModificationTimestampListener}
34
+* - NamedQuery: get-param by key
35
+*
36
+* @author JRA
37
+* Last reviewed by JRA on Oct 5, 2025.
38
+*/
39
+@Entity
40
+@EntityListeners({ ModificationTimestampListener.class })
3141 @Table(name = "settings")
3242 @NamedQueries({
3343 @NamedQuery(name = "get-param", query = "SELECT p FROM Settings p where p.key = :key")
3444 })
3545 public class Settings implements ModificationTimestampEntity, Serializable {
46
+
3647 @SuppressWarnings("unused")
3748 private static final Logger LOG = LogManager.getLogger(Settings.class);
3849
3950 private static final long serialVersionUID = 1L;
4051
52
+ /** Primary key: setting key. */
4153 @Id
4254 String key;
4355
56
+ /** Setting value as string. */
4457 String value;
4558
59
+ /** Last modification timestamp. */
4660 @Column(name = "modification_timestamp")
4761 @JsonProperty("modification_timestamp")
4862 private Date modificationTimestamp;
4963
50
- public String getKey() {
51
- return key;
52
- }
64
+ // -------- Getters/setters --------
5365
54
- public void setKey(String key) {
55
- this.key = key;
56
- }
66
+ /**
67
+ * getKey<p>
68
+ * Return setting key.
69
+ *
70
+ * @return key
71
+ */
72
+ public String getKey() { return key; }
5773
58
- public String getValue() {
59
- return value;
60
- }
74
+ /**
75
+ * setKey<p>
76
+ * Set setting key.
77
+ *
78
+ * @param key
79
+ */
80
+ public void setKey(String key) { this.key = key; }
6181
62
- public void setValue(String value) {
63
- this.value = value;
64
- }
82
+ /**
83
+ * getValue<p>
84
+ * Return value.
85
+ *
86
+ * @return value
87
+ */
88
+ public String getValue() { return value; }
6589
90
+ /**
91
+ * setValue<p>
92
+ * Set value.
93
+ *
94
+ * @param value
95
+ */
96
+ public void setValue(String value) { this.value = value; }
97
+
98
+ /**
99
+ * getModificationTimestamp<p>
100
+ * Required by ModificationTimestampEntity.
101
+ *
102
+ * @return modificationTimestamp
103
+ */
66104 @Override
67
- public Date getModificationTimestamp() {
68
- return modificationTimestamp;
69
- }
105
+ public Date getModificationTimestamp() { return modificationTimestamp; }
70106
107
+ /**
108
+ * setModificationTimestamp<p>
109
+ * Required by ModificationTimestampEntity.
110
+ *
111
+ * @param modificationTimestamp
112
+ */
71113 @Override
72
- public void setModificationTimestamp(Date modificationTimestamp) {
73
- this.modificationTimestamp = modificationTimestamp;
74
- }
114
+ public void setModificationTimestamp(Date modificationTimestamp) { this.modificationTimestamp = modificationTimestamp; }
75115
116
+ /**
117
+ * toString<p>
118
+ * Get the string describing the current object
119
+ *
120
+ * @return object string
121
+ */
76122 @Override
77
- public String toString() {
78
-
79
- return String.format("{key: %s, value: %s, ts: %s}", key, value, modificationTimestamp);
80
- }
81
-
123
+ public String toString() { return String.format("{key: %s, value: %s, ts: %s}", key, value, modificationTimestamp); }
82124 }
125
+
securis/src/main/java/net/curisit/securis/db/User.java
....@@ -1,3 +1,6 @@
1
+/*
2
+* Copyright @ 2013 CurisTEC, S.A.S. All Rights Reserved.
3
+*/
14 package net.curisit.securis.db;
25
36 import java.io.Serializable;
....@@ -27,275 +30,426 @@
2730 import com.fasterxml.jackson.annotation.JsonProperty;
2831
2932 /**
30
- * Entity implementation class for Entity: Users
31
- *
32
- */
33
+* User
34
+* <p>
35
+* Application user with bitmask-based roles and membership in organizations
36
+* and applications. Exposes convenience JSON properties to fetch/set related
37
+* entity IDs without fetching full entities.
38
+*
39
+* Mapping details:
40
+* - Table: user
41
+* - ManyToMany organizations via user_organization
42
+* - ManyToMany applications via user_application
43
+* - Named queries: list-users, get-user, auth-user, delete-all-users
44
+*
45
+* Roles:
46
+* - Stored as integer bitmask; see {@link Rol}.
47
+*
48
+* @author JRA
49
+* Last reviewed by JRA on Oct 5, 2025.
50
+*/
3351 @JsonAutoDetect
3452 @JsonInclude(Include.NON_NULL)
3553 @JsonIgnoreProperties(ignoreUnknown = true)
3654 @Entity
3755 @Table(name = "user")
38
-@NamedQueries({ @NamedQuery(name = "list-users", query = "SELECT u FROM User u"), @NamedQuery(name = "get-user", query = "SELECT u FROM User u where u.username = :username"),
39
- @NamedQuery(name = "auth-user", query = "SELECT u FROM User u where u.username = :username and u.password = :password"),
40
- @NamedQuery(name = "delete-all-users", query = "delete FROM User u") })
56
+@NamedQueries({
57
+ @NamedQuery(name = "list-users", query = "SELECT u FROM User u"),
58
+ @NamedQuery(name = "get-user", query = "SELECT u FROM User u where u.username = :username"),
59
+ @NamedQuery(name = "auth-user", query = "SELECT u FROM User u where u.username = :username and u.password = :password"),
60
+ @NamedQuery(name = "delete-all-users", query = "delete FROM User u")
61
+})
4162 public class User implements Serializable {
4263
43
- private static final long serialVersionUID = 1L;
64
+ private static final long serialVersionUID = 1L;
4465
45
- @Id
46
- private String username;
66
+ /** Username (PK). */
67
+ @Id
68
+ private String username;
4769
48
- private String password;
70
+ /** Password hash/string (not exposed in JSON). */
71
+ private String password;
4972
50
- @JsonProperty(value = "first_name")
51
- @Column(name = "first_name")
52
- private String firstName;
73
+ @JsonProperty("first_name")
74
+ @Column(name = "first_name")
75
+ private String firstName;
5376
54
- @JsonProperty(value = "last_name")
55
- @Column(name = "last_name")
56
- private String lastName;
77
+ @JsonProperty("last_name")
78
+ @Column(name = "last_name")
79
+ private String lastName;
5780
58
- private int roles;
81
+ /** Roles bitmask (see Rol constants). */
82
+ private int roles;
5983
60
- @Column(name = "last_login")
61
- private Date lastLogin;
84
+ @Column(name = "last_login")
85
+ private Date lastLogin;
6286
63
- @Column(name = "modification_timestamp")
64
- private Date modificationTimestamp;
87
+ @Column(name = "modification_timestamp")
88
+ private Date modificationTimestamp;
6589
66
- @Column(name = "creation_timestamp")
67
- @JsonProperty("creation_timestamp")
68
- private Date creationTimestamp;
90
+ @Column(name = "creation_timestamp")
91
+ @JsonProperty("creation_timestamp")
92
+ private Date creationTimestamp;
6993
70
- private String lang;
94
+ private String lang;
95
+ private String email;
7196
72
- private String email;
97
+ @JsonIgnore
98
+ @ManyToMany
99
+ @JoinTable(name = "user_organization",
100
+ joinColumns = { @JoinColumn(name = "username", referencedColumnName = "username") },
101
+ inverseJoinColumns = { @JoinColumn(name = "organization_id", referencedColumnName = "id") })
102
+ private Set<Organization> organizations;
73103
74
- @JsonIgnore
75
- @ManyToMany
76
- @JoinTable(name = "user_organization", //
77
- joinColumns = { @JoinColumn(name = "username", referencedColumnName = "username") }, //
78
- inverseJoinColumns = { @JoinColumn(name = "organization_id", referencedColumnName = "id") } //
79
- )
80
- private Set<Organization> organizations;
104
+ @JsonIgnore
105
+ @ManyToMany
106
+ @JoinTable(name = "user_application",
107
+ joinColumns = { @JoinColumn(name = "username", referencedColumnName = "username") },
108
+ inverseJoinColumns = { @JoinColumn(name = "application_id", referencedColumnName = "id") })
109
+ private Set<Application> applications;
81110
82
- @JsonIgnore
83
- @ManyToMany
84
- @JoinTable(name = "user_application", //
85
- joinColumns = { @JoinColumn(name = "username", referencedColumnName = "username") }, //
86
- inverseJoinColumns = { @JoinColumn(name = "application_id", referencedColumnName = "id") } //
87
- )
88
- private Set<Application> applications;
111
+ // -------- Getters & setters --------
89112
90
- public String getUsername() {
91
- return username;
92
- }
113
+ /**
114
+ * getUsername<p>
115
+ * Return username (PK).
116
+ *
117
+ * @return username
118
+ */
119
+ public String getUsername() { return username; }
93120
94
- public void setUsername(String username) {
95
- this.username = username;
96
- }
121
+ /**
122
+ * setUsername<p>
123
+ * Set username (PK).
124
+ *
125
+ * @param username
126
+ */
127
+ public void setUsername(String username) { this.username = username; }
97128
98
- @JsonProperty("password")
99
- public String getDummyPassword() {
100
- return null;
101
- }
129
+ /**
130
+ * getDummyPassword<p>
131
+ * Forces password to be omitted in JSON responses.
132
+ *
133
+ * @return always null
134
+ */
135
+ @JsonProperty("password")
136
+ public String getDummyPassword() { return null; }
102137
103
- public String getPassword() {
104
- return password;
105
- }
138
+ /**
139
+ * getPassword<p>
140
+ * Return raw/hashed password (internal use).
141
+ *
142
+ * @return password
143
+ */
144
+ public String getPassword() { return password; }
106145
107
- public void setPassword(String password) {
108
- this.password = password;
109
- }
146
+ /**
147
+ * setPassword<p>
148
+ * Set raw/hashed password (internal use).
149
+ *
150
+ * @param password
151
+ */
152
+ public void setPassword(String password) { this.password = password; }
110153
111
- public List<Integer> getRoles() {
112
- if (roles == 0) {
113
- return null;
114
- }
115
- List<Integer> aux = new ArrayList<>();
116
- for (int rol : Rol.ALL) {
117
- if ((roles & rol) != 0) { // Each rol is a number with only 1 bit ==
118
- // 1 in binary representation
119
- aux.add(rol);
120
- }
121
- }
122
- return aux;
123
- }
154
+ /**
155
+ * getRoles<p>
156
+ * Return list of individual role flags contained in the bitmask.
157
+ *
158
+ * @return list of role integers or null if no roles
159
+ */
160
+ public List<Integer> getRoles() {
161
+ if (roles == 0) return null;
162
+ List<Integer> aux = new ArrayList<>();
163
+ for (int rol : Rol.ALL) {
164
+ if ((roles & rol) != 0) aux.add(rol);
165
+ }
166
+ return aux;
167
+ }
124168
125
- public void setRoles(List<Integer> roles) {
126
- this.roles = 0;
127
- if (roles != null) {
128
- for (Integer rol : roles) {
129
- this.roles |= rol;
130
- }
131
- }
132
- }
169
+ /**
170
+ * setRoles<p>
171
+ * Set the roles bitmask from a list of role flags.
172
+ *
173
+ * @param roles list of flags
174
+ */
175
+ public void setRoles(List<Integer> roles) {
176
+ this.roles = 0;
177
+ if (roles != null) {
178
+ for (Integer rol : roles) this.roles |= rol;
179
+ }
180
+ }
133181
134
- public String getFirstName() {
135
- return firstName;
136
- }
182
+ /**
183
+ * getFirstName<p>
184
+ * Return first name.
185
+ *
186
+ * @return firstName
187
+ */
188
+ public String getFirstName() { return firstName; }
137189
138
- public void setFirstName(String firstName) {
139
- this.firstName = firstName;
140
- }
190
+ /**
191
+ * setFirstName<p>
192
+ * Set first name.
193
+ *
194
+ * @param firstName
195
+ */
196
+ public void setFirstName(String firstName) { this.firstName = firstName; }
141197
142
- public String getLastName() {
143
- return lastName;
144
- }
198
+ /**
199
+ * getLastName<p>
200
+ * Return last name.
201
+ *
202
+ * @return lastName
203
+ */
204
+ public String getLastName() { return lastName; }
145205
146
- public void setLastName(String lastName) {
147
- this.lastName = lastName;
148
- }
206
+ /**
207
+ * setLastName<p>
208
+ * Set last name.
209
+ *
210
+ * @param lastName
211
+ */
212
+ public void setLastName(String lastName) { this.lastName = lastName; }
149213
150
- public Date getLastLogin() {
151
- return lastLogin;
152
- }
214
+ /**
215
+ * getLastLogin<p>
216
+ * Return last login timestamp.
217
+ *
218
+ * @return lastLogin
219
+ */
220
+ public Date getLastLogin() { return lastLogin; }
153221
154
- public void setLastLogin(Date lastLogin) {
155
- this.lastLogin = lastLogin;
156
- }
222
+ /**
223
+ * setLastLogin<p>
224
+ * Set last login timestamp.
225
+ *
226
+ * @param lastLogin
227
+ */
228
+ public void setLastLogin(Date lastLogin) { this.lastLogin = lastLogin; }
157229
158
- public Date getModificationTimestamp() {
159
- return modificationTimestamp;
160
- }
230
+ /**
231
+ * getModificationTimestamp<p>
232
+ * Return modification timestamp.
233
+ *
234
+ * @return modificationTimestamp
235
+ */
236
+ public Date getModificationTimestamp() { return modificationTimestamp; }
161237
162
- public void setModificationTimestamp(Date modificationTimestamp) {
163
- this.modificationTimestamp = modificationTimestamp;
164
- }
238
+ /**
239
+ * setModificationTimestamp<p>
240
+ * Set modification timestamp.
241
+ *
242
+ * @param modificationTimestamp
243
+ */
244
+ public void setModificationTimestamp(Date modificationTimestamp) { this.modificationTimestamp = modificationTimestamp; }
165245
166
- public Date getCreationTimestamp() {
167
- return creationTimestamp;
168
- }
246
+ /**
247
+ * getCreationTimestamp<p>
248
+ * Return creation timestamp.
249
+ *
250
+ * @return creationTimestamp
251
+ */
252
+ public Date getCreationTimestamp() { return creationTimestamp; }
169253
170
- public void setCreationTimestamp(Date creationTimestamp) {
171
- this.creationTimestamp = creationTimestamp;
172
- }
254
+ /**
255
+ * setCreationTimestamp<p>
256
+ * Set creation timestamp.
257
+ *
258
+ * @param creationTimestamp
259
+ */
260
+ public void setCreationTimestamp(Date creationTimestamp) { this.creationTimestamp = creationTimestamp; }
173261
174
- @Override
175
- public String toString() {
176
- return "{User: " + username + " Name: " + firstName + " " + lastName + ", last login: " + lastLogin + "}";
177
- }
262
+ /**
263
+ * getLang<p>
264
+ * Return preferred language.
265
+ *
266
+ * @return lang
267
+ */
268
+ public String getLang() { return lang; }
178269
179
- public String getLang() {
180
- return lang;
181
- }
270
+ /**
271
+ * setLang<p>
272
+ * Set preferred language.
273
+ *
274
+ * @param lang
275
+ */
276
+ public void setLang(String lang) { this.lang = lang; }
182277
183
- public void setLang(String lang) {
184
- this.lang = lang;
185
- }
278
+ /**
279
+ * getEmail<p>
280
+ * Return email address.
281
+ *
282
+ * @return email
283
+ */
284
+ public String getEmail() { return email; }
186285
187
- public Set<Organization> getOrganizations() {
188
- return organizations;
189
- }
286
+ /**
287
+ * setEmail<p>
288
+ * Set email address.
289
+ *
290
+ * @param email
291
+ */
292
+ public void setEmail(String email) { this.email = email; }
190293
191
- public void setOrganizations(Set<Organization> organizations) {
192
- this.organizations = organizations;
193
- }
294
+ /**
295
+ * getOrganizations<p>
296
+ * Return organizations (entity set).
297
+ *
298
+ * @return organizations
299
+ */
300
+ public Set<Organization> getOrganizations() { return organizations; }
194301
195
- public Set<Application> getApplications() {
196
- return applications;
197
- }
302
+ /**
303
+ * setOrganizations<p>
304
+ * Set organizations (entity set).
305
+ *
306
+ * @param organizations
307
+ */
308
+ public void setOrganizations(Set<Organization> organizations) { this.organizations = organizations; }
198309
199
- public void setApplications(Set<Application> applications) {
200
- this.applications = applications;
201
- }
310
+ /**
311
+ * getApplications<p>
312
+ * Return applications (entity set).
313
+ *
314
+ * @return applications
315
+ */
316
+ public Set<Application> getApplications() { return applications; }
202317
203
- @JsonProperty("organizations_ids")
204
- public void setOrgsIds(List<Integer> orgsIds) {
205
- organizations = new HashSet<>();
206
- for (Integer orgid : orgsIds) {
207
- Organization o = new Organization();
208
- o.setId(orgid);
209
- organizations.add(o);
210
- }
211
- }
318
+ /**
319
+ * setApplications<p>
320
+ * Set applications (entity set).
321
+ *
322
+ * @param applications
323
+ */
324
+ public void setApplications(Set<Application> applications) { this.applications = applications; }
212325
213
- @JsonProperty("organizations_ids")
214
- public Set<Integer> getOrgsIds() {
215
- if (organizations == null) {
216
- return null;
217
- }
218
- Set<Integer> ids = new HashSet<>();
219
- for (Organization org : organizations) {
220
- ids.add(org.getId());
221
- }
222
- return ids;
223
- }
326
+ // -------- JSON helpers for related IDs --------
224327
225
- @JsonProperty("applications_ids")
226
- public void setAppsIds(Collection<Integer> appIds) {
227
- applications = new HashSet<>();
228
- for (Integer appid : appIds) {
229
- Application a = new Application();
230
- a.setId(appid);
231
- applications.add(a);
232
- }
233
- }
328
+ /**
329
+ * setOrgsIds<p>
330
+ * Replace organizations from a list of org IDs.
331
+ *
332
+ * @param orgsIds
333
+ */
334
+ @JsonProperty("organizations_ids")
335
+ public void setOrgsIds(List<Integer> orgsIds) {
336
+ organizations = new HashSet<>();
337
+ for (Integer orgid : orgsIds) {
338
+ Organization o = new Organization();
339
+ o.setId(orgid);
340
+ organizations.add(o);
341
+ }
342
+ }
234343
235
- @JsonProperty("applications_ids")
236
- public Set<Integer> getAppsIds() {
237
- if (applications == null) {
238
- return null;
239
- }
240
- Set<Integer> ids = new HashSet<>();
241
- for (Application app : applications) {
242
- ids.add(app.getId());
243
- }
244
- return ids;
245
- }
344
+ /**
345
+ * getOrgsIds<p>
346
+ * Expose organization IDs.
347
+ *
348
+ * @return orgsIds
349
+ */
350
+ @JsonProperty("organizations_ids")
351
+ public Set<Integer> getOrgsIds() {
352
+ if (organizations == null) return null;
353
+ Set<Integer> ids = new HashSet<>();
354
+ for (Organization org : organizations) ids.add(org.getId());
355
+ return ids;
356
+ }
246357
247
- @JsonIgnore
248
- public Set<Integer> getAllOrgsIds() {
249
- if (organizations == null) {
250
- return null;
251
- }
252
- Set<Integer> ids = new HashSet<>();
253
- includeAllOrgs(this.organizations, ids);
254
- return ids;
255
- }
358
+ /**
359
+ * setAppsIds<p>
360
+ * Replace applications from a collection of app IDs.
361
+ *
362
+ * @param appIds
363
+ */
364
+ @JsonProperty("applications_ids")
365
+ public void setAppsIds(Collection<Integer> appIds) {
366
+ applications = new HashSet<>();
367
+ for (Integer appid : appIds) {
368
+ Application a = new Application();
369
+ a.setId(appid);
370
+ applications.add(a);
371
+ }
372
+ }
256373
257
- @JsonIgnore
258
- public Set<Integer> getAllAppsIds() {
259
- if (applications == null) {
260
- return null;
261
- }
262
- Set<Integer> ids = this.applications.parallelStream().map(app -> app.getId()).collect(Collectors.toSet());
374
+ /**
375
+ * getAppsIds<p>
376
+ * Expose application IDs.
377
+ *
378
+ * @return appsIds
379
+ */
380
+ @JsonProperty("applications_ids")
381
+ public Set<Integer> getAppsIds() {
382
+ if (applications == null) return null;
383
+ Set<Integer> ids = new HashSet<>();
384
+ for (Application app : applications) ids.add(app.getId());
385
+ return ids;
386
+ }
263387
264
- return ids;
265
- }
388
+ // -------- Derived scopes --------
266389
267
- /**
268
- * Walk into the organization hierarchy to include all descendants
269
- *
270
- * @param list
271
- * @param orgIds
272
- */
273
- private void includeAllOrgs(Set<Organization> list, Set<Integer> orgIds) {
274
- for (Organization org : list) {
275
- orgIds.add(org.getId());
276
- includeAllOrgs(org.getChildOrganizations(), orgIds);
277
- }
278
- }
390
+ /**
391
+ * getAllOrgsIds<p>
392
+ * Compute full organization scope including descendants.
393
+ *
394
+ * @return set of org IDs (may be null when no organizations)
395
+ */
396
+ @JsonIgnore
397
+ public Set<Integer> getAllOrgsIds() {
398
+ if (organizations == null) return null;
399
+ Set<Integer> ids = new HashSet<>();
400
+ includeAllOrgs(this.organizations, ids);
401
+ return ids;
402
+ }
279403
280
- public String getEmail() {
281
- return email;
282
- }
404
+ /**
405
+ * getAllAppsIds<p>
406
+ * Compute application scope (direct associations only).
407
+ *
408
+ * @return set of application IDs (may be null when no applications)
409
+ */
410
+ @JsonIgnore
411
+ public Set<Integer> getAllAppsIds() {
412
+ if (applications == null) return null;
413
+ return this.applications.parallelStream().map(Application::getId).collect(Collectors.toSet());
414
+ }
283415
284
- public void setEmail(String email) {
285
- this.email = email;
286
- }
416
+ /**
417
+ * includeAllOrgs<p>
418
+ * Walk organization hierarchy to include all descendants.
419
+ *
420
+ * @param list current level orgs
421
+ * @param orgIds accumulator of ids
422
+ */
423
+ private void includeAllOrgs(Set<Organization> list, Set<Integer> orgIds) {
424
+ for (Organization org : list) {
425
+ orgIds.add(org.getId());
426
+ includeAllOrgs(org.getChildOrganizations(), orgIds);
427
+ }
428
+ }
287429
288
- /**
289
- * Numeric rol mask. Be aware to use different bit position for each role
290
- *
291
- * @author rob
292
- */
293
- public static class Rol {
294
- public static final int ADVANCE = 0x01;
295
- public static final int ADMIN = 0x02;
296
- public static final int BASIC = 0x04;
297
- public static final int API_CLIENT = 0x80;
298
- public static final int[] ALL = new int[] { ADVANCE, ADMIN, BASIC, API_CLIENT };
299
- }
430
+ /**
431
+ * toString<p>
432
+ * Get the string describing the current object
433
+ *
434
+ * @return object string
435
+ */
436
+ @Override
437
+ public String toString() {
438
+ return "{User: " + username + " Name: " + firstName + " " + lastName + ", last login: " + lastLogin + "}";
439
+ }
300440
441
+
442
+ /**
443
+ * Rol
444
+ * <p>
445
+ * Bitmask constants for user roles. Each constant must occupy a distinct bit.
446
+ */
447
+ public static class Rol {
448
+ public static final int ADVANCE = 0x01;
449
+ public static final int ADMIN = 0x02;
450
+ public static final int BASIC = 0x04;
451
+ public static final int API_CLIENT= 0x80;
452
+ public static final int[] ALL = new int[] { ADVANCE, ADMIN, BASIC, API_CLIENT };
453
+ }
301454 }
455
+
securis/src/main/java/net/curisit/securis/db/common/CodedEnum.java
....@@ -1,7 +1,23 @@
1
+/*
2
+* Copyright @ 2013 CurisTEC, S.A.S. All Rights Reserved.
3
+*/
14 package net.curisit.securis.db.common;
25
6
+/**
7
+* CodedEnum
8
+* <p>
9
+* Small contract for enums persisted as short codes
10
+*/
311 public interface CodedEnum {
412
5
- public String getCode();
13
+ /**
14
+ * getCode<p>
15
+ * Return the short string code for persistence/JSON.
16
+ *
17
+ * @return codeEnum
18
+ */
19
+ String getCode();
620
721 }
22
+
23
+
securis/src/main/java/net/curisit/securis/db/common/CreationTimestampEntity.java
....@@ -1,10 +1,31 @@
1
+/*
2
+* Copyright @ 2013 CurisTEC, S.A.S. All Rights Reserved.
3
+*/
14 package net.curisit.securis.db.common;
25
36 import java.util.Date;
47
8
+/**
9
+* CreationTimestampEntity
10
+* <p>
11
+* Contract for entities that expose a creation timestamp property.
12
+*/
513 public interface CreationTimestampEntity {
614
7
- public Date getCreationTimestamp();
15
+ /**
16
+ * getCreationTimestamp<p>
17
+ * Return creation timestamp.
18
+ *
19
+ * @return creationTimeStamp
20
+ */
21
+ Date getCreationTimestamp();
822
9
- public void setCreationTimestamp(Date creationTimestamp);
23
+ /**
24
+ * setCreationTimestamp<p>
25
+ * Set creation timestamp.
26
+ *
27
+ * @param creationTimestamp
28
+ */
29
+ void setCreationTimestamp(Date creationTimestamp);
1030 }
31
+
securis/src/main/java/net/curisit/securis/db/common/LicenseStatusType.java
....@@ -1,12 +1,30 @@
1
+/*
2
+* Copyright @ 2013 CurisTEC, S.A.S. All Rights Reserved.
3
+*/
14 package net.curisit.securis.db.common;
25
36 import net.curisit.securis.db.LicenseStatus;
47
8
+/**
9
+* LicenseStatusType
10
+* <p>
11
+* Hibernate user type that persists {@link LicenseStatus} using its code.
12
+* Delegates specifics to {@link PersistentEnumUserType}.
13
+*
14
+* @author JRA
15
+* Last reviewed by JRA on Oct 5, 2025.
16
+*/
517 public class LicenseStatusType extends PersistentEnumUserType<LicenseStatus> {
618
19
+ /**
20
+ * returnedClass<p>
21
+ * Return the enum class handled by this type.
22
+ *
23
+ * @return licenseStatus
24
+ */
725 @Override
826 public Class<LicenseStatus> returnedClass() {
927 return LicenseStatus.class;
1028 }
11
-
1229 }
30
+
securis/src/main/java/net/curisit/securis/db/common/Metadata.java
....@@ -1,16 +1,64 @@
1
+/*
2
+* Copyright @ 2013 CurisTEC, S.A.S. All Rights Reserved.
3
+*/
14 package net.curisit.securis.db.common;
25
6
+/**
7
+* Metadata
8
+* <p>
9
+* Contract for metadata entries (key/value/mandatory).
10
+* Implemented by ApplicationMetadata, LicenseTypeMetadata, PackMetadata, etc.
11
+*
12
+* @author JRA
13
+* Last reviewed by JRA on Oct 5, 2025.
14
+*/
315 public interface Metadata {
4
- public String getKey();
16
+
17
+ /**
18
+ * getKey<p>
19
+ * Return entry key.
20
+ *
21
+ * @return key
22
+ */
23
+ String getKey();
24
+
25
+ /**
26
+ * setKey<p>
27
+ * Set entry key.
28
+ *
29
+ * @param key
30
+ */
31
+ void setKey(String key);
532
6
- public void setKey(String key);
33
+ /**
34
+ * getValue<p>
35
+ * Return entry value.
36
+ *
37
+ * @return value
38
+ */
39
+ String getValue();
40
+
41
+ /**
42
+ * setValue<p>
43
+ * Set entry value.
44
+ *
45
+ * @param value
46
+ */
47
+ void setValue(String value);
748
8
- public String getValue();
9
-
10
- public void setValue(String value);
11
-
12
- public boolean isMandatory();
13
-
14
- public void setMandatory(boolean mandatory);
15
-
49
+ /**
50
+ * isMandatory<p>
51
+ * Whether this metadata is required.
52
+ *
53
+ * @return isMandatory
54
+ */
55
+ boolean isMandatory();
56
+
57
+ /**
58
+ * setMandatory<p>
59
+ * Set required flag.
60
+ *
61
+ * @param mandatory
62
+ */
63
+ void setMandatory(boolean mandatory);
1664 }
securis/src/main/java/net/curisit/securis/db/common/ModificationTimestampEntity.java
....@@ -1,10 +1,39 @@
1
+/*
2
+* Copyright @ 2013 CurisTEC, S.A.S. All Rights Reserved.
3
+*/
14 package net.curisit.securis.db.common;
25
36 import java.util.Date;
47
8
+/**
9
+* ModificationTimestampEntity
10
+* <p>
11
+* Contract for entities that track a <b>modification timestamp</b>.
12
+* Typical usage: attach {@code @EntityListeners(ModificationTimestampListener.class)}
13
+* so the value is updated automatically on persist/update.
14
+*
15
+* Methods:
16
+* - {@link #getModificationTimestamp()} exposes the timestamp.
17
+* - {@link #setModificationTimestamp(Date)} sets the timestamp.
18
+*
19
+* @author JRA
20
+* Last reviewed by JRA on Oct 7, 2025.
21
+*/
522 public interface ModificationTimestampEntity {
623
7
- public Date getModificationTimestamp();
24
+ /**
25
+ * getModificationTimestamp<p>
26
+ * Return the last modification timestamp.
27
+ *
28
+ * @return modificationTimestamp
29
+ */
30
+ Date getModificationTimestamp();
831
9
- public void setModificationTimestamp(Date modificationTimestamp);
32
+ /**
33
+ * setModificationTimestamp<p>
34
+ * Set/update the last modification timestamp.
35
+ *
36
+ * @param modificationTimestamp
37
+ */
38
+ void setModificationTimestamp(Date modificationTimestamp);
1039 }
securis/src/main/java/net/curisit/securis/db/common/PackStatusType.java
....@@ -1,12 +1,29 @@
1
+/*
2
+* Copyright @ 2013 CurisTEC, S.A.S. All Rights Reserved.
3
+*/
14 package net.curisit.securis.db.common;
25
36 import net.curisit.securis.db.PackStatus;
47
8
+/**
9
+* PackStatusType
10
+* <p>
11
+* Hibernate {@code UserType} for persisting {@link PackStatus} as its short code.
12
+* Delegates common enum-code mapping behavior to {@link PersistentEnumUserType}.
13
+*
14
+* @author JRA
15
+* Last reviewed by JRA on Oct 7, 2025.
16
+*/
517 public class PackStatusType extends PersistentEnumUserType<PackStatus> {
618
19
+ /**
20
+ * returnedClass<p>
21
+ * Return enum class handled by this type.
22
+ *
23
+ * @return packStatus
24
+ */
725 @Override
826 public Class<PackStatus> returnedClass() {
927 return PackStatus.class;
1028 }
11
-
1229 }
securis/src/main/java/net/curisit/securis/db/common/PersistentEnumUserType.java
....@@ -1,3 +1,6 @@
1
+/*
2
+* Copyright @ 2013 CurisTEC, S.A.S. All Rights Reserved.
3
+*/
14 package net.curisit.securis.db.common;
25
36 import java.io.Serializable;
....@@ -10,57 +13,150 @@
1013 import org.hibernate.engine.spi.SharedSessionContractImplementor;
1114 import org.hibernate.usertype.UserType;
1215
16
+/**
17
+* PersistentEnumUserType
18
+* <p>
19
+* Base Hibernate {@link UserType} for enums implementing {@link CodedEnum}.
20
+* Stores the enum's <b>short code</b> (VARCHAR) and reconstructs the enum
21
+* from that code on load.
22
+*
23
+* Notes:
24
+* - SQL type is {@code VARCHAR}.
25
+* - Immutable by default ({@link #isMutable()} returns false).
26
+* - {@link #equals(Object, Object)} compares by reference (adequate for enums).
27
+*
28
+* @param <T> enum type implementing {@link CodedEnum}
29
+*
30
+* @author JRA
31
+* Last reviewed by JRA on Oct 7, 2025.
32
+*/
1333 public abstract class PersistentEnumUserType<T extends CodedEnum> implements UserType {
1434
35
+ /**
36
+ * assemble<p>
37
+ * Return cached value as-is (immutable semantics).
38
+ *
39
+ * @param cached
40
+ * @param owner
41
+ * @return assembleObject
42
+ * @throws HibernateException
43
+ */
1544 @Override
1645 public Object assemble(Serializable cached, Object owner) throws HibernateException {
1746 return cached;
1847 }
1948
49
+ /**
50
+ * deepCopy<p>
51
+ * Enums are immutable; return value as-is.
52
+ *
53
+ * @param value
54
+ * @return deepCopy
55
+ */
2056 @Override
2157 public Object deepCopy(Object value) throws HibernateException {
2258 return value;
2359 }
2460
61
+ /**
62
+ * disassemble<p>
63
+ * Return value for 2nd-level cache.
64
+ *
65
+ * @param value
66
+ * @return disassembleObject
67
+ * @throw HibernateException
68
+ */
2569 @Override
2670 public Serializable disassemble(Object value) throws HibernateException {
2771 return (Serializable) value;
2872 }
2973
74
+ /**
75
+ * equals<p>
76
+ * Reference equality is fine for enums.
77
+ *
78
+ * @param x
79
+ * @param y
80
+ * @param isEqual
81
+ * @throws HibernateException
82
+ */
3083 @Override
3184 public boolean equals(Object x, Object y) throws HibernateException {
3285 return x == y;
3386 }
3487
88
+ /**
89
+ * hashCode<p>
90
+ * Delegate to value hashCode; 0 for null.
91
+ *
92
+ * @param object
93
+ * @return hashCode
94
+ * @throws HibernateException
95
+ */
3596 @Override
3697 public int hashCode(Object x) throws HibernateException {
3798 return x == null ? 0 : x.hashCode();
3899 }
39100
101
+ /**
102
+ * isMutable<p>
103
+ * Enums are immutable.
104
+ *
105
+ * @return isMutable
106
+ */
40107 @Override
41108 public boolean isMutable() {
42109 return false;
43110 }
44111
112
+ /**
113
+ * replace<p>
114
+ * Immutable; return original.
115
+ *
116
+ * @param original
117
+ * @param target
118
+ * @param owner
119
+ * @return object
120
+ * @throws HibernateException
121
+ */
45122 @Override
46123 public Object replace(Object original, Object target, Object owner) throws HibernateException {
47124 return original;
48125 }
49126
127
+ /**
128
+ * returnedClass<p>
129
+ * Concrete types must provide the enum class.
130
+ */
50131 @Override
51132 public abstract Class<T> returnedClass();
52133
134
+ /**
135
+ * sqlTypes<p>
136
+ * Persist as single VARCHAR column
137
+ *
138
+ * @return sqlTypes
139
+ */
53140 @Override
54141 public int[] sqlTypes() {
55
- return new int[] {
56
- Types.VARCHAR
57
- };
142
+ return new int[] { Types.VARCHAR };
58143 }
59144
145
+ /**
146
+ * nullSafeGet<p>
147
+ * Read code from result set and map to the corresponding enum constant.
148
+ *
149
+ * @param resultSet
150
+ * @param names
151
+ * @param session
152
+ * @param owner
153
+ * @return enum instance or null if DB value is null or code not matched
154
+ */
60155 @Override
61156 public Object nullSafeGet(ResultSet rs, String[] names, SharedSessionContractImplementor session, Object owner)
62157 throws HibernateException, SQLException {
63158 String code = rs.getString(names[0]);
159
+ if (code == null) return null;
64160 for (CodedEnum en : returnedClass().getEnumConstants()) {
65161 if (en.getCode().equals(code)) {
66162 return en;
....@@ -69,6 +165,15 @@
69165 return null;
70166 }
71167
168
+ /**
169
+ * nullSafeSet<p>
170
+ * Write enum code as VARCHAR or set NULL if value is null.
171
+ *
172
+ * @param statement
173
+ * @param value
174
+ * @param index
175
+ * @param session
176
+ */
72177 @Override
73178 public void nullSafeSet(PreparedStatement st, Object value, int index, SharedSessionContractImplementor session)
74179 throws HibernateException, SQLException {
....@@ -78,5 +183,5 @@
78183 st.setString(index, ((CodedEnum) value).getCode());
79184 }
80185 }
81
-
82186 }
187
+
securis/src/main/java/net/curisit/securis/db/common/SystemParams.java
....@@ -1,3 +1,6 @@
1
+/*
2
+* Copyright @ 2013 CurisTEC, S.A.S. All Rights Reserved.
3
+*/
14 package net.curisit.securis.db.common;
25
36 import java.util.Date;
....@@ -13,120 +16,148 @@
1316 import org.apache.logging.log4j.LogManager;
1417 import org.apache.logging.log4j.Logger;
1518
19
+/**
20
+* SystemParams
21
+* <p>
22
+* Simple façade to read/write application-wide parameters stored in the
23
+* {@code settings} table (key/value + timestamps).
24
+*
25
+* Features:
26
+* - Typed getters: {@code String}, {@code Integer}, {@code Boolean}, {@code Double}, {@code Date}.
27
+* - Default value overloads.
28
+* - Upsert semantics in {@link #setParam(String, String)} and typed variants.
29
+* - Removal with {@link #removeParam(String)}.
30
+*
31
+* Transaction note:
32
+* - Each write method starts and commits its own transaction. Rollback is invoked
33
+* only on exceptions.
34
+*
35
+* @author JRA
36
+* Last reviewed by JRA on Oct 7, 2025.
37
+*/
1638 @ApplicationScoped
1739 public class SystemParams {
1840
1941 @SuppressWarnings("unused")
2042 private static final Logger LOG = LogManager.getLogger(SystemParams.class);
2143
22
- @Inject
23
- private EntityManagerProvider emProvider;
44
+ @Inject private EntityManagerProvider emProvider;
45
+
46
+ // -------------------- Read API --------------------
2447
2548 /**
26
- * Returns the system parameter value for given key
27
- *
28
- * @param key
29
- * @return the value of the param or null if it doesn't exist
30
- */
49
+ * getParam<p>
50
+ * Get raw string value or {@code null} when absent.
51
+ *
52
+ * @param key setting key
53
+ * @return string value or null
54
+ */
3155 public String getParam(String key) {
3256 return getParam(key, null);
3357 }
3458
3559 /**
36
- * Returns the system parameter as int value for given key
37
- *
38
- * @param key
39
- * @return the value of the param or null if it doesn't exist
40
- */
60
+ * getParamAsInt<p>
61
+ * Get value as Integer or null when absent.
62
+ *
63
+ * @param key setting key
64
+ * @return integer value or null
65
+ */
4166 public Integer getParamAsInt(String key) {
4267 String value = getParam(key, null);
4368 return value == null ? null : Integer.parseInt(value);
4469 }
4570
4671 /**
47
- *
48
- * @param key
49
- * @param defaulValue
50
- * returned if key doesn't exist in params table
51
- * @return
52
- */
72
+ * getParamAsInt<p>
73
+ * Get value as Integer with default.
74
+ *
75
+ * @param key setting key
76
+ * @param defaulValue returned if key is missing
77
+ * @return integer value or default
78
+ */
5379 public Integer getParamAsInt(String key, Integer defaulValue) {
5480 String value = getParam(key, null);
5581 return value == null ? defaulValue : Integer.parseInt(value);
5682 }
5783
5884 /**
59
- * Returns the system parameter as Date value for given key
60
- *
61
- * @param key
62
- * @return the value of the param or null if it doesn't exist
63
- */
85
+ * getParamAsDate<p>
86
+ * Get value parsed from ISO-8601.
87
+ *
88
+ * @param key setting key
89
+ * @return date value or null
90
+ */
6491 public Date getParamAsDate(String key) {
6592 String value = getParam(key, null);
6693 return value == null ? null : Utils.toDateFromIso(value);
6794 }
6895
6996 /**
70
- * Returns the system parameter as boolean value for given key
71
- *
72
- * @param key
73
- * @return the value of the param or null if it doesn't exist
74
- */
97
+ * getParamAsBool<p>
98
+ * Get value parsed as boolean.
99
+ *
100
+ * @param key setting key
101
+ * @return boolean value or null
102
+ */
75103 public Boolean getParamAsBool(String key) {
76104 String value = getParam(key, null);
77105 return value == null ? null : Boolean.parseBoolean(value);
78106 }
79107
80108 /**
81
- *
82
- * @param key
83
- * @param defaulValue
84
- * returned if key doesn't exist in params table
85
- * @return
86
- */
109
+ * getParamAsBool<p>
110
+ * Get value parsed as boolean with default.
111
+ *
112
+ * @param key setting key
113
+ * @param defaulValue default when missing
114
+ * @return boolean value or default
115
+ */
87116 public Boolean getParamAsBool(String key, boolean defaulValue) {
88117 String value = getParam(key, null);
89118 return value == null ? defaulValue : Boolean.parseBoolean(value);
90119 }
91120
92121 /**
93
- * Returns the system parameter as boolean value for given key
94
- *
95
- * @param key
96
- * @return the value of the param or null if it doesn't exist
97
- */
122
+ * getParamAsDouble<p>
123
+ * Get value parsed as double.
124
+ *
125
+ * @param key setting key
126
+ * @return double value or null
127
+ */
98128 public Double getParamAsDouble(String key) {
99129 String value = getParam(key, null);
100130 return value == null ? null : Double.parseDouble(value);
101131 }
102132
103133 /**
104
- * Returns the system parameter value for given key
105
- *
106
- * @param key
107
- * @param defaultValue
108
- * returned if key doesn't exist in params table
109
- * @return
110
- */
134
+ * getParam<p>
135
+ * Get raw string value or a default when missing.
136
+ *
137
+ * @param key setting key
138
+ * @param defaultValue default when missing
139
+ * @return value or default
140
+ */
111141 public String getParam(String key, String defaultValue) {
112142 EntityManager em = emProvider.getEntityManager();
113143 Settings p = em.find(Settings.class, key);
114144 return p == null ? defaultValue : p.getValue();
115145 }
116146
147
+ // -------------------- Write API --------------------
148
+
117149 /**
118
- * Returns the system parameter value passed as parameter to method
119
- *
120
- * @param key
121
- * @param defaultValue
122
- * @return
123
- */
150
+ * setParam<p>
151
+ * Upsert a parameter as string.
152
+ *
153
+ * @param key setting key
154
+ * @param value string value
155
+ */
124156 public void setParam(String key, String value) {
125157 EntityManager em = emProvider.getEntityManager();
126158 em.getTransaction().begin();
127159 try {
128160 Settings p = em.find(Settings.class, key);
129
-
130161 if (p == null) {
131162 p = new Settings();
132163 p.setKey(key);
....@@ -136,15 +167,16 @@
136167 p.setValue(value);
137168 em.merge(p);
138169 }
139
- em.flush();
140170 em.getTransaction().commit();
141
- } finally {
171
+ } catch (Exception ex) {
142172 em.getTransaction().rollback();
173
+ throw ex;
143174 }
144175 }
145176
146
- /**
147
- * Save a parameter as a Date
177
+ /**
178
+ * setParam<p>
179
+ * Save parameter as ISO date string.
148180 *
149181 * @param key
150182 * @param value
....@@ -153,8 +185,9 @@
153185 setParam(key, Utils.toIsoFormat(value));
154186 }
155187
156
- /**
157
- * Save a parameter as a integer
188
+ /**
189
+ * setParam<p>
190
+ * Save parameter as integer.
158191 *
159192 * @param key
160193 * @param value
....@@ -163,8 +196,9 @@
163196 setParam(key, String.valueOf(value));
164197 }
165198
166
- /**
167
- * Save a parameter as a boolean
199
+ /**
200
+ * setParam<p>
201
+ * Save parameter as boolean.
168202 *
169203 * @param key
170204 * @param value
....@@ -173,8 +207,9 @@
173207 setParam(key, String.valueOf(value));
174208 }
175209
176
- /**
177
- * Save a parameter as a double
210
+ /**
211
+ * setParam<p>
212
+ * Save parameter as double.
178213 *
179214 * @param key
180215 * @param value
....@@ -184,11 +219,11 @@
184219 }
185220
186221 /**
187
- * Remove a parameter from params table
188
- *
189
- * @param key
190
- * @return
191
- */
222
+ * removeParam<p>
223
+ * Delete a parameter by key (no-op if missing).
224
+ *
225
+ * @param key setting key
226
+ */
192227 public void removeParam(String key) {
193228 EntityManager em = emProvider.getEntityManager();
194229 em.getTransaction().begin();
....@@ -198,13 +233,19 @@
198233 em.remove(p);
199234 }
200235 em.getTransaction().commit();
201
- } finally {
236
+ } catch (Exception ex) {
202237 em.getTransaction().rollback();
238
+ throw ex;
203239 }
204240 }
205241
242
+ /**
243
+ * Keys
244
+ * <p>
245
+ * Centralized constants for parameter keys (client/common/server).
246
+ */
206247 public static class Keys {
207
- // Keys used in basic app
248
+ // Client app keys
208249 public static final String CONFIG_CLIENT_HOST = "config.client.host";
209250 public static final String CONFIG_CLIENT_PORT = "config.client.port";
210251 public static final String CONFIG_CLIENT_LAST_UPDATE = "config.client.last_update";
....@@ -217,9 +258,9 @@
217258 public static final String CONFIG_CLIENT_GS_HOST = "config.client.gs_host";
218259 public static final String CONFIG_CLIENT_GS_PORT = "config.client.gs_port";
219260
220
- // Keys used in both app
221
- public static final String CONFIG_COMMON_CUSTOMER_CODE = "config.common.customer_code"; // BP
222
- public static final String CONFIG_COMMON_CS_CODE = "config.common.cs_code"; // 0000
261
+ // Shared keys
262
+ public static final String CONFIG_COMMON_CUSTOMER_CODE = "config.common.customer_code";
263
+ public static final String CONFIG_COMMON_CS_CODE = "config.common.cs_code";
223264 public static final String CONFIG_COMMON_USERS_VERSION = "config.common.user_version";
224265 public static final String CONFIG_COMMON_SETTINGS_VERSION = "config.common.settings_version";
225266 public static final String CONFIG_COMMON_DATASET_VERSION = "config.common.dataset_version";
....@@ -227,7 +268,7 @@
227268 public static final String CONFIG_COMMON_TIMEOUT_SESSION_BA = "config.common.timeout_session_ba";
228269 public static final String CONFIG_COMMON_TIMEOUT_SESSION_CS = "config.common.timeout_session_cs";
229270
230
- // Keys used in server app
271
+ // Server app keys
231272 public static final String CONFIG_SERVER_LICENSE_EXPIRATION = "config.server.license.expiation";
232273 public static final String CONFIG_SERVER_MAX_INSTANCES = "config.server.max_instances";
233274 public static final String CONFIG_SERVER_MAX_USERS = "config.server.max_users";
....@@ -240,5 +281,4 @@
240281 public static final String CONFIG_SERVER_CREATE_DATASET = "config.server.create_dataset_in_next_startup";
241282 public static final String CONFIG_SERVER_PORT = "config.server.port";
242283 }
243
-
244284 }
securis/src/main/java/net/curisit/securis/db/listeners/CreationTimestampListener.java
....@@ -1,3 +1,6 @@
1
+/*
2
+* Copyright @ 2013 CurisTEC, S.A.S. All Rights Reserved.
3
+*/
14 package net.curisit.securis.db.listeners;
25
36 import java.util.Date;
....@@ -9,14 +12,31 @@
912 import org.apache.logging.log4j.LogManager;
1013 import org.apache.logging.log4j.Logger;
1114
15
+/**
16
+* CreationTimestampListener
17
+* <p>
18
+* JPA entity listener that sets the <b>creation timestamp</b> right before persisting.
19
+* Entities must implement {@link CreationTimestampEntity} to be compatible.
20
+*
21
+* Usage:
22
+* {@code @EntityListeners(CreationTimestampListener.class)}
23
+*
24
+* @author JRA
25
+* Last reviewed by JRA on Oct 5, 2025.
26
+*/
1227 public class CreationTimestampListener {
1328
1429 private static final Logger log = LogManager.getLogger(CreationTimestampListener.class);
1530
31
+ /**
32
+ * updateTimestamp<p>
33
+ * Set creation timestamp during @PrePersist.
34
+ *
35
+ * @param creationTimeStampEntity
36
+ */
1637 @PrePersist
1738 public void updateTimestamp(CreationTimestampEntity p) {
18
- log.info("Settings creation timestmap date");
39
+ log.info("Settings creation timestamp date");
1940 p.setCreationTimestamp(new Date());
2041 }
21
-
2242 }
securis/src/main/java/net/curisit/securis/db/listeners/ModificationTimestampListener.java
....@@ -1,3 +1,6 @@
1
+/*
2
+* Copyright @ 2013 CurisTEC, S.A.S. All Rights Reserved.
3
+*/
14 package net.curisit.securis.db.listeners;
25
36 import java.util.Date;
....@@ -10,15 +13,30 @@
1013 import org.apache.logging.log4j.LogManager;
1114 import org.apache.logging.log4j.Logger;
1215
16
+/**
17
+* ModificationTimestampListener
18
+* <p>
19
+* JPA entity listener that updates the <b>modification timestamp</b> on both
20
+* persist and update events. Entities must implement
21
+* {@link ModificationTimestampEntity}.
22
+*
23
+* @author JRA
24
+* Last reviewed by JRA on Oct 5, 2025.
25
+*/
1326 public class ModificationTimestampListener {
1427
1528 private static final Logger log = LogManager.getLogger(ModificationTimestampListener.class);
1629
30
+ /**
31
+ * updateTimestamp<p>
32
+ * Set modification timestamp during @PrePersist/@PreUpdate.
33
+ *
34
+ * @param modificationTimestampEntity
35
+ */
1736 @PreUpdate
1837 @PrePersist
1938 public void updateTimestamp(ModificationTimestampEntity p) {
20
- log.info("Settings modification timestmap date");
39
+ log.info("Settings modification timestamp date");
2140 p.setModificationTimestamp(new Date());
2241 }
23
-
2442 }
securis/src/main/java/net/curisit/securis/ioc/EnsureTransaction.java
....@@ -1,3 +1,6 @@
1
+/*
2
+* Copyright @ 2013 CurisTEC, S.A.S. All Rights Reserved.
3
+*/
14 package net.curisit.securis.ioc;
25
36 import java.lang.annotation.ElementType;
....@@ -7,11 +10,20 @@
710
811 import jakarta.interceptor.InterceptorBinding;
912
10
-@Target({
11
- ElementType.METHOD, ElementType.TYPE
12
-})
13
+/**
14
+* EnsureTransaction
15
+* <p>
16
+* CDI interceptor binding to mark resource methods that require a
17
+* transaction boundary. Interceptors (e.g., in a request filter / writer
18
+* interceptor) can check this annotation to begin/commit/rollback.
19
+*
20
+* Usage:
21
+* {@code @EnsureTransaction} on JAX-RS methods.
22
+*
23
+* @author JRA
24
+* Last reviewed by JRA on Oct 5, 2025.
25
+*/
26
+@Target({ ElementType.METHOD, ElementType.TYPE })
1327 @Retention(RetentionPolicy.RUNTIME)
1428 @InterceptorBinding
15
-public @interface EnsureTransaction {
16
-
17
-}
29
+public @interface EnsureTransaction { }
securis/src/main/java/net/curisit/securis/ioc/EntityManagerProvider.java
....@@ -1,3 +1,6 @@
1
+/*
2
+* Copyright @ 2013 CurisTEC, S.A.S. All Rights Reserved.
3
+*/
14 package net.curisit.securis.ioc;
25
36 import jakarta.enterprise.context.ApplicationScoped;
....@@ -8,16 +11,38 @@
811 import org.apache.logging.log4j.LogManager;
912 import org.apache.logging.log4j.Logger;
1013
14
+/**
15
+* EntityManagerProvider
16
+* <p>
17
+* Simple provider for JPA {@link EntityManager} instances using the
18
+* persistence unit "localdb". Creates an {@link EntityManagerFactory}
19
+* once per application and returns a fresh {@link EntityManager} per call.
20
+*
21
+* Note:
22
+* - Callers are responsible for closing the obtained EntityManager.
23
+*
24
+* @author JRA
25
+* Last reviewed by JRA on Oct 5, 2025.
26
+*/
1127 @ApplicationScoped
1228 public class EntityManagerProvider {
1329
1430 @SuppressWarnings("unused")
15
- private static final Logger log = LogManager.getLogger(EntityManagerProvider.class);
31
+ private static final Logger log = LogManager.getLogger(EntityManagerProvider.class);
1632
33
+ /**
34
+ * entityManagerFactory<p>
35
+ * Application-wide EMF built from persistence.xml PU "localdb".
36
+ */
1737 private final EntityManagerFactory entityManagerFactory = Persistence.createEntityManagerFactory("localdb");
1838
39
+ /**
40
+ * getEntityManager<p>
41
+ * Create a new {@link EntityManager}.
42
+ *
43
+ * @return a new EntityManager; caller must close it
44
+ */
1945 public EntityManager getEntityManager() {
2046 return entityManagerFactory.createEntityManager();
2147 }
22
-
2348 }
securis/src/main/java/net/curisit/securis/ioc/RequestsInterceptor.java
....@@ -1,3 +1,6 @@
1
+/*
2
+* Copyright @ 2013 CurisTEC, S.A.S. All Rights Reserved.
3
+*/
14 package net.curisit.securis.ioc;
25
36 import java.io.IOException;
....@@ -31,143 +34,214 @@
3134 import net.curisit.securis.utils.CacheTTL;
3235 import net.curisit.securis.utils.TokenHelper;
3336
37
+/**
38
+* RequestsInterceptor
39
+* <p>
40
+* Authentication/authorization interceptor that:
41
+* <ul>
42
+* <li>Loads and stores the {@link EntityManager} in the request context.</li>
43
+* <li>Validates tokens for methods annotated with {@link Securable}.</li>
44
+* <li>Builds a {@link BasicSecurityContext} with roles and scoped organization/application IDs.</li>
45
+* <li>Manages transactions when {@code @EnsureTransaction} is present.</li>
46
+* </ul>
47
+*
48
+* <p><b>Cache usage:</b> Uses {@link CacheTTL} to cache roles and scope sets.
49
+* The new {@link CacheTTL#getSet(String, Class)} helper removes unchecked
50
+* conversion warnings when retrieving {@code Set<Integer>} from the cache.
51
+*
52
+* @author JRA
53
+* Last reviewed by JRA on Oct 5, 2025.
54
+*/
3455 @Provider
3556 @Priority(Priorities.AUTHENTICATION)
3657 public class RequestsInterceptor implements ContainerRequestFilter, WriterInterceptor {
3758
38
- private static final Logger LOG = LogManager.getLogger(RequestsInterceptor.class);
59
+ private static final Logger LOG = LogManager.getLogger(RequestsInterceptor.class);
3960
40
- @Context
41
- private HttpServletResponse servletResponse;
61
+ @Inject private CacheTTL cache;
62
+ @Inject private TokenHelper tokenHelper;
63
+ @Inject private EntityManagerProvider emProvider;
4264
43
- @Context
44
- private HttpServletRequest servletRequest;
65
+ @Context private HttpServletResponse servletResponse;
66
+ @Context private HttpServletRequest servletRequest;
67
+ @Context private ResourceInfo resourceInfo;
4568
46
- @Context
47
- private ResourceInfo resourceInfo;
69
+ private static final String EM_CONTEXT_PROPERTY = "curisit.entitymanager";
4870
49
- @Inject
50
- private CacheTTL cache;
71
+ // -------------------------------------------------------------
72
+ // Request filter (authN/authZ + EM handling)
73
+ // -------------------------------------------------------------
5174
52
- @Inject
53
- private TokenHelper tokenHelper;
75
+ /**
76
+ * filter<p>
77
+ * Entry point before resource method invocation.
78
+ *
79
+ * @param requestContext
80
+ * @throws IOException
81
+ */
82
+ @Override
83
+ public void filter(ContainerRequestContext requestContext) throws IOException {
84
+ EntityManager em = emProvider.getEntityManager();
85
+ LOG.debug("GETTING EM: {}", em);
5486
55
- @Inject
56
- private EntityManagerProvider emProvider;
87
+ // Store EntityManager for later retrieval (writer interceptor)
88
+ requestContext.setProperty(EM_CONTEXT_PROPERTY, em);
5789
58
- private static final String EM_CONTEXT_PROPERTY = "curisit.entitymanager";
90
+ Method method = resourceInfo.getResourceMethod();
5991
60
- @Override
61
- public void filter(ContainerRequestContext requestContext) throws IOException {
62
- EntityManager em = emProvider.getEntityManager();
63
- LOG.debug("GETTING EM: {}", em);
92
+ if (checkSecurableMethods(requestContext, method)) {
93
+ if (method.isAnnotationPresent(EnsureTransaction.class)) {
94
+ LOG.debug("Beginning transaction");
95
+ em.getTransaction().begin();
96
+ }
97
+ }
98
+ }
6499
65
- // Guardamos el EntityManager en el contexto para recuperación posterior
66
- requestContext.setProperty(EM_CONTEXT_PROPERTY, em);
100
+ /**
101
+ * checkSecurableMethods<p>
102
+ * Enforce security checks for methods annotated with {@link Securable}.
103
+ * Builds {@link BasicSecurityContext} when authorized.
104
+ *
105
+ * @param ctx
106
+ * @param method
107
+ * @return true if request can proceed; false when aborted
108
+ */
109
+ private boolean checkSecurableMethods(ContainerRequestContext ctx, Method method) {
110
+ if (!method.isAnnotationPresent(Securable.class)) return true;
67111
68
- Method method = resourceInfo.getResourceMethod();
112
+ String token = servletRequest.getHeader(TokenHelper.TOKEN_HEADER_PÀRAM);
113
+ if (token == null || !tokenHelper.isTokenValid(token)) {
114
+ LOG.warn("Access denied, invalid token");
115
+ ctx.abortWith(Response.status(Status.UNAUTHORIZED).build());
116
+ return false;
117
+ }
69118
70
- if (checkSecurableMethods(requestContext, method)) {
71
- if (method.isAnnotationPresent(EnsureTransaction.class)) {
72
- LOG.debug("Beginning transaction");
73
- em.getTransaction().begin();
74
- }
75
- }
76
- }
119
+ String username = tokenHelper.extractUserFromToken(token);
120
+ int roles = getUserRoles(username);
121
+ Securable securable = method.getAnnotation(Securable.class);
77122
78
- private boolean checkSecurableMethods(ContainerRequestContext ctx, Method method) {
79
- if (!method.isAnnotationPresent(Securable.class)) return true;
123
+ if (securable.roles() != 0 && (securable.roles() & roles) == 0) {
124
+ LOG.warn("User {} lacks required roles for method {}", username, method.getName());
125
+ ctx.abortWith(Response.status(Status.UNAUTHORIZED).build());
126
+ return false;
127
+ }
80128
81
- String token = servletRequest.getHeader(TokenHelper.TOKEN_HEADER_PÀRAM);
82
- if (token == null || !tokenHelper.isTokenValid(token)) {
83
- LOG.warn("Access denied, invalid token");
84
- ctx.abortWith(Response.status(Status.UNAUTHORIZED).build());
85
- return false;
86
- }
129
+ BasicSecurityContext sc = new BasicSecurityContext(username, roles, servletRequest.isSecure());
130
+ sc.setOrganizationsIds(getUserOrganizations(username));
131
+ sc.setApplicationsIds(getUserApplications(username));
132
+ ctx.setSecurityContext(sc);
133
+ return true;
134
+ }
87135
88
- String username = tokenHelper.extractUserFromToken(token);
89
- int roles = getUserRoles(username);
90
- Securable securable = method.getAnnotation(Securable.class);
136
+ // -------------------------------------------------------------
137
+ // Cached lookups (roles/orgs/apps)
138
+ // -------------------------------------------------------------
91139
92
- if (securable.roles() != 0 && (securable.roles() & roles) == 0) {
93
- LOG.warn("User {} lacks required roles for method {}", username, method.getName());
94
- ctx.abortWith(Response.status(Status.UNAUTHORIZED).build());
95
- return false;
96
- }
140
+ /**
141
+ * getUserRoles<p>
142
+ * Retrieve roles bitmask for the given user (cached).
143
+ *
144
+ * @param username
145
+ * @return userRoles
146
+ */
147
+ private int getUserRoles(String username) {
148
+ if (username == null) return 0;
149
+ Integer cached = cache.get("roles_" + username, Integer.class);
150
+ if (cached != null) return cached;
97151
98
- BasicSecurityContext sc = new BasicSecurityContext(username, roles, servletRequest.isSecure());
99
- sc.setOrganizationsIds(getUserOrganizations(username));
100
- sc.setApplicationsIds(getUserApplications(username));
101
- ctx.setSecurityContext(sc);
102
- return true;
103
- }
152
+ EntityManager em = emProvider.getEntityManager();
153
+ User user = em.find(User.class, username);
154
+ int roles = 0;
155
+ if (user != null) {
156
+ List<Integer> r = user.getRoles();
157
+ if (r != null) for (Integer role : r) roles += role;
158
+ cache.set("roles_" + username, roles, 3600);
159
+ // also warm some caches
160
+ cache.set("orgs_" + username, user.getOrgsIds(), 3600);
161
+ }
162
+ return roles;
163
+ }
104164
105
- private int getUserRoles(String username) {
106
- if (username == null) return 0;
107
- Integer cached = cache.get("roles_" + username, Integer.class);
108
- if (cached != null) return cached;
165
+ /**
166
+ * getUserOrganizations<p>
167
+ * Retrieve organization scope for the user as a typed {@code Set<Integer>}
168
+ * using the cache helper that validates element types.
169
+ *
170
+ * @param username
171
+ * @return userOrganizations
172
+ */
173
+ private Set<Integer> getUserOrganizations(String username) {
174
+ Set<Integer> cached = cache.getSet("orgs_" + username, Integer.class);
175
+ if (cached != null) return cached;
109176
110
- EntityManager em = emProvider.getEntityManager();
111
- User user = em.find(User.class, username);
112
- int roles = 0;
113
- if (user != null) {
114
- List<Integer> r = user.getRoles();
115
- if (r != null) for (Integer role : r) roles += role;
116
- cache.set("roles_" + username, roles, 3600);
117
- cache.set("orgs_" + username, user.getOrgsIds(), 3600);
118
- }
119
- return roles;
120
- }
177
+ User user = emProvider.getEntityManager().find(User.class, username);
178
+ if (user != null) {
179
+ Set<Integer> result = user.getAllOrgsIds();
180
+ cache.set("orgs_" + username, result, 3600);
181
+ return result;
182
+ }
183
+ return Set.of();
184
+ }
121185
122
- private Set<Integer> getUserOrganizations(String username) {
123
- Set<Integer> cached = cache.get("orgs_" + username, Set.class);
124
- if (cached != null) return cached;
125
- User user = emProvider.getEntityManager().find(User.class, username);
126
- if (user != null) {
127
- Set<Integer> result = user.getAllOrgsIds();
128
- cache.set("orgs_" + username, result, 3600);
129
- return result;
130
- }
131
- return Set.of();
132
- }
186
+ /**
187
+ * getUserApplications<p>
188
+ * Retrieve application scope for the user as a typed {@code Set<Integer>}
189
+ * using the cache helper that validates element types.
190
+ *
191
+ * @param username
192
+ * @return userApplications
193
+ */
194
+ private Set<Integer> getUserApplications(String username) {
195
+ Set<Integer> cached = cache.getSet("apps_" + username, Integer.class);
196
+ if (cached != null) return cached;
133197
134
- private Set<Integer> getUserApplications(String username) {
135
- Set<Integer> cached = cache.get("apps_" + username, Set.class);
136
- if (cached != null) return cached;
137
- User user = emProvider.getEntityManager().find(User.class, username);
138
- if (user != null) {
139
- Set<Integer> result = user.getAllAppsIds();
140
- cache.set("apps_" + username, result, 3600);
141
- return result;
142
- }
143
- return Set.of();
144
- }
198
+ User user = emProvider.getEntityManager().find(User.class, username);
199
+ if (user != null) {
200
+ Set<Integer> result = user.getAllAppsIds();
201
+ cache.set("apps_" + username, result, 3600);
202
+ return result;
203
+ }
204
+ return Set.of();
205
+ }
145206
146
- @Override
147
- public void aroundWriteTo(WriterInterceptorContext context) throws IOException, WebApplicationException {
148
- context.proceed();
207
+ // -------------------------------------------------------------
208
+ // Writer interceptor (transaction finalize)
209
+ // -------------------------------------------------------------
149210
150
- EntityManager em = (EntityManager) context.getProperty(EM_CONTEXT_PROPERTY);
151
- if (em == null) return;
211
+ /**
212
+ * aroundWriteTo<p>
213
+ * Commit/rollback and close EM after response writing.
214
+ *
215
+ * @param context
216
+ * @throws IOException
217
+ * @throws WebApplicationException
218
+ */
219
+ @Override
220
+ public void aroundWriteTo(WriterInterceptorContext context) throws IOException, WebApplicationException {
221
+ context.proceed();
152222
153
- try {
154
- if (em.getTransaction().isActive()) {
155
- if (servletResponse.getStatus() == Status.OK.getStatusCode()) {
156
- em.getTransaction().commit();
157
- LOG.debug("Transaction committed");
158
- } else {
159
- em.getTransaction().rollback();
160
- LOG.debug("Transaction rolled back");
161
- }
162
- }
163
- } finally {
164
- if (em.isOpen()) {
165
- try {
166
- em.close();
167
- } catch (Exception e) {
168
- LOG.error("Error closing EntityManager", e);
169
- }
170
- }
171
- }
172
- }
223
+ EntityManager em = (EntityManager) context.getProperty(EM_CONTEXT_PROPERTY);
224
+ if (em == null) return;
225
+
226
+ try {
227
+ if (em.getTransaction().isActive()) {
228
+ if (servletResponse.getStatus() == Status.OK.getStatusCode()) {
229
+ em.getTransaction().commit();
230
+ LOG.debug("Transaction committed");
231
+ } else {
232
+ em.getTransaction().rollback();
233
+ LOG.debug("Transaction rolled back");
234
+ }
235
+ }
236
+ } finally {
237
+ if (em.isOpen()) {
238
+ try {
239
+ em.close();
240
+ } catch (Exception e) {
241
+ LOG.error("Error closing EntityManager", e);
242
+ }
243
+ }
244
+ }
245
+ }
173246 }
247
+
securis/src/main/java/net/curisit/securis/ioc/RequestsModule.java
....@@ -1,3 +1,6 @@
1
+/*
2
+* Copyright @ 2013 CurisTEC, S.A.S. All Rights Reserved.
3
+*/
14 package net.curisit.securis.ioc;
25
36 import net.curisit.securis.services.ApiResource;
....@@ -11,28 +14,42 @@
1114
1215 import com.google.inject.AbstractModule;
1316
17
+/**
18
+* RequestsModule
19
+* <p>
20
+* Guice module that binds JAX-RS resource classes so they can be
21
+* injected and discovered by the DI container.
22
+* <p>
23
+* Notes:
24
+* - Currently binds resources explicitly. TODO indicates a future
25
+* improvement to bind dynamically via reflection / classpath scanning.
26
+*
27
+* @author JRA
28
+* Last reviewed by JRA on Oct 7, 2025.
29
+*/
1430 public class RequestsModule extends AbstractModule {
1531
32
+ /**
33
+ * configure<p>
34
+ * Register resource types in the injector.
35
+ */
1636 @Override
1737 protected void configure() {
1838 // TODO Securis: Make the bind using reflection dynamically
19
-
2039 bind(BasicServices.class);
2140 bind(UserResource.class);
22
-
2341 bind(ApplicationResource.class);
2442 bind(LicenseTypeResource.class);
2543 bind(OrganizationResource.class);
2644 bind(ApiResource.class);
2745 bind(LicenseResource.class);
2846 bind(PackResource.class);
29
-
3047 }
3148
49
+ // Example provider (kept commented for reference)
3250 // @Provides
3351 // @RequestScoped
3452 // public User provideUser() {
35
- // return ResteasyProviderFactory.getContextData(User.class);
53
+ // return ResteasyProviderFactory.getContextData(User.class);
3654 // }
37
-
3855 }
securis/src/main/java/net/curisit/securis/ioc/SecurisModule.java
....@@ -1,3 +1,6 @@
1
+/*
2
+* Copyright @ 2013 CurisTEC, S.A.S. All Rights Reserved.
3
+*/
14 package net.curisit.securis.ioc;
25
36 import java.io.File;
....@@ -18,6 +21,25 @@
1821 import com.google.inject.AbstractModule;
1922 import com.google.inject.Provides;
2023
24
+/**
25
+* SecurisModule
26
+* <p>
27
+* Guice module that provides application-level infrastructural dependencies
28
+* (base URI, app directories, DB files list, support email/hash, etc.).
29
+* <p>
30
+* Configuration:
31
+* - Reads server port from /securis-server.properties (key: "port").
32
+* - Defaults to port 9997 when not present or on read errors.
33
+* - Constructs base URI as http://0.0.0.0:{port}/ with UriBuilder.
34
+* - Creates working directories under ${user.home}/.SeCuris on demand.
35
+*
36
+* Security note:
37
+* - getPassword/getFilePassword are simple helpers; secrets should be
38
+* managed via a secure vault/env vars in production.
39
+*
40
+* @author JRA
41
+* Last reviewed by JRA on Oct 7, 2025.
42
+*/
2143 public class SecurisModule extends AbstractModule {
2244
2345 private static final int DEFAULT_PORT = 9997;
....@@ -25,28 +47,51 @@
2547
2648 private static final Logger LOG = LogManager.getLogger(SecurisModule.class);
2749
50
+ /** configure<p>Currently no explicit bindings; providers below supply instances. */
2851 @Override
29
- protected void configure() {
52
+ protected void configure() { }
3053
31
- }
32
-
54
+ /**
55
+ * getPassword<p>
56
+ * Composite password (example use with encrypted H2 URL).
57
+ *
58
+ * @return concatenated password string
59
+ */
3360 public String getPassword() {
3461 return getFilePassword() + " " + "53curi5";
3562 }
3663
64
+ /**
65
+ * getFilePassword<p>
66
+ * Standalone file password (for H2 CIPHER).
67
+ *
68
+ * @return file password string
69
+ */
3770 public String getFilePassword() {
3871 return "cur151T";
3972 }
4073
74
+ /**
75
+ * getUrl<p>
76
+ * H2 JDBC URL with AES cipher pointing to {appDir}/db/securis.
77
+ *
78
+ * @param appDir application working directory
79
+ * @return JDBC URL (H2)
80
+ */
4181 public String getUrl(File appDir) {
4282 return String.format("jdbc:h2:%s/db/securis;CIPHER=AES", appDir.getAbsolutePath());
4383 }
4484
85
+ /**
86
+ * getBaseURI<p>
87
+ * Provide the base URI for the HTTP server using configured or default port.
88
+ *
89
+ * @return base URI (http://0.0.0.0:{port}/)
90
+ */
4591 @Named("base-uri")
4692 @Provides
4793 @ApplicationScoped
4894 public URI getBaseURI() {
49
- // Read from configuration, where?
5095 try {
5196 String url = MessageFormat.format("http://{0}/", "0.0.0.0");
5297 LOG.debug("Server url{}", url);
....@@ -56,33 +101,46 @@
56101 }
57102 }
58103
104
+ /**
105
+ * getPort<p>
106
+ * Read port from properties file or return default.
107
+ *
108
+ * @return HTTP port
109
+ */
59110 private int getPort() {
60111 Integer port;
61112 Properties prop = new Properties();
62113 try {
63114 prop.load(getClass().getResourceAsStream(PROPERTIES_FILE_NAME));
64115 port = Integer.valueOf(prop.getProperty("port"));
65
- if (port == null) {
66
- return DEFAULT_PORT;
67
- } else {
68
- return port;
69
- }
116
+ return (port == null ? DEFAULT_PORT : port);
70117 } catch (Exception ex) {
71118 return DEFAULT_PORT;
72119 }
73120 }
74121
122
+ /**
123
+ * getAppDbFiles<p>
124
+ * List of SQL files to initialize the application DB.
125
+ *
126
+ * @return list of classpath resource paths
127
+ */
75128 protected List<String> getAppDbFiles() {
76
-
77129 return Arrays.asList("/db/schema.sql");
78130 }
79131
132
+ /**
133
+ * getTemporaryDir<p>
134
+ * Provide a temp directory inside the app working dir (.TEMP).
135
+ * Creates it if missing and marks for deletion on exit.
136
+ *
137
+ * @return temp directory or null if creation failed
138
+ */
80139 @Named("temporary-dir")
81140 @Provides
82141 @ApplicationScoped
83142 public File getTemporaryDir() {
84
- String tmp = getAppDir().getAbsolutePath();
85
- tmp += File.separator + ".TEMP";
143
+ String tmp = getAppDir().getAbsolutePath() + File.separator + ".TEMP";
86144 File ftmp = new File(tmp);
87145 if (!ftmp.exists()) {
88146 if (!ftmp.mkdirs()) {
....@@ -94,14 +152,18 @@
94152 return ftmp;
95153 }
96154
155
+ /**
156
+ * getAppDir<p>
157
+ * Provide the app working directory under ${user.home}/.SeCuris (creates if missing).
158
+ *
159
+ * @return working directory or null if creation failed
160
+ */
97161 @Named("app-dir")
98162 @Provides
99163 @ApplicationScoped
100164 public File getAppDir() {
101165 String appDir = System.getProperty("user.home", System.getProperty("user.dir"));
102
- if (appDir == null) {
103
- appDir = ".";
104
- }
166
+ if (appDir == null) appDir = ".";
105167 appDir += File.separator + ".SeCuris";
106168 File fAppDir = new File(appDir);
107169 if (!fAppDir.exists()) {
....@@ -113,6 +175,12 @@
113175 return fAppDir;
114176 }
115177
178
+ /**
179
+ * getSupportEmail<p>
180
+ * Provide support email address.
181
+ *
182
+ * @return email
183
+ */
116184 @Named("support-email")
117185 @Provides
118186 @ApplicationScoped
....@@ -120,6 +188,12 @@
120188 return "support@curisit.net";
121189 }
122190
191
+ /**
192
+ * getHashLogo<p>
193
+ * Provide a static content hash for the logo (cache-busting or integrity).
194
+ *
195
+ * @return hex SHA-256
196
+ */
123197 @Named("hash-logo")
124198 @Provides
125199 @ApplicationScoped
....@@ -127,11 +201,17 @@
127201 return "1b42616809d4cd8ccf109e3c30d0ab25067f160b30b7354a08ddd563de0096ba";
128202 }
129203
204
+ /**
205
+ * getDbFiles<p>
206
+ * Provide DB initialization files list (delegates to {@link #getAppDbFiles()}).
207
+ *
208
+ * @return list of SQL resource paths
209
+ */
130210 @Named("db-files")
131211 @Provides
132212 @ApplicationScoped
133213 public List<String> getDbFiles() {
134214 return getAppDbFiles();
135215 }
136
-
137216 }
217
+
securis/src/main/java/net/curisit/securis/security/BasicSecurityContext.java
....@@ -1,3 +1,6 @@
1
+/*
2
+* Copyright @ 2013 CurisTEC, S.A.S. All Rights Reserved.
3
+*/
14 package net.curisit.securis.security;
25
36 import java.security.Principal;
....@@ -9,103 +12,193 @@
912 import net.curisit.integrity.commons.Utils;
1013 import net.curisit.securis.db.User;
1114
15
+/**
16
+* BasicSecurityContext
17
+* <p>
18
+* Lightweight implementation of JAX-RS {@link SecurityContext} based on:
19
+* - A {@link Principal} holding the username.
20
+* - An integer bitmask of roles (see {@link User.Rol}).
21
+* - Optional scope restrictions (organization/application IDs).
22
+*
23
+* Role checks:
24
+* - {@link #isUserInRole(String)} maps string names to bit constants via {@link #ROLES}.
25
+*
26
+* Scope helpers:
27
+* - {@link #isOrgAccesible(Integer)} and {@link #isAppAccesible(Integer)}.
28
+*
29
+* @author JRA
30
+* Last reviewed by JRA on Oct 5, 2025.
31
+*/
1232 public class BasicSecurityContext implements SecurityContext {
1333
14
- final public static String ROL_ADVANCE = "advance";
15
- final public static String ROL_ADMIN = "admin";
16
- final public static String ROL_BASIC = "basic";
34
+ /** String role names mapped to bit flags. */
35
+ public static final String ROL_ADVANCE = "advance";
36
+ public static final String ROL_ADMIN = "admin";
37
+ public static final String ROL_BASIC = "basic";
1738
18
- final static Map<String, Integer> ROLES = Utils.<String, Integer> createMap(ROL_BASIC, User.Rol.BASIC, ROL_ADVANCE, User.Rol.ADVANCE, ROL_ADMIN, User.Rol.ADMIN);
39
+ /** Mapping from role name to bit flag. */
40
+ static final Map<String, Integer> ROLES =
41
+ Utils.<String, Integer>createMap(ROL_BASIC, User.Rol.BASIC,
42
+ ROL_ADVANCE, User.Rol.ADVANCE,
43
+ ROL_ADMIN, User.Rol.ADMIN);
1944
20
- Principal user = null;
21
- int roles = 0;
22
- boolean secure = false;
23
- Set<Integer> organizationsIds = null;
24
- Set<Integer> applicationsIds = null;
25
- double ran = 0;
45
+ Principal user = null;
46
+ int roles = 0;
47
+ boolean secure = false;
48
+ Set<Integer> organizationsIds = null;
49
+ Set<Integer> applicationsIds = null;
50
+ double ran = 0; // small unique marker for debugging instances
2651
27
- public BasicSecurityContext(String username, int roles, boolean secure) {
28
- user = new UserPrincipal(username);
29
- this.roles = roles;
30
- this.secure = secure;
31
- ran = Math.random();
32
- }
52
+ /**
53
+ * BasicSecurityContext<p>
54
+ * Construct a context for given user, roles and transport security flag.
55
+ *
56
+ * @param username principal name
57
+ * @param roles bitmask of roles
58
+ * @param secure whether the request is HTTPS
59
+ */
60
+ public BasicSecurityContext(String username, int roles, boolean secure) {
61
+ user = new UserPrincipal(username);
62
+ this.roles = roles;
63
+ this.secure = secure;
64
+ ran = Math.random();
65
+ }
3366
34
- @Override
35
- public Principal getUserPrincipal() {
36
- return user;
37
- }
67
+ /**
68
+ * getUserPrincipal<p>
69
+ * Return the user principal.
70
+ *
71
+ * @return mainUser
72
+ */
73
+ @Override
74
+ public Principal getUserPrincipal() { return user; }
3875
39
- @Override
40
- public boolean isUserInRole(String role) {
41
- Integer introle = ROLES.get(role);
42
- return introle != null && (introle & roles) != 0;
43
- }
76
+ /**
77
+ * isUserInRole<p>
78
+ * Check role membership by name (mapped to bitmask).
79
+ *
80
+ * @param role
81
+ * @return isUserInRole
82
+ */
83
+ @Override
84
+ public boolean isUserInRole(String role) {
85
+ Integer introle = ROLES.get(role);
86
+ return introle != null && (introle & roles) != 0;
87
+ }
4488
45
- @Override
46
- public boolean isSecure() {
47
- return secure;
48
- }
89
+ /**
90
+ * isSecure<p>
91
+ * Return whether transport is secure (HTTPS).
92
+ *
93
+ * @return isSecure
94
+ */
95
+ @Override
96
+ public boolean isSecure() { return secure; }
4997
50
- @Override
51
- public String getAuthenticationScheme() {
52
- return null;
53
- }
98
+ /**
99
+ * getAuthenticationScheme<p>
100
+ * Not used; returns null.
101
+ *
102
+ * @return authenticationsScheme
103
+ */
104
+ @Override
105
+ public String getAuthenticationScheme() { return null; }
54106
55
- @Override
56
- public String toString() {
107
+ /**
108
+ * toString<p>
109
+ * Get the string describing the current object
110
+ *
111
+ * @return object string
112
+ */
113
+ @Override
114
+ public String toString() { return String.format("SecurityContextWrapper(%f) %s", ran, user); }
57115
58
- return String.format("SecurityContextWrapper(%f) %s", ran, user);
59
- }
116
+ /**
117
+ * setOrganizationsIds<p>
118
+ * Set org scope (IDs allowed).
119
+ *
120
+ * @param organizationsIds
121
+ */
122
+ public void setOrganizationsIds(Set<Integer> orgs) { this.organizationsIds = orgs; }
60123
61
- public void setOrganizationsIds(Set<Integer> orgs) {
62
- this.organizationsIds = orgs;
63
- }
124
+ /**
125
+ * getOrganizationsIds<p>
126
+ * Return org scope.
127
+ *
128
+ * @return organizationsIds
129
+ */
130
+ public Set<Integer> getOrganizationsIds() { return this.organizationsIds; }
64131
65
- public Set<Integer> getOrganizationsIds() {
66
- return this.organizationsIds;
67
- }
132
+ /**
133
+ * getApplicationsIds<p>
134
+ * Return app scope.
135
+ *
136
+ * @return applicationIds
137
+ */
138
+ public Set<Integer> getApplicationsIds() { return applicationsIds; }
68139
69
- public Set<Integer> getApplicationsIds() {
70
- return applicationsIds;
71
- }
140
+ /**
141
+ * setApplicationsIds<p>
142
+ * Set app scope.
143
+ *
144
+ * @param applicationIds
145
+ */
146
+ public void setApplicationsIds(Set<Integer> applicationsIds) { this.applicationsIds = applicationsIds; }
72147
73
- public void setApplicationsIds(Set<Integer> applicationsIds) {
74
- this.applicationsIds = applicationsIds;
75
- }
148
+ /**
149
+ * UserPrincipal<p>
150
+ * Inner Principal holding only the username.
151
+ */
152
+ private class UserPrincipal implements Principal {
153
+ final String name;
154
+
155
+ /**
156
+ * UserPrincipal<p>
157
+ * Main user
158
+ *
159
+ * @param username
160
+ */
161
+ public UserPrincipal(String name) { this.name = name; }
162
+
163
+ /**
164
+ * getName<p>
165
+ * Get the username
166
+ *
167
+ * @return userName
168
+ */
169
+ @Override public String getName() { return this.name; }
170
+
171
+ /**
172
+ * toString<p>
173
+ * Get the string describing the current object
174
+ *
175
+ * @return object string
176
+ */
177
+ @Override public String toString() { return String.format("[%s]", name); }
178
+ }
76179
77
- private class UserPrincipal implements Principal {
180
+ /**
181
+ * isOrgAccesible<p>
182
+ * Check if org id is within scope.
183
+ *
184
+ * @param orgId
185
+ * @return isOrgAccesible
186
+ */
187
+ public boolean isOrgAccesible(Integer orgid) {
188
+ if (organizationsIds == null || orgid == null) return false;
189
+ return organizationsIds.contains(orgid);
190
+ }
78191
79
- final String name;
80
-
81
- public UserPrincipal(String name) {
82
- this.name = name;
83
- }
84
-
85
- @Override
86
- public String getName() {
87
- return this.name;
88
- }
89
-
90
- @Override
91
- public String toString() {
92
- return String.format("[%s]", name);
93
- }
94
-
95
- }
96
-
97
- public boolean isOrgAccesible(Integer orgid) {
98
- if (organizationsIds == null || orgid == null) {
99
- return false;
100
- }
101
- return organizationsIds.contains(orgid);
102
- }
103
-
104
- public boolean isAppAccesible(Integer appid) {
105
- if (applicationsIds == null || appid == null) {
106
- return false;
107
- }
108
- return applicationsIds.contains(appid);
109
- }
110
-
192
+ /**
193
+ * isAppAccesible<p>
194
+ * Check if app id is within scope.
195
+ *
196
+ * @param appId
197
+ * @return isAppAccesible
198
+ */
199
+ public boolean isAppAccesible(Integer appid) {
200
+ if (applicationsIds == null || appid == null) return false;
201
+ return applicationsIds.contains(appid);
202
+ }
111203 }
204
+
securis/src/main/java/net/curisit/securis/security/Securable.java
....@@ -1,3 +1,6 @@
1
+/*
2
+* Copyright @ 2013 CurisTEC, S.A.S. All Rights Reserved.
3
+*/
14 package net.curisit.securis.security;
25
36 import java.lang.annotation.ElementType;
....@@ -7,16 +10,25 @@
710
811 import net.curisit.securis.utils.TokenHelper;
912
13
+/**
14
+* Securable
15
+* <p>
16
+* Method-level annotation to declare security requirements:
17
+* - {@link #header()} name containing the auth token (defaults to {@link TokenHelper#TOKEN_HEADER_PÀRAM}).
18
+* - {@link #roles()} required role bitmask; {@code 0} means no role restriction.
19
+*
20
+* Intended to be enforced by request filters/interceptors (e.g., RequestsInterceptor).
21
+*
22
+* @author JRA
23
+* Last reviewed by JRA on Oct 5, 2025.
24
+*/
1025 @Retention(RetentionPolicy.RUNTIME)
1126 @Target(ElementType.METHOD)
1227 public @interface Securable {
13
- /**
14
- * Name of header parameter with the auth token to validate
15
- */
28
+
29
+ /** Header name carrying the token to validate. */
1630 String header() default TokenHelper.TOKEN_HEADER_PÀRAM;
1731
18
- /**
19
- * Bit mask with the rol or roles necessary to access the method
20
- */
32
+ /** Bitmask of required roles; set 0 for public endpoints (token still may be required). */
2133 int roles() default 0;
2234 }
securis/src/main/java/net/curisit/securis/services/ApiResource.java
....@@ -1,3 +1,6 @@
1
+/*
2
+* Copyright @ 2013 CurisTEC, S.A.S. All Rights Reserved.
3
+*/
14 package net.curisit.securis.services;
25
36 import java.io.IOException;
....@@ -49,446 +52,491 @@
4952 import net.curisit.securis.utils.TokenHelper;
5053
5154 /**
52
- * External API to be accessed by third parties
53
- *
54
- * @author roberto <roberto.sanchez@curisit.net>
55
- */
55
+* ApiResource
56
+* <p>
57
+* External API for license operations, intended for third-party clients.
58
+*
59
+* Endpoints:
60
+* - GET /api/ -> Plain-text status with date (health check).
61
+* - GET /api/ping -> JSON status (message + date).
62
+* - POST /api/request -> Create license from RequestBean (JSON).
63
+* - POST /api/request -> Create license from request file (multipart).
64
+* - POST /api/renew -> Renew from previous LicenseBean (JSON).
65
+* - POST /api/renew -> Renew from previous license file (multipart).
66
+* - POST /api/validate -> Server-side validation of a license.
67
+*
68
+* Security:
69
+* - Methods that mutate/inspect licenses require {@link Securable} with role {@link Rol#API_CLIENT}.
70
+* - {@link EnsureTransaction} ensures transaction handling at the filter/interceptor layer.
71
+*
72
+* Errors:
73
+* - Business errors are mapped to {@link SeCurisServiceException} with {@link ErrorCodes}.
74
+*
75
+* @author JRA
76
+* Last reviewed by JRA on Oct 5, 2025.
77
+*/
5678 @Path("/api")
5779 public class ApiResource {
5880
59
- private static final Logger LOG = LogManager.getLogger(ApiResource.class);
81
+ private static final Logger LOG = LogManager.getLogger(ApiResource.class);
6082
61
- @Inject
62
- TokenHelper tokenHelper;
83
+ @Inject TokenHelper tokenHelper;
84
+ @Inject private LicenseHelper licenseHelper;
85
+ @Context EntityManager em;
86
+ @Inject LicenseGenerator licenseGenerator;
6387
64
- @Inject
65
- private LicenseHelper licenseHelper;
88
+ /** Fixed username representing API client actor for audit trails. */
89
+ public static final String API_CLIENT_USERNAME = "_client";
6690
67
- @Context
68
- EntityManager em;
91
+ /** Default constructor (required by JAX-RS). */
92
+ public ApiResource() { }
6993
70
- @Inject
71
- LicenseGenerator licenseGenerator;
94
+ // -------------------- Health checks --------------------
7295
73
- public static final String API_CLIENT_USERNAME = "_client";
96
+ /**
97
+ * index<p>
98
+ * Plain text endpoint to verify API is reachable.
99
+ *
100
+ * @return 200 OK with simple message
101
+ */
102
+ @GET
103
+ @Path("/")
104
+ @Produces({ MediaType.TEXT_PLAIN })
105
+ public Response index() {
106
+ return Response.ok("SeCuris API. Date: " + new Date()).build();
107
+ }
74108
75
- public ApiResource() {
76
- }
109
+ /**
110
+ * ping<p>
111
+ * JSON endpoint for health checks.
112
+ *
113
+ * @return 200 OK with {@link StatusBean}
114
+ */
115
+ @GET
116
+ @Path("/ping")
117
+ @Produces({ MediaType.APPLICATION_JSON })
118
+ public Response ping() {
119
+ StatusBean status = new StatusBean();
120
+ status.setDate(new Date());
121
+ status.setMessage(LicenseManager.PING_MESSAGE);
122
+ return Response.ok(status).build();
123
+ }
77124
78
- /**
79
- *
80
- * @return Simple text message to check API status
81
- */
82
- @GET
83
- @Path("/")
84
- @Produces({ MediaType.TEXT_PLAIN })
85
- public Response index() {
86
- return Response.ok("SeCuris API. Date: " + new Date()).build();
87
- }
125
+ // -------------------- License creation --------------------
88126
89
- /**
90
- *
91
- * @return Simple text message to check API status
92
- */
93
- @GET
94
- @Path("/ping")
95
- @Produces({ MediaType.APPLICATION_JSON })
96
- public Response ping() {
97
- StatusBean status = new StatusBean();
98
- status.setDate(new Date());
99
- status.setMessage(LicenseManager.PING_MESSAGE);
100
- return Response.ok(status).build();
101
- }
127
+ /**
128
+ * createFromRequest<p>
129
+ * Create a new license from JSON request data.
130
+ *
131
+ * @param request RequestBean payload
132
+ * @param nameOrReference Holder name or external reference (header)
133
+ * @param userEmail Email (header)
134
+ * @return {@link SignedLicenseBean} JSON
135
+ */
136
+ @POST
137
+ @Path("/request")
138
+ @Consumes(MediaType.APPLICATION_JSON)
139
+ @Securable(roles = Rol.API_CLIENT)
140
+ @Produces({ MediaType.APPLICATION_JSON })
141
+ @EnsureTransaction
142
+ public Response createFromRequest(RequestBean request,
143
+ @HeaderParam(LicenseManager.HEADER_LICENSE_NAME_OR_REFERENCE) String nameOrReference,
144
+ @HeaderParam(LicenseManager.HEADER_LICENSE_EMAIL) String userEmail)
145
+ throws IOException, SeCurisServiceException, SeCurisException {
146
+ LOG.info("Request to get license: {}", request);
147
+ SignedLicenseBean lic = createLicense(request, em, nameOrReference, userEmail);
148
+ return Response.ok(lic).build();
149
+ }
102150
103
- /**
104
- * Request a new license file based in a RequestBean object sent as
105
- * parameter
106
- *
107
- * @param mpfdi
108
- * @param bsc
109
- * @return
110
- * @throws IOException
111
- * @throws SeCurisServiceException
112
- */
113
- @POST
114
- @Path("/request")
115
- @Consumes(MediaType.APPLICATION_JSON)
116
- @Securable(roles = Rol.API_CLIENT)
117
- @Produces({ MediaType.APPLICATION_JSON })
118
- @EnsureTransaction
119
- public Response createFromRequest(RequestBean request, @HeaderParam(LicenseManager.HEADER_LICENSE_NAME_OR_REFERENCE) String nameOrReference,
120
- @HeaderParam(LicenseManager.HEADER_LICENSE_EMAIL) String userEmail) throws IOException, SeCurisServiceException, SeCurisException {
121
- LOG.info("Request to get license: {}", request);
122
- SignedLicenseBean lic = createLicense(request, em, nameOrReference, userEmail);
151
+ /**
152
+ * createFromRequestFile<p>
153
+ * Create a new license from a multipart form (uploaded request fields).
154
+ *
155
+ * @param mpfdi multipart input
156
+ * @param nameOrReference holder name/reference (header)
157
+ * @param userEmail email (header)
158
+ * @return {@link SignedLicenseBean} JSON
159
+ */
160
+ @POST
161
+ @Path("/request")
162
+ @Consumes(MediaType.MULTIPART_FORM_DATA)
163
+ @Securable(roles = Rol.API_CLIENT)
164
+ @Produces({ MediaType.APPLICATION_JSON })
165
+ @EnsureTransaction
166
+ @SuppressWarnings("unchecked")
167
+ public Response createFromRequestFile(MultipartFormDataInput mpfdi,
168
+ @HeaderParam(LicenseManager.HEADER_LICENSE_NAME_OR_REFERENCE) String nameOrReference,
169
+ @HeaderParam(LicenseManager.HEADER_LICENSE_EMAIL) String userEmail)
170
+ throws IOException, SeCurisServiceException, SeCurisException {
171
+ RequestBean req = new RequestBean();
172
+ req.setAppCode(mpfdi.getFormDataPart("appCode", String.class, null));
173
+ req.setActivationCode(mpfdi.getFormDataPart("activationCode", String.class, null));
174
+ req.setPackCode(mpfdi.getFormDataPart("packCode", String.class, null));
175
+ req.setLicenseTypeCode(mpfdi.getFormDataPart("licenseTypeCode", String.class, null));
176
+ req.setCustomerCode(mpfdi.getFormDataPart("customerCode", String.class, null));
177
+ req.setArch(mpfdi.getFormDataPart("arch", String.class, null));
178
+ req.setCrcLogo(mpfdi.getFormDataPart("crcLogo", String.class, null));
179
+ req.setMacAddresses(mpfdi.getFormDataPart("macAddresses", List.class, null));
180
+ req.setOsName(mpfdi.getFormDataPart("osName", String.class, null));
123181
124
- return Response.ok(lic).build();
125
- }
182
+ return createFromRequest(req, nameOrReference, userEmail);
183
+ }
126184
127
- /**
128
- * Returns a License file in JSON format from an uploaded Request file
129
- *
130
- * @param mpfdi
131
- * @param bsc
132
- * @return
133
- * @throws IOException
134
- * @throws SeCurisServiceException
135
- * @throws SeCurisException
136
- */
137
- @POST
138
- @Path("/request")
139
- @Consumes(MediaType.MULTIPART_FORM_DATA)
140
- @Securable(roles = Rol.API_CLIENT)
141
- @Produces({ MediaType.APPLICATION_JSON })
142
- @EnsureTransaction
143
- @SuppressWarnings("unchecked")
144
- public Response createFromRequestFile(MultipartFormDataInput mpfdi, @HeaderParam(LicenseManager.HEADER_LICENSE_NAME_OR_REFERENCE) String nameOrReference,
145
- @HeaderParam(LicenseManager.HEADER_LICENSE_EMAIL) String userEmail) throws IOException, SeCurisServiceException, SeCurisException {
146
- RequestBean req = new RequestBean();
147
- req.setAppCode(mpfdi.getFormDataPart("appCode", String.class, null));
148
- req.setActivationCode(mpfdi.getFormDataPart("activationCode", String.class, null));
149
- req.setPackCode(mpfdi.getFormDataPart("packCode", String.class, null));
150
- req.setLicenseTypeCode(mpfdi.getFormDataPart("licenseTypeCode", String.class, null));
151
- req.setCustomerCode(mpfdi.getFormDataPart("customerCode", String.class, null));
152
- req.setArch(mpfdi.getFormDataPart("arch", String.class, null));
153
- req.setCrcLogo(mpfdi.getFormDataPart("crcLogo", String.class, null));
154
- req.setMacAddresses(mpfdi.getFormDataPart("macAddresses", List.class, null));
155
- req.setOsName(mpfdi.getFormDataPart("osName", String.class, null));
185
+ // -------------------- License renew --------------------
156186
157
- return createFromRequest(req, nameOrReference, userEmail);
158
- }
187
+ /**
188
+ * renewFromPreviousLicense<p>
189
+ * Renew a license from an existing {@link LicenseBean} JSON payload.
190
+ * Only <b>Active</b> licenses within one month of expiration are eligible.
191
+ *
192
+ * @param previousLic current license bean
193
+ * @param bsc security context
194
+ * @return new {@link SignedLicenseBean}
195
+ */
196
+ @POST
197
+ @Path("/renew")
198
+ @Consumes(MediaType.APPLICATION_JSON)
199
+ @Securable(roles = Rol.API_CLIENT)
200
+ @Produces({ MediaType.APPLICATION_JSON })
201
+ @EnsureTransaction
202
+ public Response renewFromPreviousLicense(LicenseBean previousLic, @Context BasicSecurityContext bsc)
203
+ throws IOException, SeCurisServiceException, SeCurisException {
204
+ LOG.info("Renew license: {}", previousLic);
159205
160
- /**
161
- * Create a new License file based in a previous one
162
- *
163
- * @param request
164
- * @param bsc
165
- * @return
166
- * @throws IOException
167
- * @throws SeCurisServiceException
168
- * @throws SeCurisException
169
- */
170
- @POST
171
- @Path("/renew")
172
- @Consumes(MediaType.APPLICATION_JSON)
173
- @Securable(roles = Rol.API_CLIENT)
174
- @Produces({ MediaType.APPLICATION_JSON })
175
- @EnsureTransaction
176
- public Response renewFromPreviousLicense(LicenseBean previousLic, @Context BasicSecurityContext bsc) throws IOException, SeCurisServiceException, SeCurisException {
177
- LOG.info("Renew license: {}", previousLic);
206
+ if (previousLic.getExpirationDate().after(DateUtils.addMonths(new Date(), 1))) {
207
+ throw new SeCurisServiceException(ErrorCodes.UNNECESSARY_RENEW, "The license is still valid, not ready for renew");
208
+ }
178209
179
- if (previousLic.getExpirationDate().after(DateUtils.addMonths(new Date(), 1))) {
180
- throw new SeCurisServiceException(ErrorCodes.UNNECESSARY_RENEW, "The license is still valid, not ready for renew");
181
- }
210
+ License lic = License.findLicenseByCode(previousLic.getLicenseCode(), em);
211
+ if (lic == null) {
212
+ throw new SeCurisServiceException(ErrorCodes.LICENSE_DATA_IS_NOT_VALID, "Current license is missing in DB");
213
+ }
214
+ if (lic.getStatus() != LicenseStatus.ACTIVE) {
215
+ throw new SeCurisServiceException(ErrorCodes.LICENSE_NOT_READY_FOR_RENEW, "Only licenses with status 'Active' can be renew");
216
+ }
182217
183
- // EntityManager em = emProvider.get();
184
- License lic = License.findLicenseByCode(previousLic.getLicenseCode(), em);
185
- if (lic == null) {
186
- throw new SeCurisServiceException(ErrorCodes.LICENSE_DATA_IS_NOT_VALID, "Current license is missing in DB");
187
- }
218
+ SignedLicenseBean signedLic = renewLicense(previousLic, em);
219
+ LOG.info("Renewed license code: {}, until: {}", signedLic.getLicenseCode(), signedLic.getExpirationDate());
188220
189
- if (lic.getStatus() != LicenseStatus.ACTIVE) {
190
- throw new SeCurisServiceException(ErrorCodes.LICENSE_NOT_READY_FOR_RENEW, "Only licenses with status 'Active' can be renew");
191
- }
221
+ return Response.ok(signedLic).build();
222
+ }
192223
193
- SignedLicenseBean signedLic = renewLicense(previousLic, em);
194
- LOG.info("Renewed license code: {}, until: {}", signedLic.getLicenseCode(), signedLic.getExpirationDate());
224
+ /**
225
+ * renewFromLicenseFile<p>
226
+ * Renew a license from multipart (uploaded prior license fields).
227
+ *
228
+ * @param mpfdi multipart input
229
+ * @param bsc security context
230
+ * @return new {@link SignedLicenseBean}
231
+ */
232
+ @POST
233
+ @Path("/renew")
234
+ @Consumes(MediaType.MULTIPART_FORM_DATA)
235
+ @Securable(roles = Rol.API_CLIENT)
236
+ @Produces({ MediaType.APPLICATION_JSON })
237
+ @EnsureTransaction
238
+ @SuppressWarnings("unchecked")
239
+ public Response renewFromLicenseFile(MultipartFormDataInput mpfdi, @Context BasicSecurityContext bsc)
240
+ throws IOException, SeCurisServiceException, SeCurisException {
241
+ LicenseBean lic = new LicenseBean();
195242
196
- return Response.ok(signedLic).build();
197
- }
243
+ lic.setAppCode(mpfdi.getFormDataPart("appCode", String.class, null));
244
+ lic.setActivationCode(mpfdi.getFormDataPart("activationName", String.class, null));
245
+ lic.setAppName(mpfdi.getFormDataPart("appName", String.class, null));
246
+ lic.setArch(mpfdi.getFormDataPart("arch", String.class, null));
247
+ lic.setCrcLogo(mpfdi.getFormDataPart("crcLogo", String.class, null));
248
+ lic.setPackCode(mpfdi.getFormDataPart("packCode", String.class, null));
249
+ lic.setLicenseTypeCode(mpfdi.getFormDataPart("licenseCode", String.class, null));
250
+ lic.setCustomerCode(mpfdi.getFormDataPart("customerCode", String.class, null));
251
+ lic.setMacAddresses(mpfdi.getFormDataPart("macAddresses", List.class, null));
252
+ lic.setOsName(mpfdi.getFormDataPart("osName", String.class, null));
253
+ lic.setExpirationDate(mpfdi.getFormDataPart("expirationDate", Date.class, null));
198254
199
- /**
200
- * License validation on server side, in this case we validate that the
201
- * current licenses has not been cancelled and they are still in valid
202
- * period. If the pack has reached the end valid period, the license is no
203
- * longer valid.
204
- *
205
- * @param currentLic
206
- * @param bsc
207
- * @return
208
- * @throws IOException
209
- * @throws SeCurisServiceException
210
- * @throws SeCurisException
211
- */
212
- @POST
213
- @Path("/validate")
214
- @Consumes(MediaType.APPLICATION_JSON)
215
- @Securable(roles = Rol.API_CLIENT)
216
- @Produces({ MediaType.APPLICATION_JSON })
217
- @EnsureTransaction
218
- public Response validate(LicenseBean currentLic, @Context BasicSecurityContext bsc) throws IOException, SeCurisServiceException, SeCurisException {
219
- LOG.info("Validate license: {}", currentLic);
255
+ LOG.info("Lic expires at: {}", lic.getExpirationDate());
256
+ if (lic.getExpirationDate().after(DateUtils.addMonths(new Date(), 1))) {
257
+ throw new SeCurisServiceException(ErrorCodes.LICENSE_NOT_READY_FOR_RENEW, "The license is still valid, not ready for renew");
258
+ }
220259
221
- if (currentLic.getExpirationDate().before(new Date())) {
222
- throw new SeCurisServiceException(ErrorCodes.LICENSE_IS_EXPIRED, "The license is expired");
223
- }
260
+ return renewFromPreviousLicense(lic, bsc);
261
+ }
224262
225
- License existingLic = licenseHelper.getActiveLicenseFromDB(currentLic, em);
263
+ // -------------------- Validation --------------------
226264
227
- Pack pack = existingLic.getPack();
228
- if (pack.getEndValidDate().before(new Date())) {
229
- throw new SeCurisServiceException(ErrorCodes.LICENSE_PACK_IS_NOT_VALID, "The pack end valid date has been reached");
230
- }
231
- if (pack.getStatus() != PackStatus.ACTIVE) {
232
- LOG.error("The Pack {} status is not active, is: {}", pack.getCode(), pack.getStatus());
233
- throw new SeCurisServiceException(ErrorCodes.LICENSE_PACK_IS_NOT_VALID, "The pack status is not Active");
234
- }
265
+ /**
266
+ * validate<p>
267
+ * Server-side validation of a license:
268
+ * - Not expired
269
+ * - Pack still valid and active
270
+ * - Signature valid
271
+ *
272
+ * @param currentLic license to validate
273
+ * @param bsc security context
274
+ * @return same license if valid
275
+ */
276
+ @POST
277
+ @Path("/validate")
278
+ @Consumes(MediaType.APPLICATION_JSON)
279
+ @Securable(roles = Rol.API_CLIENT)
280
+ @Produces({ MediaType.APPLICATION_JSON })
281
+ @EnsureTransaction
282
+ public Response validate(LicenseBean currentLic, @Context BasicSecurityContext bsc)
283
+ throws IOException, SeCurisServiceException, SeCurisException {
284
+ LOG.info("Validate license: {}", currentLic);
235285
236
- try {
237
- SignatureHelper.getInstance().validateSignature(currentLic);
238
- } catch (SeCurisException ex) {
239
- throw new SeCurisServiceException(ErrorCodes.LICENSE_DATA_IS_NOT_VALID, "The license signature is not valid");
240
- }
286
+ if (currentLic.getExpirationDate().before(new Date())) {
287
+ throw new SeCurisServiceException(ErrorCodes.LICENSE_IS_EXPIRED, "The license is expired");
288
+ }
241289
242
- return Response.ok(currentLic).build();
243
- }
290
+ License existingLic = licenseHelper.getActiveLicenseFromDB(currentLic, em);
244291
245
- /**
246
- * Returns a new License file in JSON format based in a previous license
247
- * There is 2 /renew services with json input and with upload file
248
- *
249
- * @param mpfdi
250
- * @param bsc
251
- * @return
252
- * @throws IOException
253
- * @throws SeCurisServiceException
254
- * @throws SeCurisException
255
- */
256
- @POST
257
- @Path("/renew")
258
- @Consumes(MediaType.MULTIPART_FORM_DATA)
259
- @Securable(roles = Rol.API_CLIENT)
260
- @Produces({ MediaType.APPLICATION_JSON })
261
- @EnsureTransaction
262
- @SuppressWarnings("unchecked")
263
- public Response renewFromLicenseFile(MultipartFormDataInput mpfdi, @Context BasicSecurityContext bsc) throws IOException, SeCurisServiceException, SeCurisException {
264
- LicenseBean lic = new LicenseBean();
292
+ Pack pack = existingLic.getPack();
293
+ if (pack.getEndValidDate().before(new Date())) {
294
+ throw new SeCurisServiceException(ErrorCodes.LICENSE_PACK_IS_NOT_VALID, "The pack end valid date has been reached");
295
+ }
296
+ if (pack.getStatus() != PackStatus.ACTIVE) {
297
+ LOG.error("The Pack {} status is not active, is: {}", pack.getCode(), pack.getStatus());
298
+ throw new SeCurisServiceException(ErrorCodes.LICENSE_PACK_IS_NOT_VALID, "The pack status is not Active");
299
+ }
265300
266
- lic.setAppCode(mpfdi.getFormDataPart("appCode", String.class, null));
267
- lic.setActivationCode(mpfdi.getFormDataPart("activationName", String.class, null));
268
- lic.setAppName(mpfdi.getFormDataPart("appName", String.class, null));
269
- lic.setArch(mpfdi.getFormDataPart("arch", String.class, null));
270
- lic.setCrcLogo(mpfdi.getFormDataPart("crcLogo", String.class, null));
271
- lic.setPackCode(mpfdi.getFormDataPart("packCode", String.class, null));
272
- lic.setLicenseTypeCode(mpfdi.getFormDataPart("licenseCode", String.class, null));
273
- lic.setCustomerCode(mpfdi.getFormDataPart("customerCode", String.class, null));
274
- lic.setMacAddresses(mpfdi.getFormDataPart("macAddresses", List.class, null));
275
- lic.setOsName(mpfdi.getFormDataPart("osName", String.class, null));
276
- lic.setExpirationDate(mpfdi.getFormDataPart("expirationDate", Date.class, null));
277
- LOG.info("Lic expires at: {}", lic.getExpirationDate());
278
- if (lic.getExpirationDate().after(DateUtils.addMonths(new Date(), 1))) {
279
- throw new SeCurisServiceException(ErrorCodes.LICENSE_NOT_READY_FOR_RENEW, "The license is still valid, not ready for renew");
280
- }
301
+ try {
302
+ SignatureHelper.getInstance().validateSignature(currentLic);
303
+ } catch (SeCurisException ex) {
304
+ throw new SeCurisServiceException(ErrorCodes.LICENSE_DATA_IS_NOT_VALID, "The license signature is not valid");
305
+ }
281306
282
- return renewFromPreviousLicense(lic, bsc);
283
- }
307
+ return Response.ok(currentLic).build();
308
+ }
284309
285
- /**
286
- * Creates a new signed license from request data or from previous license
287
- * if It's a renew
288
- *
289
- * @param req
290
- * @param em
291
- * @param renew
292
- * @return
293
- * @throws SeCurisServiceException
294
- */
295
- private SignedLicenseBean createLicense(RequestBean req, EntityManager em, String nameOrReference, String email) throws SeCurisServiceException {
296
- License lic = null;
310
+ // -------------------- Internal helpers --------------------
297311
298
- if (req.getActivationCode() != null) {
299
- lic = License.findLicenseByActivationCode(req.getActivationCode(), em);
300
- if (lic == null) {
301
- throw new SeCurisServiceException(ErrorCodes.INVALID_DATA, "The given activation code is invalid: " + req.getActivationCode());
302
- }
303
- if (lic.getStatus() == LicenseStatus.ACTIVE) {
304
- RequestBean initialRequest;
305
- try {
306
- initialRequest = JsonUtils.json2object(lic.getRequestData(), RequestBean.class);
307
- if (!req.match(initialRequest)) {
308
- throw new SeCurisServiceException(ErrorCodes.INVALID_DATA, "There is already an active license for given activation code: " + req.getActivationCode());
309
- } else {
310
- return JsonUtils.json2object(lic.getLicenseData(), SignedLicenseBean.class);
311
- }
312
- } catch (SeCurisException e) {
313
- LOG.error("Error getting existing license", e);
314
- throw new SeCurisServiceException(ErrorCodes.INVALID_FORMAT, "Original request is wrong");
315
- }
316
- } else {
317
- if (req.getAppCode() != null && !req.getAppCode().equals(lic.getPack().getLicenseType().getApplication().getCode())) {
318
- LOG.error("Activation code {} belongs to app: {} but was sent by: {}", req.getActivationCode(), lic.getPack().getLicenseType().getApplication().getCode(),
319
- req.getAppCode());
320
- throw new SeCurisServiceException(ErrorCodes.INVALID_DATA, "The given activation code belongs to a different application: " + req.getActivationCode());
321
- }
322
- }
323
- // We validate if the HW is the same, otherwise an error is
324
- // thrown
325
- } else {
326
- try {
327
- lic = License.findValidLicenseByRequestData(JsonUtils.toJSON(req), em);
328
- } catch (SeCurisException e1) {
329
- throw new SeCurisServiceException(ErrorCodes.INVALID_FORMAT, "Request sent is not valid");
330
- }
331
- if (lic != null) {
332
- try {
333
- if (lic.getStatus() == LicenseStatus.ACTIVE || lic.getStatus() == LicenseStatus.PRE_ACTIVE) {
334
- return JsonUtils.json2object(lic.getLicenseData(), SignedLicenseBean.class);
335
- }
336
- } catch (SeCurisException e) {
337
- throw new SeCurisServiceException(ErrorCodes.INVALID_FORMAT, "Error trying to get the license bean from license code: " + lic.getCode());
338
- }
339
- } else {
340
- lic = new License();
341
- }
342
- }
312
+ /**
313
+ * createLicense<p>
314
+ * Creates a new signed license from request data or reuses an existing
315
+ * pre-active/active one when allowed by business rules.
316
+ *
317
+ * @param req request bean
318
+ * @param em entity manager
319
+ * @param nameOrReference license holder name/reference (header)
320
+ * @param email email (header)
321
+ * @return signed license bean
322
+ */
323
+ private SignedLicenseBean createLicense(RequestBean req, EntityManager em, String nameOrReference, String email)
324
+ throws SeCurisServiceException {
343325
344
- Pack pack;
345
- if (lic.getActivationCode() == null) {
346
- try {
347
- pack = em.createNamedQuery("pack-by-code", Pack.class).setParameter("code", req.getPackCode()).getSingleResult();
348
- } catch (NoResultException e) {
349
- throw new SeCurisServiceException(ErrorCodes.NOT_FOUND, "No pack found for code: " + req.getPackCode());
350
- }
326
+ License lic = null;
351327
352
- if (pack.getNumAvailables() <= 0) {
353
- throw new SeCurisServiceException(ErrorCodes.NO_AVAILABLE_LICENSES, "The current pack has no licenses availables");
354
- }
355
- if (lic.getStatus() == LicenseStatus.REQUESTED && !pack.isLicensePreactivation()) {
356
- throw new SeCurisServiceException(ErrorCodes.NO_AVAILABLE_LICENSES, "Current pack doesn't allow license preactivation");
357
- }
328
+ // (1) Activation-code flow
329
+ if (req.getActivationCode() != null) {
330
+ lic = License.findLicenseByActivationCode(req.getActivationCode(), em);
331
+ if (lic == null) {
332
+ throw new SeCurisServiceException(ErrorCodes.INVALID_DATA, "The given activation code is invalid: " + req.getActivationCode());
333
+ }
334
+ if (lic.getStatus() == LicenseStatus.ACTIVE) {
335
+ try {
336
+ RequestBean initialRequest = JsonUtils.json2object(lic.getRequestData(), RequestBean.class);
337
+ if (!req.match(initialRequest)) {
338
+ throw new SeCurisServiceException(ErrorCodes.INVALID_DATA,
339
+ "There is already an active license for given activation code: " + req.getActivationCode());
340
+ } else {
341
+ return JsonUtils.json2object(lic.getLicenseData(), SignedLicenseBean.class);
342
+ }
343
+ } catch (SeCurisException e) {
344
+ LOG.error("Error getting existing license", e);
345
+ throw new SeCurisServiceException(ErrorCodes.INVALID_FORMAT, "Original request is wrong");
346
+ }
347
+ } else {
348
+ if (req.getAppCode() != null &&
349
+ !req.getAppCode().equals(lic.getPack().getLicenseType().getApplication().getCode())) {
350
+ LOG.error("Activation code {} belongs to app: {} but was sent by: {}",
351
+ req.getActivationCode(), lic.getPack().getLicenseType().getApplication().getCode(), req.getAppCode());
352
+ throw new SeCurisServiceException(ErrorCodes.INVALID_DATA,
353
+ "The given activation code belongs to a different application: " + req.getActivationCode());
354
+ }
355
+ }
356
+ } else {
357
+ // (2) Request-data flow (idempotent check)
358
+ try {
359
+ lic = License.findValidLicenseByRequestData(JsonUtils.toJSON(req), em);
360
+ } catch (SeCurisException e1) {
361
+ throw new SeCurisServiceException(ErrorCodes.INVALID_FORMAT, "Request sent is not valid");
362
+ }
363
+ if (lic != null) {
364
+ try {
365
+ if (lic.getStatus() == LicenseStatus.ACTIVE || lic.getStatus() == LicenseStatus.PRE_ACTIVE) {
366
+ return JsonUtils.json2object(lic.getLicenseData(), SignedLicenseBean.class);
367
+ }
368
+ } catch (SeCurisException e) {
369
+ throw new SeCurisServiceException(ErrorCodes.INVALID_FORMAT,
370
+ "Error trying to get the license bean from license code: " + lic.getCode());
371
+ }
372
+ } else {
373
+ lic = new License();
374
+ }
375
+ }
358376
359
- if (!req.getCustomerCode().equals(pack.getOrganization().getCode())) {
360
- throw new SeCurisServiceException(ErrorCodes.INVALID_LICENSE_REQUEST_DATA, "Customer code is not valid: " + req.getCustomerCode());
361
- }
377
+ // (3) Pack validation & constraints
378
+ Pack pack;
379
+ if (lic.getActivationCode() == null) {
380
+ try {
381
+ pack = em.createNamedQuery("pack-by-code", Pack.class)
382
+ .setParameter("code", req.getPackCode())
383
+ .getSingleResult();
384
+ } catch (NoResultException e) {
385
+ throw new SeCurisServiceException(ErrorCodes.NOT_FOUND, "No pack found for code: " + req.getPackCode());
386
+ }
362387
363
- if (!req.getLicenseTypeCode().equals(pack.getLicenseTypeCode())) {
364
- throw new SeCurisServiceException(ErrorCodes.INVALID_LICENSE_REQUEST_DATA, "License type code is not valid: " + req.getLicenseTypeCode());
365
- }
366
- } else {
367
- pack = lic.getPack();
368
- }
369
- if (pack.getStatus() != PackStatus.ACTIVE) {
370
- LOG.error("The Pack {} status is not active, is: {}", pack.getCode(), pack.getStatus());
371
- throw new SeCurisServiceException(ErrorCodes.LICENSE_DATA_IS_NOT_VALID, "The pack status is not Active");
372
- }
373
- SignedLicenseBean signedLicense;
374
- try {
375
- String licCode;
376
- if (lic.getCode() == null) {
377
- licCode = LicUtils.getLicenseCode(pack.getCode(), licenseHelper.getNextCodeSuffix(pack.getId(), em));
378
- } else {
379
- licCode = lic.getCode();
380
- }
381
- Date expirationDate = licenseHelper.getExpirationDateFromPack(pack, lic.getActivationCode() == null);
388
+ if (pack.getNumAvailables() <= 0) {
389
+ throw new SeCurisServiceException(ErrorCodes.NO_AVAILABLE_LICENSES, "The current pack has no licenses availables");
390
+ }
391
+ if (lic.getStatus() == LicenseStatus.REQUESTED && !pack.isLicensePreactivation()) {
392
+ throw new SeCurisServiceException(ErrorCodes.NO_AVAILABLE_LICENSES, "Current pack doesn't allow license preactivation");
393
+ }
394
+ if (!req.getCustomerCode().equals(pack.getOrganization().getCode())) {
395
+ throw new SeCurisServiceException(ErrorCodes.INVALID_LICENSE_REQUEST_DATA, "Customer code is not valid: " + req.getCustomerCode());
396
+ }
397
+ if (!req.getLicenseTypeCode().equals(pack.getLicenseTypeCode())) {
398
+ throw new SeCurisServiceException(ErrorCodes.INVALID_LICENSE_REQUEST_DATA, "License type code is not valid: " + req.getLicenseTypeCode());
399
+ }
400
+ } else {
401
+ pack = lic.getPack();
402
+ }
382403
383
- LicenseBean lb = licenseGenerator.generateLicense(req, licenseHelper.extractPackMetadata(pack.getMetadata()), expirationDate, licCode, pack.getAppName());
384
- signedLicense = new SignedLicenseBean(lb);
385
- } catch (SeCurisException e) {
386
- throw new SeCurisServiceException(ErrorCodes.INVALID_LICENSE_REQUEST_DATA, "Error generating license: " + e.toString());
387
- }
388
- try {
389
- lic.setRequestData(JsonUtils.toJSON(req));
390
- if (BlockedRequest.isRequestBlocked(lic.getRequestData(), em)) {
391
- throw new SeCurisServiceException(ErrorCodes.BLOCKED_REQUEST_DATA, "Given request data is blocked and cannot be activated");
392
- }
393
- lic.setLicenseData(JsonUtils.toJSON(signedLicense));
394
- } catch (SeCurisException e) {
395
- LOG.error("Error generating license JSON", e);
396
- throw new SeCurisServiceException(ErrorCodes.INVALID_FORMAT, "Error generating license JSON");
397
- }
404
+ if (pack.getStatus() != PackStatus.ACTIVE) {
405
+ LOG.error("The Pack {} status is not active, is: {}", pack.getCode(), pack.getStatus());
406
+ throw new SeCurisServiceException(ErrorCodes.LICENSE_DATA_IS_NOT_VALID, "The pack status is not Active");
407
+ }
398408
399
- lic.setModificationTimestamp(new Date());
400
- lic.setExpirationDate(signedLicense.getExpirationDate());
401
- User user = em.find(User.class, API_CLIENT_USERNAME);
402
- if (lic.getStatus() != LicenseStatus.REQUESTED) {
403
- lic.setPack(pack);
404
- lic.setCreatedBy(user);
405
- lic.setCreationTimestamp(new Date());
406
- if (lic.getActivationCode() != null) {
407
- lic.setStatus(LicenseStatus.ACTIVE);
408
- } else {
409
- lic.setStatus(pack.isLicensePreactivation() ? LicenseStatus.PRE_ACTIVE : LicenseStatus.REQUESTED);
410
- }
411
- lic.setCode(signedLicense.getLicenseCode());
412
- lic.setCodeSuffix(LicUtils.getLicenseCodeSuffix(signedLicense.getLicenseCode()));
413
- if (lic.getEmail() == null || "".equals(lic.getEmail())) {
414
- lic.setEmail(email);
415
- }
416
- if (lic.getFullName() == null || "".equals(lic.getFullName())) {
417
- lic.setFullName(nameOrReference);
418
- }
419
- if (lic.getId() != null) {
420
- em.merge(lic);
421
- } else {
422
- em.persist(lic);
423
- }
424
- em.persist(licenseHelper.createLicenseHistoryAction(lic, user, LicenseHistory.Actions.CREATE));
425
- if (lic.getActivationCode() != null) {
426
- em.persist(licenseHelper.createLicenseHistoryAction(lic, user, LicenseHistory.Actions.ACTIVATE, "Activated by code on creation"));
427
- } else {
428
- if (pack.isLicensePreactivation()) {
429
- em.persist(licenseHelper.createLicenseHistoryAction(lic, user, LicenseHistory.Actions.PRE_ACTIVATE, "Pre-activated on creation"));
430
- } else {
431
- LOG.warn("License ({}) created, but the pack doesn't allow preactivation", lic.getCode());
432
- throw new SeCurisServiceException(ErrorCodes.NO_AVAILABLE_LICENSES, "Current pack doesn't allow license preactivation");
433
- }
434
- }
435
- } else {
436
- lic.setStatus(LicenseStatus.PRE_ACTIVE);
437
- em.merge(lic);
438
- em.persist(licenseHelper.createLicenseHistoryAction(lic, user, LicenseHistory.Actions.PRE_ACTIVATE, "Pre-activated after request"));
439
- }
409
+ // (4) License generation
410
+ SignedLicenseBean signedLicense;
411
+ try {
412
+ String licCode = (lic.getCode() == null)
413
+ ? LicUtils.getLicenseCode(pack.getCode(), licenseHelper.getNextCodeSuffix(pack.getId(), em))
414
+ : lic.getCode();
440415
441
- return signedLicense;
442
- }
416
+ Date expirationDate = licenseHelper.getExpirationDateFromPack(pack, lic.getActivationCode() == null);
417
+ LicenseBean lb = licenseGenerator.generateLicense(
418
+ req,
419
+ licenseHelper.extractPackMetadata(pack.getMetadata()),
420
+ expirationDate,
421
+ licCode,
422
+ pack.getAppName()
423
+ );
424
+ signedLicense = new SignedLicenseBean(lb);
425
+ } catch (SeCurisException e) {
426
+ throw new SeCurisServiceException(ErrorCodes.INVALID_LICENSE_REQUEST_DATA, "Error generating license: " + e.toString());
427
+ }
443428
444
- /**
445
- * Creates a new signed license from request data or from previous license
446
- * if It's a renew
447
- *
448
- * @param req
449
- * @param em
450
- * @param renew
451
- * @return
452
- * @throws SeCurisServiceException
453
- */
454
- private SignedLicenseBean renewLicense(LicenseBean previousLicenseBean, EntityManager em) throws SeCurisServiceException {
429
+ // (5) Persist/merge license + history
430
+ try {
431
+ lic.setRequestData(JsonUtils.toJSON(req));
432
+ if (BlockedRequest.isRequestBlocked(lic.getRequestData(), em)) {
433
+ throw new SeCurisServiceException(ErrorCodes.BLOCKED_REQUEST_DATA, "Given request data is blocked and cannot be activated");
434
+ }
435
+ lic.setLicenseData(JsonUtils.toJSON(signedLicense));
436
+ } catch (SeCurisException e) {
437
+ LOG.error("Error generating license JSON", e);
438
+ throw new SeCurisServiceException(ErrorCodes.INVALID_FORMAT, "Error generating license JSON");
439
+ }
455440
456
- License lic = License.findLicenseByCode(previousLicenseBean.getLicenseCode(), em);
457
- if (lic.getStatus() != LicenseStatus.ACTIVE && lic.getStatus() != LicenseStatus.PRE_ACTIVE) {
458
- throw new SeCurisServiceException(ErrorCodes.INVALID_DATA, "The current license has been cancelled");
459
- }
441
+ lic.setModificationTimestamp(new Date());
442
+ lic.setExpirationDate(signedLicense.getExpirationDate());
443
+ User user = em.find(User.class, API_CLIENT_USERNAME);
460444
461
- Pack pack = lic.getPack();
462
- SignedLicenseBean signedLicense;
463
- try {
464
- String licCode = lic.getCode();
465
- Date expirationDate = licenseHelper.getExpirationDateFromPack(pack, false);
445
+ if (lic.getStatus() != LicenseStatus.REQUESTED) {
446
+ lic.setPack(pack);
447
+ lic.setCreatedBy(user);
448
+ lic.setCreationTimestamp(new Date());
449
+ if (lic.getActivationCode() != null) {
450
+ lic.setStatus(LicenseStatus.ACTIVE);
451
+ } else {
452
+ lic.setStatus(pack.isLicensePreactivation() ? LicenseStatus.PRE_ACTIVE : LicenseStatus.REQUESTED);
453
+ }
454
+ lic.setCode(signedLicense.getLicenseCode());
455
+ lic.setCodeSuffix(LicUtils.getLicenseCodeSuffix(signedLicense.getLicenseCode()));
456
+ if (lic.getEmail() == null || "".equals(lic.getEmail())) {
457
+ lic.setEmail(email);
458
+ }
459
+ if (lic.getFullName() == null || "".equals(lic.getFullName())) {
460
+ lic.setFullName(nameOrReference);
461
+ }
462
+ if (lic.getId() != null) {
463
+ em.merge(lic);
464
+ } else {
465
+ em.persist(lic);
466
+ }
467
+ em.persist(licenseHelper.createLicenseHistoryAction(lic, user, LicenseHistory.Actions.CREATE));
468
+ if (lic.getActivationCode() != null) {
469
+ em.persist(licenseHelper.createLicenseHistoryAction(lic, user, LicenseHistory.Actions.ACTIVATE, "Activated by code on creation"));
470
+ } else {
471
+ if (pack.isLicensePreactivation()) {
472
+ em.persist(licenseHelper.createLicenseHistoryAction(lic, user, LicenseHistory.Actions.PRE_ACTIVATE, "Pre-activated on creation"));
473
+ } else {
474
+ LOG.warn("License ({}) created, but the pack doesn't allow preactivation", lic.getCode());
475
+ throw new SeCurisServiceException(ErrorCodes.NO_AVAILABLE_LICENSES, "Current pack doesn't allow license preactivation");
476
+ }
477
+ }
478
+ } else {
479
+ lic.setStatus(LicenseStatus.PRE_ACTIVE);
480
+ em.merge(lic);
481
+ em.persist(licenseHelper.createLicenseHistoryAction(lic, user, LicenseHistory.Actions.PRE_ACTIVATE, "Pre-activated after request"));
482
+ }
466483
467
- LicenseBean lb = licenseGenerator.generateLicense(previousLicenseBean, licenseHelper.extractPackMetadata(pack.getMetadata()), expirationDate, licCode,
468
- pack.getAppName());
469
- signedLicense = new SignedLicenseBean(lb);
470
- } catch (SeCurisException e) {
471
- throw new SeCurisServiceException(ErrorCodes.INVALID_LICENSE_REQUEST_DATA, "Error generating license: " + e.toString());
472
- }
473
- try {
474
- lic.setRequestData(JsonUtils.toJSON(signedLicense, RequestBean.class));
475
- if (BlockedRequest.isRequestBlocked(lic.getRequestData(), em)) {
476
- throw new SeCurisServiceException(ErrorCodes.BLOCKED_REQUEST_DATA, "Given request data is blocked and cannot be activated");
477
- }
478
- lic.setLicenseData(JsonUtils.toJSON(signedLicense));
479
- } catch (SeCurisException e) {
480
- LOG.error("Error generating license JSON", e);
481
- throw new SeCurisServiceException(ErrorCodes.INVALID_FORMAT, "Error generating license JSON");
482
- }
484
+ return signedLicense;
485
+ }
483486
484
- lic.setModificationTimestamp(new Date());
485
- lic.setExpirationDate(signedLicense.getExpirationDate());
486
- User user = em.find(User.class, API_CLIENT_USERNAME);
487
+ /**
488
+ * renewLicense<p>
489
+ * Internal renew logic used by JSON and multipart variants.
490
+ *
491
+ * @param previousLicenseBean previous license data
492
+ * @param em entity manager
493
+ * @return new signed license bean
494
+ */
495
+ private SignedLicenseBean renewLicense(LicenseBean previousLicenseBean, EntityManager em)
496
+ throws SeCurisServiceException {
487497
488
- lic.setStatus(LicenseStatus.ACTIVE);
489
- em.merge(lic);
490
- em.persist(licenseHelper.createLicenseHistoryAction(lic, user, LicenseHistory.Actions.RENEW));
498
+ License lic = License.findLicenseByCode(previousLicenseBean.getLicenseCode(), em);
499
+ if (lic.getStatus() != LicenseStatus.ACTIVE && lic.getStatus() != LicenseStatus.PRE_ACTIVE) {
500
+ throw new SeCurisServiceException(ErrorCodes.INVALID_DATA, "The current license has been cancelled");
501
+ }
491502
492
- return signedLicense;
493
- }
503
+ Pack pack = lic.getPack();
504
+ SignedLicenseBean signedLicense;
505
+ try {
506
+ String licCode = lic.getCode();
507
+ Date expirationDate = licenseHelper.getExpirationDateFromPack(pack, false);
508
+
509
+ LicenseBean lb = licenseGenerator.generateLicense(
510
+ previousLicenseBean,
511
+ licenseHelper.extractPackMetadata(pack.getMetadata()),
512
+ expirationDate,
513
+ licCode,
514
+ pack.getAppName()
515
+ );
516
+ signedLicense = new SignedLicenseBean(lb);
517
+ } catch (SeCurisException e) {
518
+ throw new SeCurisServiceException(ErrorCodes.INVALID_LICENSE_REQUEST_DATA, "Error generating license: " + e.toString());
519
+ }
520
+ try {
521
+ lic.setRequestData(JsonUtils.toJSON(signedLicense, RequestBean.class));
522
+ if (BlockedRequest.isRequestBlocked(lic.getRequestData(), em)) {
523
+ throw new SeCurisServiceException(ErrorCodes.BLOCKED_REQUEST_DATA, "Given request data is blocked and cannot be activated");
524
+ }
525
+ lic.setLicenseData(JsonUtils.toJSON(signedLicense));
526
+ } catch (SeCurisException e) {
527
+ LOG.error("Error generating license JSON", e);
528
+ throw new SeCurisServiceException(ErrorCodes.INVALID_FORMAT, "Error generating license JSON");
529
+ }
530
+
531
+ lic.setModificationTimestamp(new Date());
532
+ lic.setExpirationDate(signedLicense.getExpirationDate());
533
+ User user = em.find(User.class, API_CLIENT_USERNAME);
534
+
535
+ lic.setStatus(LicenseStatus.ACTIVE);
536
+ em.merge(lic);
537
+ em.persist(licenseHelper.createLicenseHistoryAction(lic, user, LicenseHistory.Actions.RENEW));
538
+
539
+ return signedLicense;
540
+ }
494541 }
542
+
securis/src/main/java/net/curisit/securis/services/ApplicationResource.java
....@@ -1,3 +1,6 @@
1
+/*
2
+ * Copyright @ 2013 CurisTEC, S.A.S. All Rights Reserved.
3
+ */
14 package net.curisit.securis.services;
25
36 import java.util.Date;
....@@ -42,204 +45,246 @@
4245 import net.curisit.securis.utils.TokenHelper;
4346
4447 /**
45
- * Application resource, this service will provide methods to create, modify and
46
- * delete applications
47
- *
48
- * @author roberto <roberto.sanchez@curisit.net>
48
+ * ApplicationResource
49
+ * <p>
50
+ * REST endpoints to list, fetch, create, update and delete {@link Application}s.
51
+ * Security:
52
+ * <ul>
53
+ * <li>Listing filters by user's accessible application IDs unless ADMIN.</li>
54
+ * <li>Create/Modify/Delete restricted to ADMIN.</li>
55
+ * </ul>
56
+ * Side-effects:
57
+ * <ul>
58
+ * <li>Manages {@link ApplicationMetadata} lifecycle on create/update.</li>
59
+ * <li>Propagates metadata changes via {@link MetadataHelper}.</li>
60
+ * </ul>
61
+ *
62
+ * Author: roberto &lt;roberto.sanchez@curisit.net&gt;<br>
63
+ * Last reviewed by JRA on Oct 5, 2025.
4964 */
5065 @Path("/application")
5166 public class ApplicationResource {
5267
53
- @Inject
54
- TokenHelper tokenHelper;
68
+ @Inject TokenHelper tokenHelper;
69
+ @Inject MetadataHelper metadataHelper;
5570
56
- @Inject
57
- MetadataHelper metadataHelper;
71
+ @Context EntityManager em;
5872
59
- @Context
60
- EntityManager em;
73
+ private static final Logger LOG = LogManager.getLogger(ApplicationResource.class);
6174
62
- private static final Logger LOG = LogManager.getLogger(ApplicationResource.class);
75
+ /**
76
+ * ApplicationResource<p>
77
+ * Constructor
78
+ */
79
+ public ApplicationResource() {}
6380
64
- public ApplicationResource() {
65
- }
81
+ /**
82
+ * index<p>
83
+ * List applications visible to the current user.
84
+ *
85
+ * @param bsc security context
86
+ * @return 200 with list (possibly empty) or 200 empty if user has no app scope
87
+ */
88
+ @GET
89
+ @Path("/")
90
+ @Produces({ MediaType.APPLICATION_JSON })
91
+ @Securable
92
+ public Response index(@Context BasicSecurityContext bsc) {
93
+ LOG.info("Getting applications list ");
94
+ em.clear();
6695
67
- /**
68
- *
69
- * @return the server version in format majorVersion.minorVersion
70
- */
71
- @GET
72
- @Path("/")
73
- @Produces({ MediaType.APPLICATION_JSON })
74
- @Securable
75
- public Response index(@Context BasicSecurityContext bsc) {
76
- LOG.info("Getting applications list ");
96
+ TypedQuery<Application> q;
97
+ if (bsc.isUserInRole(BasicSecurityContext.ROL_ADMIN)) {
98
+ q = em.createNamedQuery("list-applications", Application.class);
99
+ } else {
100
+ if (bsc.getApplicationsIds() == null || bsc.getApplicationsIds().isEmpty()) {
101
+ return Response.ok().build();
102
+ }
103
+ q = em.createNamedQuery("list-applications-by_ids", Application.class);
104
+ q.setParameter("list_ids", bsc.getApplicationsIds());
105
+ }
106
+ List<Application> list = q.getResultList();
107
+ return Response.ok(list).build();
108
+ }
77109
78
- // EntityManager em = emProvider.get();
79
- em.clear();
110
+ /**
111
+ * get<p>
112
+ * Fetch a single application by ID.
113
+ *
114
+ * @param appid string ID
115
+ * @return 200 + entity or 404 if not found
116
+ * @throws SeCurisServiceException when ID is invalid or not found
117
+ */
118
+ @GET
119
+ @Path("/{appid}")
120
+ @Produces({ MediaType.APPLICATION_JSON })
121
+ @Securable
122
+ public Response get(@PathParam("appid") String appid) throws SeCurisServiceException {
123
+ LOG.info("Getting application data for id: {}: ", appid);
124
+ if (appid == null || "".equals(appid)) {
125
+ LOG.error("Application ID is mandatory");
126
+ return Response.status(Status.NOT_FOUND).build();
127
+ }
80128
81
- TypedQuery<Application> q;
82
- if (bsc.isUserInRole(BasicSecurityContext.ROL_ADMIN)) {
83
- q = em.createNamedQuery("list-applications", Application.class);
84
- } else {
85
- if (bsc.getApplicationsIds() == null || bsc.getApplicationsIds().isEmpty()) {
86
- return Response.ok().build();
87
- }
88
- q = em.createNamedQuery("list-applications-by_ids", Application.class);
129
+ em.clear();
130
+ Application app = null;
131
+ try {
132
+ LOG.info("READY to GET app: {}", appid);
133
+ app = em.find(Application.class, Integer.parseInt(appid));
134
+ } catch (Exception e) {
135
+ LOG.info("ERROR GETTING app: {}", e);
136
+ }
137
+ if (app == null) {
138
+ LOG.error("Application with id {} not found in DB", appid);
139
+ throw new SeCurisServiceException(ErrorCodes.NOT_FOUND, "Application not found with ID: " + appid);
140
+ }
141
+ return Response.ok(app).build();
142
+ }
89143
90
- q.setParameter("list_ids", bsc.getApplicationsIds());
91
- }
92
- List<Application> list = q.getResultList();
144
+ /**
145
+ * create<p>
146
+ * Create a new application with optional metadata entries.
147
+ *
148
+ * @param app application payload
149
+ * @param token auth token (audited externally)
150
+ * @return 200 + persisted entity
151
+ */
152
+ @POST
153
+ @Path("/")
154
+ @Consumes(MediaType.APPLICATION_JSON)
155
+ @Produces({ MediaType.APPLICATION_JSON })
156
+ @EnsureTransaction
157
+ @Securable(roles = Rol.ADMIN)
158
+ @RolesAllowed(BasicSecurityContext.ROL_ADMIN)
159
+ public Response create(Application app, @HeaderParam(TokenHelper.TOKEN_HEADER_PÀRAM) String token) {
160
+ LOG.info("Creating new application");
161
+ app.setCreationTimestamp(new Date());
162
+ em.persist(app);
93163
94
- return Response.ok(list).build();
95
- }
164
+ if (app.getApplicationMetadata() != null) {
165
+ for (ApplicationMetadata md : app.getApplicationMetadata()) {
166
+ md.setApplication(app);
167
+ md.setCreationTimestamp(new Date());
168
+ em.persist(md);
169
+ }
170
+ }
171
+ LOG.info("Creating application ({}) with date: {}", app.getId(), app.getCreationTimestamp());
172
+ return Response.ok(app).build();
173
+ }
96174
97
- /**
98
- *
99
- * @return the server version in format majorVersion.minorVersion
100
- * @throws SeCurisServiceException
101
- */
102
- @GET
103
- @Path("/{appid}")
104
- @Produces({ MediaType.APPLICATION_JSON })
105
- @Securable
106
- public Response get(@PathParam("appid") String appid) throws SeCurisServiceException {
107
- LOG.info("Getting application data for id: {}: ", appid);
108
- if (appid == null || "".equals(appid)) {
109
- LOG.error("Application ID is mandatory");
110
- return Response.status(Status.NOT_FOUND).build();
111
- }
175
+ /**
176
+ * modify<p>
177
+ * Update core fields and reconcile metadata set:
178
+ * <ul>
179
+ * <li>Removes missing keys, merges existing, persists new.</li>
180
+ * <li>Propagates metadata if there were changes.</li>
181
+ * </ul>
182
+ *
183
+ * @param appid path ID
184
+ * @param app new state
185
+ */
186
+ @PUT
187
+ @POST
188
+ @Path("/{appid}")
189
+ @EnsureTransaction
190
+ @Consumes(MediaType.APPLICATION_JSON)
191
+ @Produces({ MediaType.APPLICATION_JSON })
192
+ @Securable(roles = Rol.ADMIN)
193
+ @RolesAllowed(BasicSecurityContext.ROL_ADMIN)
194
+ public Response modify(Application app, @PathParam("appid") String appid, @HeaderParam(TokenHelper.TOKEN_HEADER_PÀRAM) String token) {
195
+ LOG.info("Modifying application with id: {}", appid);
196
+ Application currentapp = em.find(Application.class, Integer.parseInt(appid));
197
+ if (currentapp == null) {
198
+ LOG.error("Application with id {} not found in DB", appid);
199
+ return Response.status(Status.NOT_FOUND)
200
+ .header(DefaultExceptionHandler.ERROR_MESSAGE_HEADER, "Application not found with ID: " + appid)
201
+ .build();
202
+ }
112203
113
- em.clear();
204
+ currentapp.setCode(app.getCode());
205
+ currentapp.setName(app.getName());
206
+ currentapp.setLicenseFilename(app.getLicenseFilename());
207
+ currentapp.setDescription(app.getDescription());
114208
115
- Application app = null;
116
- try {
117
- LOG.info("READY to GET app: {}", appid);
118
- app = em.find(Application.class, Integer.parseInt(appid));
119
- } catch (Exception e) {
120
- LOG.info("ERROR GETTING app: {}", e);
121
- }
122
- if (app == null) {
123
- LOG.error("Application with id {} not found in DB", appid);
124
- throw new SeCurisServiceException(ErrorCodes.NOT_FOUND, "Application not found with ID: " + appid);
125
- }
209
+ Set<ApplicationMetadata> newMD = app.getApplicationMetadata();
210
+ Set<ApplicationMetadata> oldMD = currentapp.getApplicationMetadata();
211
+ boolean metadataChanges = !metadataHelper.match(newMD, oldMD);
212
+ if (metadataChanges) {
213
+ Map<String, ApplicationMetadata> directOldMD = getMapMD(oldMD);
214
+ Map<String, ApplicationMetadata> directNewMD = getMapMD(newMD);
126215
127
- return Response.ok(app).build();
128
- }
216
+ // Remove deleted MD
217
+ for (ApplicationMetadata currentMd : oldMD) {
218
+ if (newMD == null || !directNewMD.containsKey(currentMd.getKey())) {
219
+ em.remove(currentMd);
220
+ }
221
+ }
222
+ // Merge or persist
223
+ if (newMD != null) {
224
+ for (ApplicationMetadata md : newMD) {
225
+ if (directOldMD.containsKey(md.getKey())) {
226
+ em.merge(md);
227
+ } else {
228
+ md.setApplication(currentapp);
229
+ if (md.getCreationTimestamp() == null) {
230
+ md.setCreationTimestamp(app.getCreationTimestamp());
231
+ }
232
+ em.persist(md);
233
+ }
234
+ }
235
+ }
236
+ currentapp.setApplicationMetadata(app.getApplicationMetadata());
237
+ }
129238
130
- @POST
131
- @Path("/")
132
- @Consumes(MediaType.APPLICATION_JSON)
133
- @Produces({ MediaType.APPLICATION_JSON })
134
- @EnsureTransaction
135
- @Securable(roles = Rol.ADMIN)
136
- @RolesAllowed(BasicSecurityContext.ROL_ADMIN)
137
- public Response create(Application app, @HeaderParam(TokenHelper.TOKEN_HEADER_PÀRAM) String token) {
138
- LOG.info("Creating new application");
139
- // EntityManager em = emProvider.get();
140
- app.setCreationTimestamp(new Date());
141
- em.persist(app);
239
+ em.merge(currentapp);
240
+ if (metadataChanges) {
241
+ metadataHelper.propagateMetadata(em, currentapp);
242
+ }
243
+ return Response.ok(currentapp).build();
244
+ }
142245
143
- if (app.getApplicationMetadata() != null) {
144
- for (ApplicationMetadata md : app.getApplicationMetadata()) {
145
- md.setApplication(app);
146
- md.setCreationTimestamp(new Date());
147
- em.persist(md);
148
- }
149
- }
150
- LOG.info("Creating application ({}) with date: {}", app.getId(), app.getCreationTimestamp());
246
+ /**
247
+ * getMapMD<p>
248
+ * Build a map from metadata key → entity for fast reconciliation.
249
+ *
250
+ * @param applicationMetadata
251
+ * @return mapMD
252
+ */
253
+ private Map<String, ApplicationMetadata> getMapMD(Set<ApplicationMetadata> amd) {
254
+ Map<String, ApplicationMetadata> map = new HashMap<>();
255
+ if (amd != null) {
256
+ for (ApplicationMetadata m : amd) {
257
+ map.put(m.getKey(), m);
258
+ }
259
+ }
260
+ return map;
261
+ }
151262
152
- return Response.ok(app).build();
153
- }
154
-
155
- @PUT
156
- @POST
157
- @Path("/{appid}")
158
- @EnsureTransaction
159
- @Consumes(MediaType.APPLICATION_JSON)
160
- @Produces({ MediaType.APPLICATION_JSON })
161
- @Securable(roles = Rol.ADMIN)
162
- @RolesAllowed(BasicSecurityContext.ROL_ADMIN)
163
- public Response modify(Application app, @PathParam("appid") String appid, @HeaderParam(TokenHelper.TOKEN_HEADER_PÀRAM) String token) {
164
- LOG.info("Modifying application with id: {}", appid);
165
- // EntityManager em = emProvider.get();
166
- Application currentapp = em.find(Application.class, Integer.parseInt(appid));
167
- if (currentapp == null) {
168
- LOG.error("Application with id {} not found in DB", appid);
169
- return Response.status(Status.NOT_FOUND).header(DefaultExceptionHandler.ERROR_MESSAGE_HEADER, "Application not found with ID: " + appid).build();
170
- }
171
- currentapp.setCode(app.getCode());
172
- currentapp.setName(app.getName());
173
- currentapp.setLicenseFilename(app.getLicenseFilename());
174
- currentapp.setDescription(app.getDescription());
175
-
176
- Set<ApplicationMetadata> newMD = app.getApplicationMetadata();
177
- Set<ApplicationMetadata> oldMD = currentapp.getApplicationMetadata();
178
- boolean metadataChanges = !metadataHelper.match(newMD, oldMD);
179
- if (metadataChanges) {
180
- Map<String, ApplicationMetadata> directOldMD = getMapMD(oldMD);
181
- Map<String, ApplicationMetadata> directNewMD = getMapMD(newMD);
182
- for (ApplicationMetadata currentMd : oldMD) {
183
- if (newMD == null || !directNewMD.containsKey(currentMd.getKey())) {
184
- em.remove(currentMd);
185
- }
186
- }
187
-
188
- if (newMD != null) {
189
- for (ApplicationMetadata md : newMD) {
190
- if (directOldMD.containsKey(md.getKey())) {
191
- em.merge(md);
192
- } else {
193
- md.setApplication(currentapp);
194
- if (md.getCreationTimestamp() == null) {
195
- md.setCreationTimestamp(app.getCreationTimestamp());
196
- }
197
- em.persist(md);
198
- }
199
- }
200
- }
201
- currentapp.setApplicationMetadata(app.getApplicationMetadata());
202
- }
203
- em.merge(currentapp);
204
- if (metadataChanges) {
205
- metadataHelper.propagateMetadata(em, currentapp);
206
- }
207
- return Response.ok(currentapp).build();
208
- }
209
-
210
- private Map<String, ApplicationMetadata> getMapMD(Set<ApplicationMetadata> amd) {
211
- Map<String, ApplicationMetadata> map = new HashMap<String, ApplicationMetadata>();
212
- if (amd != null) {
213
- for (ApplicationMetadata applicationMetadata : amd) {
214
- map.put(applicationMetadata.getKey(), applicationMetadata);
215
- }
216
- }
217
- return map;
218
- }
219
-
220
- @DELETE
221
- @Path("/{appid}")
222
- @EnsureTransaction
223
- @Produces({ MediaType.APPLICATION_JSON })
224
- @Securable(roles = Rol.ADMIN)
225
- @RolesAllowed(BasicSecurityContext.ROL_ADMIN)
226
- public Response delete(@PathParam("appid") String appid, @Context HttpServletRequest request) {
227
- LOG.info("Deleting app with id: {}", appid);
228
- // EntityManager em = emProvider.get();
229
- Application app = em.find(Application.class, Integer.parseInt(appid));
230
- if (app == null) {
231
- LOG.error("Application with id {} can not be deleted, It was not found in DB", appid);
232
- return Response.status(Status.NOT_FOUND).header(DefaultExceptionHandler.ERROR_MESSAGE_HEADER, "Application not found with ID: " + appid).build();
233
- }
234
- /*
235
- * if (app.getLicenseTypes() != null &&
236
- * !app.getLicenseTypes().isEmpty()) { throw new
237
- * SeCurisServiceException(ErrorCodes.NOT_FOUND,
238
- * "Application can not be deleted becasue has assigned one or more License types, ID: "
239
- * + appid); }
240
- */
241
- em.remove(app);
242
- return Response.ok(Utils.createMap("success", true, "id", appid)).build();
243
- }
244
-
263
+ /**
264
+ * delete<p>
265
+ * Delete an application by ID.
266
+ * <p>Note: deletion is not allowed if there are dependent entities (enforced by DB/cascade).</p>
267
+ *
268
+ * @param appId
269
+ * @param request
270
+ */
271
+ @DELETE
272
+ @Path("/{appid}")
273
+ @EnsureTransaction
274
+ @Produces({ MediaType.APPLICATION_JSON })
275
+ @Securable(roles = Rol.ADMIN)
276
+ @RolesAllowed(BasicSecurityContext.ROL_ADMIN)
277
+ public Response delete(@PathParam("appid") String appid, @Context HttpServletRequest request) {
278
+ LOG.info("Deleting app with id: {}", appid);
279
+ Application app = em.find(Application.class, Integer.parseInt(appid));
280
+ if (app == null) {
281
+ LOG.error("Application with id {} can not be deleted, It was not found in DB", appid);
282
+ return Response.status(Status.NOT_FOUND)
283
+ .header(DefaultExceptionHandler.ERROR_MESSAGE_HEADER, "Application not found with ID: " + appid)
284
+ .build();
285
+ }
286
+ em.remove(app);
287
+ return Response.ok(Utils.createMap("success", true, "id", appid)).build();
288
+ }
245289 }
290
+
securis/src/main/java/net/curisit/securis/services/BasicServices.java
....@@ -1,3 +1,6 @@
1
+/*
2
+ * Copyright @ 2013 CurisTEC, S.A.S. All Rights Reserved.
3
+ */
14 package net.curisit.securis.services;
25
36 import java.net.URI;
....@@ -32,96 +35,124 @@
3235 import net.curisit.securis.utils.TokenHelper;
3336
3437 /**
35
- * Basic services for login and basic app wrkflow
36
- *
37
- * @author roberto <roberto.sanchez@curisit.net>
38
+ * BasicServices
39
+ * <p>
40
+ * Minimal public endpoints for service liveness, version info and token checks.
41
+ * Also provides entry routing to SPA (admin/login/licenses) via /index.jsp.
42
+ *
43
+ * Security:
44
+ * <ul>
45
+ * <li>/check requires a valid bearer token (via {@link Securable}).</li>
46
+ * <li>/logout just logs intention; token invalidation is outside this class.</li>
47
+ * </ul>
48
+ *
49
+ * Author: roberto &lt;roberto.sanchez@curisit.net&gt;
50
+ * Last reviewed by JRA on Oct 5, 2025.
3851 */
3952 @Path("/")
4053 @ApplicationScoped
4154 public class BasicServices {
4255
43
- private static final Logger LOG = LogManager.getLogger(BasicServices.class);
56
+ private static final Logger LOG = LogManager.getLogger(BasicServices.class);
4457
45
- @Inject
46
- TokenHelper tokenHelper;
58
+ @Inject TokenHelper tokenHelper;
59
+ @Context EntityManager em;
4760
48
- @Context
49
- EntityManager em;
61
+ @Inject public BasicServices() {}
5062
51
- @Inject
52
- public BasicServices() {
53
- }
63
+ /**
64
+ * info<p>
65
+ * Simple liveness text endpoint.
66
+ *
67
+ * @param request
68
+ * @return response
69
+ */
70
+ @GET
71
+ @Path("/info")
72
+ @Produces({ MediaType.TEXT_PLAIN })
73
+ public Response info(@Context HttpServletRequest request) {
74
+ return Response.ok().entity("License server running OK. Date: " + new Date()).build();
75
+ }
5476
55
- @GET
56
- @Path("/info")
57
- @Produces({ MediaType.TEXT_PLAIN })
58
- public Response info(@Context HttpServletRequest request) {
59
- return Response.ok().entity("License server running OK. Date: " + new Date()).build();
60
- }
77
+ /**
78
+ * version<p>
79
+ * Returns semantic app version as JSON.
80
+ *
81
+ * @param request
82
+ * @return version
83
+ */
84
+ @GET
85
+ @Path("/version")
86
+ @Produces({ MediaType.APPLICATION_JSON })
87
+ public Map<String, String> version(@Context HttpServletRequest request) {
88
+ Map<String, String> resp = new HashMap<>();
89
+ resp.put("version", AppVersion.getInstance().getCompleteVersion());
90
+ return resp;
91
+ }
6192
62
- @GET
63
- @Path("/version")
64
- @Produces({ MediaType.APPLICATION_JSON })
65
- public Map<String, String> version(@Context HttpServletRequest request) {
66
- Map<String, String> resp = new HashMap<>();
67
-
68
- // Get the real version
69
- String version = AppVersion.getInstance().getCompleteVersion();
70
- resp.put("version", version);
71
- return resp;
72
- }
93
+ /**
94
+ * init<p>
95
+ * Redirects SPA modules to the main index page.
96
+ *
97
+ * @param module
98
+ * @param request
99
+ * @return response
100
+ */
101
+ @GET
102
+ @Path("/{module:(admin)|(login)|(licenses)}")
103
+ @Produces({ MediaType.TEXT_HTML })
104
+ public Response init(@PathParam("module") String module, @Context HttpServletRequest request) {
105
+ LOG.info("App index main.html");
106
+ URI uri = UriBuilder.fromUri("/index.jsp").build();
107
+ return Response.seeOther(uri).build();
108
+ }
73109
74
- @GET
75
- @Path("/{module:(admin)|(login)|(licenses)}")
76
- @Produces({ MediaType.TEXT_HTML })
77
- public Response init(@PathParam("module") String module, @Context HttpServletRequest request) {
78
- LOG.info("App index main.html");
79
- String page = "/index.jsp";
80
- URI uri = UriBuilder.fromUri(page).build();
81
- return Response.seeOther(uri).build();
82
- }
110
+ /**
111
+ * check<p>
112
+ * Validates a token (from header or query param).
113
+ *
114
+ * @param token X-Token header
115
+ * @param token2 token query param fallback
116
+ * @return 200 with user/date if valid, 401/403 otherwise
117
+ */
118
+ @GET
119
+ @Securable()
120
+ @Path("/check")
121
+ @Produces({ MediaType.APPLICATION_JSON })
122
+ @EnsureTransaction
123
+ public Response check(@HeaderParam(TokenHelper.TOKEN_HEADER_PÀRAM) String token, @QueryParam("token") String token2) {
124
+ if (token == null) token = token2;
125
+ if (token == null) {
126
+ return Response.status(Status.FORBIDDEN).build();
127
+ }
128
+ boolean valid = tokenHelper.isTokenValid(token);
129
+ if (!valid) {
130
+ return Response.status(Status.UNAUTHORIZED).build();
131
+ }
83132
84
- /**
85
- * Check if current token is valid
86
- *
87
- * @param user
88
- * @param password
89
- * @param request
90
- * @return
91
- */
92
- @GET
93
- @Securable()
94
- @Path("/check")
95
- @Produces({ MediaType.APPLICATION_JSON })
96
- @EnsureTransaction
97
- public Response check(@HeaderParam(TokenHelper.TOKEN_HEADER_PÀRAM) String token, @QueryParam("token") String token2) {
98
- if (token == null) {
99
- token = token2;
100
- }
101
- if (token == null) {
102
- return Response.status(Status.FORBIDDEN).build();
103
- }
104
- boolean valid = tokenHelper.isTokenValid(token);
105
- if (!valid) {
106
- return Response.status(Status.UNAUTHORIZED).build();
107
- }
133
+ String user = tokenHelper.extractUserFromToken(token);
134
+ Date date = tokenHelper.extractDateCreationFromToken(token);
135
+ return Response.ok(Utils.createMap("valid", true, "user", user, "date", date)).build();
136
+ }
108137
109
- String user = tokenHelper.extractUserFromToken(token);
110
- Date date = tokenHelper.extractDateCreationFromToken(token);
111
-
112
- return Response.ok(Utils.createMap("valid", true, "user", user, "date", date)).build();
113
- }
114
-
115
- @GET
116
- @POST
117
- @Path("/logout")
118
- @Produces({ MediaType.APPLICATION_JSON })
119
- public Response logout(@HeaderParam(TokenHelper.TOKEN_HEADER_PÀRAM) String token) {
120
- if (token == null) {
121
- Response.status(Status.BAD_REQUEST).build();
122
- }
123
- String user = tokenHelper.extractUserFromToken(token);
124
- LOG.info("User {} has logged out", user);
125
- return Response.ok().build();
126
- }
138
+ /**
139
+ * logout<p>
140
+ * Logs logout event. (Token invalidation is handled elsewhere.)
141
+ *
142
+ * @param token
143
+ * @return response
144
+ */
145
+ @GET
146
+ @POST
147
+ @Path("/logout")
148
+ @Produces({ MediaType.APPLICATION_JSON })
149
+ public Response logout(@HeaderParam(TokenHelper.TOKEN_HEADER_PÀRAM) String token) {
150
+ if (token == null) {
151
+ Response.status(Status.BAD_REQUEST).build();
152
+ }
153
+ String user = tokenHelper.extractUserFromToken(token);
154
+ LOG.info("User {} has logged out", user);
155
+ return Response.ok().build();
156
+ }
127157 }
158
+
securis/src/main/java/net/curisit/securis/services/LicenseResource.java
....@@ -1,3 +1,6 @@
1
+/*
2
+ * Copyright @ 2013 CurisTEC, S.A.S. All Rights Reserved.
3
+ */
14 package net.curisit.securis.services;
25
36 import java.io.File;
....@@ -63,34 +66,45 @@
6366 import net.curisit.securis.utils.LicUtils;
6467
6568 /**
66
- * License resource, this service will provide methods to create, modify and
67
- * delete licenses
68
- *
69
- * @author roberto <roberto.sanchez@curisit.net>
69
+ * LicenseResource
70
+ * <p>
71
+ * REST resource in charge of managing licenses: list, fetch, create, activate,
72
+ * email delivery, cancel, block/unblock, modify and delete. It relies on
73
+ * {@link BasicSecurityContext} to scope access (organizations/apps) and
74
+ * on {@link EnsureTransaction} for mutating endpoints that need a TX.
75
+ * <p>
76
+ * Key rules:
77
+ * <ul>
78
+ * <li>Non-admin users must belong to the license's organization.</li>
79
+ * <li>License creation validates code CRC, activation code and email.</li>
80
+ * <li>Request payload must match Pack constraints (org/type/pack codes).</li>
81
+ * <li>History is recorded for key actions (CREATE/ACTIVATE/DOWNLOAD/etc.).</li>
82
+ * </ul>
83
+ *
84
+ * @author roberto
85
+ * Last reviewed by JRA on Oct 5, 2025.
7086 */
7187 @Path("/license")
7288 public class LicenseResource {
7389
7490 private static final Logger LOG = LogManager.getLogger(LicenseResource.class);
7591
76
- @Inject
77
- private EmailManager emailManager;
92
+ @Inject private EmailManager emailManager;
93
+ @Inject private UserHelper userHelper;
94
+ @Inject private LicenseHelper licenseHelper;
95
+ @Inject private LicenseGenerator licenseGenerator;
7896
79
- @Inject
80
- private UserHelper userHelper;
81
-
82
- @Inject
83
- private LicenseHelper licenseHelper;
84
-
85
- @Context
86
- EntityManager em;
87
-
88
- @Inject
89
- private LicenseGenerator licenseGenerator;
97
+ @Context EntityManager em;
9098
9199 /**
92
- *
93
- * @return the server version in format majorVersion.minorVersion
100
+ * index
101
+ * <p>
102
+ * List all licenses for a given pack. If the caller is not admin,
103
+ * verifies the pack belongs to an accessible organization.
104
+ *
105
+ * @param packId Pack identifier to filter licenses (required).
106
+ * @param bsc Security context to evaluate roles and scoping.
107
+ * @return 200 OK with a list (possibly empty), or 401 if unauthorized.
94108 */
95109 @GET
96110 @Path("/")
....@@ -98,31 +112,33 @@
98112 @Produces({ MediaType.APPLICATION_JSON })
99113 public Response index(@QueryParam("packId") Integer packId, @Context BasicSecurityContext bsc) {
100114 LOG.info("Getting licenses list ");
101
-
102
- // EntityManager em = emProvider.get();
103115 em.clear();
104116
105117 if (!bsc.isUserInRole(BasicSecurityContext.ROL_ADMIN)) {
106118 Pack pack = em.find(Pack.class, packId);
107
- if (pack == null) {
108
- return Response.ok().build();
109
- }
119
+ if (pack == null) return Response.ok().build();
110120 if (!bsc.getOrganizationsIds().contains(pack.getOrganization().getId())) {
111121 LOG.error("Pack with id {} not accesible by user {}", pack, bsc.getUserPrincipal());
112
- return Response.status(Status.UNAUTHORIZED).header(DefaultExceptionHandler.ERROR_MESSAGE_HEADER, "Unathorized access to pack licenses").build();
122
+ return Response.status(Status.UNAUTHORIZED)
123
+ .header(DefaultExceptionHandler.ERROR_MESSAGE_HEADER, "Unathorized access to pack licenses")
124
+ .build();
113125 }
114126 }
115127 TypedQuery<License> q = em.createNamedQuery("list-licenses-by-pack", License.class);
116128 q.setParameter("packId", packId);
117129 List<License> list = q.getResultList();
118
-
119130 return Response.ok(list).build();
120131 }
121132
122133 /**
123
- *
124
- * @return the server version in format majorVersion.minorVersion
125
- * @throws SeCurisServiceException
134
+ * get
135
+ * <p>
136
+ * Fetch a single license by id, enforcing access scope for non-admin users.
137
+ *
138
+ * @param licId License id (required).
139
+ * @param bsc Security context.
140
+ * @return 200 OK with the license.
141
+ * @throws SeCurisServiceException 404 if not found, 401 if out of scope.
126142 */
127143 @GET
128144 @Path("/{licId}")
....@@ -130,17 +146,22 @@
130146 @Produces({ MediaType.APPLICATION_JSON })
131147 public Response get(@PathParam("licId") Integer licId, @Context BasicSecurityContext bsc) throws SeCurisServiceException {
132148 LOG.info("Getting organization data for id: {}: ", licId);
133
-
134
- // EntityManager em = emProvider.get();
135149 em.clear();
136150 License lic = getCurrentLicense(licId, bsc, em);
137151 return Response.ok(lic).build();
138152 }
139153
140154 /**
141
- *
142
- * @return The license file, only of license is active
143
- * @throws SeCurisServiceException
155
+ * download
156
+ * <p>
157
+ * Download the license file. Only allowed when the license is ACTIVE
158
+ * and license data exists. Adds a DOWNLOAD entry in history.
159
+ *
160
+ * @param licId License id.
161
+ * @param bsc Security context.
162
+ * @return 200 OK with the binary as application/octet-stream and a
163
+ * Content-Disposition header; otherwise specific error codes.
164
+ * @throws SeCurisServiceException if state or data is invalid.
144165 */
145166 @GET
146167 @Path("/{licId}/download")
....@@ -149,7 +170,6 @@
149170 @EnsureTransaction
150171 public Response download(@PathParam("licId") Integer licId, @Context BasicSecurityContext bsc) throws SeCurisServiceException {
151172
152
- // EntityManager em = emProvider.get();
153173 License lic = getCurrentLicense(licId, bsc, em);
154174
155175 if (lic.getLicenseData() == null) {
....@@ -166,12 +186,16 @@
166186 }
167187
168188 /**
169
- * Activate the given license
170
- *
171
- * @param licId
172
- * @param bsc
173
- * @return
174
- * @throws SeCurisServiceException
189
+ * activate
190
+ * <p>
191
+ * Set license to ACTIVE provided status transition is valid, pack has
192
+ * available units and request data passes validation/uniqueness.
193
+ * Adds an ACTIVATE entry in history.
194
+ *
195
+ * @param licId License id.
196
+ * @param bsc Security context.
197
+ * @return 200 OK with updated license.
198
+ * @throws SeCurisServiceException if invalid transition, no availability or invalid request data.
175199 */
176200 @PUT
177201 @POST
....@@ -182,7 +206,6 @@
182206 @Produces({ MediaType.APPLICATION_JSON })
183207 public Response activate(@PathParam("licId") Integer licId, @Context BasicSecurityContext bsc) throws SeCurisServiceException {
184208
185
- // EntityManager em = emProvider.get();
186209 License lic = getCurrentLicense(licId, bsc, em);
187210
188211 if (!License.Status.isActionValid(License.Action.ACTIVATION, lic.getStatus())) {
....@@ -211,12 +234,18 @@
211234 }
212235
213236 /**
214
- * Send license file by email to the organization
215
- *
216
- * @param licId
217
- * @param bsc
218
- * @return
219
- * @throws SeCurisServiceException
237
+ * send
238
+ * <p>
239
+ * Email the license file to the license owner. Builds a temporary file
240
+ * using the application license filename and cleans it afterwards.
241
+ * Adds a SEND entry in history.
242
+ *
243
+ * @param licId License id.
244
+ * @param addCC whether to CC the current operator.
245
+ * @param bsc Security context.
246
+ * @return 200 OK with the license (no state change).
247
+ * @throws SeCurisServiceException when no license file exists or user full name is missing.
248
+ * @throws SeCurisException if JSON/signature process fails.
220249 */
221250 @SuppressWarnings("deprecation")
222251 @PUT
....@@ -229,7 +258,6 @@
229258 public Response send(@PathParam("licId") Integer licId, @DefaultValue("false") @FormParam("add_cc") Boolean addCC, @Context BasicSecurityContext bsc)
230259 throws SeCurisServiceException, SeCurisException {
231260
232
- // EntityManager em = emProvider.get();
233261 License lic = getCurrentLicense(licId, bsc, em);
234262 Application app = lic.getPack().getLicenseType().getApplication();
235263 File licFile = null;
....@@ -259,19 +287,21 @@
259287 }
260288 }
261289
262
- // lic.setModificationTimestamp(new Date());
263
- // em.merge(lic);
264290 em.persist(licenseHelper.createLicenseHistoryAction(lic, user, LicenseHistory.Actions.SEND, "Email sent to: " + lic.getEmail()));
265291 return Response.ok(lic).build();
266292 }
267293
268294 /**
269
- * Cancel given license
270
- *
271
- * @param licId
272
- * @param bsc
273
- * @return
274
- * @throws SeCurisServiceException
295
+ * cancel
296
+ * <p>
297
+ * Cancel a license (requires valid state transition and a non-null reason).
298
+ * Delegates to {@link LicenseHelper#cancelLicense}.
299
+ *
300
+ * @param licId License id.
301
+ * @param actionData DTO carrying the cancellation reason.
302
+ * @param bsc Security context.
303
+ * @return 200 OK with updated license.
304
+ * @throws SeCurisServiceException when state is invalid or reason is missing.
275305 */
276306 @PUT
277307 @POST
....@@ -282,7 +312,6 @@
282312 @Produces({ MediaType.APPLICATION_JSON })
283313 public Response cancel(@PathParam("licId") Integer licId, CancellationLicenseActionBean actionData, @Context BasicSecurityContext bsc) throws SeCurisServiceException {
284314
285
- // EntityManager em = emProvider.get();
286315 License lic = getCurrentLicense(licId, bsc, em);
287316
288317 if (!License.Status.isActionValid(License.Action.CANCEL, lic.getStatus())) {
....@@ -300,22 +329,24 @@
300329 }
301330
302331 /**
303
- * Check if there is some pack with the same code
304
- *
305
- * @param code
306
- * Pack code
307
- * @param em
308
- * DB session object
309
- * @return <code>true</code> if code is already used, <code>false</code>
310
- * otherwise
332
+ * create
333
+ * <p>
334
+ * Create a license. Validates:
335
+ * <ul>
336
+ * <li>Unique license code and valid CRC.</li>
337
+ * <li>Activation code presence and uniqueness.</li>
338
+ * <li>Valid user email.</li>
339
+ * <li>Pack existence, ACTIVE status and scope authorization.</li>
340
+ * <li>Request data consistency and unblock status (if provided).</li>
341
+ * </ul>
342
+ * If request data is provided and the Pack has availability, the license is
343
+ * generated and set to ACTIVE immediately.
344
+ *
345
+ * @param lic License payload.
346
+ * @param bsc Security context.
347
+ * @return 200 OK with created license.
348
+ * @throws SeCurisServiceException on validation failures.
311349 */
312
- private boolean checkIfCodeExists(String code, EntityManager em) {
313
- TypedQuery<License> query = em.createNamedQuery("license-by-code", License.class);
314
- query.setParameter("code", code);
315
- int lics = query.getResultList().size();
316
- return lics > 0;
317
- }
318
-
319350 @POST
320351 @Path("/")
321352 @Consumes(MediaType.APPLICATION_JSON)
....@@ -323,8 +354,6 @@
323354 @Produces({ MediaType.APPLICATION_JSON })
324355 @EnsureTransaction
325356 public Response create(License lic, @Context BasicSecurityContext bsc) throws SeCurisServiceException {
326
- // EntityManager em = emProvider.get();
327
-
328357 if (checkIfCodeExists(lic.getCode(), em)) {
329358 throw new SeCurisServiceException(ErrorCodes.INVALID_DATA, "The license code is already used in an existing license");
330359 }
....@@ -370,10 +399,8 @@
370399 }
371400
372401 if (pack.getNumAvailables() > 0) {
373
-
374402 SignedLicenseBean signedLicense = generateLicense(lic, em);
375
- // If user provide a request data the license status is passed
376
- // directly to ACTIVE
403
+ // Move directly to ACTIVE when request data is provided
377404 lic.setStatus(LicenseStatus.ACTIVE);
378405 try {
379406 lic.setRequestData(JsonUtils.toJSON(signedLicense, RequestBean.class));
....@@ -404,6 +431,203 @@
404431 return Response.ok(lic).build();
405432 }
406433
434
+ /**
435
+ * modify
436
+ * <p>
437
+ * Update license basic fields (comments, fullName, email) and, when
438
+ * status is CREATED and request payload changes, re-normalize/validate and
439
+ * regenerate the signed license data. Adds a MODIFY history entry.
440
+ *
441
+ * @param lic New values.
442
+ * @param licId License id.
443
+ * @param bsc Security context.
444
+ * @return 200 OK with updated license.
445
+ * @throws SeCurisServiceException if validation fails.
446
+ */
447
+ @SuppressWarnings("deprecation")
448
+ @PUT
449
+ @POST
450
+ @Path("/{licId}")
451
+ @Securable(roles = Rol.ADMIN | Rol.ADVANCE)
452
+ @EnsureTransaction
453
+ @Consumes(MediaType.APPLICATION_JSON)
454
+ @Produces({ MediaType.APPLICATION_JSON })
455
+ public Response modify(License lic, @PathParam("licId") Integer licId, @Context BasicSecurityContext bsc) throws SeCurisServiceException {
456
+ LOG.info("Modifying license with id: {}", licId);
457
+
458
+ License currentLicense = getCurrentLicense(licId, bsc, em);
459
+ currentLicense.setComments(lic.getComments());
460
+ currentLicense.setFullName(lic.getFullName());
461
+ currentLicense.setEmail(lic.getEmail());
462
+ if (currentLicense.getActivationCode() == null) {
463
+ currentLicense.setActivationCode(lic.getActivationCode());
464
+ }
465
+
466
+ if (currentLicense.getStatus() == LicenseStatus.CREATED && !ObjectUtils.equals(currentLicense.getReqDataHash(), lic.getReqDataHash())) {
467
+ if (lic.getRequestData() != null) {
468
+ SignedLicenseBean signedLicense = generateLicense(lic, em);
469
+ try {
470
+ // Normalize the request JSON and update signed license JSON
471
+ currentLicense.setRequestData(JsonUtils.toJSON(signedLicense, RequestBean.class));
472
+ LOG.info("JSON generated for request: \n{}", currentLicense.getRequestData());
473
+ if (BlockedRequest.isRequestBlocked(currentLicense.getRequestData(), em)) {
474
+ throw new SeCurisServiceException(ErrorCodes.BLOCKED_REQUEST_DATA, "Given request data is blocked and cannot be used again");
475
+ }
476
+ currentLicense.setLicenseData(JsonUtils.toJSON(signedLicense));
477
+ } catch (SeCurisException e) {
478
+ LOG.error("Error generaing license JSON", e);
479
+ throw new SeCurisServiceException(ErrorCodes.INVALID_FORMAT, "Error generaing license JSON");
480
+ }
481
+ } else {
482
+ currentLicense.setRequestData(null);
483
+ }
484
+ }
485
+
486
+ currentLicense.setModificationTimestamp(new Date());
487
+ em.persist(currentLicense);
488
+ em.persist(licenseHelper.createLicenseHistoryAction(lic, userHelper.getUser(bsc, em), LicenseHistory.Actions.MODIFY));
489
+
490
+ return Response.ok(currentLicense).build();
491
+ }
492
+
493
+ /**
494
+ * delete
495
+ * <p>
496
+ * Delete the license when the current status allows it. If the license
497
+ * was BLOCKED, removes the BlockedRequest entry to unblock the request.
498
+ *
499
+ * @param licId License id.
500
+ * @param bsc Security context.
501
+ * @return 200 OK with a success payload.
502
+ * @throws SeCurisServiceException if status does not allow deletion.
503
+ */
504
+ @DELETE
505
+ @Path("/{licId}")
506
+ @EnsureTransaction
507
+ @Securable(roles = Rol.ADMIN | Rol.ADVANCE)
508
+ @Produces({ MediaType.APPLICATION_JSON })
509
+ public Response delete(@PathParam("licId") Integer licId, @Context BasicSecurityContext bsc) throws SeCurisServiceException {
510
+ LOG.info("Deleting license with id: {}", licId);
511
+ License lic = getCurrentLicense(licId, bsc, em);
512
+
513
+ if (!License.Status.isActionValid(License.Action.DELETE, lic.getStatus())) {
514
+ LOG.error("License {} can not be deleted with status {}", lic.getCode(), lic.getStatus());
515
+ throw new SeCurisServiceException(ErrorCodes.WRONG_STATUS, "License can not be deleted in current status: " + lic.getStatus().name());
516
+ }
517
+ if (lic.getStatus() == LicenseStatus.BLOCKED) {
518
+ BlockedRequest blockedReq = em.find(BlockedRequest.class, lic.getReqDataHash());
519
+ if (blockedReq != null) {
520
+ em.remove(blockedReq);
521
+ }
522
+ }
523
+
524
+ em.remove(lic);
525
+ return Response.ok(Utils.createMap("success", true, "id", licId)).build();
526
+ }
527
+
528
+ /**
529
+ * block
530
+ * <p>
531
+ * Block the license request data (allowed only from CANCELLED state).
532
+ * Persists a {@link BlockedRequest} and transitions the license to BLOCKED.
533
+ *
534
+ * @param licId License id.
535
+ * @param bsc Security context.
536
+ * @return 200 OK with a success payload.
537
+ * @throws SeCurisServiceException if state is not CANCELLED or already blocked.
538
+ */
539
+ @POST
540
+ @Path("/{licId}/block")
541
+ @EnsureTransaction
542
+ @Securable(roles = Rol.ADMIN | Rol.ADVANCE)
543
+ @Produces({ MediaType.APPLICATION_JSON })
544
+ public Response block(@PathParam("licId") Integer licId, @Context BasicSecurityContext bsc) throws SeCurisServiceException {
545
+ LOG.info("Blocking license with id: {}", licId);
546
+ License lic = getCurrentLicense(licId, bsc, em);
547
+
548
+ if (!License.Status.isActionValid(License.Action.BLOCK, lic.getStatus())) {
549
+ LOG.error("License can only be blocked in CANCELLED status, current: {}", lic.getStatus().name());
550
+ throw new SeCurisServiceException(ErrorCodes.WRONG_STATUS, "License can only be blocked in CANCELLED status");
551
+ }
552
+ if (BlockedRequest.isRequestBlocked(lic.getRequestData(), em)) {
553
+ throw new SeCurisServiceException(ErrorCodes.BLOCKED_REQUEST_DATA, "Given request data is already blocked");
554
+ }
555
+ BlockedRequest blockedReq = new BlockedRequest();
556
+ blockedReq.setCreationTimestamp(new Date());
557
+ blockedReq.setBlockedBy(userHelper.getUser(bsc, em));
558
+ blockedReq.setRequestData(lic.getRequestData());
559
+
560
+ em.persist(blockedReq);
561
+ lic.setStatus(LicenseStatus.BLOCKED);
562
+ lic.setModificationTimestamp(new Date());
563
+ em.merge(lic);
564
+
565
+ em.persist(licenseHelper.createLicenseHistoryAction(lic, userHelper.getUser(bsc, em), LicenseHistory.Actions.BLOCK));
566
+ return Response.ok(Utils.createMap("success", true, "id", licId)).build();
567
+ }
568
+
569
+ /**
570
+ * unblock
571
+ * <p>
572
+ * Remove the block for the license request data (if present) and move
573
+ * license back to CANCELLED. Adds an UNBLOCK history entry.
574
+ *
575
+ * @param licId License id.
576
+ * @param bsc Security context.
577
+ * @return 200 OK with a success payload.
578
+ * @throws SeCurisServiceException never if not blocked (returns success anyway).
579
+ */
580
+ @POST
581
+ @Path("/{licId}/unblock")
582
+ @EnsureTransaction
583
+ @Securable(roles = Rol.ADMIN | Rol.ADVANCE)
584
+ @Produces({ MediaType.APPLICATION_JSON })
585
+ public Response unblock(@PathParam("licId") Integer licId, @Context BasicSecurityContext bsc) throws SeCurisServiceException {
586
+ LOG.info("Unblocking license with id: {}", licId);
587
+ License lic = getCurrentLicense(licId, bsc, em);
588
+
589
+ if (BlockedRequest.isRequestBlocked(lic.getRequestData(), em)) {
590
+ BlockedRequest blockedReq = em.find(BlockedRequest.class, lic.getReqDataHash());
591
+ em.remove(blockedReq);
592
+
593
+ lic.setStatus(LicenseStatus.CANCELLED);
594
+ lic.setModificationTimestamp(new Date());
595
+ em.merge(lic);
596
+ em.persist(licenseHelper.createLicenseHistoryAction(lic, userHelper.getUser(bsc, em), LicenseHistory.Actions.UNBLOCK));
597
+ } else {
598
+ LOG.info("Request data for license {} is NOT blocked", licId);
599
+ }
600
+
601
+ return Response.ok(Utils.createMap("success", true, "id", licId)).build();
602
+ }
603
+
604
+ // ---------------------------------------------------------------------
605
+ // Helpers
606
+ // ---------------------------------------------------------------------
607
+
608
+ /**
609
+ * checkIfCodeExists<p>
610
+ * Check if there is an existing license with the same code.
611
+ *
612
+ * @param code
613
+ * @param entityManager
614
+ */
615
+ private boolean checkIfCodeExists(String code, EntityManager em) {
616
+ TypedQuery<License> query = em.createNamedQuery("license-by-code", License.class);
617
+ query.setParameter("code", code);
618
+ int lics = query.getResultList().size();
619
+ return lics > 0;
620
+ }
621
+
622
+ /**
623
+ * generateLicense<p>
624
+ * Generate a signed license from request data and pack metadata/expiration.
625
+ *
626
+ * @param license License with requestData and packId populated.
627
+ * @param em Entity manager.
628
+ * @return Signed license bean.
629
+ * @throws SeCurisServiceException if validation/generation fails.
630
+ */
407631 private SignedLicenseBean generateLicense(License license, EntityManager em) throws SeCurisServiceException {
408632 SignedLicenseBean sl = null;
409633 Pack pack = em.find(Pack.class, license.getPackId());
....@@ -419,12 +643,14 @@
419643 }
420644
421645 /**
422
- * We check if the given request data is valid for the current Pack and has
423
- * a valid format
424
- *
425
- * @param pack
426
- * @param requestData
427
- * @throws SeCurisServiceException
646
+ * validateRequestData<p>
647
+ * Validate that requestData matches the Pack and is well-formed.
648
+ *
649
+ * @param pack Target pack (org/type constraints).
650
+ * @param requestData Raw JSON string with the license request.
651
+ * @param activationCode Activation code from the license payload.
652
+ * @return Parsed {@link RequestBean}.
653
+ * @throws SeCurisServiceException on format mismatch or wrong codes.
428654 */
429655 private RequestBean validateRequestData(Pack pack, String requestData, String activationCode) throws SeCurisServiceException {
430656 if (requestData == null) {
....@@ -456,143 +682,16 @@
456682 return rb;
457683 }
458684
459
- @SuppressWarnings("deprecation")
460
- @PUT
461
- @POST
462
- @Path("/{licId}")
463
- @Securable(roles = Rol.ADMIN | Rol.ADVANCE)
464
- @EnsureTransaction
465
- @Consumes(MediaType.APPLICATION_JSON)
466
- @Produces({ MediaType.APPLICATION_JSON })
467
- public Response modify(License lic, @PathParam("licId") Integer licId, @Context BasicSecurityContext bsc) throws SeCurisServiceException {
468
- LOG.info("Modifying license with id: {}", licId);
469
-
470
- // EntityManager em = emProvider.get();
471
-
472
- License currentLicense = getCurrentLicense(licId, bsc, em);
473
- currentLicense.setComments(lic.getComments());
474
- currentLicense.setFullName(lic.getFullName());
475
- currentLicense.setEmail(lic.getEmail());
476
- if (currentLicense.getActivationCode() == null) {
477
- currentLicense.setActivationCode(lic.getActivationCode());
478
- }
479
-
480
- if (currentLicense.getStatus() == LicenseStatus.CREATED && !ObjectUtils.equals(currentLicense.getReqDataHash(), lic.getReqDataHash())) {
481
- if (lic.getRequestData() != null) {
482
- SignedLicenseBean signedLicense = generateLicense(lic, em);
483
- try {
484
- // Next 2 lines are necessary to normalize the String that
485
- // contains
486
- // the request.
487
- currentLicense.setRequestData(JsonUtils.toJSON(signedLicense, RequestBean.class));
488
- LOG.info("JSON generated for request: \n{}", currentLicense.getRequestData());
489
- if (BlockedRequest.isRequestBlocked(currentLicense.getRequestData(), em)) {
490
- throw new SeCurisServiceException(ErrorCodes.BLOCKED_REQUEST_DATA, "Given request data is blocked and cannot be used again");
491
- }
492
- currentLicense.setLicenseData(JsonUtils.toJSON(signedLicense));
493
- } catch (SeCurisException e) {
494
- LOG.error("Error generaing license JSON", e);
495
- throw new SeCurisServiceException(ErrorCodes.INVALID_FORMAT, "Error generaing license JSON");
496
- }
497
- } else {
498
- // This set method could pass a null value
499
- currentLicense.setRequestData(null);
500
- }
501
- }
502
-
503
- currentLicense.setModificationTimestamp(new Date());
504
- em.persist(currentLicense);
505
- em.persist(licenseHelper.createLicenseHistoryAction(lic, userHelper.getUser(bsc, em), LicenseHistory.Actions.MODIFY));
506
-
507
- return Response.ok(currentLicense).build();
508
- }
509
-
510
- @DELETE
511
- @Path("/{licId}")
512
- @EnsureTransaction
513
- @Securable(roles = Rol.ADMIN | Rol.ADVANCE)
514
- @Produces({ MediaType.APPLICATION_JSON })
515
- public Response delete(@PathParam("licId") Integer licId, @Context BasicSecurityContext bsc) throws SeCurisServiceException {
516
- LOG.info("Deleting license with id: {}", licId);
517
- // EntityManager em = emProvider.get();
518
- License lic = getCurrentLicense(licId, bsc, em);
519
-
520
- if (!License.Status.isActionValid(License.Action.DELETE, lic.getStatus())) {
521
- LOG.error("License {} can not be deleted with status {}", lic.getCode(), lic.getStatus());
522
- throw new SeCurisServiceException(ErrorCodes.WRONG_STATUS, "License can not be deleted in current status: " + lic.getStatus().name());
523
- }
524
- if (lic.getStatus() == LicenseStatus.BLOCKED) {
525
- // If license is removed and it's blocked then the blocked request
526
- // should be removed, that is,
527
- // the license deletion will unblock the request data
528
- BlockedRequest blockedReq = em.find(BlockedRequest.class, lic.getReqDataHash());
529
- if (blockedReq != null) {
530
- // This if is to avoid some race condition or if the request has
531
- // been already removed manually
532
- em.remove(blockedReq);
533
- }
534
- }
535
-
536
- em.remove(lic);
537
- return Response.ok(Utils.createMap("success", true, "id", licId)).build();
538
- }
539
-
540
- @POST
541
- @Path("/{licId}/block")
542
- @EnsureTransaction
543
- @Securable(roles = Rol.ADMIN | Rol.ADVANCE)
544
- @Produces({ MediaType.APPLICATION_JSON })
545
- public Response block(@PathParam("licId") Integer licId, @Context BasicSecurityContext bsc) throws SeCurisServiceException {
546
- LOG.info("Blocking license with id: {}", licId);
547
- // EntityManager em = emProvider.get();
548
- License lic = getCurrentLicense(licId, bsc, em);
549
-
550
- if (!License.Status.isActionValid(License.Action.BLOCK, lic.getStatus())) {
551
- LOG.error("License can only be blocked in CANCELLED status, current: {}", lic.getStatus().name());
552
- throw new SeCurisServiceException(ErrorCodes.WRONG_STATUS, "License can only be blocked in CANCELLED status");
553
- }
554
- if (BlockedRequest.isRequestBlocked(lic.getRequestData(), em)) {
555
- throw new SeCurisServiceException(ErrorCodes.BLOCKED_REQUEST_DATA, "Given request data is already blocked");
556
- }
557
- BlockedRequest blockedReq = new BlockedRequest();
558
- blockedReq.setCreationTimestamp(new Date());
559
- blockedReq.setBlockedBy(userHelper.getUser(bsc, em));
560
- blockedReq.setRequestData(lic.getRequestData());
561
-
562
- em.persist(blockedReq);
563
- lic.setStatus(LicenseStatus.BLOCKED);
564
- lic.setModificationTimestamp(new Date());
565
- em.merge(lic);
566
-
567
- em.persist(licenseHelper.createLicenseHistoryAction(lic, userHelper.getUser(bsc, em), LicenseHistory.Actions.BLOCK));
568
- return Response.ok(Utils.createMap("success", true, "id", licId)).build();
569
- }
570
-
571
- @POST
572
- @Path("/{licId}/unblock")
573
- @EnsureTransaction
574
- @Securable(roles = Rol.ADMIN | Rol.ADVANCE)
575
- @Produces({ MediaType.APPLICATION_JSON })
576
- public Response unblock(@PathParam("licId") Integer licId, @Context BasicSecurityContext bsc) throws SeCurisServiceException {
577
- LOG.info("Unblocking license with id: {}", licId);
578
- // EntityManager em = emProvider.get();
579
- License lic = getCurrentLicense(licId, bsc, em);
580
-
581
- if (BlockedRequest.isRequestBlocked(lic.getRequestData(), em)) {
582
- BlockedRequest blockedReq = em.find(BlockedRequest.class, lic.getReqDataHash());
583
- em.remove(blockedReq);
584
-
585
- lic.setStatus(LicenseStatus.CANCELLED);
586
- lic.setModificationTimestamp(new Date());
587
- em.merge(lic);
588
- em.persist(licenseHelper.createLicenseHistoryAction(lic, userHelper.getUser(bsc, em), LicenseHistory.Actions.UNBLOCK));
589
- } else {
590
- LOG.info("Request data for license {} is NOT blocked", licId);
591
- }
592
-
593
- return Response.ok(Utils.createMap("success", true, "id", licId)).build();
594
- }
595
-
685
+ /**
686
+ * getCurrentLicense<p>
687
+ * Load a license and verify scope for non-admin users.
688
+ *
689
+ * @param licId License id.
690
+ * @param bsc Security context.
691
+ * @param em Entity manager.
692
+ * @return License entity.
693
+ * @throws SeCurisServiceException if id is missing, not found or unauthorized.
694
+ */
596695 private License getCurrentLicense(Integer licId, BasicSecurityContext bsc, EntityManager em) throws SeCurisServiceException {
597696 if (licId == null || "".equals(Integer.toString(licId))) {
598697 LOG.error("License ID is mandatory");
....@@ -611,6 +710,13 @@
611710 return lic;
612711 }
613712
713
+ // ---------------------------------------------------------------------
714
+ // DTOs
715
+ // ---------------------------------------------------------------------
716
+
717
+ /**
718
+ * DTO used to carry a cancellation reason for the cancel endpoint.
719
+ */
614720 @JsonAutoDetect
615721 @JsonIgnoreProperties(ignoreUnknown = true)
616722 static class CancellationLicenseActionBean {
....@@ -618,3 +724,4 @@
618724 private String reason;
619725 }
620726 }
727
+
securis/src/main/java/net/curisit/securis/services/LicenseTypeResource.java
....@@ -1,3 +1,6 @@
1
+/*
2
+ * Copyright @ 2013 CurisTEC, S.A.S. All Rights Reserved.
3
+ */
14 package net.curisit.securis.services;
25
36 import java.util.Date;
....@@ -44,31 +47,34 @@
4447 import net.curisit.securis.utils.TokenHelper;
4548
4649 /**
47
- * LicenseType resource, this service will provide methods to create, modify and
48
- * delete license types
49
- *
50
- * @author roberto <roberto.sanchez@curisit.net>
50
+ * LicenseTypeResource
51
+ * <p>
52
+ * CRUD for license types. Non-admin queries are scoped to the applications
53
+ * accessible by the caller. Metadata changes are reconciled and, when keys
54
+ * change, can be propagated to dependent entities via {@link MetadataHelper}.
55
+ *
56
+ * @author JRA
57
+ * Last reviewed by JRA on Oct 5, 2025.
5158 */
5259 @Path("/licensetype")
5360 public class LicenseTypeResource {
5461
5562 private static final Logger LOG = LogManager.getLogger(LicenseTypeResource.class);
5663
57
- @Inject
58
- TokenHelper tokenHelper;
64
+ @Inject TokenHelper tokenHelper;
65
+ @Inject MetadataHelper metadataHelper;
5966
60
- @Inject
61
- MetadataHelper metadataHelper;
67
+ @Context EntityManager em;
6268
63
- @Context
64
- EntityManager em;
65
-
66
- public LicenseTypeResource() {
67
- }
69
+ public LicenseTypeResource() { }
6870
6971 /**
70
- *
71
- * @return the server version in format majorVersion.minorVersion
72
+ * index
73
+ * <p>
74
+ * List license types. Non-admin users get only types for their allowed apps.
75
+ *
76
+ * @param bsc security context.
77
+ * @return 200 OK with list (possibly empty).
7278 */
7379 @GET
7480 @Path("/")
....@@ -76,8 +82,6 @@
7682 @Securable
7783 public Response index(@Context BasicSecurityContext bsc) {
7884 LOG.info("Getting license types list ");
79
-
80
- // EntityManager em = emProvider.get();
8185 em.clear();
8286 TypedQuery<LicenseType> q;
8387 if (bsc.isUserInRole(BasicSecurityContext.ROL_ADMIN)) {
....@@ -87,18 +91,21 @@
8791 return Response.ok().build();
8892 }
8993 q = em.createNamedQuery("list-license_types-by_apps-id", LicenseType.class);
90
-
9194 q.setParameter("list_ids", bsc.getApplicationsIds());
9295 }
9396 List<LicenseType> list = q.getResultList();
94
-
9597 return Response.ok(list).build();
9698 }
9799
98100 /**
99
- *
100
- * @return the server version in format majorVersion.minorVersion
101
- * @throws SeCurisServiceException
101
+ * get
102
+ * <p>
103
+ * Fetch a license type by id.
104
+ *
105
+ * @param ltid LicenseType id (string form).
106
+ * @param token (unused) header token.
107
+ * @return 200 OK with the entity.
108
+ * @throws SeCurisServiceException 404 if not found.
102109 */
103110 @GET
104111 @Path("/{ltid}")
....@@ -111,7 +118,6 @@
111118 return Response.status(Status.NOT_FOUND).build();
112119 }
113120
114
- // EntityManager em = emProvider.get();
115121 em.clear();
116122 LicenseType lt = em.find(LicenseType.class, Integer.parseInt(ltid));
117123 if (lt == null) {
....@@ -121,6 +127,16 @@
121127 return Response.ok(lt).build();
122128 }
123129
130
+ /**
131
+ * create
132
+ * <p>
133
+ * Create a new license type. Requires ADMIN. Sets application reference,
134
+ * persists metadata entries and stamps creation time.
135
+ *
136
+ * @param lt Payload.
137
+ * @param token (unused) token header.
138
+ * @return 200 OK with created entity, or 404 if app missing.
139
+ */
124140 @POST
125141 @Path("/")
126142 @Consumes(MediaType.APPLICATION_JSON)
....@@ -130,7 +146,6 @@
130146 @RolesAllowed(BasicSecurityContext.ROL_ADMIN)
131147 public Response create(LicenseType lt, @HeaderParam(TokenHelper.TOKEN_HEADER_PÀRAM) String token) {
132148 LOG.info("Creating new license type");
133
- // EntityManager em = emProvider.get();
134149
135150 try {
136151 setApplication(lt, lt.getApplicationId(), em);
....@@ -158,16 +173,18 @@
158173 return Response.ok(lt).build();
159174 }
160175
161
- private Set<String> getMdKeys(Set<LicenseTypeMetadata> mds) {
162
- Set<String> ids = new HashSet<String>();
163
- if (mds != null) {
164
- for (LicenseTypeMetadata md : mds) {
165
- ids.add(md.getKey());
166
- }
167
- }
168
- return ids;
169
- }
170
-
176
+ /**
177
+ * modify
178
+ * <p>
179
+ * Update an existing license type. Reconciles metadata:
180
+ * removes keys not present in the new set; merges existing; persists new ones.
181
+ * If keys changed, {@link MetadataHelper#propagateMetadata} is invoked.
182
+ *
183
+ * @param lt New values.
184
+ * @param ltid LicenseType id.
185
+ * @param token (unused) token.
186
+ * @return 200 OK with updated entity; 404 if not found or app missing.
187
+ */
171188 @PUT
172189 @POST
173190 @Path("/{ltid}")
....@@ -178,7 +195,6 @@
178195 @RolesAllowed(BasicSecurityContext.ROL_ADMIN)
179196 public Response modify(LicenseType lt, @PathParam("ltid") String ltid, @HeaderParam(TokenHelper.TOKEN_HEADER_PÀRAM) String token) {
180197 LOG.info("Modifying license type with id: {}", ltid);
181
- // EntityManager em = emProvider.get();
182198 LicenseType currentlt = em.find(LicenseType.class, Integer.parseInt(ltid));
183199 if (currentlt == null) {
184200 LOG.error("LicenseType with id {} not found in DB", ltid);
....@@ -230,28 +246,23 @@
230246 return Response.ok(currentlt).build();
231247 }
232248
233
- private void setApplication(LicenseType licType, Integer applicationId, EntityManager em) throws SeCurisException {
234
- Application app = null;
235
- if (applicationId != null) {
236
- app = em.find(Application.class, applicationId);
237
- if (app == null) {
238
- LOG.error("LicenseType application with id {} not found in DB", applicationId);
239
-
240
- throw new SecurityException("License type's app not found with ID: " + applicationId);
241
- }
242
- }
243
- licType.setApplication(app);
244
- }
245
-
249
+ /**
250
+ * delete
251
+ * <p>
252
+ * Delete a license type by id. Requires ADMIN.
253
+ *
254
+ * @param ltid LicenseType id.
255
+ * @param req request (unused).
256
+ * @return 200 OK on success; 404 if not found.
257
+ */
246258 @DELETE
247259 @Path("/{ltid}")
248260 @EnsureTransaction
249261 @Produces({ MediaType.APPLICATION_JSON })
250262 @Securable(roles = Rol.ADMIN)
251263 @RolesAllowed(BasicSecurityContext.ROL_ADMIN)
252
- public Response delete(@PathParam("ltid") String ltid, @Context HttpServletRequest request) {
264
+ public Response delete(@PathParam("ltid") String ltid, @Context HttpServletRequest req) {
253265 LOG.info("Deleting app with id: {}", ltid);
254
- // EntityManager em = emProvider.get();
255266 LicenseType app = em.find(LicenseType.class, Integer.parseInt(ltid));
256267 if (app == null) {
257268 LOG.error("LicenseType with id {} can not be deleted, It was not found in DB", ltid);
....@@ -262,4 +273,39 @@
262273 return Response.ok(Utils.createMap("success", true, "id", ltid)).build();
263274 }
264275
276
+ // ---------------------------------------------------------------------
277
+ // Helpers
278
+ // ---------------------------------------------------------------------
279
+
280
+ private Set<String> getMdKeys(Set<LicenseTypeMetadata> mds) {
281
+ Set<String> ids = new HashSet<String>();
282
+ if (mds != null) {
283
+ for (LicenseTypeMetadata md : mds) {
284
+ ids.add(md.getKey());
285
+ }
286
+ }
287
+ return ids;
288
+ }
289
+
290
+ /**
291
+ * setApplication<p>
292
+ * Resolve and set the application of a license type.
293
+ *
294
+ * @param licType target LicenseType.
295
+ * @param applicationId id of the application (nullable).
296
+ * @param em entity manager.
297
+ * @throws SeCurisException if id provided but not found.
298
+ */
299
+ private void setApplication(LicenseType licType, Integer applicationId, EntityManager em) throws SeCurisException {
300
+ Application app = null;
301
+ if (applicationId != null) {
302
+ app = em.find(Application.class, applicationId);
303
+ if (app == null) {
304
+ LOG.error("LicenseType application with id {} not found in DB", applicationId);
305
+ throw new SecurityException("License type's app not found with ID: " + applicationId);
306
+ }
307
+ }
308
+ licType.setApplication(app);
309
+ }
265310 }
311
+
securis/src/main/java/net/curisit/securis/services/OrganizationResource.java
....@@ -1,3 +1,6 @@
1
+/*
2
+ * Copyright @ 2013 CurisTEC, S.A.S. All Rights Reserved.
3
+ */
14 package net.curisit.securis.services;
25
36 import java.util.Date;
....@@ -39,10 +42,12 @@
3942 import net.curisit.securis.utils.TokenHelper;
4043
4144 /**
42
- * Organization resource, this service will provide methods to create, modify
43
- * and delete organizations
44
- *
45
- * @author roberto <roberto.sanchez@curisit.net>
45
+ * OrganizationResource
46
+ * <p>
47
+ * CRUD and listing of organizations. Non-admin users are scoped by their
48
+ * accessible organization ids when listing.
49
+ *
50
+ * Last reviewed by JRA on Oct 5, 2025.
4651 */
4752 @Path("/organization")
4853 @RequestScoped
....@@ -50,18 +55,18 @@
5055
5156 private static final Logger LOG = LogManager.getLogger(OrganizationResource.class);
5257
53
- @Context
54
- EntityManager em;
58
+ @Context EntityManager em;
59
+ @Context BasicSecurityContext bsc;
5560
56
- @Context
57
- BasicSecurityContext bsc;
58
-
59
- public OrganizationResource() {
60
- }
61
+ public OrganizationResource() { }
6162
6263 /**
63
- *
64
- * @return the server version in format majorVersion.minorVersion
64
+ * index
65
+ * <p>
66
+ * List organizations. For admins returns all; for non-admins filters
67
+ * by the ids in {@link BasicSecurityContext#getOrganizationsIds()}.
68
+ *
69
+ * @return 200 OK with the list.
6570 */
6671 @GET
6772 @Path("/")
....@@ -69,8 +74,6 @@
6974 @Securable
7075 public Response index() {
7176 LOG.info("Getting organizations list ");
72
-
73
- // EntityManager em = emProvider.get();
7477 em.clear();
7578 TypedQuery<Organization> q;
7679 if (bsc.isUserInRole(BasicSecurityContext.ROL_ADMIN)) {
....@@ -84,15 +87,18 @@
8487 q.setParameter("list_ids", bsc.getOrganizationsIds());
8588 }
8689 }
87
-
8890 List<Organization> list = q.getResultList();
89
-
9091 return Response.ok(list).build();
9192 }
9293
9394 /**
94
- *
95
- * @return the server version in format majorVersion.minorVersion
95
+ * get
96
+ * <p>
97
+ * Fetch an organization by id.
98
+ *
99
+ * @param orgid organization id (string form).
100
+ * @param token header token (unused).
101
+ * @return 200 OK with entity or 404 if not found.
96102 */
97103 @GET
98104 @Path("/{orgid}")
....@@ -104,8 +110,6 @@
104110 LOG.error("Organization ID is mandatory");
105111 return Response.status(Status.NOT_FOUND).build();
106112 }
107
-
108
- // EntityManager em = emProvider.get();
109113 em.clear();
110114 Organization org = em.find(Organization.class, Integer.parseInt(orgid));
111115 if (org == null) {
....@@ -115,16 +119,15 @@
115119 return Response.ok(org).build();
116120 }
117121
118
- private boolean isCyclicalRelationship(int currentId, Organization parent) {
119
- while (parent != null) {
120
- if (parent.getId() == currentId) {
121
- return true;
122
- }
123
- parent = parent.getParentOrganization();
124
- }
125
- return false;
126
- }
127
-
122
+ /**
123
+ * create
124
+ * <p>
125
+ * Create a new organization, setting optional parent and user members.
126
+ * Requires ADMIN.
127
+ *
128
+ * @param org payload with code/name/etc., optional parentOrgId and usersIds.
129
+ * @return 200 OK with created organization or 404 when parent/user not found.
130
+ */
128131 @POST
129132 @Path("/")
130133 @Consumes(MediaType.APPLICATION_JSON)
....@@ -134,7 +137,6 @@
134137 @RolesAllowed(BasicSecurityContext.ROL_ADMIN)
135138 public Response create(Organization org) {
136139 LOG.info("Creating new organization");
137
- // EntityManager em = emProvider.get();
138140
139141 try {
140142 this.setParentOrg(org, org.getParentOrgId(), em);
....@@ -162,36 +164,17 @@
162164 return Response.ok(org).build();
163165 }
164166
165
- private void setParentOrg(Organization org, Integer parentOrgId, EntityManager em) throws SeCurisException {
166
- Organization parentOrg = null;
167
- if (parentOrgId != null) {
168
- parentOrg = em.find(Organization.class, parentOrgId);
169
- if (parentOrg == null) {
170
- LOG.error("Organization parent with id {} not found in DB", org.getParentOrgId());
171
- throw new SecurityException("Organization's parent not found with ID: " + org.getParentOrgId());
172
- }
173
- }
174
-
175
- org.setParentOrganization(parentOrg);
176
- }
177
-
178
- private void setOrgUsers(Organization org, Set<String> usersIds, EntityManager em) throws SeCurisException {
179
- Set<User> users = null;
180
- if (usersIds != null && !usersIds.isEmpty()) {
181
- users = new HashSet<>();
182
- for (String username : usersIds) {
183
- User user = em.find(User.class, username);
184
- if (user == null) {
185
- LOG.error("Organization user with id '{}' not found in DB", username);
186
- throw new SecurityException("Organization's user not found with ID: " + username);
187
- }
188
- users.add(user);
189
- }
190
- }
191
-
192
- org.setUsers(users);
193
- }
194
-
167
+ /**
168
+ * modify
169
+ * <p>
170
+ * Update an organization. Validates no cyclic parent relationship,
171
+ * updates parent and user set. Requires ADMIN.
172
+ *
173
+ * @param org new values (including optional parent/usersIds).
174
+ * @param orgid target id.
175
+ * @param token (unused) header token.
176
+ * @return 200 OK with updated organization, or specific error status.
177
+ */
195178 @PUT
196179 @POST
197180 @Path("/{orgid}")
....@@ -202,7 +185,6 @@
202185 @RolesAllowed(BasicSecurityContext.ROL_ADMIN)
203186 public Response modify(Organization org, @PathParam("orgid") String orgid, @HeaderParam(TokenHelper.TOKEN_HEADER_PÀRAM) String token) {
204187 LOG.info("Modifying organization with id: {}", orgid);
205
- // EntityManager em = emProvider.get();
206188 Organization currentOrg = em.find(Organization.class, Integer.parseInt(orgid));
207189 if (currentOrg == null) {
208190 LOG.error("Organization with id {} not found in DB", orgid);
....@@ -233,15 +215,23 @@
233215 return Response.ok(currentOrg).build();
234216 }
235217
218
+ /**
219
+ * delete
220
+ * <p>
221
+ * Delete an organization if it has no children. Requires ADMIN.
222
+ *
223
+ * @param orgid target id.
224
+ * @param req request (unused).
225
+ * @return 200 OK with success map, or 404/403 on constraints.
226
+ */
236227 @DELETE
237228 @Path("/{orgid}")
238229 @EnsureTransaction
239230 @Produces({ MediaType.APPLICATION_JSON })
240231 @Securable(roles = Rol.ADMIN)
241232 @RolesAllowed(BasicSecurityContext.ROL_ADMIN)
242
- public Response delete(@PathParam("orgid") String orgid, @Context HttpServletRequest request) {
233
+ public Response delete(@PathParam("orgid") String orgid, @Context HttpServletRequest req) {
243234 LOG.info("Deleting organization with id: {}", orgid);
244
- // EntityManager em = emProvider.get();
245235 Organization org = em.find(Organization.class, Integer.parseInt(orgid));
246236 if (org == null) {
247237 LOG.error("Organization with id {} can not be deleted, It was not found in DB", orgid);
....@@ -256,4 +246,70 @@
256246 return Response.ok(Utils.createMap("success", true, "id", orgid)).build();
257247 }
258248
249
+ // ---------------------------------------------------------------------
250
+ // Helpers
251
+ // ---------------------------------------------------------------------
252
+
253
+ /**
254
+ * isCyclicalRelationship<p>
255
+ * Detects cycles by walking up the parent chain.
256
+ *
257
+ * @param currentId
258
+ * @param parent
259
+ * @return isCyclicalRelationship
260
+ */
261
+ private boolean isCyclicalRelationship(int currentId, Organization parent) {
262
+ while (parent != null) {
263
+ if (parent.getId() == currentId) return true;
264
+ parent = parent.getParentOrganization();
265
+ }
266
+ return false;
267
+ }
268
+
269
+ /**
270
+ * setParentOrg<p>
271
+ * Resolve and set parent organization (nullable).
272
+ *
273
+ * @param org
274
+ * @param parentOrgId
275
+ * @param entitymanager
276
+ * @throws SeCurisException
277
+ */
278
+ private void setParentOrg(Organization org, Integer parentOrgId, EntityManager em) throws SeCurisException {
279
+ Organization parentOrg = null;
280
+ if (parentOrgId != null) {
281
+ parentOrg = em.find(Organization.class, parentOrgId);
282
+ if (parentOrg == null) {
283
+ LOG.error("Organization parent with id {} not found in DB", org.getParentOrgId());
284
+ throw new SecurityException("Organization's parent not found with ID: " + org.getParentOrgId());
285
+ }
286
+ }
287
+ org.setParentOrganization(parentOrg);
288
+ }
289
+
290
+ /**
291
+ * setOrgUsers<p>
292
+ * Replace organization users from the provided usernames set.
293
+ *
294
+ * @param org
295
+ * @param usersIds
296
+ * @param entityManager
297
+ * @throws SeCurisException
298
+ */
299
+ private void setOrgUsers(Organization org, Set<String> usersIds, EntityManager em) throws SeCurisException {
300
+ Set<User> users = null;
301
+ if (usersIds != null && !usersIds.isEmpty()) {
302
+ users = new HashSet<>();
303
+ for (String username : usersIds) {
304
+ User user = em.find(User.class, username);
305
+ if (user == null) {
306
+ LOG.error("Organization user with id '{}' not found in DB", username);
307
+ throw new SecurityException("Organization's user not found with ID: " + username);
308
+ }
309
+ users.add(user);
310
+ }
311
+ }
312
+ org.setUsers(users);
313
+ }
259314 }
315
+
securis/src/main/java/net/curisit/securis/services/PackResource.java
....@@ -1,3 +1,6 @@
1
+/*
2
+ * Copyright @ 2013 CurisTEC, S.A.S. All Rights Reserved.
3
+ */
14 package net.curisit.securis.services;
25
36 import java.security.Principal;
....@@ -53,31 +56,35 @@
5356 import net.curisit.securis.utils.TokenHelper;
5457
5558 /**
56
- * Pack resource, this service will provide methods to create, modify and delete
57
- * packs
58
- *
59
- * @author roberto <roberto.sanchez@curisit.net>
59
+ * PackResource
60
+ * <p>
61
+ * Manages Packs (group of licenses bound to an organization, application/type,
62
+ * and configuration/metadata). Provides list/filter, get, create, modify,
63
+ * state transitions (activate/hold/cancel) and deletion.
64
+ *
65
+ * @author JRA
66
+ * Last reviewed by JRA on Oct 5, 2025.
6067 */
6168 @Path("/pack")
6269 public class PackResource {
6370
6471 private static final Logger LOG = LogManager.getLogger(PackResource.class);
6572
66
- @Inject
67
- TokenHelper tokenHelper;
73
+ @Inject TokenHelper tokenHelper;
74
+ @Inject MetadataHelper metadataHelper;
75
+ @Inject private LicenseHelper licenseHelper;
6876
69
- @Inject
70
- MetadataHelper metadataHelper;
71
-
72
- @Context
73
- EntityManager em;
74
-
75
- @Inject
76
- private LicenseHelper licenseHelper;
77
+ @Context EntityManager em;
7778
7879 /**
79
- *
80
- * @return the server version in format majorVersion.minorVersion
80
+ * index
81
+ * <p>
82
+ * List packs with optional filters (organizationId, applicationId, licenseTypeId).
83
+ * For non-admins, results are scoped by both apps and orgs from {@link BasicSecurityContext}.
84
+ *
85
+ * @param uriInfo supplies query parameters.
86
+ * @param bsc security scope/roles.
87
+ * @return 200 OK with the list (possibly empty).
8188 */
8289 @GET
8390 @Path("/")
....@@ -86,70 +93,25 @@
8693 public Response index(@Context UriInfo uriInfo, @Context BasicSecurityContext bsc) {
8794 LOG.info("Getting packs list ");
8895 MultivaluedMap<String, String> queryParams = uriInfo.getQueryParameters();
89
-
90
- // EntityManager em = emProvider.get();
9196 em.clear();
9297
9398 TypedQuery<Pack> q = createQuery(queryParams, bsc);
9499 if (q == null) {
95100 return Response.ok().build();
96101 }
97
-
98102 List<Pack> list = q.getResultList();
99
-
100103 return Response.ok(list).build();
101104 }
102105
103
- private String generateWhereFromParams(boolean addWhere, MultivaluedMap<String, String> queryParams) {
104
- List<String> conditions = new ArrayList<>();
105
- if (queryParams.containsKey("organizationId")) {
106
- conditions.add(String.format("pa.organization.id = %s", queryParams.getFirst("organizationId")));
107
- }
108
- if (queryParams.containsKey("applicationId")) {
109
- conditions.add(String.format("pa.licenseType.application.id = %s", queryParams.getFirst("applicationId")));
110
- }
111
- if (queryParams.containsKey("licenseTypeId")) {
112
- conditions.add(String.format("pa.licenseType.id = %s", queryParams.getFirst("licenseTypeId")));
113
- }
114
- String connector = addWhere ? " where " : " and ";
115
- return (conditions.isEmpty() ? "" : connector) + String.join(" and ", conditions);
116
- }
117
-
118
- private TypedQuery<Pack> createQuery(MultivaluedMap<String, String> queryParams, BasicSecurityContext bsc) {
119
- TypedQuery<Pack> q;
120
- String hql = "SELECT pa FROM Pack pa";
121
- if (bsc.isUserInRole(BasicSecurityContext.ROL_ADMIN)) {
122
- hql += generateWhereFromParams(true, queryParams);
123
- q = em.createQuery(hql, Pack.class);
124
- } else {
125
- if (bsc.getApplicationsIds() == null || bsc.getApplicationsIds().isEmpty()) {
126
- return null;
127
- }
128
- if (bsc.getOrganizationsIds() == null || bsc.getOrganizationsIds().isEmpty()) {
129
- hql += " where pa.licenseType.application.id in :list_ids_app ";
130
- } else {
131
- hql += " where pa.organization.id in :list_ids_org and pa.licenseType.application.id in :list_ids_app ";
132
- }
133
- hql += generateWhereFromParams(false, queryParams);
134
- q = em.createQuery(hql, Pack.class);
135
- if (hql.contains("list_ids_org")) {
136
- q.setParameter("list_ids_org", bsc.getOrganizationsIds());
137
- }
138
- q.setParameter("list_ids_app", bsc.getApplicationsIds());
139
- LOG.info("Getting packs from orgs: {} and apps: {}", bsc.getOrganizationsIds(), bsc.getApplicationsIds());
140
- }
141
-
142
- return q;
143
- }
144
-
145
- private Response generateErrorUnathorizedAccess(Pack pack, Principal user) {
146
- LOG.error("Pack with id {} not accesible by user {}", pack, user);
147
- return Response.status(Status.UNAUTHORIZED).header(DefaultExceptionHandler.ERROR_MESSAGE_HEADER, "Unathorized access to pack").build();
148
- }
149
-
150106 /**
151
- *
152
- * @return the server version in format majorVersion.minorVersion
107
+ * get
108
+ * <p>
109
+ * Fetch a pack by id. If the caller is an ADVANCE user, validates
110
+ * the organization scope.
111
+ *
112
+ * @param packId pack id.
113
+ * @param bsc security context.
114
+ * @return 200 OK with entity, or 404/401 accordingly.
153115 */
154116 @GET
155117 @Path("/{packId}")
....@@ -162,7 +124,6 @@
162124 return Response.status(Status.NOT_FOUND).build();
163125 }
164126
165
- // EntityManager em = emProvider.get();
166127 em.clear();
167128 Pack pack = em.find(Pack.class, packId);
168129 if (pack == null) {
....@@ -175,6 +136,18 @@
175136 return Response.ok(pack).build();
176137 }
177138
139
+ /**
140
+ * create
141
+ * <p>
142
+ * Create a new pack. Validates code uniqueness, sets organization and
143
+ * license type references, stamps creator and timestamps, and persists
144
+ * metadata entries.
145
+ *
146
+ * @param pack payload.
147
+ * @param bsc security context (for createdBy).
148
+ * @return 200 OK with created pack or 404 when references not found.
149
+ * @throws SeCurisServiceException on duplicated code.
150
+ */
178151 @POST
179152 @Path("/")
180153 @Securable(roles = Rol.ADMIN | Rol.ADVANCE)
....@@ -184,7 +157,6 @@
184157 @EnsureTransaction
185158 public Response create(Pack pack, @Context BasicSecurityContext bsc) throws SeCurisServiceException {
186159 LOG.info("Creating new pack");
187
- // EntityManager em = emProvider.get();
188160
189161 if (checkIfCodeExists(pack.getCode(), em)) {
190162 throw new SeCurisServiceException(ErrorCodes.INVALID_DATA, "The pack code is already used in an existing pack");
....@@ -221,67 +193,16 @@
221193 }
222194
223195 /**
224
- * Check if there is some pack with the same code
225
- *
226
- * @param code
227
- * Pack code
228
- * @param em
229
- * DB session object
230
- * @return <code>true</code> if code is already used, <code>false</code>
231
- * otherwise
196
+ * modify
197
+ * <p>
198
+ * Update a pack basic fields and reconcile metadata (remove/merge/persist).
199
+ * If metadata keys changed, marks dependent licenses metadata as obsolete via
200
+ * {@link MetadataHelper#markObsoleteMetadata}.
201
+ *
202
+ * @param pack payload values.
203
+ * @param packId target id.
204
+ * @return 200 OK with updated pack or 404 on ref errors.
232205 */
233
- private boolean checkIfCodeExists(String code, EntityManager em) {
234
- TypedQuery<Pack> query = em.createNamedQuery("pack-by-code", Pack.class);
235
- query.setParameter("code", code);
236
- int packs = query.getResultList().size();
237
- return packs > 0;
238
- }
239
-
240
- /**
241
- *
242
- * @return The next available code suffix in pack for license code
243
- * @throws SeCurisServiceException
244
- */
245
- @GET
246
- @Path("/{packId}/next_license_code")
247
- @Securable(roles = Rol.ADMIN | Rol.ADVANCE)
248
- @Produces({ MediaType.TEXT_PLAIN })
249
- public Response getCodeSuffix(@PathParam("packId") Integer packId, @Context BasicSecurityContext bsc) throws SeCurisServiceException {
250
- // EntityManager em = emProvider.get();
251
-
252
- if (packId == null) {
253
- throw new SeCurisServiceException(ErrorCodes.INVALID_DATA, "The pack code is mandatory");
254
- }
255
- Integer codeSuffix = licenseHelper.getNextCodeSuffix(packId, em);
256
- Pack pack = em.find(Pack.class, packId);
257
- ;
258
-
259
- String licCode = LicUtils.getLicenseCode(pack.getCode(), codeSuffix);
260
- return Response.ok(licCode).build();
261
- }
262
-
263
- private void setPackLicenseType(Pack pack, Integer licTypeId, EntityManager em) throws SeCurisException {
264
- LicenseType lt = null;
265
- if (licTypeId != null) {
266
- lt = em.find(LicenseType.class, pack.getLicTypeId());
267
- if (lt == null) {
268
- LOG.error("Pack license type with id {} not found in DB", licTypeId);
269
- throw new SeCurisException("Pack license type not found with ID: " + licTypeId);
270
- }
271
- }
272
- pack.setLicenseType(lt);
273
- }
274
-
275
- private Set<String> getMdKeys(Set<PackMetadata> mds) {
276
- Set<String> ids = new HashSet<String>();
277
- if (mds != null) {
278
- for (PackMetadata md : mds) {
279
- ids.add(md.getKey());
280
- }
281
- }
282
- return ids;
283
- }
284
-
285206 @PUT
286207 @POST
287208 @Path("/{packId}")
....@@ -292,7 +213,6 @@
292213 @Produces({ MediaType.APPLICATION_JSON })
293214 public Response modify(Pack pack, @PathParam("packId") Integer packId) {
294215 LOG.info("Modifying pack with id: {}", packId);
295
- // EntityManager em = emProvider.get();
296216 Pack currentPack = em.find(Pack.class, packId);
297217
298218 try {
....@@ -348,6 +268,15 @@
348268 return Response.ok(currentPack).build();
349269 }
350270
271
+ /**
272
+ * activate
273
+ * <p>
274
+ * Move a pack to ACTIVE (only from allowed states).
275
+ *
276
+ * @param packId target pack id.
277
+ * @return 200 OK with updated pack or error when invalid transition.
278
+ * @throws SeCurisServiceException when invalid state transition.
279
+ */
351280 @POST
352281 @Path("/{packId}/activate")
353282 @EnsureTransaction
....@@ -357,7 +286,6 @@
357286 @Produces({ MediaType.APPLICATION_JSON })
358287 public Response activate(@PathParam("packId") Integer packId) throws SeCurisServiceException {
359288 LOG.info("Activating pack with id: {}", packId);
360
- // EntityManager em = emProvider.get();
361289
362290 Pack currentPack = em.find(Pack.class, packId);
363291
....@@ -372,8 +300,17 @@
372300 return Response.ok(currentPack).build();
373301 }
374302
303
+ /**
304
+ * onhold
305
+ * <p>
306
+ * Put a pack ON_HOLD from allowed states.
307
+ *
308
+ * @param packId id.
309
+ * @return 200 OK with updated pack or error on invalid state.
310
+ * @throws SeCurisServiceException on invalid state.
311
+ */
375312 @POST
376
- @Path("/{packId}/putonhold")
313
+ @Path("/{packId}/putonhold}")
377314 @EnsureTransaction
378315 @Securable(roles = Rol.ADMIN | Rol.ADVANCE)
379316 @RolesAllowed(BasicSecurityContext.ROL_ADMIN)
....@@ -381,7 +318,6 @@
381318 @Produces({ MediaType.APPLICATION_JSON })
382319 public Response onhold(@PathParam("packId") Integer packId) throws SeCurisServiceException {
383320 LOG.info("Putting On hold pack with id: {}", packId);
384
- // EntityManager em = emProvider.get();
385321
386322 Pack currentPack = em.find(Pack.class, packId);
387323
....@@ -396,6 +332,18 @@
396332 return Response.ok(currentPack).build();
397333 }
398334
335
+ /**
336
+ * cancel
337
+ * <p>
338
+ * Cancel a pack. Cascades cancel to ACTIVE/PRE_ACTIVE licenses in the pack
339
+ * via {@link LicenseHelper#cancelLicense}.
340
+ *
341
+ * @param packId id.
342
+ * @param reason cancellation reason.
343
+ * @param bsc actor for history entries.
344
+ * @return 200 OK with updated pack.
345
+ * @throws SeCurisServiceException on invalid state.
346
+ */
399347 @POST
400348 @Path("/{packId}/cancel")
401349 @EnsureTransaction
....@@ -405,7 +353,6 @@
405353 @Produces({ MediaType.APPLICATION_JSON })
406354 public Response cancel(@PathParam("packId") Integer packId, @FormParam("reason") String reason, @Context BasicSecurityContext bsc) throws SeCurisServiceException {
407355 LOG.info("Cancelling pack with id: {}", packId);
408
- // EntityManager em = emProvider.get();
409356
410357 Pack currentPack = em.find(Pack.class, packId);
411358
....@@ -426,18 +373,41 @@
426373 return Response.ok(currentPack).build();
427374 }
428375
429
- private void setPackOrganization(Pack currentPack, Integer orgId, EntityManager em) throws SeCurisException {
430
- Organization org = null;
431
- if (orgId != null) {
432
- org = em.find(Organization.class, orgId);
433
- if (org == null) {
434
- LOG.error("Organization pack with id {} not found in DB", orgId);
435
- throw new SeCurisException("Pack organization not found with ID: " + orgId);
436
- }
376
+ /**
377
+ * getCodeSuffix
378
+ * <p>
379
+ * Compute the next available license code for a pack, by asking the helper
380
+ * for the next numeric suffix and composing with {@link LicUtils}.
381
+ *
382
+ * @param packId id.
383
+ * @param bsc (unused) security context.
384
+ * @return 200 OK with the full code text.
385
+ * @throws SeCurisServiceException if packId missing.
386
+ */
387
+ @GET
388
+ @Path("/{packId}/next_license_code")
389
+ @Securable(roles = Rol.ADMIN | Rol.ADVANCE)
390
+ @Produces({ MediaType.TEXT_PLAIN })
391
+ public Response getCodeSuffix(@PathParam("packId") Integer packId, @Context BasicSecurityContext bsc) throws SeCurisServiceException {
392
+ if (packId == null) {
393
+ throw new SeCurisServiceException(ErrorCodes.INVALID_DATA, "The pack code is mandatory");
437394 }
438
- currentPack.setOrganization(org);
395
+ Integer codeSuffix = licenseHelper.getNextCodeSuffix(packId, em);
396
+ Pack pack = em.find(Pack.class, packId);
397
+ String licCode = LicUtils.getLicenseCode(pack.getCode(), codeSuffix);
398
+ return Response.ok(licCode).build();
439399 }
440400
401
+ /**
402
+ * delete
403
+ * <p>
404
+ * Delete a pack after ensuring there are no ACTIVE/PRE_ACTIVE licenses.
405
+ * Removes remaining licenses then the pack itself.
406
+ *
407
+ * @param packId String id.
408
+ * @return 200 OK with success map, 404 if missing, or 409 if active license exists.
409
+ * @throws SeCurisServiceException on constraint errors.
410
+ */
441411 @DELETE
442412 @Path("/{packId}")
443413 @Securable(roles = Rol.ADMIN | Rol.ADVANCE)
....@@ -446,13 +416,11 @@
446416 @Produces({ MediaType.APPLICATION_JSON })
447417 public Response delete(@PathParam("packId") String packId) throws SeCurisServiceException {
448418 LOG.info("Deleting pack with id: {}", packId);
449
- // EntityManager em = emProvider.get();
450419 Pack pack = em.find(Pack.class, Integer.parseInt(packId));
451420 if (pack == null) {
452421 LOG.error("Pack with id {} can not be deleted, It was not found in DB", packId);
453422 return Response.status(Status.NOT_FOUND).header(DefaultExceptionHandler.ERROR_MESSAGE_HEADER, "Pack was not found, ID: " + packId).build();
454423 }
455
- // Pack metadata is removed in cascade automatically.
456424
457425 Set<License> licenses = pack.getLicenses();
458426 for (License license : licenses) {
....@@ -466,4 +434,149 @@
466434 return Response.ok(Utils.createMap("success", true, "id", packId)).build();
467435 }
468436
437
+ // ---------------------------------------------------------------------
438
+ // Helpers
439
+ // ---------------------------------------------------------------------
440
+
441
+ /**
442
+ * generateWhereFromParams<p>
443
+ * Generate where clause to include to a query
444
+ *
445
+ * @param addWhere
446
+ * @param queryParams
447
+ * @return whereClause
448
+ */
449
+ private String generateWhereFromParams(boolean addWhere, MultivaluedMap<String, String> queryParams) {
450
+ List<String> conditions = new ArrayList<>();
451
+ if (queryParams.containsKey("organizationId")) {
452
+ conditions.add(String.format("pa.organization.id = %s", queryParams.getFirst("organizationId")));
453
+ }
454
+ if (queryParams.containsKey("applicationId")) {
455
+ conditions.add(String.format("pa.licenseType.application.id = %s", queryParams.getFirst("applicationId")));
456
+ }
457
+ if (queryParams.containsKey("licenseTypeId")) {
458
+ conditions.add(String.format("pa.licenseType.id = %s", queryParams.getFirst("licenseTypeId")));
459
+ }
460
+ String connector = addWhere ? " where " : " and ";
461
+ return (conditions.isEmpty() ? "" : connector) + String.join(" and ", conditions);
462
+ }
463
+
464
+ /**
465
+ * createQuery<p>
466
+ * Build a typed query considering role-based scopes and filters.
467
+ *
468
+ * @param queryParams
469
+ * @param basicSecurityContext
470
+ */
471
+ private TypedQuery<Pack> createQuery(MultivaluedMap<String, String> queryParams, BasicSecurityContext bsc) {
472
+ TypedQuery<Pack> q;
473
+ String hql = "SELECT pa FROM Pack pa";
474
+ if (bsc.isUserInRole(BasicSecurityContext.ROL_ADMIN)) {
475
+ hql += generateWhereFromParams(true, queryParams);
476
+ q = em.createQuery(hql, Pack.class);
477
+ } else {
478
+ if (bsc.getApplicationsIds() == null || bsc.getApplicationsIds().isEmpty()) {
479
+ return null;
480
+ }
481
+ if (bsc.getOrganizationsIds() == null || bsc.getOrganizationsIds().isEmpty()) {
482
+ hql += " where pa.licenseType.application.id in :list_ids_app ";
483
+ } else {
484
+ hql += " where pa.organization.id in :list_ids_org and pa.licenseType.application.id in :list_ids_app ";
485
+ }
486
+ hql += generateWhereFromParams(false, queryParams);
487
+ q = em.createQuery(hql, Pack.class);
488
+ if (hql.contains("list_ids_org")) {
489
+ q.setParameter("list_ids_org", bsc.getOrganizationsIds());
490
+ }
491
+ q.setParameter("list_ids_app", bsc.getApplicationsIds());
492
+ LOG.info("Getting packs from orgs: {} and apps: {}", bsc.getOrganizationsIds(), bsc.getApplicationsIds());
493
+ }
494
+ return q;
495
+ }
496
+
497
+ /**
498
+ * generateErrorUnathorizedAccess<p>
499
+ * Convenience 401 generator with log.
500
+ *
501
+ * @param pack
502
+ * @param user
503
+ */
504
+ private Response generateErrorUnathorizedAccess(Pack pack, Principal user) {
505
+ LOG.error("Pack with id {} not accesible by user {}", pack, user);
506
+ return Response.status(Status.UNAUTHORIZED).header(DefaultExceptionHandler.ERROR_MESSAGE_HEADER, "Unathorized access to pack").build();
507
+ }
508
+
509
+ /**
510
+ * setPackLicenseType<p>
511
+ * Set the pack type
512
+ *
513
+ * @param pack
514
+ * @param licTypeId
515
+ * @param em
516
+ * @throws SeCurisException
517
+ */
518
+ private void setPackLicenseType(Pack pack, Integer licTypeId, EntityManager em) throws SeCurisException {
519
+ LicenseType lt = null;
520
+ if (licTypeId != null) {
521
+ lt = em.find(LicenseType.class, pack.getLicTypeId());
522
+ if (lt == null) {
523
+ LOG.error("Pack license type with id {} not found in DB", licTypeId);
524
+ throw new SeCurisException("Pack license type not found with ID: " + licTypeId);
525
+ }
526
+ }
527
+ pack.setLicenseType(lt);
528
+ }
529
+
530
+ /**
531
+ * getMdKeys<p>
532
+ * Get the MD keys
533
+ *
534
+ * @param mds
535
+ * @return mdKeys
536
+ */
537
+ private Set<String> getMdKeys(Set<PackMetadata> mds) {
538
+ Set<String> ids = new HashSet<String>();
539
+ if (mds != null) {
540
+ for (PackMetadata md : mds) {
541
+ ids.add(md.getKey());
542
+ }
543
+ }
544
+ return ids;
545
+ }
546
+
547
+ /**
548
+ * checkIfCodeExists<p>
549
+ * Check if the code already exist
550
+ *
551
+ * @param code
552
+ * @param em
553
+ * @return codeExist
554
+ */
555
+ private boolean checkIfCodeExists(String code, EntityManager em) {
556
+ TypedQuery<Pack> query = em.createNamedQuery("pack-by-code", Pack.class);
557
+ query.setParameter("code", code);
558
+ int packs = query.getResultList().size();
559
+ return packs > 0;
560
+ }
561
+
562
+ /**
563
+ * setPackOrganization<p>
564
+ * Set the organization of the pack
565
+ *
566
+ * @param currentPack
567
+ * @param orgId
568
+ * @param em
569
+ * @throws SeCurisException
570
+ */
571
+ private void setPackOrganization(Pack currentPack, Integer orgId, EntityManager em) throws SeCurisException {
572
+ Organization org = null;
573
+ if (orgId != null) {
574
+ org = em.find(Organization.class, orgId);
575
+ if (org == null) {
576
+ LOG.error("Organization pack with id {} not found in DB", orgId);
577
+ throw new SeCurisException("Pack organization not found with ID: " + orgId);
578
+ }
579
+ }
580
+ currentPack.setOrganization(org);
581
+ }
469582 }
securis/src/main/java/net/curisit/securis/services/UserResource.java
....@@ -1,3 +1,6 @@
1
+/*
2
+ * Copyright @ 2013 CurisTEC, S.A.S. All Rights Reserved.
3
+ */
14 package net.curisit.securis.services;
25
36 import java.util.Date;
....@@ -47,291 +50,437 @@
4750 import net.curisit.securis.utils.TokenHelper;
4851
4952 /**
50
- * User resource
51
- *
53
+ * UserResource
54
+ * <p>
55
+ * REST resource that manages users (CRUD + authentication helpers).
56
+ * All endpoints are guarded and ADMIN-only unless otherwise stated.
57
+ * <p>
58
+ * Notes:
59
+ * - Uses {@link BasicSecurityContext} authorization via @Securable and @RolesAllowed.
60
+ * - Uses JPA {@link EntityManager} injected through @Context.
61
+ * - Mutating endpoints are wrapped in @EnsureTransaction to guarantee commit/rollback.
62
+ * - Passwords are stored as SHA-256 hashes (see {@link Utils#sha256(String)}).
63
+ *
64
+ * Endpoints:
65
+ * GET /user/ -> list users
66
+ * GET /user/{uid} -> get user by username
67
+ * POST /user/ -> create user (idempotent: upsert semantics)
68
+ * PUT /user/{uid} -> update user (creates if not exists)
69
+ * POST /user/login -> password authentication; returns token and basic identity
70
+ * POST /user/check -> validates a token and returns token metadata
71
+ * GET /user/logout -> invalidates HTTP session (non-token based)
72
+ *
73
+ * Thread-safety: RequestScoped. No shared mutable state.
74
+ *
5275 * @author roberto <roberto.sanchez@curisit.net>
76
+ * Last reviewed by JRA on Oct 5, 2025.
5377 */
5478 @Path("/user")
5579 @RequestScoped
5680 public class UserResource {
5781
58
- @Inject
59
- TokenHelper tokenHelper;
82
+ /** Token encoder/decoder & validator. */
83
+ @Inject TokenHelper tokenHelper;
6084
61
- @Inject
62
- private CacheTTL cache;
85
+ /** Small cache to invalidate role/org derived data after user mutations. */
86
+ @Inject private CacheTTL cache;
6387
64
- @Context
65
- EntityManager em;
88
+ /** JPA entity manager bound to the current request context. */
89
+ @Context EntityManager em;
6690
67
- private static final Logger LOG = LogManager.getLogger(UserResource.class);
91
+ private static final Logger LOG = LogManager.getLogger(UserResource.class);
6892
69
- public UserResource() {
70
- }
93
+ /**
94
+ * UserResource
95
+ * Default constructor for CDI.
96
+ */
97
+ public UserResource() {
98
+ }
7199
72
- /**
73
- *
74
- * @return the server version in format majorVersion.minorVersion
75
- */
76
- @GET
77
- @Path("/")
78
- @Produces({ MediaType.APPLICATION_JSON })
79
- @Securable(roles = Rol.ADMIN)
80
- @RolesAllowed(BasicSecurityContext.ROL_ADMIN)
81
- public Response index() {
82
- LOG.info("Getting users list ");
100
+ // ---------------------------------------------------------------------
101
+ // Read operations
102
+ // ---------------------------------------------------------------------
83103
84
- // EntityManager em = emProvider.get();
85
- em.clear();
86
- TypedQuery<User> q = em.createNamedQuery("list-users", User.class);
104
+ /**
105
+ * index
106
+ * <p>
107
+ * List all users.
108
+ *
109
+ * Security: ADMIN only.
110
+ *
111
+ * @return 200 OK with JSON array of {@link User}, or 200 OK with empty list.
112
+ */
113
+ @GET
114
+ @Path("/")
115
+ @Produces({ MediaType.APPLICATION_JSON })
116
+ @Securable(roles = Rol.ADMIN)
117
+ @RolesAllowed(BasicSecurityContext.ROL_ADMIN)
118
+ public Response index() {
119
+ LOG.info("Getting users list ");
87120
88
- List<User> list = q.getResultList();
121
+ em.clear();
122
+ TypedQuery<User> q = em.createNamedQuery("list-users", User.class);
123
+ List<User> list = q.getResultList();
89124
90
- return Response.ok(list).build();
91
- }
125
+ return Response.ok(list).build();
126
+ }
92127
93
- /**
94
- *
95
- * @return The user
96
- */
97
- @GET
98
- @Path("/{uid}")
99
- @Produces({ MediaType.APPLICATION_JSON })
100
- @Securable(roles = Rol.ADMIN)
101
- @RolesAllowed(BasicSecurityContext.ROL_ADMIN)
102
- public Response get(@PathParam("uid") String uid, @HeaderParam(TokenHelper.TOKEN_HEADER_PÀRAM) String token) {
103
- LOG.info("Getting user data for id: {}: ", uid);
104
- if (uid == null || "".equals(uid)) {
105
- LOG.error("User ID is mandatory");
106
- return Response.status(Status.NOT_FOUND).build();
107
- }
128
+ /**
129
+ * get
130
+ * <p>
131
+ * Retrieve a single user by username.
132
+ *
133
+ * Security: ADMIN only.
134
+ *
135
+ * @param uid Username (primary key).
136
+ * @param token Optional token header (unused here, enforced by filters).
137
+ * @return 200 OK with user payload or 404 if not found/invalid uid.
138
+ */
139
+ @GET
140
+ @Path("/{uid}")
141
+ @Produces({ MediaType.APPLICATION_JSON })
142
+ @Securable(roles = Rol.ADMIN)
143
+ @RolesAllowed(BasicSecurityContext.ROL_ADMIN)
144
+ public Response get(@PathParam("uid") String uid, @HeaderParam(TokenHelper.TOKEN_HEADER_PÀRAM) String token) {
145
+ LOG.info("Getting user data for id: {}: ", uid);
146
+ if (uid == null || "".equals(uid)) {
147
+ LOG.error("User ID is mandatory");
148
+ return Response.status(Status.NOT_FOUND).build();
149
+ }
108150
109
- // EntityManager em = emProvider.get();
110
- em.clear();
111
- User lt = em.find(User.class, uid);
112
- if (lt == null) {
113
- LOG.error("User with id {} not found in DB", uid);
114
- return Response.status(Status.NOT_FOUND).build();
115
- }
116
- return Response.ok(lt).build();
117
- }
151
+ em.clear();
152
+ User lt = em.find(User.class, uid);
153
+ if (lt == null) {
154
+ LOG.error("User with id {} not found in DB", uid);
155
+ return Response.status(Status.NOT_FOUND).build();
156
+ }
157
+ return Response.ok(lt).build();
158
+ }
118159
119
- @POST
120
- @Path("/")
121
- @Consumes(MediaType.APPLICATION_JSON)
122
- @Produces({ MediaType.APPLICATION_JSON })
123
- @EnsureTransaction
124
- @Securable(roles = Rol.ADMIN)
125
- @RolesAllowed(BasicSecurityContext.ROL_ADMIN)
126
- public Response create(User user, @HeaderParam(TokenHelper.TOKEN_HEADER_PÀRAM) String token) {
127
- LOG.info("Creating new user");
128
- // EntityManager em = emProvider.get();
129
- User currentUser = em.find(User.class, user.getUsername());
130
- if (currentUser != null) {
131
- LOG.info("User with id {} was found in DB, we'll try to modify it", user.getUsername());
132
- return modify(user, user.getUsername(), token);
133
- }
160
+ // ---------------------------------------------------------------------
161
+ // Create / Update / Delete
162
+ // ---------------------------------------------------------------------
134163
135
- try {
136
- this.setUserOrgs(user, user.getOrgsIds(), em);
137
- } catch (SeCurisException e) {
138
- return Response.status(Status.NOT_FOUND).header(DefaultExceptionHandler.ERROR_MESSAGE_HEADER, e.getMessage()).build();
139
- }
140
- try {
141
- this.setUserApps(user, user.getAppsIds(), em);
142
- } catch (SeCurisException e) {
143
- return Response.status(Status.NOT_FOUND).header(DefaultExceptionHandler.ERROR_MESSAGE_HEADER, e.getMessage()).build();
144
- }
145
- if (user.getPassword() != null && !"".equals(user.getPassword())) {
146
- user.setPassword(Utils.sha256(user.getPassword()));
147
- } else {
148
- return Response.status(DefaultExceptionHandler.DEFAULT_APP_ERROR_STATUS_CODE).header(DefaultExceptionHandler.ERROR_MESSAGE_HEADER, "User password is mandatory")
149
- .build();
150
- }
151
- user.setModificationTimestamp(new Date());
152
- user.setLastLogin(null);
153
- user.setCreationTimestamp(new Date());
154
- em.persist(user);
164
+ /**
165
+ * create
166
+ * <p>
167
+ * Create a new user. If the username already exists, delegates to {@link #modify(User, String, String)}
168
+ * to behave like an upsert.
169
+ *
170
+ * Security: ADMIN only.
171
+ * Transaction: yes (via @EnsureTransaction).
172
+ *
173
+ * @param user Incoming user payload. Password must be non-empty (plain text).
174
+ * Password is SHA-256 hashed before persist.
175
+ * @param token Security token header (unused here; enforced by filters).
176
+ * @return 200 OK with created/updated user; 4xx on validation errors.
177
+ */
178
+ @POST
179
+ @Path("/")
180
+ @Consumes(MediaType.APPLICATION_JSON)
181
+ @Produces({ MediaType.APPLICATION_JSON })
182
+ @EnsureTransaction
183
+ @Securable(roles = Rol.ADMIN)
184
+ @RolesAllowed(BasicSecurityContext.ROL_ADMIN)
185
+ public Response create(User user, @HeaderParam(TokenHelper.TOKEN_HEADER_PÀRAM) String token) {
186
+ LOG.info("Creating new user");
155187
156
- return Response.ok(user).build();
157
- }
188
+ User currentUser = em.find(User.class, user.getUsername());
189
+ if (currentUser != null) {
190
+ LOG.info("User with id {} was found in DB, we'll try to modify it", user.getUsername());
191
+ return modify(user, user.getUsername(), token);
192
+ }
158193
159
- private void setUserOrgs(User user, Set<Integer> orgsIds, EntityManager em) throws SeCurisException {
160
- Set<Organization> orgs = null;
161
- if (orgsIds != null && !orgsIds.isEmpty()) {
162
- orgs = new HashSet<>();
163
- for (Integer orgId : orgsIds) {
164
- Organization o = em.find(Organization.class, orgId);
165
- if (o == null) {
166
- LOG.error("User organization with id {} not found in DB", orgId);
167
- throw new SeCurisException("User's organization not found with ID: " + orgId);
168
- }
169
- orgs.add(o);
170
- }
171
- }
194
+ try {
195
+ this.setUserOrgs(user, user.getOrgsIds(), em);
196
+ } catch (SeCurisException e) {
197
+ return Response.status(Status.NOT_FOUND).header(DefaultExceptionHandler.ERROR_MESSAGE_HEADER, e.getMessage()).build();
198
+ }
199
+ try {
200
+ this.setUserApps(user, user.getAppsIds(), em);
201
+ } catch (SeCurisException e) {
202
+ return Response.status(Status.NOT_FOUND).header(DefaultExceptionHandler.ERROR_MESSAGE_HEADER, e.getMessage()).build();
203
+ }
172204
173
- user.setOrganizations(orgs);
205
+ // Password must be provided on create
206
+ if (user.getPassword() != null && !"".equals(user.getPassword())) {
207
+ user.setPassword(Utils.sha256(user.getPassword()));
208
+ } else {
209
+ return Response.status(DefaultExceptionHandler.DEFAULT_APP_ERROR_STATUS_CODE)
210
+ .header(DefaultExceptionHandler.ERROR_MESSAGE_HEADER, "User password is mandatory")
211
+ .build();
212
+ }
174213
175
- }
214
+ user.setModificationTimestamp(new Date());
215
+ user.setLastLogin(null);
216
+ user.setCreationTimestamp(new Date());
217
+ em.persist(user);
176218
177
- private void setUserApps(User user, Set<Integer> appsIds, EntityManager em) throws SeCurisException {
178
- Set<Application> apps = null;
179
- if (appsIds != null && !appsIds.isEmpty()) {
180
- apps = new HashSet<>();
181
- for (Integer appId : appsIds) {
182
- Application o = em.find(Application.class, appId);
183
- if (o == null) {
184
- LOG.error("User application with id {} not found in DB", appId);
185
- throw new SeCurisException("User's application not found with ID: " + appId);
186
- }
187
- apps.add(o);
188
- }
189
- }
219
+ return Response.ok(user).build();
220
+ }
190221
191
- user.setApplications(apps);
192
- }
222
+ /**
223
+ * setUserOrgs
224
+ * <p>
225
+ * Resolve and set the organizations for a user from a set of IDs.
226
+ * Validates each id exists in DB.
227
+ *
228
+ * @param user Target user entity.
229
+ * @param orgsIds Organization ids to assign (nullable/empty allowed).
230
+ * @param em EntityManager.
231
+ * @throws SeCurisException if any of the referenced organizations does not exist.
232
+ */
233
+ private void setUserOrgs(User user, Set<Integer> orgsIds, EntityManager em) throws SeCurisException {
234
+ Set<Organization> orgs = null;
235
+ if (orgsIds != null && !orgsIds.isEmpty()) {
236
+ orgs = new HashSet<>();
237
+ for (Integer orgId : orgsIds) {
238
+ Organization o = em.find(Organization.class, orgId);
239
+ if (o == null) {
240
+ LOG.error("User organization with id {} not found in DB", orgId);
241
+ throw new SeCurisException("User's organization not found with ID: " + orgId);
242
+ }
243
+ orgs.add(o);
244
+ }
245
+ }
246
+ user.setOrganizations(orgs);
247
+ }
193248
194
- @PUT
195
- @POST
196
- @Path("/{uid}")
197
- @EnsureTransaction
198
- @Consumes(MediaType.APPLICATION_JSON)
199
- @Produces({ MediaType.APPLICATION_JSON })
200
- @Securable(roles = Rol.ADMIN)
201
- @RolesAllowed(BasicSecurityContext.ROL_ADMIN)
202
- public Response modify(User user, @PathParam("uid") String uid, @HeaderParam(TokenHelper.TOKEN_HEADER_PÀRAM) String token) {
203
- LOG.info("Modifying user with id: {}", uid);
204
- // EntityManager em = emProvider.get();
205
- User currentUser = em.find(User.class, uid);
206
- if (currentUser == null) {
207
- LOG.info("User with id {} not found in DB, we'll try to create it", uid);
208
- return create(user, token);
209
- }
249
+ /**
250
+ * setUserApps
251
+ * <p>
252
+ * Resolve and set the applications for a user from a set of IDs.
253
+ * Validates each id exists in DB.
254
+ *
255
+ * @param user Target user entity.
256
+ * @param appsIds Application ids to assign (nullable/empty allowed).
257
+ * @param em EntityManager.
258
+ * @throws SeCurisException if any of the referenced applications does not exist.
259
+ */
260
+ private void setUserApps(User user, Set<Integer> appsIds, EntityManager em) throws SeCurisException {
261
+ Set<Application> apps = null;
262
+ if (appsIds != null && !appsIds.isEmpty()) {
263
+ apps = new HashSet<>();
264
+ for (Integer appId : appsIds) {
265
+ Application o = em.find(Application.class, appId);
266
+ if (o == null) {
267
+ LOG.error("User application with id {} not found in DB", appId);
268
+ throw new SeCurisException("User's application not found with ID: " + appId);
269
+ }
270
+ apps.add(o);
271
+ }
272
+ }
273
+ user.setApplications(apps);
274
+ }
210275
211
- try {
212
- this.setUserOrgs(currentUser, user.getOrgsIds(), em);
213
- } catch (SeCurisException e) {
214
- return Response.status(Status.NOT_FOUND).header(DefaultExceptionHandler.ERROR_MESSAGE_HEADER, e.getMessage()).build();
215
- }
216
- try {
217
- this.setUserApps(currentUser, user.getAppsIds(), em);
218
- } catch (SeCurisException e) {
219
- return Response.status(Status.NOT_FOUND).header(DefaultExceptionHandler.ERROR_MESSAGE_HEADER, e.getMessage()).build();
220
- }
221
- currentUser.setFirstName(user.getFirstName());
222
- currentUser.setLastName(user.getLastName());
223
- currentUser.setRoles(user.getRoles());
224
- currentUser.setLang(user.getLang());
225
- currentUser.setModificationTimestamp(new Date());
226
- if (user.getPassword() != null && !"".equals(user.getPassword())) {
227
- currentUser.setPassword(Utils.sha256(user.getPassword()));
228
- } else {
229
- // Password has not been modified
230
- // return
231
- }
276
+ /**
277
+ * modify
278
+ * <p>
279
+ * Update an existing user. If the user does not exist, delegates to {@link #create(User, String)}.
280
+ * Password is updated only if a non-empty password is provided.
281
+ * Organizations & applications are fully replaced with the given ids.
282
+ *
283
+ * Security: ADMIN only.
284
+ * Transaction: yes (via @EnsureTransaction).
285
+ *
286
+ * @param user Incoming user payload.
287
+ * @param uid Username (path param) to update.
288
+ * @param token Security token header (unused here).
289
+ * @return 200 OK with updated user; 404 if reference entities are missing.
290
+ */
291
+ @PUT
292
+ @POST
293
+ @Path("/{uid}")
294
+ @EnsureTransaction
295
+ @Consumes(MediaType.APPLICATION_JSON)
296
+ @Produces({ MediaType.APPLICATION_JSON })
297
+ @Securable(roles = Rol.ADMIN)
298
+ @RolesAllowed(BasicSecurityContext.ROL_ADMIN)
299
+ public Response modify(User user, @PathParam("uid") String uid, @HeaderParam(TokenHelper.TOKEN_HEADER_PÀRAM) String token) {
300
+ LOG.info("Modifying user with id: {}", uid);
232301
233
- currentUser.setLastLogin(user.getLastLogin());
302
+ User currentUser = em.find(User.class, uid);
303
+ if (currentUser == null) {
304
+ LOG.info("User with id {} not found in DB, we'll try to create it", uid);
305
+ return create(user, token);
306
+ }
234307
235
- em.persist(currentUser);
236
- clearUserCache(currentUser.getUsername());
308
+ try {
309
+ this.setUserOrgs(currentUser, user.getOrgsIds(), em);
310
+ } catch (SeCurisException e) {
311
+ return Response.status(Status.NOT_FOUND).header(DefaultExceptionHandler.ERROR_MESSAGE_HEADER, e.getMessage()).build();
312
+ }
313
+ try {
314
+ this.setUserApps(currentUser, user.getAppsIds(), em);
315
+ } catch (SeCurisException e) {
316
+ return Response.status(Status.NOT_FOUND).header(DefaultExceptionHandler.ERROR_MESSAGE_HEADER, e.getMessage()).build();
317
+ }
237318
238
- return Response.ok(currentUser).build();
239
- }
319
+ currentUser.setFirstName(user.getFirstName());
320
+ currentUser.setLastName(user.getLastName());
321
+ currentUser.setRoles(user.getRoles());
322
+ currentUser.setLang(user.getLang());
323
+ currentUser.setModificationTimestamp(new Date());
240324
241
- @DELETE
242
- @Path("/{uid}")
243
- @EnsureTransaction
244
- @Produces({ MediaType.APPLICATION_JSON })
245
- @Securable(roles = Rol.ADMIN)
246
- @RolesAllowed(BasicSecurityContext.ROL_ADMIN)
247
- public Response delete(@PathParam("uid") String uid, @Context HttpServletRequest request) {
248
- LOG.info("Deleting app with id: {}", uid);
249
- // EntityManager em = emProvider.get();
250
- User user = em.find(User.class, uid);
251
- if (user == null) {
252
- LOG.error("User with id {} can not be deleted, It was not found in DB", uid);
253
- return Response.status(Status.NOT_FOUND).build();
254
- }
325
+ // Optional password update
326
+ if (user.getPassword() != null && !"".equals(user.getPassword())) {
327
+ currentUser.setPassword(Utils.sha256(user.getPassword()));
328
+ }
255329
256
- em.remove(user);
257
- clearUserCache(user.getUsername());
258
- return Response.ok(Utils.createMap("success", true, "id", uid)).build();
259
- }
330
+ // lastLogin can be set through API (rare), otherwise managed at login
331
+ currentUser.setLastLogin(user.getLastLogin());
260332
261
- private void clearUserCache(String username) {
262
- cache.remove("roles_" + username);
263
- cache.remove("orgs_" + username);
264
- }
333
+ em.persist(currentUser);
334
+ clearUserCache(currentUser.getUsername());
265335
266
- @POST
267
- @Path("/login")
268
- @Produces({ MediaType.APPLICATION_JSON })
269
- public Response login(@FormParam("username") String username, @FormParam("password") String password, @Context HttpServletRequest request) throws SeCurisServiceException {
270
- LOG.info("index session: " + request.getSession());
336
+ return Response.ok(currentUser).build();
337
+ }
271338
272
- // EntityManager em = emProvider.get();
273
- User user = em.find(User.class, username);
274
- if (user == null) {
275
- LOG.error("Unknown username {} used in login service", username);
276
- throw new SeCurisServiceException(ErrorCodes.UNAUTHORIZED_ACCESS, "Wrong credentials");
277
- }
278
- String securedPassword = Utils.sha256(password);
339
+ /**
340
+ * delete
341
+ * <p>
342
+ * Delete a user by username.
343
+ *
344
+ * Security: ADMIN only.
345
+ * Transaction: yes (via @EnsureTransaction).
346
+ *
347
+ * @param uid Username to delete.
348
+ * @param request Http servlet request (unused).
349
+ * @return 200 OK on success; 404 if user does not exist.
350
+ */
351
+ @DELETE
352
+ @Path("/{uid}")
353
+ @EnsureTransaction
354
+ @Produces({ MediaType.APPLICATION_JSON })
355
+ @Securable(roles = Rol.ADMIN)
356
+ @RolesAllowed(BasicSecurityContext.ROL_ADMIN)
357
+ public Response delete(@PathParam("uid") String uid, @Context HttpServletRequest request) {
358
+ LOG.info("Deleting app with id: {}", uid);
279359
280
- if (securedPassword == null || !securedPassword.equals(user.getPassword())) {
281
- throw new SeCurisServiceException(ErrorCodes.UNAUTHORIZED_ACCESS, "Wrong credentials");
282
- }
283
- user.setLastLogin(new Date());
284
- em.getTransaction().begin();
285
- try {
286
- em.persist(user);
287
- em.getTransaction().commit();
288
- } catch (PersistenceException ex) {
289
- LOG.error("Error updating last login date for user: {}", username);
290
- LOG.error(ex);
291
- em.getTransaction().rollback();
292
- }
293
- clearUserCache(username);
294
- String userFullName = String.format("%s %s", user.getFirstName(), user.getLastName() == null ? "" : user.getLastName()).trim();
295
- String tokenAuth = tokenHelper.generateToken(username);
296
- return Response.ok(Utils.createMap("success", true, "token", tokenAuth, "username", username, "full_name", userFullName)).build();
297
- }
360
+ User user = em.find(User.class, uid);
361
+ if (user == null) {
362
+ LOG.error("User with id {} can not be deleted, It was not found in DB", uid);
363
+ return Response.status(Status.NOT_FOUND).build();
364
+ }
298365
299
- /**
300
- * Check if current token is valid
301
- *
302
- * @param user
303
- * @param password
304
- * @param request
305
- * @return
306
- */
307
- @POST
308
- @Path("/check")
309
- @Produces({ MediaType.APPLICATION_JSON })
310
- public Response check(@HeaderParam(TokenHelper.TOKEN_HEADER_PÀRAM) String token, @QueryParam("token") String token2) {
311
- if (token == null) {
312
- token = token2;
313
- }
314
- if (token == null) {
315
- return Response.status(Status.FORBIDDEN).build();
316
- }
366
+ em.remove(user);
367
+ clearUserCache(user.getUsername());
368
+ return Response.ok(Utils.createMap("success", true, "id", uid)).build();
369
+ }
317370
318
- LOG.info("Token : " + token);
319
- String user = tokenHelper.extractUserFromToken(token);
320
- LOG.info("Token user: " + user);
321
- Date date = tokenHelper.extractDateCreationFromToken(token);
322
- LOG.info("Token date: " + date);
323
- boolean valid = tokenHelper.isTokenValid(token);
371
+ /**
372
+ * clearUserCache
373
+ * <p>
374
+ * Helper to invalidate cached role/org projections after changes.
375
+ *
376
+ * @param username The user whose cache entries must be cleared.
377
+ */
378
+ private void clearUserCache(String username) {
379
+ cache.remove("roles_" + username);
380
+ cache.remove("orgs_" + username);
381
+ }
324382
325
- LOG.info("Is Token valid: " + valid);
383
+ // ---------------------------------------------------------------------
384
+ // Auth helpers
385
+ // ---------------------------------------------------------------------
326386
327
- return Response.ok(Utils.createMap("valid", true, "user", user, "date", date, "token", token)).build();
328
- }
387
+ /**
388
+ * login
389
+ * <p>
390
+ * Validates username & password against stored SHA-256 hash. On success,
391
+ * updates lastLogin timestamp, clears cache and returns an auth token.
392
+ *
393
+ * Token format: Base64("<secret> <user> <ISO8601-date>")
394
+ * where secret = SHA-256(seed + user + date).
395
+ *
396
+ * @param username Plain username.
397
+ * @param password Plain password (SHA-256 will be computed server-side).
398
+ * @param request Http request, used to log underlying session (not required for token flow).
399
+ * @return 200 OK with {token, username, full_name}; 401 on invalid credentials.
400
+ * @throws SeCurisServiceException if user is missing or password mismatch.
401
+ */
402
+ @POST
403
+ @Path("/login")
404
+ @Produces({ MediaType.APPLICATION_JSON })
405
+ public Response login(@FormParam("username") String username, @FormParam("password") String password, @Context HttpServletRequest request) throws SeCurisServiceException {
406
+ LOG.info("index session: " + request.getSession());
329407
330
- @GET
331
- @Path("/logout")
332
- @Produces({ MediaType.APPLICATION_JSON })
333
- public Response logout(@Context HttpServletRequest request) {
334
- request.getSession().invalidate();
335
- return Response.ok().build();
336
- }
408
+ User user = em.find(User.class, username);
409
+ if (user == null) {
410
+ LOG.error("Unknown username {} used in login service", username);
411
+ throw new SeCurisServiceException(ErrorCodes.UNAUTHORIZED_ACCESS, "Wrong credentials");
412
+ }
413
+ String securedPassword = Utils.sha256(password);
414
+
415
+ if (securedPassword == null || !securedPassword.equals(user.getPassword())) {
416
+ throw new SeCurisServiceException(ErrorCodes.UNAUTHORIZED_ACCESS, "Wrong credentials");
417
+ }
418
+
419
+ user.setLastLogin(new Date());
420
+ em.getTransaction().begin();
421
+ try {
422
+ em.persist(user);
423
+ em.getTransaction().commit();
424
+ } catch (PersistenceException ex) {
425
+ LOG.error("Error updating last login date for user: {}", username);
426
+ LOG.error(ex);
427
+ em.getTransaction().rollback();
428
+ }
429
+
430
+ clearUserCache(username);
431
+ String userFullName = String.format("%s %s", user.getFirstName(), user.getLastName() == null ? "" : user.getLastName()).trim();
432
+ String tokenAuth = tokenHelper.generateToken(username);
433
+ return Response.ok(Utils.createMap("success", true, "token", tokenAuth, "username", username, "full_name", userFullName)).build();
434
+ }
435
+
436
+ /**
437
+ * check
438
+ * <p>
439
+ * Validates a token and echoes token claims (user, creation date, token string).
440
+ * Accepts header or query param for convenience.
441
+ *
442
+ * @param token Token in header {@link TokenHelper#TOKEN_HEADER_PÀRAM}, may be null.
443
+ * @param token2 Token in query param 'token', used if header is null.
444
+ * @return 200 OK with {valid, user, date, token} or 403 if token missing.
445
+ */
446
+ @POST
447
+ @Path("/check")
448
+ @Produces({ MediaType.APPLICATION_JSON })
449
+ public Response check(@HeaderParam(TokenHelper.TOKEN_HEADER_PÀRAM) String token, @QueryParam("token") String token2) {
450
+ if (token == null) {
451
+ token = token2;
452
+ }
453
+ if (token == null) {
454
+ return Response.status(Status.FORBIDDEN).build();
455
+ }
456
+
457
+ LOG.info("Token : " + token);
458
+ String user = tokenHelper.extractUserFromToken(token);
459
+ LOG.info("Token user: " + user);
460
+ Date date = tokenHelper.extractDateCreationFromToken(token);
461
+ LOG.info("Token date: " + date);
462
+ boolean valid = tokenHelper.isTokenValid(token);
463
+
464
+ LOG.info("Is Token valid: " + valid);
465
+
466
+ return Response.ok(Utils.createMap("valid", true, "user", user, "date", date, "token", token)).build();
467
+ }
468
+
469
+ /**
470
+ * logout
471
+ * <p>
472
+ * Invalidates the HTTP session (useful if the UI also tracks session).
473
+ * Note: token-based auth is stateless; tokens are not revoked here.
474
+ *
475
+ * @param request HttpServletRequest to invalidate session.
476
+ * @return 200 OK always.
477
+ */
478
+ @GET
479
+ @Path("/logout")
480
+ @Produces({ MediaType.APPLICATION_JSON })
481
+ public Response logout(@Context HttpServletRequest request) {
482
+ request.getSession().invalidate();
483
+ return Response.ok().build();
484
+ }
337485 }
486
+
securis/src/main/java/net/curisit/securis/services/exception/SeCurisServiceException.java
....@@ -1,51 +1,90 @@
1
+/*
2
+ * Copyright @ 2013 CurisTEC, S.A.S. All Rights Reserved.
3
+ */
14 package net.curisit.securis.services.exception;
25
36 import net.curisit.integrity.exception.CurisException;
47
8
+/**
9
+ * SeCurisServiceException
10
+ * <p>
11
+ * Checked exception for service-layer errors with an attached numeric error code.
12
+ * Extends {@link CurisException} and is intended to be translated to HTTP responses
13
+ * by upstream exception mappers/handlers.
14
+ *
15
+ * Usage:
16
+ * - Prefer specific {@code ErrorCodes.*} when throwing.
17
+ * - Use the single-arg constructor to default to UNEXPECTED_ERROR.
18
+ *
19
+ * @author JRA
20
+ * Last reviewed by JRA on Oct 5, 2025.
21
+ */
522 public class SeCurisServiceException extends CurisException {
623
7
- private int errorCode = 0;
24
+ /** Numeric error code associated with this exception. */
25
+ private int errorCode = 0;
826
9
- public SeCurisServiceException(int errorCode, String msg) {
10
- super(msg);
11
- this.errorCode = errorCode;
12
- }
27
+ /**
28
+ * Constructor with explicit error code.
29
+ *
30
+ * @param errorCode See {@link ErrorCodes}.
31
+ * @param msg Human-readable message (safe to expose).
32
+ */
33
+ public SeCurisServiceException(int errorCode, String msg) {
34
+ super(msg);
35
+ this.errorCode = errorCode;
36
+ }
1337
14
- public SeCurisServiceException(String msg) {
15
- super(msg);
16
- this.errorCode = ErrorCodes.UNEXPECTED_ERROR;
17
- }
38
+ /**
39
+ * Constructor defaulting to {@link ErrorCodes#UNEXPECTED_ERROR}.
40
+ *
41
+ * @param msg Human-readable message (safe to expose).
42
+ */
43
+ public SeCurisServiceException(String msg) {
44
+ super(msg);
45
+ this.errorCode = ErrorCodes.UNEXPECTED_ERROR;
46
+ }
1847
19
- public int getStatus() {
20
- return errorCode;
21
- }
48
+ /**
49
+ * getStatus
50
+ * <p>
51
+ * Returns the stored numeric error code.
52
+ *
53
+ * @return integer error code.
54
+ */
55
+ public int getStatus() {
56
+ return errorCode;
57
+ }
2258
23
- /**
24
- *
25
- */
26
- private static final long serialVersionUID = 1L;
59
+ private static final long serialVersionUID = 1L;
2760
28
- public static class ErrorCodes {
29
- public static int UNEXPECTED_ERROR = 1000;
30
- public static int INVALID_CREDENTIALS = 1001;
31
- public static int UNAUTHORIZED_ACCESS = 1002;
32
- public static int NOT_FOUND = 1003;
33
- public static int INVALID_FORMAT = 1004;
34
- public static int WRONG_STATUS = 1005;
35
- public static int UNNECESSARY_RENEW = 1006;
61
+ /**
62
+ * ErrorCodes
63
+ * <p>
64
+ * Canonical set of service-layer error codes.
65
+ * Grouped by feature areas (1000 generic, 11xx license, 12xx request data, 13xx validation).
66
+ */
67
+ public static class ErrorCodes {
68
+ public static int UNEXPECTED_ERROR = 1000;
69
+ public static int INVALID_CREDENTIALS = 1001;
70
+ public static int UNAUTHORIZED_ACCESS = 1002;
71
+ public static int NOT_FOUND = 1003;
72
+ public static int INVALID_FORMAT = 1004;
73
+ public static int WRONG_STATUS = 1005;
74
+ public static int UNNECESSARY_RENEW = 1006;
3675
37
- public static int INVALID_LICENSE_REQUEST_DATA = 1100;
38
- public static int LICENSE_NOT_READY_FOR_RENEW = 1101;
39
- public static int LICENSE_DATA_IS_NOT_VALID = 1102;
40
- public static int LICENSE_IS_EXPIRED = 1103;
41
- public static int LICENSE_PACK_IS_NOT_VALID = 1104;
76
+ public static int INVALID_LICENSE_REQUEST_DATA = 1100;
77
+ public static int LICENSE_NOT_READY_FOR_RENEW = 1101;
78
+ public static int LICENSE_DATA_IS_NOT_VALID = 1102;
79
+ public static int LICENSE_IS_EXPIRED = 1103;
80
+ public static int LICENSE_PACK_IS_NOT_VALID = 1104;
4281
43
- public static int INVALID_REQUEST_DATA = 1201;
44
- public static int INVALID_REQUEST_DATA_FORMAT = 1202;
45
- public static int BLOCKED_REQUEST_DATA = 1203;
46
- public static int DUPLICATED_REQUEST_DATA = 1204;
47
- public static int NO_AVAILABLE_LICENSES = 1205;
82
+ public static int INVALID_REQUEST_DATA = 1201;
83
+ public static int INVALID_REQUEST_DATA_FORMAT = 1202;
84
+ public static int BLOCKED_REQUEST_DATA = 1203;
85
+ public static int DUPLICATED_REQUEST_DATA = 1204;
86
+ public static int NO_AVAILABLE_LICENSES = 1205;
4887
49
- public static int INVALID_DATA = 1301;
50
- }
88
+ public static int INVALID_DATA = 1301;
89
+ }
5190 }
securis/src/main/java/net/curisit/securis/services/helpers/LicenseHelper.java
....@@ -1,3 +1,6 @@
1
+/*
2
+ * Copyright @ 2013 CurisTEC, S.A.S. All Rights Reserved.
3
+ */
14 package net.curisit.securis.services.helpers;
25
36 import java.io.File;
....@@ -30,128 +33,192 @@
3033 import net.curisit.securis.services.exception.SeCurisServiceException;
3134 import net.curisit.securis.services.exception.SeCurisServiceException.ErrorCodes;
3235
36
+/**
37
+ * LicenseHelper
38
+ * <p>
39
+ * Stateless utility component for license lifecycle operations and helpers:
40
+ * - cancelation with history auditing
41
+ * - license resolution and validity checks
42
+ * - license file generation in a temp directory
43
+ * - metadata extraction and expiration date computation from packs
44
+ * - sequential code suffix allocation per pack
45
+ *
46
+ * Thread-safety: ApplicationScoped, stateless.
47
+ *
48
+ * @author JRA
49
+ * Last reviewed by JRA on Oct 5, 2025.
50
+ */
3351 @ApplicationScoped
3452 public class LicenseHelper {
3553
36
- @SuppressWarnings("unused")
37
- private static final Logger LOG = LogManager.getLogger(LicenseHelper.class);
38
- private static final long MS_PER_DAY = 24L * 3600L * 1000L;
54
+ @SuppressWarnings("unused")
55
+ private static final Logger LOG = LogManager.getLogger(LicenseHelper.class);
3956
40
- @Inject
41
- private UserHelper userHelper;
57
+ /** Milliseconds per day (used to derive expiration dates). */
58
+ private static final long MS_PER_DAY = 24L * 3600L * 1000L;
4259
43
- /**
44
- * Cancel the license
45
- *
46
- * @param lic
47
- * @param em
48
- */
49
- public void cancelLicense(License lic, String reason, BasicSecurityContext bsc, EntityManager em) throws SeCurisServiceException {
50
- lic.setStatus(LicenseStatus.CANCELLED);
51
- lic.setCancelledById(bsc.getUserPrincipal().getName());
52
- lic.setModificationTimestamp(new Date());
53
- em.persist(lic);
60
+ @Inject private UserHelper userHelper;
5461
55
- em.persist(createLicenseHistoryAction(lic, userHelper.getUser(bsc, em), LicenseHistory.Actions.CANCEL, "Cancellation reason: " + reason));
56
- }
62
+ /**
63
+ * cancelLicense
64
+ * <p>
65
+ * Transitions a license to CANCELLED, records who canceled it and why,
66
+ * and appends a {@link LicenseHistory} entry.
67
+ *
68
+ * @param lic Target license (managed).
69
+ * @param reason Human-readable cancellation reason (auditable).
70
+ * @param bsc Current security context (used to identify the user).
71
+ * @param em Entity manager to persist changes.
72
+ * @throws SeCurisServiceException never thrown here, declared for symmetry with callers.
73
+ */
74
+ public void cancelLicense(License lic, String reason, BasicSecurityContext bsc, EntityManager em) throws SeCurisServiceException {
75
+ lic.setStatus(LicenseStatus.CANCELLED);
76
+ lic.setCancelledById(bsc.getUserPrincipal().getName());
77
+ lic.setModificationTimestamp(new Date());
78
+ em.persist(lic);
5779
58
- /**
59
- * Validates that the passed license exists and is still valid
60
- *
61
- * @param licBean
62
- * @param em
63
- * @return The License instance in DB
64
- * @throws SeCurisServiceException
65
- */
66
- public License getActiveLicenseFromDB(LicenseBean licBean, EntityManager em) throws SeCurisServiceException {
67
- License lic = License.findLicenseByCode(licBean.getLicenseCode(), em);
68
- if (lic == null) {
69
- throw new SeCurisServiceException(ErrorCodes.LICENSE_DATA_IS_NOT_VALID, "Current license code doesn't exist");
70
- }
71
- if (lic.getStatus() != LicenseStatus.ACTIVE && lic.getStatus() != LicenseStatus.PRE_ACTIVE) {
72
- throw new SeCurisServiceException(ErrorCodes.LICENSE_DATA_IS_NOT_VALID, "Current license in not active");
73
- }
74
- return lic;
75
- }
80
+ em.persist(createLicenseHistoryAction(lic, userHelper.getUser(bsc, em), LicenseHistory.Actions.CANCEL, "Cancellation reason: " + reason));
81
+ }
7682
77
- public LicenseHistory createLicenseHistoryAction(License lic, User user, String action, String comments) {
78
- LicenseHistory lh = new LicenseHistory();
79
- lh.setLicense(lic);
80
- lh.setUser(user);
81
- lh.setCreationTimestamp(new Date());
82
- lh.setAction(action);
83
- lh.setComments(comments);
84
- return lh;
85
- }
83
+ /**
84
+ * getActiveLicenseFromDB
85
+ * <p>
86
+ * Resolve license by code and verify that it's ACTIVE or PRE_ACTIVE.
87
+ *
88
+ * @param licBean License bean containing the code to check.
89
+ * @param em EntityManager for DB access.
90
+ * @return The managed {@link License} instance.
91
+ * @throws SeCurisServiceException if code not found or license not in an active-ish state.
92
+ */
93
+ public License getActiveLicenseFromDB(LicenseBean licBean, EntityManager em) throws SeCurisServiceException {
94
+ License lic = License.findLicenseByCode(licBean.getLicenseCode(), em);
95
+ if (lic == null) {
96
+ throw new SeCurisServiceException(ErrorCodes.LICENSE_DATA_IS_NOT_VALID, "Current license code doesn't exist");
97
+ }
98
+ if (lic.getStatus() != LicenseStatus.ACTIVE && lic.getStatus() != LicenseStatus.PRE_ACTIVE) {
99
+ throw new SeCurisServiceException(ErrorCodes.LICENSE_DATA_IS_NOT_VALID, "Current license in not active");
100
+ }
101
+ return lic;
102
+ }
86103
87
- public LicenseHistory createLicenseHistoryAction(License lic, User user, String action) {
88
- return createLicenseHistoryAction(lic, user, action, null);
89
- }
104
+ /**
105
+ * createLicenseHistoryAction
106
+ * <p>
107
+ * Helper to build a {@link LicenseHistory} entry.
108
+ *
109
+ * @param lic License affected.
110
+ * @param user User performing the action.
111
+ * @param action Action code (see {@link LicenseHistory.Actions}).
112
+ * @param comments Optional comments, can be null.
113
+ * @return transient {@link LicenseHistory} ready to persist.
114
+ */
115
+ public LicenseHistory createLicenseHistoryAction(License lic, User user, String action, String comments) {
116
+ LicenseHistory lh = new LicenseHistory();
117
+ lh.setLicense(lic);
118
+ lh.setUser(user);
119
+ lh.setCreationTimestamp(new Date());
120
+ lh.setAction(action);
121
+ lh.setComments(comments);
122
+ return lh;
123
+ }
90124
91
- /**
92
- * Create a license file in a temporary directory
93
- *
94
- * @param lic
95
- * @param licFileName
96
- * @return
97
- * @throws IOException
98
- */
99
- public File createTemporaryLicenseFile(License lic, String licFileName) throws IOException {
100
- File f = Files.createTempDirectory("securis-server").toFile();
101
- f = new File(f, licFileName);
102
- FileUtils.writeStringToFile(f, lic.getLicenseData(), StandardCharsets.UTF_8);
103
- return f;
104
- }
125
+ /**
126
+ * createLicenseHistoryAction
127
+ * <p>
128
+ * Overload without comments.
129
+ *
130
+ * @param lic License affected.
131
+ * @param user User performing the action.
132
+ * @param action Action code.
133
+ * @return transient {@link LicenseHistory}.
134
+ */
135
+ public LicenseHistory createLicenseHistoryAction(License lic, User user, String action) {
136
+ return createLicenseHistoryAction(lic, user, action, null);
137
+ }
105138
106
- public Map<String, Object> extractPackMetadata(Set<PackMetadata> packMetadata) {
107
- Map<String, Object> metadata = new HashMap<>();
108
- for (PackMetadata md : packMetadata) {
109
- metadata.put(md.getKey(), md.getValue());
110
- }
139
+ /**
140
+ * createTemporaryLicenseFile
141
+ * <p>
142
+ * Materializes the license payload into a temporary file for emailing/download.
143
+ * The file is created under a unique temporary directory.
144
+ *
145
+ * Caller is responsible for deleting the file and its parent directory.
146
+ *
147
+ * @param lic License whose JSON/XML/text payload is in {@code getLicenseData()}.
148
+ * @param licFileName Desired file name (e.g. "license.lic").
149
+ * @return A {@link File} pointing to the newly created file.
150
+ * @throws IOException If the temporary directory or file cannot be created/written.
151
+ */
152
+ public File createTemporaryLicenseFile(License lic, String licFileName) throws IOException {
153
+ File f = Files.createTempDirectory("securis-server").toFile();
154
+ f = new File(f, licFileName);
155
+ FileUtils.writeStringToFile(f, lic.getLicenseData(), StandardCharsets.UTF_8);
156
+ return f;
157
+ }
111158
112
- return metadata;
113
- }
159
+ /**
160
+ * extractPackMetadata
161
+ * <p>
162
+ * Converts pack metadata set to a map for license generation.
163
+ *
164
+ * @param packMetadata Set of {@link PackMetadata}.
165
+ * @return Map with keys/values copied from metadata entries.
166
+ */
167
+ public Map<String, Object> extractPackMetadata(Set<PackMetadata> packMetadata) {
168
+ Map<String, Object> metadata = new HashMap<>();
169
+ for (PackMetadata md : packMetadata) {
170
+ metadata.put(md.getKey(), md.getValue());
171
+ }
172
+ return metadata;
173
+ }
114174
115
- /**
116
- * If the action is a renew the expiration date is got form pack end valid
117
- * date, if the action is a pre-activation the expiration date is calculated
118
- * using the pack default valid period
119
- *
120
- * @param pack
121
- * @param isPreActivation
122
- * @return
123
- */
124
- public Date getExpirationDateFromPack(Pack pack, boolean isPreActivation) {
125
- Long validPeriod;
126
- if (pack.getEndValidDate().before(new Date())) {
127
- throw new CurisRuntimeException("Pack end valid period is reached, no new licenses can be activated.");
128
- }
129
- if (isPreActivation) {
130
- validPeriod = pack.getPreactivationValidPeriod() * MS_PER_DAY;
131
- } else {
132
- if (pack.getRenewValidPeriod() <= 0) {
133
- return pack.getEndValidDate();
134
- }
135
- long renewPeriod = pack.getRenewValidPeriod() * MS_PER_DAY;
136
- long expirationPeriod = pack.getEndValidDate().getTime() - new Date().getTime();
137
- validPeriod = renewPeriod < expirationPeriod ? renewPeriod : expirationPeriod;
138
- }
139
- Date expirationDate = new Date(new Date().getTime() + validPeriod);
140
- return expirationDate;
141
- }
175
+ /**
176
+ * getExpirationDateFromPack
177
+ * <p>
178
+ * Computes license expiration date depending on action type:
179
+ * - Pre-activation: {@code preactivationValidPeriod} days from now.
180
+ * - Renew/Activation: min(renewValidPeriod days, pack end date - now).
181
+ * Fails fast if pack end date is already in the past.
182
+ *
183
+ * @param pack Pack with policy data.
184
+ * @param isPreActivation Whether the operation is a pre-activation.
185
+ * @return Calculated expiration {@link Date}.
186
+ * @throws CurisRuntimeException if the pack's end date is in the past.
187
+ */
188
+ public Date getExpirationDateFromPack(Pack pack, boolean isPreActivation) {
189
+ Long validPeriod;
190
+ if (pack.getEndValidDate().before(new Date())) {
191
+ throw new CurisRuntimeException("Pack end valid period is reached, no new licenses can be activated.");
192
+ }
193
+ if (isPreActivation) {
194
+ validPeriod = pack.getPreactivationValidPeriod() * MS_PER_DAY;
195
+ } else {
196
+ if (pack.getRenewValidPeriod() <= 0) {
197
+ return pack.getEndValidDate();
198
+ }
199
+ long renewPeriod = pack.getRenewValidPeriod() * MS_PER_DAY;
200
+ long expirationPeriod = pack.getEndValidDate().getTime() - new Date().getTime();
201
+ validPeriod = renewPeriod < expirationPeriod ? renewPeriod : expirationPeriod;
202
+ }
203
+ Date expirationDate = new Date(new Date().getTime() + validPeriod);
204
+ return expirationDate;
205
+ }
142206
143
- /**
144
- * Get the next free code suffis for a given Pack
145
- *
146
- * @param packId
147
- * @param em
148
- * @return
149
- */
150
- public int getNextCodeSuffix(int packId, EntityManager em) {
151
- TypedQuery<Integer> query = em.createNamedQuery("last-code-suffix-used-in-pack", Integer.class);
152
- query.setParameter("packId", packId);
153
- Integer lastCodeSuffix = query.getSingleResult();
154
- return lastCodeSuffix == null ? 1 : lastCodeSuffix + 1;
155
- }
156
-
207
+ /**
208
+ * getNextCodeSuffix
209
+ * <p>
210
+ * Retrieves the last used code suffix for a given pack and returns the next one.
211
+ * If none found, returns 1.
212
+ *
213
+ * @param packId Pack identifier.
214
+ * @param em EntityManager to query the DB.
215
+ * @return Next sequential suffix (>= 1).
216
+ */
217
+ public int getNextCodeSuffix(int packId, EntityManager em) {
218
+ TypedQuery<Integer> query = em.createNamedQuery("last-code-suffix-used-in-pack", Integer.class);
219
+ query.setParameter("packId", packId);
220
+ Integer lastCodeSuffix = query.getSingleResult();
221
+ return lastCodeSuffix == null ? 1 : lastCodeSuffix + 1;
222
+ }
157223 }
224
+
securis/src/main/java/net/curisit/securis/services/helpers/MetadataHelper.java
....@@ -1,3 +1,6 @@
1
+/*
2
+ * Copyright @ 2013 CurisTEC, S.A.S. All Rights Reserved.
3
+ */
14 package net.curisit.securis.services.helpers;
25
36 import java.util.Collection;
....@@ -24,147 +27,258 @@
2427 import net.curisit.securis.db.PackMetadata;
2528 import net.curisit.securis.db.common.Metadata;
2629
30
+/**
31
+ * MetadataHelper
32
+ * <p>
33
+ * Utilities to compare, merge and propagate metadata across the hierarchy:
34
+ * Application -> LicenseType -> Pack -> (marks License as metadata-obsolete)
35
+ * <p>
36
+ * Provides:
37
+ * - Equality checks on metadata sets.
38
+ * - Merge semantics: remove keys not present, update changed values/mandatory flags.
39
+ * - Propagation from Application down to LicenseType and from LicenseType down to Packs.
40
+ * - Marking existing licenses as "metadataObsolete" when pack metadata changes and
41
+ * the license is in a state where consumers could depend on metadata snapshot.
42
+ *
43
+ * Thread-safety: ApplicationScoped, stateless.
44
+ *
45
+ * @author JRA
46
+ * Last reviewed by JRA on Oct 5, 2025.
47
+ */
2748 @ApplicationScoped
2849 public class MetadataHelper {
2950
30
- private static final Logger log = LogManager.getLogger(MetadataHelper.class);
51
+ private static final Logger log = LogManager.getLogger(MetadataHelper.class);
3152
32
- public <T extends Metadata> boolean match(T m1, T m2) {
33
- if (m1 == null || m2 == null) {
34
- return false;
35
- }
36
- return Objects.equals(m1.getKey(), m2.getKey()) && Objects.equals(m1.getValue(), m2.getValue()) && m1.isMandatory() == m2.isMandatory();
37
- }
53
+ /**
54
+ * match
55
+ * <p>
56
+ * Compare two metadata entries (key, value, mandatory).
57
+ *
58
+ * @param m1 First metadata.
59
+ * @param m2 Second metadata.
60
+ * @param <T> Metadata subtype.
61
+ * @return true if equal in key/value/mandatory, false otherwise or if any is null.
62
+ */
63
+ public <T extends Metadata> boolean match(T m1, T m2) {
64
+ if (m1 == null || m2 == null) {
65
+ return false;
66
+ }
67
+ return Objects.equals(m1.getKey(), m2.getKey()) && Objects.equals(m1.getValue(), m2.getValue()) && m1.isMandatory() == m2.isMandatory();
68
+ }
3869
39
- public <T extends Metadata> Metadata findByKey(String key, Collection<T> listMd) {
40
- return listMd.parallelStream().filter(m -> Objects.equals(key, m.getKey())).findAny().orElse(null);
41
- }
70
+ /**
71
+ * findByKey
72
+ * <p>
73
+ * Find a metadata by key in a collection.
74
+ *
75
+ * @param key Metadata key to search.
76
+ * @param listMd Collection of metadata.
77
+ * @param <T> Metadata subtype.
78
+ * @return The first matching metadata or null.
79
+ */
80
+ public <T extends Metadata> Metadata findByKey(String key, Collection<T> listMd) {
81
+ return listMd.parallelStream().filter(m -> Objects.equals(key, m.getKey())).findAny().orElse(null);
82
+ }
4283
43
- public <T extends Metadata> boolean match(Set<T> listMd1, Set<T> listMd2) {
44
- if (listMd1.size() != listMd2.size()) {
45
- return false;
46
- }
47
- return listMd1.parallelStream().allMatch(m -> this.match(m, findByKey(m.getKey(), listMd2)));
48
- }
84
+ /**
85
+ * match
86
+ * <p>
87
+ * Compare two sets of metadata for equality (size + all entries match).
88
+ *
89
+ * @param listMd1 First set.
90
+ * @param listMd2 Second set.
91
+ * @param <T> Metadata subtype.
92
+ * @return true if both sets match element-wise, false otherwise.
93
+ */
94
+ public <T extends Metadata> boolean match(Set<T> listMd1, Set<T> listMd2) {
95
+ if (listMd1.size() != listMd2.size()) {
96
+ return false;
97
+ }
98
+ return listMd1.parallelStream().allMatch(m -> this.match(m, findByKey(m.getKey(), listMd2)));
99
+ }
49100
50
- public <T extends Metadata, K extends Metadata> void mergeMetadata(EntityManager em, Set<T> srcListMd, Set<K> tgtListMd, Set<String> keys) {
101
+ /**
102
+ * mergeMetadata
103
+ * <p>
104
+ * Merge metadata from a source set (truth) into a target set.
105
+ * - Removes entries in target whose keys are not in {@code keys}.
106
+ * - Updates entries in target whose value/mandatory differ from source.
107
+ * - Does NOT create new entries; caller is expected to persist new ones separately.
108
+ *
109
+ * @param em EntityManager to remove/merge.
110
+ * @param srcListMd Source metadata set (truth).
111
+ * @param tgtListMd Target metadata set to update.
112
+ * @param keys Keys present in source.
113
+ * @param <T> Source metadata type.
114
+ * @param <K> Target metadata type.
115
+ */
116
+ public <T extends Metadata, K extends Metadata> void mergeMetadata(EntityManager em, Set<T> srcListMd, Set<K> tgtListMd, Set<String> keys) {
51117
52
- Set<K> mdToRemove = tgtListMd.parallelStream() //
53
- .filter(md -> !keys.contains(md.getKey())) //
54
- .collect(Collectors.toSet());
55
- for (K tgtMd : mdToRemove) {
56
- log.info("MD key to remove: {} - {}", tgtMd.getKey(), tgtMd);
57
- if (tgtMd instanceof LicenseTypeMetadata) {
58
- log.info("LT: {}, tx: {}, contans: {}", LicenseTypeMetadata.class.cast(tgtMd).getLicenseType(), em.isJoinedToTransaction(), em.contains(tgtMd));
59
- }
60
- em.remove(tgtMd);
61
- }
62
- Set<K> keysToUpdate = tgtListMd.parallelStream() //
63
- .filter(md -> keys.contains(md.getKey())) //
64
- .collect(Collectors.toSet());
65
- for (K tgtMd : keysToUpdate) {
66
- Metadata md = this.findByKey(tgtMd.getKey(), srcListMd);
67
- if (md.isMandatory() != tgtMd.isMandatory() || !Objects.equals(md.getValue(), tgtMd.getValue())) {
68
- tgtMd.setMandatory(md.isMandatory());
69
- tgtMd.setValue(md.getValue());
70
- log.info("MD key to update: {}", tgtMd.getKey());
71
- em.merge(tgtMd);
72
- }
73
- }
74
- }
118
+ // Remove missing keys
119
+ Set<K> mdToRemove = tgtListMd.parallelStream()
120
+ .filter(md -> !keys.contains(md.getKey()))
121
+ .collect(Collectors.toSet());
122
+ for (K tgtMd : mdToRemove) {
123
+ log.info("MD key to remove: {} - {}", tgtMd.getKey(), tgtMd);
124
+ if (tgtMd instanceof LicenseTypeMetadata) {
125
+ log.info("LT: {}, tx: {}, contans: {}", LicenseTypeMetadata.class.cast(tgtMd).getLicenseType(), em.isJoinedToTransaction(), em.contains(tgtMd));
126
+ }
127
+ em.remove(tgtMd);
128
+ }
75129
76
- private Set<LicenseTypeMetadata> createNewMetadata(Set<ApplicationMetadata> appMd, Set<LicenseTypeMetadata> existingMd, LicenseType licenseType) {
77
- Set<String> oldKeys = existingMd.stream().map(md -> md.getKey()).collect(Collectors.toSet());
78
- return appMd.parallelStream() //
79
- .filter(md -> !oldKeys.contains(md.getKey())) //
80
- .map(appmd -> {
81
- LicenseTypeMetadata ltmd = new LicenseTypeMetadata();
82
- ltmd.setLicenseType(licenseType);
83
- ltmd.setKey(appmd.getKey());
84
- ltmd.setValue(appmd.getValue());
85
- ltmd.setMandatory(appmd.isMandatory());
86
- return ltmd;
87
- }).collect(Collectors.toSet());
88
- }
130
+ // Update changed keys
131
+ Set<K> keysToUpdate = tgtListMd.parallelStream()
132
+ .filter(md -> keys.contains(md.getKey()))
133
+ .collect(Collectors.toSet());
134
+ for (K tgtMd : keysToUpdate) {
135
+ Metadata md = this.findByKey(tgtMd.getKey(), srcListMd);
136
+ if (md.isMandatory() != tgtMd.isMandatory() || !Objects.equals(md.getValue(), tgtMd.getValue())) {
137
+ tgtMd.setMandatory(md.isMandatory());
138
+ tgtMd.setValue(md.getValue());
139
+ log.info("MD key to update: {}", tgtMd.getKey());
140
+ em.merge(tgtMd);
141
+ }
142
+ }
143
+ }
89144
90
- private Set<PackMetadata> createNewMetadata(Set<LicenseTypeMetadata> ltMd, Set<PackMetadata> existingMd, Pack pack) {
91
- Set<String> oldKeys = existingMd.stream().map(md -> md.getKey()).collect(Collectors.toSet());
92
- return ltMd.parallelStream() //
93
- .filter(md -> !oldKeys.contains(md.getKey())) //
94
- .map(md -> {
95
- PackMetadata pmd = new PackMetadata();
96
- pmd.setPack(pack);
97
- pmd.setKey(md.getKey());
98
- pmd.setValue(md.getValue());
99
- pmd.setMandatory(md.isMandatory());
100
- return pmd;
101
- }).collect(Collectors.toSet());
102
- }
145
+ // -- Internal helpers to create new metadata rows when propagating
103146
104
- /**
105
- * Copy the modified app metadata to LicenseTypes and Packs
106
- *
107
- * @param em
108
- * @param app
109
- */
110
- public void propagateMetadata(EntityManager em, Application app) {
111
- Set<ApplicationMetadata> appMd = app.getApplicationMetadata();
112
- Set<String> keys = appMd.parallelStream().map(md -> md.getKey()).collect(Collectors.toSet());
113
- for (LicenseType lt : app.getLicenseTypes()) {
114
- log.info("Lic type to update: {}", lt.getCode());
115
- this.mergeMetadata(em, appMd, lt.getMetadata(), keys);
116
- Set<LicenseTypeMetadata> newMdList = createNewMetadata(appMd, lt.getMetadata(), lt);
117
- for (LicenseTypeMetadata newMetadata : newMdList) {
118
- em.persist(newMetadata);
119
- }
120
- em.detach(lt);
121
- // Probably there is a better way to get the final metadata from JPA...
122
- TypedQuery<LicenseTypeMetadata> updatedMdQuery = em.createNamedQuery("list-licensetype-metadata", LicenseTypeMetadata.class);
123
- updatedMdQuery.setParameter("licenseTypeId", lt.getId());
124
- Set<LicenseTypeMetadata> updatedMd = new HashSet<>(updatedMdQuery.getResultList());
147
+ /**
148
+ * createNewMetadata<p>
149
+ * Create new metadata
150
+ *
151
+ * @param appMd
152
+ * @param existingMd
153
+ * @param licenseType
154
+ * @return newMetadata
155
+ */
156
+ private Set<LicenseTypeMetadata> createNewMetadata(Set<ApplicationMetadata> appMd, Set<LicenseTypeMetadata> existingMd, LicenseType licenseType) {
157
+ Set<String> oldKeys = existingMd.stream().map(md -> md.getKey()).collect(Collectors.toSet());
158
+ return appMd.parallelStream()
159
+ .filter(md -> !oldKeys.contains(md.getKey()))
160
+ .map(appmd -> {
161
+ LicenseTypeMetadata ltmd = new LicenseTypeMetadata();
162
+ ltmd.setLicenseType(licenseType);
163
+ ltmd.setKey(appmd.getKey());
164
+ ltmd.setValue(appmd.getValue());
165
+ ltmd.setMandatory(appmd.isMandatory());
166
+ return ltmd;
167
+ }).collect(Collectors.toSet());
168
+ }
125169
126
- lt.setMetadata(updatedMd);
127
- propagateMetadata(em, lt, keys);
128
- }
129
- }
170
+ /**
171
+ * createNewMetadata<p>
172
+ * Create the new metadata
173
+ *
174
+ * @param ltMd
175
+ * @param existingMd
176
+ * @param pack
177
+ * @return newMetadata
178
+ */
179
+ private Set<PackMetadata> createNewMetadata(Set<LicenseTypeMetadata> ltMd, Set<PackMetadata> existingMd, Pack pack) {
180
+ Set<String> oldKeys = existingMd.stream().map(md -> md.getKey()).collect(Collectors.toSet());
181
+ return ltMd.parallelStream()
182
+ .filter(md -> !oldKeys.contains(md.getKey()))
183
+ .map(md -> {
184
+ PackMetadata pmd = new PackMetadata();
185
+ pmd.setPack(pack);
186
+ pmd.setKey(md.getKey());
187
+ pmd.setValue(md.getValue());
188
+ pmd.setMandatory(md.isMandatory());
189
+ return pmd;
190
+ }).collect(Collectors.toSet());
191
+ }
130192
131
- /**
132
- * Copy the modified licenseType metadata to Packs
133
- *
134
- * @param em
135
- * @param lt
136
- * @param keys
137
- */
138
- public void propagateMetadata(EntityManager em, LicenseType lt, Set<String> keys) {
139
- Set<LicenseTypeMetadata> ltMd = lt.getMetadata();
140
- TypedQuery<Pack> packsQuery = em.createNamedQuery("list-packs-by-lic-type", Pack.class);
141
- packsQuery.setParameter("lt_id", lt.getId());
142
- List<Pack> packs = packsQuery.getResultList();
143
- log.info("Packs to update the metadata: {}", packs.size());
144
- for (Pack pack : packs) {
145
- if (pack.isFrozen()) {
146
- log.warn("Metadata in LicenseType {} has changed but the Pack {} is frozen and won't be updated.", lt.getCode(), pack.getCode());
147
- continue;
148
- }
149
- this.mergeMetadata(em, ltMd, pack.getMetadata(), keys);
150
- Set<PackMetadata> newMdList = createNewMetadata(ltMd, pack.getMetadata(), pack);
151
- for (PackMetadata newMetadata : newMdList) {
152
- em.persist(newMetadata);
153
- }
154
- markObsoleteMetadata(em, pack);
155
- em.detach(pack);
156
- }
157
- }
193
+ /**
194
+ * propagateMetadata (Application -> LicenseTypes -> Packs)
195
+ * <p>
196
+ * Propagates application metadata changes down to all its license types and packs:
197
+ * - mergeMetadata on LicenseType
198
+ * - create new LicenseTypeMetadata for new keys
199
+ * - re-fetch LT metadata (detached/merged semantics)
200
+ * - propagateMetadata(LicenseType) to packs
201
+ *
202
+ * @param em EntityManager.
203
+ * @param app Application with updated metadata loaded.
204
+ */
205
+ public void propagateMetadata(EntityManager em, Application app) {
206
+ Set<ApplicationMetadata> appMd = app.getApplicationMetadata();
207
+ Set<String> keys = appMd.parallelStream().map(md -> md.getKey()).collect(Collectors.toSet());
208
+ for (LicenseType lt : app.getLicenseTypes()) {
209
+ log.info("Lic type to update: {}", lt.getCode());
210
+ this.mergeMetadata(em, appMd, lt.getMetadata(), keys);
211
+ Set<LicenseTypeMetadata> newMdList = createNewMetadata(appMd, lt.getMetadata(), lt);
212
+ for (LicenseTypeMetadata newMetadata : newMdList) {
213
+ em.persist(newMetadata);
214
+ }
215
+ em.detach(lt);
158216
159
- public void markObsoleteMetadata(EntityManager em, Pack pack) {
160
- TypedQuery<License> existingPackLicenses = em.createNamedQuery("list-licenses-by-pack", License.class);
161
- existingPackLicenses.setParameter("packId", pack.getId());
162
- for (License lic : existingPackLicenses.getResultList()) {
163
- log.info("License from pack: {}, status: {}", lic.getCode(), lic.getStatus());
164
- if (lic.getStatus() == LicenseStatus.ACTIVE || lic.getStatus() == LicenseStatus.PRE_ACTIVE || lic.getStatus() == LicenseStatus.CANCELLED) {
165
- lic.setMetadataObsolete(true);
166
- em.merge(lic);
167
- }
168
- }
169
- }
217
+ // Re-read updated metadata
218
+ TypedQuery<LicenseTypeMetadata> updatedMdQuery = em.createNamedQuery("list-licensetype-metadata", LicenseTypeMetadata.class);
219
+ updatedMdQuery.setParameter("licenseTypeId", lt.getId());
220
+ Set<LicenseTypeMetadata> updatedMd = new HashSet<>(updatedMdQuery.getResultList());
221
+
222
+ lt.setMetadata(updatedMd);
223
+ propagateMetadata(em, lt, keys);
224
+ }
225
+ }
226
+
227
+ /**
228
+ * propagateMetadata (LicenseType -> Packs)
229
+ * <p>
230
+ * Propagates license type metadata changes to all its packs:
231
+ * - mergeMetadata on Pack
232
+ * - create new PackMetadata for new keys
233
+ * - markObsoleteMetadata on packs to flag their licenses
234
+ *
235
+ * Frozen packs are skipped.
236
+ *
237
+ * @param em EntityManager.
238
+ * @param lt LicenseType with updated metadata set.
239
+ * @param keys Set of keys present in the source.
240
+ */
241
+ public void propagateMetadata(EntityManager em, LicenseType lt, Set<String> keys) {
242
+ Set<LicenseTypeMetadata> ltMd = lt.getMetadata();
243
+ TypedQuery<Pack> packsQuery = em.createNamedQuery("list-packs-by-lic-type", Pack.class);
244
+ packsQuery.setParameter("lt_id", lt.getId());
245
+ List<Pack> packs = packsQuery.getResultList();
246
+ log.info("Packs to update the metadata: {}", packs.size());
247
+ for (Pack pack : packs) {
248
+ if (pack.isFrozen()) {
249
+ log.warn("Metadata in LicenseType {} has changed but the Pack {} is frozen and won't be updated.", lt.getCode(), pack.getCode());
250
+ continue;
251
+ }
252
+ this.mergeMetadata(em, ltMd, pack.getMetadata(), keys);
253
+ Set<PackMetadata> newMdList = createNewMetadata(ltMd, pack.getMetadata(), pack);
254
+ for (PackMetadata newMetadata : newMdList) {
255
+ em.persist(newMetadata);
256
+ }
257
+ markObsoleteMetadata(em, pack);
258
+ em.detach(pack);
259
+ }
260
+ }
261
+
262
+ /**
263
+ * markObsoleteMetadata
264
+ * <p>
265
+ * For all licenses within the given pack, mark {@code metadataObsolete = true}
266
+ * if the license is in a relevant state (ACTIVE, PRE_ACTIVE, CANCELLED).
267
+ * This lets clients know that metadata-dependent artifacts might need refresh.
268
+ *
269
+ * @param em EntityManager.
270
+ * @param pack Pack whose licenses to mark.
271
+ */
272
+ public void markObsoleteMetadata(EntityManager em, Pack pack) {
273
+ TypedQuery<License> existingPackLicenses = em.createNamedQuery("list-licenses-by-pack", License.class);
274
+ existingPackLicenses.setParameter("packId", pack.getId());
275
+ for (License lic : existingPackLicenses.getResultList()) {
276
+ log.info("License from pack: {}, status: {}", lic.getCode(), lic.getStatus());
277
+ if (lic.getStatus() == LicenseStatus.ACTIVE || lic.getStatus() == LicenseStatus.PRE_ACTIVE || lic.getStatus() == LicenseStatus.CANCELLED) {
278
+ lic.setMetadataObsolete(true);
279
+ em.merge(lic);
280
+ }
281
+ }
282
+ }
170283 }
284
+
securis/src/main/java/net/curisit/securis/services/helpers/UserHelper.java
....@@ -1,3 +1,6 @@
1
+/*
2
+ * Copyright @ 2013 CurisTEC, S.A.S. All Rights Reserved.
3
+ */
14 package net.curisit.securis.services.helpers;
25
36 import jakarta.enterprise.context.ApplicationScoped;
....@@ -8,14 +11,45 @@
811 import net.curisit.securis.security.BasicSecurityContext;
912 import net.curisit.securis.services.exception.SeCurisServiceException;
1013
14
+/**
15
+ * UserHelper
16
+ * <p>
17
+ * Small helper to resolve the current user (from security context) or by username.
18
+ * Throws a typed {@link SeCurisServiceException} if the user cannot be found.
19
+ *
20
+ * Thread-safety: ApplicationScoped, stateless.
21
+ *
22
+ * @author JRA
23
+ * Last reviewed by JRA on Oct 5, 2025.
24
+ */
1125 @ApplicationScoped
1226 public class UserHelper {
1327
28
+ /**
29
+ * getUser
30
+ * <p>
31
+ * Resolve the current authenticated user from {@link BasicSecurityContext}.
32
+ *
33
+ * @param bsc Security context containing a principal.
34
+ * @param em EntityManager to fetch the user.
35
+ * @return Managed {@link User}.
36
+ * @throws SeCurisServiceException if the principal is null or not found in DB.
37
+ */
1438 public User getUser(BasicSecurityContext bsc, EntityManager em) throws SeCurisServiceException {
1539 String username = bsc.getUserPrincipal().getName();
1640 return getUser(username, em);
1741 }
1842
43
+ /**
44
+ * getUser
45
+ * <p>
46
+ * Resolve a user by username.
47
+ *
48
+ * @param username Username to look up (nullable allowed; returns null).
49
+ * @param em EntityManager to fetch the user.
50
+ * @return Managed {@link User} or null if username is null.
51
+ * @throws SeCurisServiceException if a non-null username does not exist.
52
+ */
1953 public User getUser(String username, EntityManager em) throws SeCurisServiceException {
2054 User user = null;
2155 if (username != null) {
securis/src/main/java/net/curisit/securis/utils/CacheTTL.java
....@@ -1,3 +1,6 @@
1
+/*
2
+* Copyright @ 2013 CurisTEC, S.A.S. All Rights Reserved.
3
+*/
14 package net.curisit.securis.utils;
25
36 import java.util.ArrayList;
....@@ -5,6 +8,7 @@
58 import java.util.HashMap;
69 import java.util.List;
710 import java.util.Map;
11
+import java.util.Set;
812
913 import jakarta.enterprise.context.ApplicationScoped;
1014 import jakarta.inject.Inject;
....@@ -13,122 +17,258 @@
1317 import org.apache.logging.log4j.Logger;
1418
1519 /**
16
- * Cache implementation with TTL (time To Live) The objects are removed from
17
- * cache when TTL is reached.
18
- *
19
- * @author roberto <roberto.sanchez@curisit.net>
20
- */
20
+* CacheTTL
21
+* <p>
22
+* Simple in-memory cache with per-entry TTL (time-to-live). A background
23
+* cleaning thread periodically removes expired entries.
24
+*
25
+* <p><b>Type-safety note:</b> Besides generic getters, this cache provides
26
+* {@link #getSet(String, Class)} to safely retrieve {@code Set<E>} values
27
+* without unchecked warnings at call sites. The method validates that all
28
+* elements match the requested {@code elementType}.
29
+*
30
+* <p><b>Threading:</b> This implementation is lightweight and uses a single
31
+* cleaner thread. The internal map is not synchronized beyond the remove loop,
32
+* which is acceptable for low-concurrency scenarios. For heavier usage,
33
+* consider switching to a {@code ConcurrentHashMap} and/or a scheduled executor.
34
+*
35
+* @author roberto
36
+* Last reviewed by JRA on Oct 5, 2025.
37
+*/
2138 @ApplicationScoped
2239 public class CacheTTL {
2340
2441 private static final Logger LOG = LogManager.getLogger(CacheTTL.class);
2542
43
+ /** Default TTL (seconds) for entries when not specified. */
44
+ private static final int DEFAULT_CACHE_DURATION = 24 * 60 * 60;
45
+
46
+ /** Backing store: key → cached object + expiration. */
47
+ private final Map<String, CachedObject> data = new HashMap<>();
48
+
49
+ /** Background cleaner thread. */
50
+ private final Thread cleaningThread;
51
+
2652 /**
27
- * Period before token expires, set in seconds.
28
- */
29
- private static int DEFAULT_CACHE_DURATION = 24 * 60 * 60;
30
-
31
- private Map<String, CachedObject> data = new HashMap<>();
32
-
33
- private Thread cleaningThread = null;
34
-
53
+ * CacheTTL<p>
54
+ * Construct a cache and start the background cleaner that removes expired
55
+ * entries every 60 seconds.
56
+ */
3557 @Inject
3658 public CacheTTL() {
37
- cleaningThread = new Thread(new Runnable() {
38
-
39
- @Override
40
- public void run() {
41
- while (CacheTTL.this.data != null) {
42
- try {
43
- // We check for expired object every 60 seconds
44
- Thread.sleep(60 * 1000);
45
- } catch (InterruptedException e) {
46
- LOG.error("Exiting from Cache Thread");
47
- data.clear();
48
- return;
49
- }
50
- Date now = new Date();
51
- List<String> keysToRemove = new ArrayList<>();
52
- for (String key : CacheTTL.this.data.keySet()) {
53
- CachedObject co = CacheTTL.this.data.get(key);
54
- if (now.after(co.getExpireAt())) {
55
- keysToRemove.add(key);
56
- }
57
- }
58
- for (String key : keysToRemove) {
59
- // If we try to remove directly in the previous loop an
60
- // exception is thrown
61
- // java.util.ConcurrentModificationException
62
- CacheTTL.this.data.remove(key);
59
+ cleaningThread = new Thread(() -> {
60
+ while (true) {
61
+ try {
62
+ // Check for expired objects every 60 seconds
63
+ Thread.sleep(60_000);
64
+ } catch (InterruptedException e) {
65
+ LOG.warn("Cache cleaner interrupted. Clearing cache and stopping.");
66
+ data.clear();
67
+ return;
68
+ }
69
+ Date now = new Date();
70
+ List<String> keysToRemove = new ArrayList<>();
71
+ for (String key : data.keySet()) {
72
+ CachedObject co = data.get(key);
73
+ if (co != null && now.after(co.getExpireAt())) {
74
+ keysToRemove.add(key);
6375 }
6476 }
77
+ for (String key : keysToRemove) {
78
+ // Avoid ConcurrentModificationException by removing after iteration
79
+ data.remove(key);
80
+ }
6581 }
66
- });
82
+ }, "CacheTTL-Cleaner");
83
+ cleaningThread.setDaemon(true);
6784 cleaningThread.start();
6885 }
6986
87
+ // ---------------------------------------------------------------------
88
+ // Putters
89
+ // ---------------------------------------------------------------------
90
+
7091 /**
71
- *
72
- * @param key
73
- * @param obj
74
- * @param ttl
75
- * Time To Live in seconds
76
- */
92
+ * set<p>
93
+ * Store a value with an explicit TTL.
94
+ *
95
+ * @param key cache key
96
+ * @param obj value to store (may be any object, including collections)
97
+ * @param ttl TTL in seconds
98
+ */
7799 public void set(String key, Object obj, int ttl) {
78
- Date expirationDate = new Date(new Date().getTime() + ttl * 1000);
100
+ Date expirationDate = new Date(System.currentTimeMillis() + (long) ttl * 1000L);
79101 data.put(key, new CachedObject(expirationDate, obj));
80102 }
81103
104
+ /**
105
+ * set<p>
106
+ * Store a value with the default TTL.
107
+ *
108
+ * @param key cache key
109
+ * @param obj value to store
110
+ */
82111 public void set(String key, Object obj) {
83112 set(key, obj, DEFAULT_CACHE_DURATION);
84113 }
85114
115
+ // ---------------------------------------------------------------------
116
+ // Getters
117
+ // ---------------------------------------------------------------------
118
+
119
+ /**
120
+ * get<p>
121
+ * Retrieve a value as {@code Object}. Returns {@code null} if not present
122
+ * or expired (expired entries are eagerly removed by the cleaner).
123
+ *
124
+ * @param key cache key
125
+ * @return cached value or null
126
+ */
86127 public Object get(String key) {
87128 CachedObject co = data.get(key);
88129 return co == null ? null : co.getObject();
89130 }
90131
132
+ /**
133
+ * get<p>
134
+ * Retrieve a value and cast it to the requested type. The cast is unchecked
135
+ * due to type erasure, but localized within the cache implementation.
136
+ *
137
+ * @param key cache key
138
+ * @param type expected value type
139
+ * @param <T> generic type
140
+ * @return cached value typed or null
141
+ */
91142 public <T> T get(String key, Class<T> type) {
92143 CachedObject co = data.get(key);
93144 return co == null ? null : co.getObject(type);
94145 }
95146
147
+ /**
148
+ * getSet<p>
149
+ * Retrieve a {@code Set<E>} in a type-safe way without unchecked warnings
150
+ * at the call site. The method validates that the cached value is a
151
+ * {@code Set} and that <b>all</b> elements are instances of {@code elementType}.
152
+ * If any element does not match, the method returns {@code null} and logs a warning.
153
+ *
154
+ * @param key cache key
155
+ * @param elementType class of the set elements (e.g., {@code Integer.class})
156
+ * @return typed set or null if missing/type-mismatch
157
+ */
158
+ @SuppressWarnings("unchecked")
159
+ public <E> Set<E> getSet(String key, Class<E> elementType) {
160
+ Object obj = get(key);
161
+ if (obj == null) return null;
162
+ if (!(obj instanceof Set<?> raw)) {
163
+ LOG.warn("Cache key '{}' expected Set<{}> but found {}", key, elementType.getSimpleName(), obj.getClass().getName());
164
+ return null;
165
+ }
166
+ // Validate element types to avoid ClassCastException later
167
+ for (Object el : raw) {
168
+ if (el != null && !elementType.isInstance(el)) {
169
+ LOG.warn("Cache key '{}' contains element of type {}, expected {}", key,
170
+ el.getClass().getName(), elementType.getName());
171
+ return null;
172
+ }
173
+ }
174
+ // Safe due to element-wise validation
175
+ return (Set<E>) raw;
176
+ }
177
+
178
+ // ---------------------------------------------------------------------
179
+ // Removers & maintenance
180
+ // ---------------------------------------------------------------------
181
+
182
+ /**
183
+ * remove<p>
184
+ * Remove and return a value typed.
185
+ *
186
+ * @param key cache key
187
+ * @param type expected type
188
+ * @return removed value or null
189
+ */
96190 public <T> T remove(String key, Class<T> type) {
97191 CachedObject co = data.remove(key);
98192 return co == null ? null : co.getObject(type);
99193 }
100194
195
+ /**
196
+ * remove<p>
197
+ * Remove and return a value as {@code Object}.
198
+ *
199
+ * @param key cache key
200
+ * @return removed value or null
201
+ */
101202 public Object remove(String key) {
102203 CachedObject co = data.remove(key);
103204 return co == null ? null : co.getObject();
104205 }
105206
207
+ /**
208
+ * clear<p>
209
+ * Remove all entries from the cache.
210
+ */
106211 public void clear() {
107212 data.clear();
108213 }
109214
110
- private class CachedObject {
111
- Date expireAt;
112
- Object object;
215
+ // ---------------------------------------------------------------------
216
+ // Internal structure
217
+ // ---------------------------------------------------------------------
113218
219
+ /**
220
+ * CachedObject
221
+ * <p>
222
+ * Internal wrapper that pairs an arbitrary object with its expiration date.
223
+ */
224
+ private static class CachedObject {
225
+ private final Date expireAt;
226
+ private final Object object;
227
+
228
+ /**
229
+ * Constructor<p>
230
+ * Set expiration and payload.
231
+ *
232
+ * @param date
233
+ * @param object
234
+ */
114235 public CachedObject(Date date, Object obj) {
115
- expireAt = date;
116
- object = obj;
236
+ this.expireAt = date;
237
+ this.object = obj;
117238 }
118239
240
+ /**
241
+ * getExpireAt<p>
242
+ * Return expiration date.
243
+ *
244
+ * @return date
245
+ */
119246 public Date getExpireAt() {
120247 return expireAt;
121248 }
122249
250
+ /**
251
+ * getObject<p>
252
+ * Return payload as {@code Object}.
253
+ *
254
+ * @return object
255
+ */
123256 public Object getObject() {
124257 return object;
125258 }
126259
260
+ /**
261
+ * getObject<p>
262
+ * Return payload cast to the requested type. Cast is localized here.
263
+ *
264
+ * @param type requested type
265
+ * @param <T> generic type
266
+ * @return typed payload
267
+ */
127268 @SuppressWarnings("unchecked")
128269 public <T> T getObject(Class<T> type) {
129270 return (T) object;
130271 }
131
-
132272 }
133
-
134273 }
274
+
securis/src/main/java/net/curisit/securis/utils/Config.java
....@@ -1,3 +1,6 @@
1
+/*
2
+ * Copyright @ 2013 CurisTEC, S.A.S. All Rights Reserved.
3
+ */
14 package net.curisit.securis.utils;
25
36 import java.io.IOException;
....@@ -11,17 +14,31 @@
1114 import org.apache.logging.log4j.Logger;
1215
1316 /**
14
- * Class that loads and serves global config parameters.
17
+ * Config
18
+ * <p>
19
+ * Class that loads and serves global config parameters from a classpath properties file
20
+ * and, as a fallback, from environment variables.
21
+ *
22
+ * Initialization:
23
+ * - Static initializer loads {@link #KEY_CONFIG_FILE} from classpath (fails hard if missing).
24
+ *
25
+ * Accessors:
26
+ * - {@link #get(String)} / {@link #get(String, String)}
27
+ * - Integer variants: {@link #getInt(String)} / {@link #getInt(String, int)}
28
+ * - Namespaced helpers: by prefix/domain, and sequential lists via {@link #getListByPrefix(String)}.
29
+ *
30
+ * Thread-safety: static-only utility; internal state is read-only after init.
1531 *
16
- * @author rsanchez
32
+ * @author JRA
33
+ * Last reviewed by JRA on Oct 5, 2025.
1734 */
1835 public class Config {
1936
2037 private static final Logger LOG = LogManager.getLogger(Config.class);
2138
2239 /**
23
- * Key used to store config file resource location. In a web application,
24
- * can be set as initial parameter in a servlet loaded on startup
40
+ * Resource path of the application properties file (in classpath).
41
+ * E.g. "/securis-server.properties".
2542 */
2643 public static final String KEY_CONFIG_FILE = "/securis-server.properties";
2744
....@@ -37,12 +54,12 @@
3754 }
3855
3956 /**
40
- * Loads application global parameters from a classpath resource
41
- *
42
- * @param resource
43
- * : Resource location in classpath, i.e:
44
- * "/resource/cp-securis.conf"
45
- * @throws IOException
57
+ * loadParameters
58
+ * <p>
59
+ * Loads application global parameters from a classpath resource.
60
+ *
61
+ * @param resource Classpath location (e.g. "/resource/cp-securis.conf").
62
+ * @throws IOException If the resource cannot be found or read.
4663 */
4764 public static void loadParameters(String resource) throws IOException {
4865
....@@ -51,7 +68,6 @@
5168
5269 params = new Properties();
5370 try {
54
-
5571 params.load(fileis);
5672 LOG.debug("Params loaded OK from {}", resource);
5773 } catch (IOException e) {
....@@ -59,84 +75,112 @@
5975 params = null;
6076 throw e;
6177 }
62
-
6378 }
6479
80
+ /**
81
+ * getByDomain
82
+ * <p>
83
+ * Convenience accessor for domain-suffixed parameters (param.domain).
84
+ *
85
+ * @param domain Domain suffix.
86
+ * @param paramname Base parameter name.
87
+ * @return String value or null if not present.
88
+ */
6589 public static String getByDomain(String domain, String paramname) {
6690 return getByDomain(domain, paramname, null);
6791 }
6892
93
+ /**
94
+ * getByPrefix
95
+ * <p>
96
+ * Returns parameter value from "{prefix}.{param}" or falls back to the plain "{param}".
97
+ *
98
+ * @param prefix Namespace prefix.
99
+ * @param paramname Parameter name.
100
+ * @return Resolved value or null.
101
+ */
69102 public static String getByPrefix(String prefix, String paramname) {
70103 return get(prefix + "." + paramname, get(paramname));
71104 }
72105
106
+ /**
107
+ * getByPrefix
108
+ * <p>
109
+ * Returns parameter value from "{prefix}.{param}" or provided default (which itself
110
+ * falls back to "{param}" or its default).
111
+ *
112
+ * @param prefix Namespace prefix.
113
+ * @param paramname Parameter name.
114
+ * @param defaultVal Default value if none resolved.
115
+ * @return Resolved value.
116
+ */
73117 public static String getByPrefix(String prefix, String paramname, String defaultVal) {
74118 return get(prefix + "." + paramname, get(paramname, defaultVal));
75119 }
76120
121
+ /**
122
+ * getByDomain
123
+ * <p>
124
+ * Returns value from "{param}.{domain}" or provided default.
125
+ *
126
+ * @param domain domain suffix.
127
+ * @param paramname base name.
128
+ * @param defaultval fallback if not found.
129
+ * @return resolved string.
130
+ */
77131 public static String getByDomain(String domain, String paramname, String defaultval) {
78132 return get(paramname + "." + domain, defaultval);
79133 }
80134
135
+ /**
136
+ * getIntByDomain
137
+ * <p>
138
+ * Integer variant of {@link #getByDomain(String, String)} with fallback to plain param.
139
+ */
81140 public static int getIntByDomain(String domain, String paramname) {
82141 return getInt(paramname + "." + domain, getInt(paramname));
83142 }
84143
144
+ /**
145
+ * getIntByDomain
146
+ * <p>
147
+ * Integer variant returning provided default when missing.
148
+ */
85149 public static int getIntByDomain(String domain, String paramname, int defaultval) {
86150 return getInt(paramname + "." + domain, defaultval);
87151 }
88152
89153 /**
90
- * Gets a List with all values of properties that begins with
91
- * <code>prefix</code> It reads sequentially. For example:
92
- *
93
- * <pre>
94
- * securis.sort.comparator.0: net.cp.securis.comparators.ComparePttidVsPtn
95
- * securis.sort.comparator.1: net.cp.securis.comparators.CompareFrequency
96
- * securis.sort.comparator.2: net.cp.securis.comparators.CompareOutgoingVsIncomming
97
- * securis.sort.comparator.3: net.cp.securis.comparators.CompareDuration
98
- * securis.sort.comparator.4: net.cp.securis.comparators.CompareCallVsSms
99
- * </pre>
100
- *
101
- * That config (for prefix: "securis.sort.comparator" ) will return a
102
- * List<String> with values:
103
- *
104
- * <pre>
105
- * "net.cp.securis.comparators.ComparePttidVsPtn",
106
- * "net.cp.securis.comparators.CompareFrequency",
107
- * "net.cp.securis.comparators.CompareOutgoingVsIncomming",
108
- * "net.cp.securis.comparators.CompareDuration",
109
- * "net.cp.securis.comparators.CompareCallVsSms"
110
- * </pre>
111
- *
112
- * Note: If there is a gap between suffixes process will stop, that is, only
113
- * will be returned properties found before gap.
114
- *
115
- * @param prefix
116
- * @return
154
+ * getListByPrefix
155
+ * <p>
156
+ * Reads sequential properties using numeric suffixes starting from 0 and stops on first gap.
157
+ * Example:
158
+ * securis.sort.comparator.0=...
159
+ * securis.sort.comparator.1=...
160
+ * ...
161
+ *
162
+ * @param prefix Base prefix (e.g. "securis.sort.comparator").
163
+ * @return Ordered list of values until first missing index.
117164 */
118165 public static List<String> getListByPrefix(String prefix) {
119166 List<String> list = new ArrayList<String>();
120
-
121167 String tpl = prefix + ".{0}";
122
-
123168 int i = 0;
124169 String value = get(MessageFormat.format(tpl, i++));
125170 while (value != null) {
126171 list.add(value);
127172 value = get(MessageFormat.format(tpl, i++));
128173 }
129
-
130174 return list;
131175 }
132176
133177 /**
134
- * Gets param value in config file or environment variables
135
- *
136
- * @param paramname
137
- * Global parameter's name
138
- * @return Value of paramname or null if paramname is not found neither in
139
- * config file nor in environment variables
178
+ * get
179
+ * <p>
180
+ * Get a parameter value from the loaded properties or environment variables.
181
+ *
182
+ * @param paramname Parameter key.
183
+ * @return Value or null if not found anywhere.
140184 */
141185 public static String get(String paramname) {
142186
....@@ -149,12 +193,13 @@
149193 }
150194
151195 /**
152
- * Gets param value from config file or environment variables
153
- *
154
- * @param paramname
155
- * Global parameter's name
156
- * @param defaultval
157
- * @return Value of paramname or defaultval if paramname is not found
196
+ * get
197
+ * <p>
198
+ * Returns parameter value or default if missing.
199
+ *
200
+ * @param paramname Key.
201
+ * @param defaultval Default fallback.
202
+ * @return value or default.
158203 */
159204 public static String get(String paramname, String defaultval) {
160205 String value = get(paramname);
....@@ -162,12 +207,12 @@
162207 }
163208
164209 /**
165
- * Gets param value in config file or environment variables
166
- *
167
- * @param paramname
168
- * Global parameter's name
169
- * @return Integer value of paramname or -1 if paramname is not found
170
- * neither in config file nor in environment variables
210
+ * getInt
211
+ * <p>
212
+ * Integer accessor, returns -1 if missing.
213
+ *
214
+ * @param paramname Key.
215
+ * @return Parsed integer or -1.
171216 */
172217 public static int getInt(String paramname) {
173218 String value = get(paramname);
....@@ -175,19 +220,24 @@
175220 }
176221
177222 /**
178
- * Gets param value from config file or environment variables
179
- *
180
- * @param paramname
181
- * Global parameter's name
182
- * @param defaultval
183
- * @return Integer value of paramname or defaultval if paramname is not
184
- * found
223
+ * getInt
224
+ * <p>
225
+ * Integer accessor, returns provided default when missing.
226
+ *
227
+ * @param paramname Key.
228
+ * @param defaultval Default fallback.
229
+ * @return Parsed integer or default.
185230 */
186231 public static int getInt(String paramname, int defaultval) {
187232 String value = get(paramname);
188233 return (value == null ? defaultval : Integer.parseInt(value));
189234 }
190235
236
+ /**
237
+ * KEYS
238
+ * <p>
239
+ * Strongly-typed keys used across the application.
240
+ */
191241 public static class KEYS {
192242
193243 public static final String SERVER_HOSTNAME = "license.server.hostname";
....@@ -205,5 +255,5 @@
205255 public static final String EMAIL_FROM_ADDRESS = "email.from.address";
206256 public static final String EMAIL_LIC_DEFAULT_SUBJECT = "email.lic.default.subject";
207257 }
208
-
209258 }
259
+
securis/src/main/java/net/curisit/securis/utils/EmailManager.java
....@@ -1,3 +1,6 @@
1
+/*
2
+ * Copyright @ 2013 CurisTEC, S.A.S. All Rights Reserved.
3
+ */
14 package net.curisit.securis.utils;
25
36 import java.io.File;
....@@ -40,22 +43,61 @@
4043 import org.apache.logging.log4j.Logger;
4144
4245 /**
43
- * Component that send emails using Mailgun API:
44
- * http://documentation.mailgun.com/user_manual.html#sending-messages
45
- *
46
- * @author roberto <roberto.sanchez@curisit.net>
46
+ * EmailManager
47
+ * <p>
48
+ * Small utility to send plain-text emails (optionally with one attachment)
49
+ * using the <b>Mailgun</b> API over HTTPS.
50
+ * <p>
51
+ * Design notes:
52
+ * <ul>
53
+ * <li>Reads Mailgun credentials and the "from" address from {@link Config}.</li>
54
+ * <li>Builds a preconfigured {@link HttpClientBuilder} with basic auth and a permissive SSL socket factory.</li>
55
+ * <li>Exposes synchronous (blocking) and asynchronous (non-blocking) send methods.</li>
56
+ * <li>Scope is {@code @ApplicationScoped}; the underlying builder is created once per container.</li>
57
+ * </ul>
58
+ *
59
+ * Thread-safety:
60
+ * <p>
61
+ * The class is effectively stateless after construction; using a shared {@link HttpClientBuilder}
62
+ * is safe as a new {@link HttpClient} is built per request.
63
+ *
64
+ * Configuration keys (see {@link Config.KEYS}):
65
+ * <ul>
66
+ * <li>{@code mailgun.domain}</li>
67
+ * <li>{@code mailgun.api.key}</li>
68
+ * <li>{@code email.from.address}</li>
69
+ * </ul>
70
+ *
71
+ * Failure handling:
72
+ * <p>
73
+ * Network and HTTP errors are surfaced as {@link SeCurisServiceException} with appropriate error codes.
74
+ *
75
+ * @author roberto &lt;roberto.sanchez@curisit.net&gt;
76
+ * Last reviewed by JRA on Oct 6, 2025.
4777 */
4878 @ApplicationScoped
4979 public class EmailManager {
5080
81
+ /** Class logger. */
5182 private static final Logger LOG = LogManager.getLogger(EmailManager.class);
5283
84
+ /** Mailgun endpoint composed from configured domain. */
5385 private final String serverUrl;
86
+
87
+ /** Preconfigured builder that carries SSL and basic-auth configuration. */
5488 private final HttpClientBuilder httpClientBuilder;
5589
90
+ // ---------------------------------------------------------------------
91
+ // Constructors
92
+ // ---------------------------------------------------------------------
93
+
5694 /**
57
- *
58
- * @throws SeCurisException
95
+ * EmailManager
96
+ * <p>
97
+ * Default constructor that validates required configuration and prepares an
98
+ * HTTP client builder with Mailgun credentials and SSL settings.
99
+ *
100
+ * @throws SeCurisException if mandatory configuration is missing or the SSL builder cannot be created
59101 */
60102 public EmailManager() throws SeCurisException {
61103 String domain = Config.get(Config.KEYS.MAILGUN_DOMAIN);
....@@ -64,13 +106,29 @@
64106 }
65107 serverUrl = String.format("https://api.mailgun.net/v2/%s/messages", domain);
66108 httpClientBuilder = createHttpClient();
67
-
68109 }
69110
111
+ // ---------------------------------------------------------------------
112
+ // Internal helpers
113
+ // ---------------------------------------------------------------------
114
+
115
+ /**
116
+ * createHttpClient
117
+ * <p>
118
+ * Builds a {@link HttpClientBuilder} that:
119
+ * <ul>
120
+ * <li>Accepts any server certificate (permissive trust strategy).</li>
121
+ * <li>Applies HTTP Basic Auth using Mailgun's API key as the password and user "api".</li>
122
+ * </ul>
123
+ *
124
+ * @return a preconfigured {@link HttpClientBuilder} ready to build clients
125
+ * @throws SeCurisException if SSL initialization fails
126
+ */
70127 private HttpClientBuilder createHttpClient() throws SeCurisException {
71128 SSLContextBuilder builder = new SSLContextBuilder();
72129 SSLConnectionSocketFactory sslsf = null;
73130 try {
131
+ // Trust all X509 certificates (relies on HTTPS + Basic Auth; consider hardening in production).
74132 builder.loadTrustMaterial((KeyStore) null, new TrustStrategy() {
75133 @Override
76134 public boolean isTrusted(X509Certificate[] chain, String authType) throws CertificateException {
....@@ -82,30 +140,45 @@
82140 LOG.error(e1);
83141 throw new SeCurisException("Error creating SSL socket factory");
84142 }
143
+
144
+ // Configure Basic Auth with Mailgun API key
85145 CredentialsProvider provider = new BasicCredentialsProvider();
86
- UsernamePasswordCredentials credentials = new UsernamePasswordCredentials("api", Config.get(Config.KEYS.MAILGUN_API_KEY));
146
+ UsernamePasswordCredentials credentials =
147
+ new UsernamePasswordCredentials("api", Config.get(Config.KEYS.MAILGUN_API_KEY));
87148 provider.setCredentials(AuthScope.ANY, credentials);
88149
89
- return HttpClientBuilder.create().setDefaultCredentialsProvider(provider).setSSLSocketFactory(sslsf);
150
+ return HttpClientBuilder.create()
151
+ .setDefaultCredentialsProvider(provider)
152
+ .setSSLSocketFactory(sslsf);
90153 }
91154
155
+ // ---------------------------------------------------------------------
156
+ // Email sending API
157
+ // ---------------------------------------------------------------------
158
+
92159 /**
93
- * Basic method to send emails in text mode with attachment. The method is
94
- * synchronous, It waits until server responses.
95
- *
96
- * @param subject
97
- * @param body
98
- * @param to
99
- * @param file
100
- * @throws SeCurisException
101
- * @throws UnsupportedEncodingException
160
+ * sendEmail
161
+ * <p>
162
+ * Sends a plain-text email (UTF-8) via Mailgun. Optionally attaches a single file.
163
+ * This call is <b>synchronous</b> (blocking) and only returns once the HTTP response is received.
164
+ *
165
+ * @param subject Email subject (will be sent as UTF-8).
166
+ * @param body Email body in plain text (UTF-8).
167
+ * @param to Recipient address (required).
168
+ * @param cc Optional CC address, may be {@code null}.
169
+ * @param file Optional file to attach, may be {@code null}.
170
+ *
171
+ * @throws SeCurisServiceException if the HTTP call fails or Mailgun responds with a non-200 status
172
+ * @throws UnsupportedEncodingException kept for API compatibility (body/subject are forced to UTF-8)
102173 */
103174 @SuppressWarnings("deprecation")
104
- public void sendEmail(String subject, String body, String to, String cc, File file) throws SeCurisServiceException, UnsupportedEncodingException {
175
+ public void sendEmail(String subject, String body, String to, String cc, File file)
176
+ throws SeCurisServiceException, UnsupportedEncodingException {
177
+
105178 HttpPost postRequest = new HttpPost(serverUrl);
106179
180
+ // Build multipart form body compatible with Mailgun
107181 MultipartEntityBuilder builder = MultipartEntityBuilder.create();
108
-
109182 builder.setCharset(Charset.forName("utf-8"));
110183 builder.setMode(HttpMultipartMode.BROWSER_COMPATIBLE);
111184 builder.addTextBody("from", Config.get(Config.KEYS.EMAIL_FROM_ADDRESS));
....@@ -113,28 +186,34 @@
113186 if (cc != null) {
114187 builder.addTextBody("cc", cc);
115188 }
116
- builder.addTextBody("subject", subject, ContentType.create(ContentType.TEXT_PLAIN.getMimeType(), Charset.forName("utf-8")));
117
- builder.addTextBody("text", body, ContentType.create(ContentType.TEXT_PLAIN.getMimeType(), Charset.forName("utf-8")));
118
- if (file != null) {
189
+ builder.addTextBody("subject",
190
+ subject, ContentType.create(ContentType.TEXT_PLAIN.getMimeType(), Charset.forName("utf-8")));
191
+ builder.addTextBody("text",
192
+ body, ContentType.create(ContentType.TEXT_PLAIN.getMimeType(), Charset.forName("utf-8")));
119193
194
+ if (file != null) {
120195 LOG.info("File to attach: {}", file.getAbsoluteFile());
121196 builder.addPart("attachment", new FileBody(file));
122197 }
123198
124199 postRequest.setEntity(builder.build());
200
+
201
+ // Execute HTTP request
125202 HttpResponse response;
126203 HttpClient httpClient = httpClientBuilder.build();
127204 try {
128205 response = httpClient.execute(postRequest);
129206
207
+ // Mailgun returns JSON. We parse it to a Map for logging/validation.
130208 String jsonLic = IOUtils.toString(response.getEntity().getContent());
131209 if (response.getStatusLine().getStatusCode() == 200) {
132210 LOG.debug("Response content read OK: {}", jsonLic);
133211 Map<String, Object> responseBean = JsonUtils.json2map(jsonLic);
134
-
135212 LOG.debug("Response mail read OK: {}", responseBean);
136213 } else {
137
- throw new SeCurisServiceException(ErrorCodes.UNEXPECTED_ERROR, "Error sending email, response estatus: " + response.getStatusLine());
214
+ throw new SeCurisServiceException(
215
+ ErrorCodes.UNEXPECTED_ERROR,
216
+ "Error sending email, response estatus: " + response.getStatusLine());
138217 }
139218 } catch (IOException e) {
140219 LOG.error("Error sending email", e);
....@@ -143,21 +222,27 @@
143222 }
144223
145224 /**
146
- * Basic method to send emails in text mode with attachment. The method is
147
- * asynchronous, It returns immediately
148
- *
149
- * @param subject
150
- * @param body
151
- * @param to
152
- * @param file
153
- * @throws SeCurisException
154
- * @throws UnsupportedEncodingException
225
+ * sendEmailAsync
226
+ * <p>
227
+ * Asynchronous variant of {@link #sendEmail(String, String, String, String, File)}.
228
+ * The call returns immediately and performs the HTTP request in a single-thread executor.
229
+ *
230
+ * @param subject Email subject (UTF-8).
231
+ * @param body Email body in plain text (UTF-8).
232
+ * @param to Recipient address.
233
+ * @param cc Optional CC address, may be {@code null}.
234
+ * @param file Optional attachment, may be {@code null}.
235
+ * @param callback Non-null callback to be notified on success or failure.
236
+ *
237
+ * @throws SeCurisException if there is a configuration or environment problem before dispatch
238
+ * @throws UnsupportedEncodingException for API compatibility (subject/body are encoded as UTF-8)
155239 */
156
- public void sendEmailAsync(String subject, String body, String to, String cc, File file, EmailCallback callback) throws SeCurisException,
157
- UnsupportedEncodingException {
240
+ public void sendEmailAsync(
241
+ String subject, String body, String to, String cc, File file, EmailCallback callback)
242
+ throws SeCurisException, UnsupportedEncodingException {
243
+
158244 Executor ex = Executors.newSingleThreadExecutor();
159245 ex.execute(new Runnable() {
160
-
161246 @Override
162247 public void run() {
163248 try {
....@@ -168,37 +253,61 @@
168253 } catch (SeCurisServiceException e) {
169254 callback.error(e);
170255 }
171
-
172256 }
173257 });
174
-
175258 }
176259
260
+ // ---------------------------------------------------------------------
261
+ // Callback contract
262
+ // ---------------------------------------------------------------------
263
+
264
+ /**
265
+ * EmailCallback
266
+ * <p>
267
+ * Functional contract to be notified when an async send finishes.
268
+ */
177269 public static interface EmailCallback {
270
+ /**
271
+ * success<p>
272
+ * Called when the email was sent and Mailgun returned HTTP 200.
273
+ */
178274 public void success();
179275
276
+ /**
277
+ * error<p>
278
+ * Called when there was a problem sending the email.
279
+ *
280
+ * @param e encapsulates the reason of failure
281
+ */
180282 public void error(SeCurisServiceException e);
181283 }
182284
285
+ // ---------------------------------------------------------------------
286
+ // Manual test harness
287
+ // ---------------------------------------------------------------------
288
+
289
+ /**
290
+ * main<p>
291
+ * Simple manual test for the async email flow. Adjust addresses and file path before use.
292
+ *
293
+ * @param args program arguments (unused)
294
+ * @throws SeCurisException if configuration is invalid
295
+ * @throws UnsupportedEncodingException if UTF-8 encoding fails (unlikely)
296
+ */
183297 public static void main(String[] args) throws SeCurisException, UnsupportedEncodingException {
184
- // new EmailManager().sendEmail("España así de bien",
185
- // "Me gusta esta prueba\nCon varias líneas\n\n\n--\nNo response",
186
- // "info@r75.es", new File(
187
- // "/Users/rob/Downloads/test.req"));
188
- new EmailManager().sendEmailAsync("España así de bien", "Me gusta esta prueba\nCon varias líneas\n\n\n--\nNo response", "info@r75.es",
189
- "dev@r75.es", new File("/Users/rob/Downloads/test.req"), new EmailCallback() {
298
+ // Example async call (subject/body contain non-ASCII content to validate UTF-8 handling).
299
+ new EmailManager().sendEmailAsync("España así de bien",
300
+ "Me gusta esta prueba\nCon varias líneas\n\n\n--\nNo response",
301
+ "info@r75.es",
302
+ "dev@r75.es",
303
+ new File("/Users/rob/Downloads/test.req"),
304
+ new EmailCallback() {
305
+ @Override
306
+ public void success() { LOG.info("Success!!!"); }
190307
191308 @Override
192
- public void success() {
193
- LOG.info("Success!!!");
194
- }
195
-
196
- @Override
197
- public void error(SeCurisServiceException e) {
198
- LOG.error("Error: {} !!!", e);
199
- }
309
+ public void error(SeCurisServiceException e) { LOG.error("Error: {} !!!", e); }
200310 });
201311 LOG.info("Waiting for email to be sent...");
202312 }
203
-
204313 }
securis/src/main/java/net/curisit/securis/utils/GZipServletResponseWrapper.java
....@@ -1,3 +1,6 @@
1
+/*
2
+ * Copyright @ 2013 CurisTEC, S.A.S. All Rights Reserved.
3
+ */
14 package net.curisit.securis.utils;
25
36 import java.io.IOException;
....@@ -10,38 +13,92 @@
1013 import jakarta.servlet.http.HttpServletResponse;
1114 import jakarta.servlet.http.HttpServletResponseWrapper;
1215
16
+/**
17
+ * GZipServletResponseWrapper
18
+ * <p>
19
+ * {@link HttpServletResponseWrapper} that transparently compresses the response
20
+ * body using GZIP. Intended for use in filters/servlets where the caller wants
21
+ * to wrap the original response and write compressed bytes to the client.
22
+ * <p>
23
+ * How it works:
24
+ * <ul>
25
+ * <li>Overrides {@link #getOutputStream()} and {@link #getWriter()} to route output through a {@link GZIPOutputStream}.</li>
26
+ * <li>Ensures mutual exclusivity between OutputStream and Writer access per Servlet API requirements.</li>
27
+ * <li>Ignores {@link #setContentLength(int)} since compressed size differs from uncompressed.</li>
28
+ * </ul>
29
+ *
30
+ * Usage:
31
+ * <pre>
32
+ * GZipServletResponseWrapper gz = new GZipServletResponseWrapper(resp);
33
+ * chain.doFilter(request, gz);
34
+ * gz.close(); // important: finish compression and flush buffers
35
+ * </pre>
36
+ *
37
+ * Thread-safety:
38
+ * <p>
39
+ * Instances are per-request and not shared.
40
+ *
41
+ * Limitations:
42
+ * <p>
43
+ * Caller is responsible for setting "Content-Encoding: gzip" and for avoiding
44
+ * double-compression scenarios.
45
+ *
46
+ * @author JRA
47
+ * Last reviewed by JRA on Oct 6, 2025.
48
+ */
1349 public class GZipServletResponseWrapper extends HttpServletResponseWrapper {
1450
1551 private GZIPServletOutputStream gzipOutputStream = null;
1652 private PrintWriter printWriter = null;
1753
54
+ // ---------------------------------------------------------------------
55
+ // Constructors
56
+ // ---------------------------------------------------------------------
57
+
58
+ /**
59
+ * GZipServletResponseWrapper
60
+ * <p>
61
+ * Wraps the given response. Actual GZIP streams are lazily created on first write.
62
+ *
63
+ * @param response the original {@link HttpServletResponse} to wrap
64
+ * @throws IOException if the underlying response streams cannot be accessed
65
+ */
1866 public GZipServletResponseWrapper(HttpServletResponse response) throws IOException {
1967 super(response);
2068 }
2169
22
- public void close() throws IOException {
70
+ // ---------------------------------------------------------------------
71
+ // Lifecycle
72
+ // ---------------------------------------------------------------------
2373
24
- //PrintWriter.close does not throw exceptions.
25
- //Hence no try-catch block.
74
+ /**
75
+ * close<p>
76
+ * Closes any open writer or output stream and finalizes the GZIP stream.
77
+ * Must be called once all response content has been written.
78
+ *
79
+ * @throws IOException if closing the underlying streams fails
80
+ */
81
+ public void close() throws IOException {
82
+ // PrintWriter.close does not throw exceptions. Hence no try-catch block.
2683 if (this.printWriter != null) {
2784 this.printWriter.close();
2885 }
29
-
3086 if (this.gzipOutputStream != null) {
3187 this.gzipOutputStream.close();
3288 }
3389 }
3490
3591 /**
36
- * Flush OutputStream or PrintWriter
92
+ * flushBuffer<p>
93
+ * Flushes the writer and the GZIP output stream, then delegates to the wrapped response.
94
+ * If multiple exceptions occur, the first encountered is thrown (typical servlet practice).
3795 *
38
- * @throws IOException
96
+ * @throws IOException if flushing any of the streams fails
3997 */
40
-
4198 @Override
4299 public void flushBuffer() throws IOException {
43100
44
- //PrintWriter.flush() does not throw exception
101
+ // PrintWriter.flush() does not throw exception
45102 if (this.printWriter != null) {
46103 this.printWriter.flush();
47104 }
....@@ -62,12 +119,23 @@
62119 exception2 = e;
63120 }
64121
65
- if (exception1 != null)
66
- throw exception1;
67
- if (exception2 != null)
68
- throw exception2;
122
+ if (exception1 != null) throw exception1;
123
+ if (exception2 != null) throw exception2;
69124 }
70125
126
+ // ---------------------------------------------------------------------
127
+ // Output acquisition
128
+ // ---------------------------------------------------------------------
129
+
130
+ /**
131
+ * getOutputStream<p>
132
+ * Returns a {@link ServletOutputStream} that writes compressed data.
133
+ * Mutually exclusive with {@link #getWriter()} as per Servlet API.
134
+ *
135
+ * @return compressed {@link ServletOutputStream}
136
+ * @throws IOException if the underlying output stream cannot be obtained
137
+ * @throws IllegalStateException if the writer has been already acquired
138
+ */
71139 @Override
72140 public ServletOutputStream getOutputStream() throws IOException {
73141 if (this.printWriter != null) {
....@@ -79,6 +147,15 @@
79147 return this.gzipOutputStream;
80148 }
81149
150
+ /**
151
+ * getWriter<p>
152
+ * Returns a {@link PrintWriter} that writes compressed data (UTF-8 by default, inherited from response).
153
+ * Mutually exclusive with {@link #getOutputStream()} as per Servlet API.
154
+ *
155
+ * @return compressed {@link PrintWriter}
156
+ * @throws IOException if streams cannot be allocated
157
+ * @throws IllegalStateException if the output stream has been already acquired
158
+ */
82159 @Override
83160 public PrintWriter getWriter() throws IOException {
84161 if (this.printWriter == null && this.gzipOutputStream != null) {
....@@ -91,50 +168,118 @@
91168 return this.printWriter;
92169 }
93170
171
+ /**
172
+ * setContentLength<p>
173
+ * No-op. The content length of the zipped content is not known a priori and
174
+ * will not match the uncompressed length; therefore we do not set it here.
175
+ *
176
+ * @param len ignored
177
+ */
94178 @Override
95179 public void setContentLength(int len) {
96
- //ignore, since content length of zipped content
97
- //does not match content length of unzipped content.
180
+ // ignore, since content length of zipped content does not match content length of unzipped content.
98181 }
99182
183
+ // ---------------------------------------------------------------------
184
+ // Inner compressed stream
185
+ // ---------------------------------------------------------------------
186
+
187
+ /**
188
+ * GZIPServletOutputStream
189
+ * <p>
190
+ * Decorates the original {@link ServletOutputStream} with a {@link GZIPOutputStream}.
191
+ * Delegates readiness and listener to the underlying (container) stream.
192
+ *
193
+ * @author JRA
194
+ * Last reviewed by JRA on Oct 5, 2025.
195
+ */
100196 private static class GZIPServletOutputStream extends ServletOutputStream {
101197 private final ServletOutputStream servletOutputStream;
102198 private final GZIPOutputStream gzipStream;
103199
200
+ /**
201
+ * GZIPServletOutputStream<p>
202
+ * Creates a new compressed stream wrapper.
203
+ *
204
+ * @param servletOutputStream underlying (container-provided) output stream
205
+ * @throws IOException if the GZIP stream cannot be created
206
+ */
104207 public GZIPServletOutputStream(ServletOutputStream servletOutputStream) throws IOException {
105208 this.servletOutputStream = servletOutputStream;
106209 this.gzipStream = new GZIPOutputStream(servletOutputStream);
107210 }
108211
212
+ /**
213
+ * isReady<p>
214
+ * Check if the output stream is ready
215
+ * {@inheritDoc}
216
+ *
217
+ * @return isReady
218
+ */
109219 @Override
110220 public boolean isReady() {
111221 return this.servletOutputStream.isReady();
112222 }
113223
224
+ /**
225
+ * setWriteListener<p>
226
+ * Set the write listener for the output stream
227
+ * {@inheritDoc}
228
+ *
229
+ * @param writeListener
230
+ */
114231 @Override
115232 public void setWriteListener(WriteListener writeListener) {
116233 this.servletOutputStream.setWriteListener(writeListener);
117234 }
118235
236
+ /**
237
+ * write<p>
238
+ * Write on the gzip stream
239
+ * {@inheritDoc}
240
+ *
241
+ * @param b
242
+ * @throws IOException
243
+ */
119244 @Override
120245 public void write(int b) throws IOException {
121246 this.gzipStream.write(b);
122247 }
123248
249
+ /**
250
+ * close<p>
251
+ * Close the gzip stream
252
+ * {@inheritDoc}
253
+ *
254
+ * @throws IOException
255
+ */
124256 @Override
125257 public void close() throws IOException {
126258 this.gzipStream.close();
127259 }
128260
261
+ /**
262
+ * flush<p>
263
+ * Flush the gzip stream
264
+ * {@inheritDoc}
265
+ *
266
+ * @throws IOException
267
+ */
129268 @Override
130269 public void flush() throws IOException {
131270 this.gzipStream.flush();
132271 }
133272
273
+ /**
274
+ * finish<p>
275
+ * Explicitly finishes writing of the GZIP stream, without closing the underlying stream.
276
+ * Not used by the wrapper but available for completeness.
277
+ *
278
+ * @throws IOException if finishing fails
279
+ */
134280 @SuppressWarnings("unused")
135281 public void finish() throws IOException {
136282 this.gzipStream.finish();
137283 }
138284 }
139
-
140285 }
securis/src/main/java/net/curisit/securis/utils/TokenHelper.java
....@@ -1,3 +1,6 @@
1
+/*
2
+ * Copyright @ 2013 CurisTEC, S.A.S. All Rights Reserved.
3
+ */
14 package net.curisit.securis.utils;
25
36 import java.io.IOException;
....@@ -20,37 +23,108 @@
2023 import java.util.Base64;
2124 import java.nio.charset.StandardCharsets;
2225
26
+/**
27
+ * TokenHelper
28
+ * <p>
29
+ * Utility component to generate and validate short-lived authentication tokens
30
+ * for SeCuris services. Tokens are:
31
+ * </p>
32
+ * <ul>
33
+ * <li>Base64-encoded UTF-8 strings.</li>
34
+ * <li>Composed as: {@code <secret> <username> <iso8601-timestamp>} (space-separated).</li>
35
+ * <li>Where {@code secret} is a deterministic SHA-256 HMAC-like hash built from a static seed,
36
+ * the username, and the issuance timestamp.</li>
37
+ * </ul>
38
+ *
39
+ * <p><b>Lifecycle & scope:</b> {@code @ApplicationScoped}. Stateless and thread-safe.</p>
40
+ *
41
+ * <p><b>Security notes:</b>
42
+ * The {@code seed} acts like a shared secret. Keep it private and rotate if compromised.
43
+ * Tokens expire after {@link #VALID_TOKEN_PERIOD} hours except a special client token
44
+ * defined by {@link ApiResource#API_CLIENT_USERNAME} issued at epoch-1 (see {@link #isTokenValid(String)}).
45
+ * </p>
46
+ *
47
+ * <p><b>Format details:</b>
48
+ * <pre>
49
+ * token = Base64( secret + " " + user + " " + Utils.toIsoFormat(date) )
50
+ * secret = hex(SHA-256(seed || user || isoDate))
51
+ * </pre>
52
+ * </p>
53
+ *
54
+ * @author JRA
55
+ * Last reviewed by JRA on Oct 6, 2025.
56
+ */
2357 @ApplicationScoped
2458 public class TokenHelper {
2559
2660 private static final Logger LOG = LogManager.getLogger(TokenHelper.class);
2761
2862 /**
29
- * Period before token expires, set in hours.
63
+ * Validity window for standard tokens, in hours.
64
+ * <p>
65
+ * Any token with a creation date older than this window will be rejected
66
+ * (unless it matches the special API client rule documented in
67
+ * {@link #isTokenValid(String)}).
68
+ * </p>
3069 */
3170 private static int VALID_TOKEN_PERIOD = 24;
71
+
72
+ /** Standard HTTP header used by SeCuris clients to carry the token. */
3273 public static final String TOKEN_HEADER_PÀRAM = "X-SECURIS-TOKEN";
3374
75
+ /**
76
+ * TokenHelper<p>
77
+ * CDI no-arg constructor.
78
+ * <p>
79
+ * Kept for dependency injection. No initialization logic is required.
80
+ * </p>
81
+ */
3482 @Inject
3583 public TokenHelper() {
3684 }
3785
86
+ /**
87
+ * Static secret seed used to derive the token {@code secret} portion.
88
+ * <p>
89
+ * Treat this as confidential. Changing it invalidates all outstanding tokens.
90
+ * </p>
91
+ */
3892 private static byte[] seed = "S3Cur15S33dForT0k3nG3n3r@tion".getBytes();
3993
94
+ // ---------------------------------------------------------------------
95
+ // Token generation
96
+ // ---------------------------------------------------------------------
97
+
4098 /**
41
- * Generate a token encoded in Base64 for user passed as parameter and
42
- * taking the current moment as token timestamp
43
- *
44
- * @param user
45
- * @return
99
+ * generateToken
100
+ * <p>
101
+ * Convenience overload that generates a token for {@code user} using the current
102
+ * system time as the issuance timestamp.
103
+ * </p>
104
+ *
105
+ * @param user Username to embed in the token (must be non-null/non-empty).
106
+ * @return Base64-encoded token string, or {@code null} if a cryptographic error occurs.
46107 */
47108 public String generateToken(String user) {
48
-
49109 return generateToken(user, new Date());
50110 }
51111
52
- ;
53
-
112
+ /**
113
+ * generateToken
114
+ * <p>
115
+ * Builds a token for a given user and issuance date. The token body is:
116
+ * </p>
117
+ * <pre>
118
+ * secret + " " + user + " " + Utils.toIsoFormat(date)
119
+ * </pre>
120
+ * <p>
121
+ * and then Base64-encoded in UTF-8.
122
+ * </p>
123
+ *
124
+ * @param user Username to embed.
125
+ * @param date Issuance date to include in the token (affects expiry and secret derivation).
126
+ * @return Base64 token, or {@code null} upon failure.
127
+ */
54128 public String generateToken(String user, Date date) {
55129 try {
56130 String secret = generateSecret(user, date);
....@@ -61,7 +135,7 @@
61135 sb.append(' ');
62136 sb.append(Utils.toIsoFormat(date));
63137
64
- // Codificación estándar con UTF-8
138
+ // Standard UTF-8 encoding before Base64
65139 return Base64.getEncoder().encodeToString(sb.toString().getBytes(StandardCharsets.UTF_8));
66140
67141 } catch (NoSuchAlgorithmException e) {
....@@ -72,42 +146,86 @@
72146 return null;
73147 }
74148
149
+ /**
150
+ * generateSecret
151
+ * <p>
152
+ * Derives the deterministic secret (a 64-hex-character SHA-256 digest) used to
153
+ * authenticate a token. Inputs are concatenated in the following order:
154
+ * </p>
155
+ * <ol>
156
+ * <li>{@link #seed}</li>
157
+ * <li>{@code user} (UTF-8 bytes)</li>
158
+ * <li>{@code Utils.toIsoFormat(date)}</li>
159
+ * </ol>
160
+ *
161
+ * @param user Username to mix in the digest.
162
+ * @param date Token issuance date to mix in the digest.
163
+ * @return 64-char hex string.
164
+ * @throws UnsupportedEncodingException If UTF-8 is unavailable (unexpected).
165
+ * @throws NoSuchAlgorithmException If SHA-256 is unavailable (unexpected).
166
+ */
75167 private String generateSecret(String user, Date date) throws UnsupportedEncodingException, NoSuchAlgorithmException {
76168 MessageDigest mDigest = MessageDigest.getInstance("SHA-256");
77169 mDigest.update(seed, 0, seed.length);
170
+
78171 byte[] userbytes = user.getBytes("utf-8");
79172 mDigest.update(userbytes, 0, userbytes.length);
173
+
80174 byte[] isodate = Utils.toIsoFormat(date).getBytes();
81175 mDigest.update(isodate, 0, isodate.length);
176
+
82177 BigInteger i = new BigInteger(1, mDigest.digest());
83178 String secret = String.format("%1$064x", i);
84179 return secret;
85180 }
86181
182
+ // ---------------------------------------------------------------------
183
+ // Token validation & parsing
184
+ // ---------------------------------------------------------------------
185
+
87186 /**
88
- * Check if passed token is still valid, It use to check if token is expired
89
- * the attribute VALID_TOKEN_PERIOD (in hours)
90
- *
91
- * @param token
92
- * @return
187
+ * isTokenValid
188
+ * <p>
189
+ * Validates the structure, signature and expiry of the given token.
190
+ * Steps performed:
191
+ * </p>
192
+ * <ol>
193
+ * <li>Base64-decode the token into {@code "secret user isoDate"}.</li>
194
+ * <li>Parse {@code user} and {@code isoDate}; recompute the expected secret via
195
+ * {@link #generateSecret(String, Date)} and compare with the provided one.</li>
196
+ * <li>Check expiry: if the token's timestamp is older than
197
+ * {@link #VALID_TOKEN_PERIOD} hours, it's invalid.</li>
198
+ * <li>Special-case: if {@code user} equals {@link ApiResource#API_CLIENT_USERNAME}
199
+ * and the date returns a non-positive epoch time (e.g., created with {@code new Date(-1)}),
200
+ * the expiry check is skipped (client integration token).</li>
201
+ * </ol>
202
+ *
203
+ * @param token Base64 token string.
204
+ * @return {@code true} if valid and not expired; {@code false} otherwise.
93205 */
94206 public boolean isTokenValid(String token) {
95207 try {
96
- String tokenDecoded = new String(Base64.getDecoder().decode(token), StandardCharsets.UTF_8);
208
+ String tokenDecoded = new String(Base64.getDecoder().decode(token), StandardCharsets.UTF_8);
97209 String[] parts = StringUtils.split(tokenDecoded, ' ');
98210 if (parts == null || parts.length < 3) {
99211 return false;
100212 }
213
+
101214 String secret = parts[0];
102215 String user = parts[1];
103216 Date date = Utils.toDateFromIso(parts[2]);
217
+
218
+ // Expiry check (unless special client token rule applies)
104219 if (date.getTime() > 0 || !user.equals(ApiResource.API_CLIENT_USERNAME)) {
105220 if (new Date().after(new Date(date.getTime() + VALID_TOKEN_PERIOD * 60 * 60 * 1000))) {
106221 return false;
107222 }
108
- } // else: It's a securis-client API call
223
+ }
224
+
225
+ // Signature check
109226 String newSecret = generateSecret(user, date);
110227 return newSecret.equals(secret);
228
+
111229 } catch (IOException e) {
112230 LOG.error("Error decoding Base64 token", e);
113231 } catch (NoSuchAlgorithmException e) {
....@@ -116,6 +234,16 @@
116234 return false;
117235 }
118236
237
+ /**
238
+ * extractUserFromToken
239
+ * <p>
240
+ * Extracts the username portion from a validly structured token.
241
+ * </p>
242
+ *
243
+ * @param token Base64 token string (may be {@code null}).
244
+ * @return Username if the token has at least three space-separated fields after decoding;
245
+ * {@code null} on error or malformed input.
246
+ */
119247 public String extractUserFromToken(String token) {
120248 try {
121249 if (token == null) {
....@@ -134,6 +262,16 @@
134262 return null;
135263 }
136264
265
+ /**
266
+ * extractDateCreationFromToken
267
+ * <p>
268
+ * Parses and returns the issuance {@link Date} embedded in the token, without
269
+ * performing validation or expiry checks.
270
+ * </p>
271
+ *
272
+ * @param token Base64 token string.
273
+ * @return Issuance {@link Date}, or {@code null} if the token is malformed or cannot be decoded.
274
+ */
137275 public Date extractDateCreationFromToken(String token) {
138276 try {
139277 String tokenDecoded = new String(Base64.getDecoder().decode(token), StandardCharsets.UTF_8);
....@@ -149,6 +287,20 @@
149287 return null;
150288 }
151289
290
+ // ---------------------------------------------------------------------
291
+ // Demo / manual test
292
+ // ---------------------------------------------------------------------
293
+
294
+ /**
295
+ * main
296
+ * <p>
297
+ * Simple manual test demonstrating generation and validation of a special
298
+ * "_client" token that bypasses expiry via a negative epoch date.
299
+ * </p>
300
+ *
301
+ * @param args CLI args (unused).
302
+ * @throws IOException If something goes wrong while encoding/decoding Base64 (unlikely).
303
+ */
152304 public static void main(String[] args) throws IOException {
153305 // client token:
154306 // OTk3ODRiMzY5NzQ5MWI5NmYyZGQyODRiYjY2ZTU2YzdmMTZjYzM3YTY3N2ExM2M3ODI2MjU5ZTMzOTIyYjUzNSBfY2xpZW50IDE5NzAtMDEtMDFUMDA6NTk6NTkuOTk5KzAxMDA=
....@@ -160,3 +312,4 @@
160312 System.out.println("is valid client token: " + new TokenHelper().isTokenValid(t));
161313 }
162314 }
315
+
securis/src/main/resources/META-INF/persistence.xml
....@@ -19,4 +19,4 @@
1919 </properties>
2020
2121 </persistence-unit>
22
-</persistence>
22
+</persistence>