package net.curisit.securis.services; import java.io.IOException; import java.util.Date; import java.util.List; import javax.inject.Inject; import javax.inject.Provider; import javax.persistence.EntityManager; import javax.ws.rs.Consumes; import javax.ws.rs.GET; import javax.ws.rs.HeaderParam; import javax.ws.rs.POST; import javax.ws.rs.Path; import javax.ws.rs.Produces; import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import net.curisit.securis.LicenseGenerator; import net.curisit.securis.LicenseManager; import net.curisit.securis.SeCurisException; import net.curisit.securis.beans.LicenseBean; import net.curisit.securis.beans.RequestBean; import net.curisit.securis.beans.SignedLicenseBean; import net.curisit.securis.beans.StatusBean; import net.curisit.securis.db.BlockedRequest; import net.curisit.securis.db.License; import net.curisit.securis.db.LicenseHistory; import net.curisit.securis.db.LicenseStatus; import net.curisit.securis.db.Pack; import net.curisit.securis.db.User; import net.curisit.securis.security.BasicSecurityContext; import net.curisit.securis.security.Securable; import net.curisit.securis.services.exception.SeCurisServiceException; import net.curisit.securis.services.exception.SeCurisServiceException.ErrorCodes; import net.curisit.securis.services.helpers.LicenseHelper; import net.curisit.securis.utils.JsonUtils; import net.curisit.securis.utils.LicUtils; import net.curisit.securis.utils.SignatureHelper; import net.curisit.securis.utils.TokenHelper; import org.apache.commons.lang.time.DateUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.jboss.resteasy.plugins.providers.multipart.MultipartFormDataInput; import com.google.inject.persist.Transactional; /** * External API to be accessed by third parties * * @author roberto */ @Path("/api") public class ApiResource { private static final Logger LOG = LogManager.getLogger(ApiResource.class); @Inject TokenHelper tokenHelper; @Inject private LicenseHelper licenseHelper; @Inject Provider emProvider; @Inject LicenseGenerator licenseGenerator; private static final String CLIENT_USERNAME = "_client"; public ApiResource() { } /** * * @return Simple text message to check API status */ @GET @Path("/") @Produces({ MediaType.TEXT_PLAIN }) public Response index() { return Response.ok("SeCuris API. Date: " + new Date()).build(); } /** * * @return Simple text message to check API status */ @GET @Path("/ping") @Produces({ MediaType.APPLICATION_JSON }) public Response ping() { StatusBean status = new StatusBean(); status.setDate(new Date()); status.setMessage(LicenseManager.PING_MESSAGE); return Response.ok(status).build(); } /** * Request a new license file based in a RequestBean object sent as * parameter * * @param mpfdi * @param bsc * @return * @throws IOException * @throws SeCurisServiceException */ @POST @Path("/request") @Consumes(MediaType.APPLICATION_JSON) // TODO: Enable this: @Securable @Produces({ MediaType.APPLICATION_JSON }) @Transactional public Response createFromRequest(RequestBean request, @HeaderParam(LicenseManager.HEADER_LICENSE_NAME_OR_REFERENCE) String nameOrReference, @HeaderParam(LicenseManager.HEADER_LICENSE_EMAIL) String userEmail) throws IOException, SeCurisServiceException, SeCurisException { LOG.info("Request to get license: {}", request); SignedLicenseBean lic = createLicense(request, emProvider.get(), false, nameOrReference, userEmail); return Response.ok(lic).build(); } /** * Returns a License file in JSON format from an uploaded Request file * * @param mpfdi * @param bsc * @return * @throws IOException * @throws SeCurisServiceException * @throws SeCurisException */ @POST @Path("/request") @Consumes(MediaType.MULTIPART_FORM_DATA) @Securable @Produces({ MediaType.APPLICATION_JSON }) @Transactional @SuppressWarnings("unchecked") public Response createFromRequestFile(MultipartFormDataInput mpfdi, @HeaderParam(LicenseManager.HEADER_LICENSE_NAME_OR_REFERENCE) String nameOrReference, @HeaderParam(LicenseManager.HEADER_LICENSE_EMAIL) String userEmail) throws IOException, SeCurisServiceException, SeCurisException { RequestBean req = new RequestBean(); req.setPackCode(mpfdi.getFormDataPart("packCode", String.class, null)); req.setLicenseTypeCode(mpfdi.getFormDataPart("licenseTypeCode", String.class, null)); req.setCustomerCode(mpfdi.getFormDataPart("customerCode", String.class, null)); req.setArch(mpfdi.getFormDataPart("arch", String.class, null)); req.setCrcLogo(mpfdi.getFormDataPart("crcLogo", String.class, null)); req.setMacAddresses(mpfdi.getFormDataPart("macAddresses", List.class, null)); req.setOsName(mpfdi.getFormDataPart("osName", String.class, null)); return createFromRequest(req, nameOrReference, userEmail); } /** * Create a new License file based in a previous one * * @param request * @param bsc * @return * @throws IOException * @throws SeCurisServiceException * @throws SeCurisException */ @POST @Path("/renew") @Consumes(MediaType.APPLICATION_JSON) // TODO: Enable this: @Securable @Produces({ MediaType.APPLICATION_JSON }) @Transactional public Response renewFromPreviousLicense(LicenseBean previousLic, @Context BasicSecurityContext bsc) throws IOException, SeCurisServiceException, SeCurisException { LOG.info("Renew license: {}", previousLic); if (previousLic.getExpirationDate().after(DateUtils.addMonths(new Date(), 1))) { throw new SeCurisServiceException(ErrorCodes.LICENSE_NOT_READY_FOR_RENEW, "The license is still valid, not ready for renew"); } EntityManager em = emProvider.get(); License lic = License.findLicenseByCode(previousLic.getLicenseCode(), em); if (lic.getStatus() != LicenseStatus.ACTIVE) { throw new SeCurisServiceException(ErrorCodes.LICENSE_NOT_READY_FOR_RENEW, "Only licenses with status 'Active' can be renew"); } SignedLicenseBean signedLic = renewLicense(previousLic, em); return Response.ok(signedLic).build(); } /** * License validation on server side, in this case we validate that the * current licenses has not been cancelled. * * @param currentLic * @param bsc * @return * @throws IOException * @throws SeCurisServiceException * @throws SeCurisException */ @POST @Path("/validate") @Consumes(MediaType.APPLICATION_JSON) // TODO: Enable this: @Securable @Produces({ MediaType.APPLICATION_JSON }) @Transactional public Response validate(LicenseBean currentLic, @Context BasicSecurityContext bsc) throws IOException, SeCurisServiceException, SeCurisException { LOG.info("Validate license: {}", currentLic); if (currentLic.getExpirationDate().before(new Date())) { throw new SeCurisServiceException(ErrorCodes.LICENSE_IS_EXPIRED, "The license is expired"); } EntityManager em = emProvider.get(); try { SignatureHelper.getInstance().validateSignature(currentLic); } catch (SeCurisException ex) { throw new SeCurisServiceException(ErrorCodes.LICENSE_DATA_IS_NOT_VALID, "The license signature is not valid"); } licenseHelper.assertLicenseStatusIsActive(currentLic, em); return Response.ok(currentLic).build(); } /** * Returns a new License file in JSON format based in a previous license * * @param mpfdi * @param bsc * @return * @throws IOException * @throws SeCurisServiceException * @throws SeCurisException */ @POST @Path("/renew") @Consumes(MediaType.MULTIPART_FORM_DATA) @Securable @Produces({ MediaType.APPLICATION_JSON }) @Transactional @SuppressWarnings("unchecked") public Response renewFromLicenseFile(MultipartFormDataInput mpfdi, @Context BasicSecurityContext bsc) throws IOException, SeCurisServiceException, SeCurisException { LicenseBean lic = new LicenseBean(); lic.setAppName(mpfdi.getFormDataPart("appName", String.class, null)); lic.setArch(mpfdi.getFormDataPart("arch", String.class, null)); lic.setCrcLogo(mpfdi.getFormDataPart("crcLogo", String.class, null)); lic.setPackCode(mpfdi.getFormDataPart("packCode", String.class, null)); lic.setLicenseTypeCode(mpfdi.getFormDataPart("licenseCode", String.class, null)); lic.setCustomerCode(mpfdi.getFormDataPart("customerCode", String.class, null)); lic.setMacAddresses(mpfdi.getFormDataPart("macAddresses", List.class, null)); lic.setOsName(mpfdi.getFormDataPart("osName", String.class, null)); lic.setExpirationDate(mpfdi.getFormDataPart("expirationDate", Date.class, null)); LOG.info("Lic expires at: {}", lic.getExpirationDate()); if (lic.getExpirationDate().after(DateUtils.addMonths(new Date(), 1))) { throw new SeCurisServiceException(ErrorCodes.LICENSE_NOT_READY_FOR_RENEW, "The license is still valid, not ready for renew"); } return renewFromPreviousLicense(lic, bsc); } private SignedLicenseBean renewLicense(RequestBean req, EntityManager em) throws SeCurisServiceException { return createLicense(req, em, true, null, null); } /** * Creates a new signed license from request data or from previous license * if It's a renew * * @param req * @param em * @param renew * @return * @throws SeCurisServiceException */ private SignedLicenseBean createLicense(RequestBean req, EntityManager em, boolean renew, String nameOrReference, String email) throws SeCurisServiceException { LicenseBean previousLicenseBean = null; License lic = null; if (renew) { previousLicenseBean = (LicenseBean) req; lic = License.findLicenseByCode(previousLicenseBean.getLicenseCode(), em); if (lic.getStatus() != LicenseStatus.ACTIVE && lic.getStatus() != LicenseStatus.PRE_ACTIVE) { throw new SeCurisServiceException(ErrorCodes.INVALID_DATA, "The current license has been cancelled"); } } if (!renew) { try { lic = License.findValidLicenseByRequestData(JsonUtils.toJSON(req), em); } catch (SeCurisException e1) { throw new SeCurisServiceException(ErrorCodes.INVALID_FORMAT, "Request sent is not valid"); } if (lic != null) { try { if (lic.getStatus() == LicenseStatus.ACTIVE || lic.getStatus() == LicenseStatus.PRE_ACTIVE) { return JsonUtils.json2object(lic.getLicenseData(), SignedLicenseBean.class); } } catch (SeCurisException e) { throw new SeCurisServiceException(ErrorCodes.INVALID_FORMAT, "Error trying to get the license bean from license code: " + lic.getCode()); } } else { lic = new License(); } } Pack pack = em.createNamedQuery("pack-by-code", Pack.class).setParameter("code", req.getPackCode()).getSingleResult(); if (!renew && pack.getNumAvailables() <= 0) { throw new SeCurisServiceException(ErrorCodes.NO_AVAILABLE_LICENSES, "The current pack has no licenses availables"); } if (!renew && lic.getStatus() == LicenseStatus.REQUESTED && !pack.isLicensePreactivation()) { throw new SeCurisServiceException(ErrorCodes.NO_AVAILABLE_LICENSES, "Current pack doesn't allow license preactivation"); } SignedLicenseBean signedLicense; try { String licCode; if (renew || lic.getStatus() == LicenseStatus.REQUESTED) { licCode = lic.getCode(); } else { licCode = LicUtils.getLicenseCode(pack.getCode(), licenseHelper.getNextCodeSuffix(pack.getId(), em)); } Date expirationDate = licenseHelper.getExpirationDateFromPack(pack, !renew); LicenseBean lb = licenseGenerator.generateLicense(req, licenseHelper.extractPackMetadata(pack.getMetadata()), expirationDate, licCode, pack.getAppName()); signedLicense = new SignedLicenseBean(lb); } catch (SeCurisException e) { throw new SeCurisServiceException(ErrorCodes.INVALID_LICENSE_REQUEST_DATA, "Error generating license: " + e.toString()); } try { lic.setRequestData(JsonUtils.toJSON(signedLicense, RequestBean.class)); if (BlockedRequest.isRequestBlocked(lic.getRequestData(), em)) { throw new SeCurisServiceException(ErrorCodes.BLOCKED_REQUEST_DATA, "Given request data is blocked and cannot be activated"); } lic.setLicenseData(JsonUtils.toJSON(signedLicense)); } catch (SeCurisException e) { LOG.error("Error generating license JSON", e); throw new SeCurisServiceException(ErrorCodes.INVALID_FORMAT, "Error generating license JSON"); } lic.setModificationTimestamp(new Date()); lic.setExpirationDate(signedLicense.getExpirationDate()); User user = em.find(User.class, CLIENT_USERNAME); if (!renew && lic.getStatus() != LicenseStatus.REQUESTED) { lic.setPack(pack); lic.setCreatedBy(user); lic.setCreationTimestamp(new Date()); if (pack.isLicensePreactivation()) { lic.setStatus(LicenseStatus.PRE_ACTIVE); } else { lic.setStatus(LicenseStatus.REQUESTED); } lic.setCode(signedLicense.getLicenseCode()); lic.setCodeSuffix(LicUtils.getLicenseCodeSuffix(signedLicense.getLicenseCode())); lic.setEmail(email); lic.setFullName(nameOrReference); em.persist(lic); em.persist(licenseHelper.createLicenseHistoryAction(lic, user, LicenseHistory.Actions.CREATE)); if (pack.isLicensePreactivation()) { em.persist(licenseHelper.createLicenseHistoryAction(lic, user, LicenseHistory.Actions.PRE_ACTIVATE, "Pre-activated on creation")); } else { LOG.warn("License ({}) created, but the pack doesn't allow preactivation", lic.getCode()); throw new SeCurisServiceException(ErrorCodes.NO_AVAILABLE_LICENSES, "Current pack doesn't allow license preactivation"); } } else { lic.setStatus(renew ? LicenseStatus.ACTIVE : LicenseStatus.PRE_ACTIVE); em.merge(lic); if (renew) { em.persist(licenseHelper.createLicenseHistoryAction(lic, user, LicenseHistory.Actions.RENEW)); } else { em.persist(licenseHelper.createLicenseHistoryAction(lic, user, LicenseHistory.Actions.PRE_ACTIVATE, "Pre-activated after request")); } } return signedLicense; } }