Joaquín Reñé
2025-10-07 146a0fb8b0e90f9196e569152f649baf60d6cc8f
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
+