diff --git a/pom.xml b/pom.xml index 10da69d61..c6b831cc7 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 br.com.rjconsultores ModelWeb - 1.21.0 + 1.22.0 @@ -203,6 +203,13 @@ 3.1.0 provided + + + com.nimbusds + nimbus-jose-jwt + jdk16 + 4.11.2 + diff --git a/src/com/rjconsultores/ventaboletos/dao/EmpresaDAO.java b/src/com/rjconsultores/ventaboletos/dao/EmpresaDAO.java index f81b99488..c088a3c8b 100644 --- a/src/com/rjconsultores/ventaboletos/dao/EmpresaDAO.java +++ b/src/com/rjconsultores/ventaboletos/dao/EmpresaDAO.java @@ -73,5 +73,7 @@ public interface EmpresaDAO { public void gerarSeqNumFolioSistema(Integer idEmpresa, String cveEstado) throws RuntimeException; public List buscarEmpresaPtoVtaComissao(); + + public boolean isPrimeiraVezLicenca(); } diff --git a/src/com/rjconsultores/ventaboletos/dao/hibernate/EmpresaHibernateDAO.java b/src/com/rjconsultores/ventaboletos/dao/hibernate/EmpresaHibernateDAO.java index fa24233f0..d9ebf341a 100644 --- a/src/com/rjconsultores/ventaboletos/dao/hibernate/EmpresaHibernateDAO.java +++ b/src/com/rjconsultores/ventaboletos/dao/hibernate/EmpresaHibernateDAO.java @@ -14,6 +14,7 @@ import java.util.List; import javax.sql.DataSource; import org.apache.commons.lang.StringUtils; +import org.apache.log4j.Logger; import org.hibernate.Criteria; import org.hibernate.Hibernate; import org.hibernate.Query; @@ -21,6 +22,7 @@ import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hibernate.criterion.MatchMode; import org.hibernate.criterion.Order; +import org.hibernate.criterion.Projections; import org.hibernate.criterion.Restrictions; import org.hibernate.type.BooleanType; import org.hibernate.type.IntegerType; @@ -50,6 +52,7 @@ public class EmpresaHibernateDAO extends GenericHibernateDAO i private static String FS_BPE = "FS_BPE_"; + private static Logger log = Logger.getLogger(EmpresaHibernateDAO.class); @Autowired private DataSource dataSource; @@ -106,6 +109,36 @@ public class EmpresaHibernateDAO extends GenericHibernateDAO i } + /** + * Indica se está no momento em que nenhuma empresa tem a licença. + * + * @return + */ + @Override + public boolean isPrimeiraVezLicenca(){ + + Criteria c = makeCriteria(); + c.setProjection(Projections.rowCount()); + c.add(Restrictions.eq("activo", Boolean.TRUE)); + c.add(Restrictions.ne("empresaId", -1)); + + Long cantEmpresasBaseDados = HibernateFix.count(c.list()); + + c = makeCriteria(); + c.setProjection(Projections.rowCount()); + c.add(Restrictions.eq("activo", Boolean.TRUE)); + c.add(Restrictions.ne("empresaId", -1)); + c.add(Restrictions.isNull("licenca")); + + Long cantEmpresasSemLicenca = HibernateFix.count(c.list()); + + return (cantEmpresasBaseDados.equals(cantEmpresasSemLicenca)); + } + + public List listarEmpresasSemLicenca(){ + + return null; + } @SuppressWarnings("unchecked") public List obtenerIndExternoFalse() { Criteria c = getSession().createCriteria(getPersistentClass()); diff --git a/src/com/rjconsultores/ventaboletos/entidad/Empresa.java b/src/com/rjconsultores/ventaboletos/entidad/Empresa.java index a9632ead3..841e3baaa 100644 --- a/src/com/rjconsultores/ventaboletos/entidad/Empresa.java +++ b/src/com/rjconsultores/ventaboletos/entidad/Empresa.java @@ -421,6 +421,9 @@ public class Empresa implements Serializable, Auditavel { @Column(name = "INDTAXACONVENIENCIASOVENDA") private Boolean indTaxaConvenienciaSoVenda; + @Column(name = "LICENCA") + private String licenca; + @Column(name = "HORAINICIOEMBARQUE") @Temporal(TemporalType.TIME) @AuditarAtributo(pattern = "HH:mm") @@ -434,6 +437,10 @@ public class Empresa implements Serializable, Auditavel { @Transient @NaoAuditar private Empresa empresaClone; + + @Transient + @NaoAuditar + private String token; public Empresa() { super(); @@ -1586,4 +1593,22 @@ public class Empresa implements Serializable, Auditavel { public void setHoraFimEmbarque(Date horaFimEmbarque) { this.horaFimEmbarque = horaFimEmbarque; } + + public String getToken() { + return token; + } + + public void setToken(String token) { + this.token = token; + } + + public String getLicenca() { + return licenca; + } + + public void setLicenca(String licenca) { + this.licenca = licenca; + } + + } diff --git a/src/com/rjconsultores/ventaboletos/entidad/Usuario.java b/src/com/rjconsultores/ventaboletos/entidad/Usuario.java index eabc61af5..c1b3310a1 100644 --- a/src/com/rjconsultores/ventaboletos/entidad/Usuario.java +++ b/src/com/rjconsultores/ventaboletos/entidad/Usuario.java @@ -30,15 +30,18 @@ import javax.persistence.Transient; import org.apache.commons.lang.BooleanUtils; import org.apache.commons.lang.StringUtils; +import org.apache.log4j.Logger; import org.hibernate.annotations.Fetch; import org.hibernate.annotations.FetchMode; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import com.rjconsultores.ventaboletos.service.ConstanteService; +import com.rjconsultores.ventaboletos.service.impl.EmpresaServiceImpl; import com.rjconsultores.ventaboletos.utilerias.ApplicationProperties; import com.rjconsultores.ventaboletos.utilerias.CustomEnum; import com.rjconsultores.ventaboletos.utilerias.DateUtil; +import com.rjconsultores.ventaboletos.web.utilerias.security.SecurityEmpresaToken; import com.rjconsultores.ventaboletos.web.utilerias.spring.AppContext; import br.com.rjconsultores.auditador.annotations.AuditarClasse; @@ -59,6 +62,7 @@ import br.com.rjconsultores.auditador.interfaces.Auditavel; public class Usuario implements Serializable, UserDetails, Auditavel { public final static int CANT_DIAS_CONTRASENA = 999; + private static Logger log = Logger.getLogger(Usuario.class); private static final long serialVersionUID = 1L; @AuditarID @@ -255,10 +259,25 @@ public class Usuario implements Serializable, UserDetails, Auditavel { public List getEmpresa() { List tmp = new ArrayList(); + + SecurityEmpresaToken security = new SecurityEmpresaToken(); + if (usuarioEmpresaList != null) { for (UsuarioEmpresa cp : this.usuarioEmpresaList) { if ((cp.getActivo())) { - tmp.add(cp.getEmpresa()); + Empresa empresa = cp.getEmpresa(); + boolean licenseValidate = false; + + try{ + licenseValidate = security.licenseValidate(empresa.getLicenca(), empresa.getEmpresaId(), empresa.getCnpj()); + }catch(Throwable th){ + log.error(String.format("Licença não validada para a empresaId: %s e cnpj: %s", empresa.getEmpresaId(),empresa.getCnpj()), th); + } + + + if (licenseValidate){ + tmp.add(cp.getEmpresa()); + } } } } diff --git a/src/com/rjconsultores/ventaboletos/service/EmpresaService.java b/src/com/rjconsultores/ventaboletos/service/EmpresaService.java index 5f9119b8d..8f89ebfe3 100644 --- a/src/com/rjconsultores/ventaboletos/service/EmpresaService.java +++ b/src/com/rjconsultores/ventaboletos/service/EmpresaService.java @@ -63,5 +63,18 @@ public interface EmpresaService { public ComEmpConferencia suscribirOrActualizacion(ComEmpConferencia comEmpConferencia); public List buscarEmpresaPtoVtaComissao(); + + /** + * Atualiza se necessário as licenças no primeiro acesso + * + * @return A quantidade de empresas atualizadas + */ + public Integer atualizarLicencaEmpresasPrimeiraVez(); + + public List filtrarApenasEmpresasLicencaValida(List lsEmpresa); + + public String token(Empresa empresa); + + public String validarTokenLicensa(Empresa empresa, String tokenLicenca); } diff --git a/src/com/rjconsultores/ventaboletos/service/impl/EmpresaServiceImpl.java b/src/com/rjconsultores/ventaboletos/service/impl/EmpresaServiceImpl.java index 352ed5e62..413220e8a 100644 --- a/src/com/rjconsultores/ventaboletos/service/impl/EmpresaServiceImpl.java +++ b/src/com/rjconsultores/ventaboletos/service/impl/EmpresaServiceImpl.java @@ -4,6 +4,7 @@ */ package com.rjconsultores.ventaboletos.service.impl; +import java.util.ArrayList; import java.util.Calendar; import java.util.List; @@ -29,6 +30,7 @@ import com.rjconsultores.ventaboletos.service.LogAuditoriaService; import com.rjconsultores.ventaboletos.service.MarcaService; import com.rjconsultores.ventaboletos.utilerias.RegistroConDependenciaException; import com.rjconsultores.ventaboletos.utilerias.UsuarioLogado; +import com.rjconsultores.ventaboletos.web.utilerias.security.SecurityEmpresaToken; /** * @@ -60,6 +62,8 @@ public class EmpresaServiceImpl implements EmpresaService { public Empresa obtenerID(Integer id) { Empresa empresa = empresaDAO.obtenerID(id); + empresa.setToken(this.token(empresa)); + try { empresa.clonar(); } catch (Exception e) { @@ -85,6 +89,7 @@ public class EmpresaServiceImpl implements EmpresaService { entidad = empresaDAO.suscribir(entidad); logAuditoriaService.auditar(null, entidad, null); + entidad.setToken(this.token(entidad)); gerarMarca(entidad); @@ -138,7 +143,7 @@ public class EmpresaServiceImpl implements EmpresaService { } public List buscarTodosExceto(List empresa, Integer... idEmpresa) { - return empresaDAO.buscarTodosExceto(empresa, idEmpresa); + return this.filtrarApenasEmpresasLicencaValida(empresaDAO.buscarTodosExceto(empresa, idEmpresa)); } public List obtenerIndExternoFalse() { @@ -170,9 +175,31 @@ public class EmpresaServiceImpl implements EmpresaService { empresaDAO.actualizaInscEstadual(inscricaoEstadual); } + @Override + public List filtrarApenasEmpresasLicencaValida(List lsEmpresa){ + SecurityEmpresaToken security = new SecurityEmpresaToken(); + List lsRetorno = new ArrayList<>(); + + if (lsEmpresa == null || lsEmpresa.isEmpty()){ + return lsEmpresa; + } + + for(Empresa empresa:lsEmpresa){ + boolean licenseValidate = security.licenseValidate(empresa.getLicenca(), empresa.getEmpresaId(), empresa.getCnpj()); + if (!licenseValidate){ + log.info(String.format("Empresa sem licença válida", empresa.getEmpresaId())); + + continue; + } + lsRetorno.add(empresa); + } + return lsRetorno; + } @Override public List buscaLike(String nombempresa) { - return empresaDAO.buscaLike(nombempresa); + List lsEmpresa = empresaDAO.buscaLike(nombempresa); + + return this.filtrarApenasEmpresasLicencaValida(lsEmpresa); } @Override @@ -240,9 +267,65 @@ public class EmpresaServiceImpl implements EmpresaService { return empresaDAO.actualizacion(comEmpConferencia); } } + + @Override + public String validarTokenLicensa(Empresa empresa,String tokenLicenca){ + if (tokenLicenca == null){ + return null; + } + try{ + SecurityEmpresaToken security = new SecurityEmpresaToken(); + + final String license = security.tokenValidate(tokenLicenca); + + return license; + }catch(Throwable th){ + log.error("Erro ao validar token",th); + } + + return null; + } + + @Override + public String token(Empresa empresa){ + + SecurityEmpresaToken security = new SecurityEmpresaToken(); + final String bodyRequest = security.bodyRequestGenerate(empresa.getEmpresaId(), empresa.getCnpj()); + final String request = security.requestGenerate(bodyRequest); + + return request; + } @Override public List buscarEmpresaPtoVtaComissao() { return empresaDAO.buscarEmpresaPtoVtaComissao(); } + + @Override + @Transactional + public Integer atualizarLicencaEmpresasPrimeiraVez(){ + boolean primeiraVezLicenca = empresaDAO.isPrimeiraVezLicenca(); + int cantEmpresasAtualizadas = 0; + + log.info(String.format("primeiraVezLicenca: %s", primeiraVezLicenca)); + + if (primeiraVezLicenca){ + SecurityEmpresaToken security = new SecurityEmpresaToken(); + List lsEmpresas = empresaDAO.obtenerTodos(); + + for(Empresa empresa:lsEmpresas){ + String licenseDefaultGenerate = security.licenseDefaultGenerate(empresa.getEmpresaId(), empresa.getCnpj()); + + log.info(String.format("licenseDefaultGenerate: %s", licenseDefaultGenerate)); + + empresa.setLicenca(licenseDefaultGenerate); + + empresaDAO.actualizacion(empresa); + cantEmpresasAtualizadas++; + } + + } + + return cantEmpresasAtualizadas; + } } diff --git a/src/com/rjconsultores/ventaboletos/web/utilerias/security/AESGSMHelper.java b/src/com/rjconsultores/ventaboletos/web/utilerias/security/AESGSMHelper.java new file mode 100644 index 000000000..bc7f43653 --- /dev/null +++ b/src/com/rjconsultores/ventaboletos/web/utilerias/security/AESGSMHelper.java @@ -0,0 +1,106 @@ +package com.rjconsultores.ventaboletos.web.utilerias.security; + +import java.nio.ByteBuffer; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.KeySpec; + +import javax.crypto.Cipher; +import javax.crypto.SecretKey; +import javax.crypto.SecretKeyFactory; +import javax.crypto.spec.GCMParameterSpec; +import javax.crypto.spec.PBEKeySpec; +import javax.crypto.spec.SecretKeySpec; + +import org.apache.commons.codec.DecoderException; +import org.apache.commons.codec.binary.Hex; + +public class AESGSMHelper { + private final String SECRET_KEY = "RJ@2019#c0n5ul10r35"; + private final String SALT = "HrqoFr44GtkAhhYN+jP8Ag=="; + private final String ENCRYPT_ALGO = "AES/GCM/NoPadding"; + private final int TAG_LENGTH_BIT = 128; + private final int IV_LENGTH_BYTE = 12; + + private final Charset UTF_8 = StandardCharsets.UTF_8; + + public String encrypt(String value) throws Exception { + SecretKey secret = getAESKeyFromPassword(SECRET_KEY.toCharArray()); + + byte[] pText = value.getBytes(StandardCharsets.UTF_8); + byte[] iv = getRandomNonce(12); + byte[] cipherText = encrypt(pText, secret, iv); + + byte[] cipherTextWithIv = ByteBuffer.allocate(iv.length + cipherText.length) + .put(iv) + .put(cipherText) + .array(); + + return hex(cipherTextWithIv); + } + + private byte[] encrypt(byte[] pText, SecretKey secret, byte[] iv) throws Exception { + Cipher cipher = Cipher.getInstance(ENCRYPT_ALGO); + cipher.init(Cipher.ENCRYPT_MODE, secret, new GCMParameterSpec(TAG_LENGTH_BIT, iv)); + + byte[] encryptedText = cipher.doFinal(pText); + + return encryptedText; + + } + + public String decrypt(String value) throws Exception { + SecretKey secret = getAESKeyFromPassword(SECRET_KEY.toCharArray()); + + byte[] cText = unhex(value); + + ByteBuffer bb = ByteBuffer.wrap(cText); + + byte[] iv = new byte[IV_LENGTH_BYTE]; + bb.get(iv); + + byte[] cipherText = new byte[bb.remaining()]; + bb.get(cipherText); + + String plainText = decrypt(cipherText, secret, iv); + + return plainText; + } + + private String decrypt(byte[] cText, SecretKey secret, byte[] iv) throws Exception { + Cipher cipher = Cipher.getInstance(ENCRYPT_ALGO); + cipher.init(Cipher.DECRYPT_MODE, secret, new GCMParameterSpec(TAG_LENGTH_BIT, iv)); + + byte[] plainText = cipher.doFinal(cText); + + return new String(plainText, UTF_8); + } + + private byte[] getRandomNonce(int numBytes) { + byte[] nonce = new byte[numBytes]; + new SecureRandom().nextBytes(nonce); + + return nonce; + } + + private SecretKey getAESKeyFromPassword(char[] password) throws NoSuchAlgorithmException, InvalidKeySpecException { + SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256"); + + KeySpec spec = new PBEKeySpec(password, SALT.getBytes(), 65536, 256); + SecretKeySpec secret = new SecretKeySpec(factory.generateSecret(spec).getEncoded(), "AES"); + + return secret; + } + + private String hex(byte[] bytes) { + char[] result = Hex.encodeHex(bytes); + return new String(result); + } + + private byte[] unhex(String hex) throws DecoderException { + return Hex.decodeHex(hex.toCharArray()); + } +} diff --git a/src/com/rjconsultores/ventaboletos/web/utilerias/security/SecurityEmpresaToken.java b/src/com/rjconsultores/ventaboletos/web/utilerias/security/SecurityEmpresaToken.java new file mode 100644 index 000000000..738bcacef --- /dev/null +++ b/src/com/rjconsultores/ventaboletos/web/utilerias/security/SecurityEmpresaToken.java @@ -0,0 +1,145 @@ +package com.rjconsultores.ventaboletos.web.utilerias.security; + +import java.text.ParseException; +import java.time.Duration; +import java.util.Calendar; + +import javax.xml.bind.DatatypeConverter; + +import org.apache.commons.lang.StringUtils; +import org.apache.log4j.Logger; + +import com.google.gson.Gson; +import com.google.gson.JsonObject; +import com.nimbusds.jose.JWSAlgorithm; +import com.nimbusds.jose.JWSHeader; +import com.nimbusds.jose.JWSObject; +import com.nimbusds.jose.Payload; +import com.nimbusds.jose.crypto.MACSigner; +import com.nimbusds.jwt.JWTClaimsSet; + +import net.minidev.json.JSONObject; + +public class SecurityEmpresaToken { + private static Logger log = Logger.getLogger(SecurityEmpresaToken.class); + + private String secret = "#KO&Fm4_k.sU9M8`6Mx'F\\\"H:*Qxu]6F4r,)JmZ2Jwafd)I.2[RET'1:)VQ6mG9,"; + private static final Duration ttl = Duration.ofDays(7); + + private Gson gson = new Gson(); + + public String bodyRequestGenerate(final Integer empresaId, final String cnpj) throws SecurityException { + try { + AESGSMHelper crypto = new AESGSMHelper(); + + JsonObject json = new JsonObject(); + json.addProperty("empresaId", empresaId); + json.addProperty("CNPJ", cnpj); + + return crypto.encrypt(json.toString()); + + } catch (Exception e) { + log.error("Erro ao gerar o body usado no request da licença: " + e.getMessage(), e); + + throw new SecurityException(e); + } + } + + public String licenseDefaultGenerate(final Integer empresaId, final String cnpj) throws SecurityException { + try { + AESGSMHelper crypto = new AESGSMHelper(); + + JsonObject json = new JsonObject(); + json.addProperty("empresaId", empresaId); + json.addProperty("CNPJ", cnpj); + json.addProperty("aprovado", 1); + + return crypto.encrypt(json.toString()); + + } catch (Exception e) { + log.error("Erro ao gerar a licença padrão para as empresas existentes: " + e.getMessage(), e); + + throw new SecurityException(e); + } + } + + public boolean licenseValidate(final String license, final Integer empresaId, final String cnpj) { + try { + if (StringUtils.isBlank(license)){ + return false; + } + + AESGSMHelper crypto = new AESGSMHelper(); + + final String value = crypto.decrypt(license); + final JsonObject json = gson.fromJson(value, JsonObject.class); + + if (json.has("empresaId") && json.get("empresaId").getAsInt() == empresaId.intValue() + && json.has("CNPJ") && json.get("CNPJ").getAsString().equals(cnpj) + && json.has("aprovado")) { + log.debug("[empresaId=" + json.get("empresaId").getAsString() + ", CNPJ=" + json.get("CNPJ").getAsString() + ", aprovado=" + json.get("aprovado").getAsString() + "]"); + + return json.get("aprovado").getAsString().equals("1"); + } + } catch (Exception e) { + log.error("Erro ao gerar o body usado no request da licença: " + e.getMessage(), e); + } + + return false; + } + + public String requestGenerate(String licenseRequest) throws SecurityException { + return requestGenerate(licenseRequest, ttl); + } + + public String requestGenerate(String licenseRequest, Duration ttl) throws SecurityException { + try { + Calendar cal = Calendar.getInstance(); + cal.add(Calendar.MILLISECOND, (int) ttl.toMillis()); + + JWTClaimsSet claims = new JWTClaimsSet.Builder() + .expirationTime(cal.getTime()) + .claim("sub", licenseRequest) + .claim("userId", "adm") + .claim("role", "ROLE_TOKEN") + .build(); + + JWSObject jwsObject = new JWSObject(new JWSHeader(JWSAlgorithm.HS256), new Payload(claims.toJSONObject())); + + jwsObject.sign(new MACSigner(DatatypeConverter.parseBase64Binary(secret))); + + return jwsObject.serialize(); + } catch (Exception e) { + log.error("Erro ao gerar a request: " + e.getMessage(), e); + + throw new SecurityException(e); + } + } + + public String tokenValidate(final String token) throws SecurityException { + try { + JWSObject jwsObject = JWSObject.parse(token); + JSONObject jsonPayload = jwsObject.getPayload().toJSONObject(); + JWTClaimsSet claims = JWTClaimsSet.parse(jsonPayload); + + if (claims.getExpirationTime().compareTo(Calendar.getInstance().getTime()) < 0) { + throw new SecurityException("Token expirado"); + } + + return claims.getSubject(); + + } catch (SecurityException e) { + throw e; + + } catch (ParseException e) { + log.error("Erro no parser do token: " + e.getMessage(), e); + + throw new SecurityException(e); + + } catch (Exception e) { + log.error("Erro ao validar o token: " + e.getMessage(), e); + + throw new SecurityException(e); + } + } +}