package net.curisit.securis.services; import java.io.File; import java.io.IOException; import java.util.Date; import java.util.List; import java.util.Map; import java.util.TreeMap; import javax.inject.Inject; import javax.inject.Provider; import javax.persistence.EntityManager; import javax.persistence.TypedQuery; import javax.ws.rs.Consumes; import javax.ws.rs.DELETE; import javax.ws.rs.GET; import javax.ws.rs.POST; import javax.ws.rs.PUT; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status; import net.curisit.integrity.commons.JsonUtils; import net.curisit.integrity.commons.Utils; import net.curisit.securis.DefaultExceptionHandler; import net.curisit.securis.LicenseGenerator; import net.curisit.securis.ReqGenerator; import net.curisit.securis.SeCurisException; import net.curisit.securis.beans.LicenseBean; import net.curisit.securis.beans.RequestBean; import net.curisit.securis.db.License; import net.curisit.securis.db.LicenseHistory; import net.curisit.securis.db.LicenseType; 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.utils.TokenHelper; import org.apache.commons.io.IOUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.bouncycastle.jce.provider.asymmetric.ec.ECUtil; import org.jboss.resteasy.plugins.providers.multipart.MultipartFormDataInput; import com.google.inject.persist.Transactional; /** * License resource, this service will provide methods to create, modify and * delete licenses * * @author roberto */ @Path("/license") public class LicenseResource { private static final Logger LOG = LogManager.getLogger(LicenseResource.class); @Inject TokenHelper tokenHelper; @Inject Provider emProvider; @Inject LicenseGenerator licenseGenerator; /** * * @return the server version in format majorVersion.minorVersion */ @GET @Path("/") @Securable @Produces({ MediaType.APPLICATION_JSON }) public Response index(@QueryParam("packId") Integer packId, @Context BasicSecurityContext bsc) { LOG.info("Getting licenses list "); EntityManager em = emProvider.get(); if (!bsc.isUserInRole(BasicSecurityContext.ROL_ADMIN)) { Pack pack = em.find(Pack.class, packId); if (pack == null) { return Response.ok().build(); } if (!bsc.getOrganizationsIds().contains(pack.getOrganization().getId())) { LOG.error("Pack with id {} not accesible by user {}", pack, bsc.getUserPrincipal()); return Response.status(Status.UNAUTHORIZED) .header(DefaultExceptionHandler.ERROR_MESSAGE_HEADER, "Unathorized access to pack licenses").build(); } } TypedQuery q = em.createNamedQuery("list-licenses-by-pack", License.class); q.setParameter("packId", packId); List list = q.getResultList(); return Response.ok(list).build(); } /** * * @return the server version in format majorVersion.minorVersion * @throws SeCurisServiceException */ @GET @Path("/{licId}") @Securable @Produces({ MediaType.APPLICATION_JSON }) public Response get(@PathParam("licId") Integer licId, @Context BasicSecurityContext bsc) throws SeCurisServiceException { LOG.info("Getting organization data for id: {}: ", licId); EntityManager em = emProvider.get(); License lic = getCurrentLicense(licId, bsc, em); return Response.ok(lic).build(); } /** * * @return The license file, only of license is active * @throws SeCurisServiceException */ @GET @Path("/{licId}/download") @Securable @Produces({ MediaType.APPLICATION_OCTET_STREAM }) public Response download(@PathParam("licId") Integer licId, @Context BasicSecurityContext bsc) throws SeCurisServiceException { EntityManager em = emProvider.get(); License lic = getCurrentLicense(licId, bsc, em); if (lic.getLicenseData() == null) { LOG.error("License with id {} has not license file generated", licId, bsc.getUserPrincipal()); throw new SeCurisServiceException(Status.FORBIDDEN.getStatusCode(), "License has not contain data to generate license file"); } if (License.Status.isActionValid(License.Action.DOWNLOAD, lic.getStatus())) { LOG.error("License with id {} is not active, so It can not downloaded", licId, bsc.getUserPrincipal()); throw new SeCurisServiceException(Status.FORBIDDEN.getStatusCode(), "License is not active, so It can not be downloaded"); } return Response.ok(lic.getLicenseData()).build(); } /** * Activate the given license * @param licId * @param bsc * @return * @throws SeCurisServiceException */ @PUT @POST @Path("/{licId}/activate") @Securable @Transactional @Consumes(MediaType.APPLICATION_JSON) @Produces({ MediaType.APPLICATION_JSON }) public Response activate(@PathParam("licId") Integer licId, @Context BasicSecurityContext bsc) throws SeCurisServiceException { EntityManager em = emProvider.get(); License lic = getCurrentLicense(licId, bsc, em); if (License.Status.isActionValid(License.Action.ACTIVATION, lic.getStatus())) { LOG.error("License with id {} can not be activated from current license status", licId); throw new SeCurisServiceException(Status.FORBIDDEN.getStatusCode(), "License with id " + licId + " can not be activated from the current license status"); } lic.setStatus(License.Status.ACTIVE); lic.setModificationTimestamp(new Date()); em.persist(lic); User user = getUser(bsc.getUserPrincipal().getName(), em); em.persist(createLicenseHistoryAction(lic, user, LicenseHistory.Actions.ACTIVATE)); return Response.ok(lic).build(); } /** * Send license file by email to the organization * @param licId * @param bsc * @return * @throws SeCurisServiceException */ @PUT @POST @Path("/{licId}/send") @Securable @Transactional @Consumes(MediaType.APPLICATION_JSON) @Produces({ MediaType.APPLICATION_JSON }) public Response send(@PathParam("licId") Integer licId, @Context BasicSecurityContext bsc) throws SeCurisServiceException { EntityManager em = emProvider.get(); License lic = getCurrentLicense(licId, bsc, em); User user = getUser(bsc.getUserPrincipal().getName(), em); // TODO: Send mail with lic file lic.setModificationTimestamp(new Date()); em.persist(lic); em.persist(createLicenseHistoryAction(lic, user, LicenseHistory.Actions.SEND, "Email sent to: " + lic.getEmail())); return Response.ok(lic).build(); } /** * Cancel given license * @param licId * @param bsc * @return * @throws SeCurisServiceException */ @PUT @POST @Path("/{licId}/cancel") @Securable @Transactional @Consumes(MediaType.APPLICATION_JSON) @Produces({ MediaType.APPLICATION_JSON }) public Response cancel(@PathParam("licId") Integer licId, @Context BasicSecurityContext bsc) throws SeCurisServiceException { EntityManager em = emProvider.get(); License lic = getCurrentLicense(licId, bsc, em); if (License.Status.isActionValid(License.Action.CANCEL, lic.getStatus())) { LOG.error("License with id {} can not be canceled from current license status", licId); throw new SeCurisServiceException(Status.FORBIDDEN.getStatusCode(), "License with id " + licId + " can not be canceled from the current license status"); } lic.setStatus(License.Status.CANCELED); lic.setModificationTimestamp(new Date()); em.persist(lic); User user = getUser(bsc.getUserPrincipal().getName(), em); em.persist(createLicenseHistoryAction(lic, user, LicenseHistory.Actions.CANCEL)); return Response.ok(lic).build(); } @POST @Path("/") @Consumes(MediaType.APPLICATION_JSON) @Securable @Produces({ MediaType.APPLICATION_JSON }) @Transactional public Response create(License lic, @Context BasicSecurityContext bsc) throws SeCurisServiceException { LOG.info("Creating new license from create()"); EntityManager em = emProvider.get(); Pack pack = null; if (lic.getPackId() != null) { pack = em.find(Pack.class, lic.getPackId()); if (pack == null) { LOG.error("License pack with id {} not found in DB", lic.getPackId()); return Response.status(Status.NOT_FOUND) .header(DefaultExceptionHandler.ERROR_MESSAGE_HEADER, "License's pack not found with ID: " + lic.getPackId()).build(); } else { if (!bsc.isUserInRole(BasicSecurityContext.ROL_ADMIN) && !bsc.getOrganizationsIds().contains(pack.getOrganization().getId())) { LOG.error("License for pack with id {} can not be created by user {}", pack.getId(), bsc.getUserPrincipal()); return Response.status(Status.UNAUTHORIZED) .header(DefaultExceptionHandler.ERROR_MESSAGE_HEADER, "Unathorized action on pack license").build(); } } } User createdBy = getUser(bsc.getUserPrincipal().getName(), em); // TODO: Manage status if request data is set lic.setCreatedBy(createdBy); lic.setStatus(License.Status.CREATED); lic.setCreationTimestamp(new Date()); lic.setModificationTimestamp(lic.getCreationTimestamp()); em.persist(lic); em.persist(createLicenseHistoryAction(lic, createdBy, LicenseHistory.Actions.CREATE)); return Response.ok(lic).build(); } /** * Extract the corresponding metadata for the Request license given * @param req * @return * @throws SeCurisException */ @SuppressWarnings("unchecked") private Map getLicenseMetadata(RequestBean req) throws SeCurisException { // TODO: The dummy metadata file is temporal, this info should be read from DB File dummyMetadata = new File(System.getProperty("user.home") + File.separator + ".SeCuris" + File.separator + "dummy_metadata.json"); Map metadata = null; try { String metadataJson = IOUtils.toString(dummyMetadata.toURI()); metadata = new TreeMap<>((Map)JsonUtils.json2map(metadataJson).get(req.getAppCode())); } catch (IOException e) { LOG.error("Error reading dummy metadata file", e); throw new SeCurisException("Error reading dummy metadata file"); } return metadata; } private License getLicenseData(RequestBean req) throws SeCurisException { // TODO: The dummy expiration date is temporal, this info should be read from DB License lic = new License(); lic.setExpirationDate(new Date(new Date().getTime() + (1000L * 3600 * 24 * 365 * 10))); lic.setCode(req.getAppCode() + "-LIC-INTERNAL"); LicenseType lt = new LicenseType(); lt.setCode("TYPE-" + req.getAppCode()); Pack pack = new Pack(); pack.setLicenseType(lt); lic.setPack(pack); return lic; } /** * 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, @Context BasicSecurityContext bsc) throws IOException, SeCurisServiceException, SeCurisException { LOG.info("Request to get license: {}", request); Map metadata = getLicenseMetadata(request); License licDB = getLicenseData(request); Date expirationDate = licDB.getExpirationDate(); String licenseTypeCode = licDB.getPack().getLicenseType().getCode(); String licenseCode = licDB.getCode(); LicenseBean lic = licenseGenerator.generateLicense(request, metadata, expirationDate, licenseTypeCode, licenseCode); return Response.ok(lic).build(); } @POST @Path("/request") @Consumes(MediaType.MULTIPART_FORM_DATA) @Securable @Produces({ MediaType.APPLICATION_JSON }) @Transactional @SuppressWarnings("unchecked") public Response createFromRequestFile(MultipartFormDataInput mpfdi, @Context BasicSecurityContext bsc) throws IOException, SeCurisServiceException, SeCurisException { RequestBean req = new RequestBean(); req.setAppCode(mpfdi.getFormDataPart("appCode", String.class, null)); req.setArch(mpfdi.getFormDataPart("arch", String.class, null)); req.setCrcLogo(mpfdi.getFormDataPart("crcLogo", String.class, null)); req.setCustomerCode(mpfdi.getFormDataPart("customerCode", String.class, null)); req.setMacAddresses(mpfdi.getFormDataPart("macAddresses", List.class, null)); req.setOsName(mpfdi.getFormDataPart("osName", String.class, null)); return createFromRequest(req, bsc); } @PUT @POST @Path("/{licId}") @Securable @Transactional @Consumes(MediaType.APPLICATION_JSON) @Produces({ MediaType.APPLICATION_JSON }) public Response modify(License lic, @PathParam("licId") Integer licId, @Context BasicSecurityContext bsc) throws SeCurisServiceException { LOG.info("Modifying organization with id: {}", licId); EntityManager em = emProvider.get(); License currentLicense = getCurrentLicense(licId, bsc, em); currentLicense.setCode(lic.getCode()); currentLicense.setFullName(lic.getFullName()); currentLicense.setEmail(lic.getEmail()); currentLicense.setRequestData(lic.getRequestData()); currentLicense.setModificationTimestamp(new Date()); em.persist(currentLicense); return Response.ok(currentLicense).build(); } @DELETE @Path("/{licId}") @Transactional @Securable @Produces({ MediaType.APPLICATION_JSON }) public Response delete(@PathParam("licId") Integer licId, @Context BasicSecurityContext bsc) throws SeCurisServiceException { LOG.info("Deleting license with id: {}", licId); EntityManager em = emProvider.get(); License lic = getCurrentLicense(licId, bsc, em); if (lic.getStatus() != License.Status.CANCELED || lic.getStatus() != License.Status.CREATED) { LOG.error("License {} can not be deleted with status {}", lic.getCode(), lic.getStatus()); return Response.status(Status.FORBIDDEN) .header(DefaultExceptionHandler.ERROR_MESSAGE_HEADER, "License can not be deleted in current status").build(); } em.remove(lic); return Response.ok(Utils.createMap("success", true, "id", licId)).build(); } private License getCurrentLicense(Integer licId, BasicSecurityContext bsc, EntityManager em) throws SeCurisServiceException { if (licId == null || "".equals(licId)) { LOG.error("License ID is mandatory"); throw new SeCurisServiceException(Status.NOT_FOUND.getStatusCode(), "Missing license ID"); } License lic = em.find(License.class, licId); if (lic == null) { LOG.error("License with id {} not found in DB", licId); throw new SeCurisServiceException(Status.NOT_FOUND.getStatusCode(), "License not found for ID: " + licId); } if (!bsc.isUserInRole(BasicSecurityContext.ROL_ADMIN) && !bsc.getOrganizationsIds().contains(lic.getPack().getOrganization().getId())) { LOG.error("License with id {} is not accesible by user {}", licId, bsc.getUserPrincipal()); throw new SeCurisServiceException(Status.UNAUTHORIZED.getStatusCode(), "Unathorized access to license data"); } return lic; } private User getUser(String username, EntityManager em) throws SeCurisServiceException { User user = null; if (username != null) { user = em.find(User.class, username); if (user == null) { throw new SeCurisServiceException(Status.NOT_FOUND.getStatusCode(), "User not found with username: " + username); } } return user; } private LicenseHistory createLicenseHistoryAction(License lic, User user, String action, String comments) { LicenseHistory lh = new LicenseHistory(); lh.setLicense(lic); lh.setUser(user); lh.setTimestamp(new Date()); lh.setAction(action); lh.setComments(comments); return lh; } private LicenseHistory createLicenseHistoryAction(License lic, User user, String action) { return createLicenseHistoryAction(lic, user, action, null); } }