| .. | .. |
|---|
| 1 | +/* |
|---|
| 2 | + * Copyright @ 2013 CurisTEC, S.A.S. All Rights Reserved. |
|---|
| 3 | + */ |
|---|
| 1 | 4 | package net.curisit.securis.services.helpers; |
|---|
| 2 | 5 | |
|---|
| 3 | 6 | import java.io.File; |
|---|
| .. | .. |
|---|
| 30 | 33 | import net.curisit.securis.services.exception.SeCurisServiceException; |
|---|
| 31 | 34 | import net.curisit.securis.services.exception.SeCurisServiceException.ErrorCodes; |
|---|
| 32 | 35 | |
|---|
| 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 | + */ |
|---|
| 33 | 51 | @ApplicationScoped |
|---|
| 34 | 52 | public class LicenseHelper { |
|---|
| 35 | 53 | |
|---|
| 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); |
|---|
| 39 | 56 | |
|---|
| 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; |
|---|
| 42 | 59 | |
|---|
| 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; |
|---|
| 54 | 61 | |
|---|
| 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); |
|---|
| 57 | 79 | |
|---|
| 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 | + } |
|---|
| 76 | 82 | |
|---|
| 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 | + } |
|---|
| 86 | 103 | |
|---|
| 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 | + } |
|---|
| 90 | 124 | |
|---|
| 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 | + } |
|---|
| 105 | 138 | |
|---|
| 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 | + } |
|---|
| 111 | 158 | |
|---|
| 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 | + } |
|---|
| 114 | 174 | |
|---|
| 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 | + } |
|---|
| 142 | 206 | |
|---|
| 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 | + } |
|---|
| 157 | 223 | } |
|---|
| 224 | + |
|---|