Merge pull request 'security' (!114) from security into master

Reviewed-on: adm/ModelWeb#114
master
gleimar 2023-11-16 13:33:53 +00:00
commit cc5b412f2d
9 changed files with 437 additions and 4 deletions

View File

@ -3,7 +3,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<groupId>br.com.rjconsultores</groupId> <groupId>br.com.rjconsultores</groupId>
<artifactId>ModelWeb</artifactId> <artifactId>ModelWeb</artifactId>
<version>1.21.0</version> <version>1.22.0</version>
<distributionManagement> <distributionManagement>
<repository> <repository>
@ -203,6 +203,13 @@
<version>3.1.0</version> <version>3.1.0</version>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
<dependency>
<groupId>com.nimbusds</groupId>
<artifactId>nimbus-jose-jwt</artifactId>
<classifier>jdk16</classifier>
<version>4.11.2</version>
</dependency>
</dependencies> </dependencies>
<dependencyManagement> <dependencyManagement>

View File

@ -73,5 +73,7 @@ public interface EmpresaDAO {
public void gerarSeqNumFolioSistema(Integer idEmpresa, String cveEstado) throws RuntimeException; public void gerarSeqNumFolioSistema(Integer idEmpresa, String cveEstado) throws RuntimeException;
public List<Empresa> buscarEmpresaPtoVtaComissao(); public List<Empresa> buscarEmpresaPtoVtaComissao();
public boolean isPrimeiraVezLicenca();
} }

View File

@ -14,6 +14,7 @@ import java.util.List;
import javax.sql.DataSource; import javax.sql.DataSource;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.hibernate.Criteria; import org.hibernate.Criteria;
import org.hibernate.Hibernate; import org.hibernate.Hibernate;
import org.hibernate.Query; import org.hibernate.Query;
@ -21,6 +22,7 @@ import org.hibernate.Session;
import org.hibernate.SessionFactory; import org.hibernate.SessionFactory;
import org.hibernate.criterion.MatchMode; import org.hibernate.criterion.MatchMode;
import org.hibernate.criterion.Order; import org.hibernate.criterion.Order;
import org.hibernate.criterion.Projections;
import org.hibernate.criterion.Restrictions; import org.hibernate.criterion.Restrictions;
import org.hibernate.type.BooleanType; import org.hibernate.type.BooleanType;
import org.hibernate.type.IntegerType; import org.hibernate.type.IntegerType;
@ -50,6 +52,7 @@ public class EmpresaHibernateDAO extends GenericHibernateDAO<Empresa, Integer> i
private static String FS_BPE = "FS_BPE_"; private static String FS_BPE = "FS_BPE_";
private static Logger log = Logger.getLogger(EmpresaHibernateDAO.class);
@Autowired @Autowired
private DataSource dataSource; private DataSource dataSource;
@ -106,6 +109,36 @@ public class EmpresaHibernateDAO extends GenericHibernateDAO<Empresa, Integer> 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<Empresa> listarEmpresasSemLicenca(){
return null;
}
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public List<Empresa> obtenerIndExternoFalse() { public List<Empresa> obtenerIndExternoFalse() {
Criteria c = getSession().createCriteria(getPersistentClass()); Criteria c = getSession().createCriteria(getPersistentClass());

View File

@ -421,6 +421,9 @@ public class Empresa implements Serializable, Auditavel<Empresa> {
@Column(name = "INDTAXACONVENIENCIASOVENDA") @Column(name = "INDTAXACONVENIENCIASOVENDA")
private Boolean indTaxaConvenienciaSoVenda; private Boolean indTaxaConvenienciaSoVenda;
@Column(name = "LICENCA")
private String licenca;
@Column(name = "HORAINICIOEMBARQUE") @Column(name = "HORAINICIOEMBARQUE")
@Temporal(TemporalType.TIME) @Temporal(TemporalType.TIME)
@AuditarAtributo(pattern = "HH:mm") @AuditarAtributo(pattern = "HH:mm")
@ -434,6 +437,10 @@ public class Empresa implements Serializable, Auditavel<Empresa> {
@Transient @Transient
@NaoAuditar @NaoAuditar
private Empresa empresaClone; private Empresa empresaClone;
@Transient
@NaoAuditar
private String token;
public Empresa() { public Empresa() {
super(); super();
@ -1586,4 +1593,22 @@ public class Empresa implements Serializable, Auditavel<Empresa> {
public void setHoraFimEmbarque(Date horaFimEmbarque) { public void setHoraFimEmbarque(Date horaFimEmbarque) {
this.horaFimEmbarque = 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;
}
} }

View File

@ -30,15 +30,18 @@ import javax.persistence.Transient;
import org.apache.commons.lang.BooleanUtils; import org.apache.commons.lang.BooleanUtils;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.hibernate.annotations.Fetch; import org.hibernate.annotations.Fetch;
import org.hibernate.annotations.FetchMode; import org.hibernate.annotations.FetchMode;
import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetails;
import com.rjconsultores.ventaboletos.service.ConstanteService; import com.rjconsultores.ventaboletos.service.ConstanteService;
import com.rjconsultores.ventaboletos.service.impl.EmpresaServiceImpl;
import com.rjconsultores.ventaboletos.utilerias.ApplicationProperties; import com.rjconsultores.ventaboletos.utilerias.ApplicationProperties;
import com.rjconsultores.ventaboletos.utilerias.CustomEnum; import com.rjconsultores.ventaboletos.utilerias.CustomEnum;
import com.rjconsultores.ventaboletos.utilerias.DateUtil; import com.rjconsultores.ventaboletos.utilerias.DateUtil;
import com.rjconsultores.ventaboletos.web.utilerias.security.SecurityEmpresaToken;
import com.rjconsultores.ventaboletos.web.utilerias.spring.AppContext; import com.rjconsultores.ventaboletos.web.utilerias.spring.AppContext;
import br.com.rjconsultores.auditador.annotations.AuditarClasse; 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<Usuario> { public class Usuario implements Serializable, UserDetails, Auditavel<Usuario> {
public final static int CANT_DIAS_CONTRASENA = 999; public final static int CANT_DIAS_CONTRASENA = 999;
private static Logger log = Logger.getLogger(Usuario.class);
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
@AuditarID @AuditarID
@ -255,10 +259,25 @@ public class Usuario implements Serializable, UserDetails, Auditavel<Usuario> {
public List<Empresa> getEmpresa() { public List<Empresa> getEmpresa() {
List<Empresa> tmp = new ArrayList<Empresa>(); List<Empresa> tmp = new ArrayList<Empresa>();
SecurityEmpresaToken security = new SecurityEmpresaToken();
if (usuarioEmpresaList != null) { if (usuarioEmpresaList != null) {
for (UsuarioEmpresa cp : this.usuarioEmpresaList) { for (UsuarioEmpresa cp : this.usuarioEmpresaList) {
if ((cp.getActivo())) { 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());
}
} }
} }
} }

View File

@ -63,5 +63,18 @@ public interface EmpresaService {
public ComEmpConferencia suscribirOrActualizacion(ComEmpConferencia comEmpConferencia); public ComEmpConferencia suscribirOrActualizacion(ComEmpConferencia comEmpConferencia);
public List<Empresa> buscarEmpresaPtoVtaComissao(); public List<Empresa> buscarEmpresaPtoVtaComissao();
/**
* Atualiza se necessário as licenças no primeiro acesso
*
* @return A quantidade de empresas atualizadas
*/
public Integer atualizarLicencaEmpresasPrimeiraVez();
public List<Empresa> filtrarApenasEmpresasLicencaValida(List<Empresa> lsEmpresa);
public String token(Empresa empresa);
public String validarTokenLicensa(Empresa empresa, String tokenLicenca);
} }

View File

@ -4,6 +4,7 @@
*/ */
package com.rjconsultores.ventaboletos.service.impl; package com.rjconsultores.ventaboletos.service.impl;
import java.util.ArrayList;
import java.util.Calendar; import java.util.Calendar;
import java.util.List; import java.util.List;
@ -29,6 +30,7 @@ import com.rjconsultores.ventaboletos.service.LogAuditoriaService;
import com.rjconsultores.ventaboletos.service.MarcaService; import com.rjconsultores.ventaboletos.service.MarcaService;
import com.rjconsultores.ventaboletos.utilerias.RegistroConDependenciaException; import com.rjconsultores.ventaboletos.utilerias.RegistroConDependenciaException;
import com.rjconsultores.ventaboletos.utilerias.UsuarioLogado; 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) { public Empresa obtenerID(Integer id) {
Empresa empresa = empresaDAO.obtenerID(id); Empresa empresa = empresaDAO.obtenerID(id);
empresa.setToken(this.token(empresa));
try { try {
empresa.clonar(); empresa.clonar();
} catch (Exception e) { } catch (Exception e) {
@ -85,6 +89,7 @@ public class EmpresaServiceImpl implements EmpresaService {
entidad = empresaDAO.suscribir(entidad); entidad = empresaDAO.suscribir(entidad);
logAuditoriaService.auditar(null, entidad, null); logAuditoriaService.auditar(null, entidad, null);
entidad.setToken(this.token(entidad));
gerarMarca(entidad); gerarMarca(entidad);
@ -138,7 +143,7 @@ public class EmpresaServiceImpl implements EmpresaService {
} }
public List<Empresa> buscarTodosExceto(List<Empresa> empresa, Integer... idEmpresa) { public List<Empresa> buscarTodosExceto(List<Empresa> empresa, Integer... idEmpresa) {
return empresaDAO.buscarTodosExceto(empresa, idEmpresa); return this.filtrarApenasEmpresasLicencaValida(empresaDAO.buscarTodosExceto(empresa, idEmpresa));
} }
public List<Empresa> obtenerIndExternoFalse() { public List<Empresa> obtenerIndExternoFalse() {
@ -170,9 +175,31 @@ public class EmpresaServiceImpl implements EmpresaService {
empresaDAO.actualizaInscEstadual(inscricaoEstadual); empresaDAO.actualizaInscEstadual(inscricaoEstadual);
} }
@Override
public List<Empresa> filtrarApenasEmpresasLicencaValida(List<Empresa> lsEmpresa){
SecurityEmpresaToken security = new SecurityEmpresaToken();
List<Empresa> 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 @Override
public List<Empresa> buscaLike(String nombempresa) { public List<Empresa> buscaLike(String nombempresa) {
return empresaDAO.buscaLike(nombempresa); List<Empresa> lsEmpresa = empresaDAO.buscaLike(nombempresa);
return this.filtrarApenasEmpresasLicencaValida(lsEmpresa);
} }
@Override @Override
@ -240,9 +267,65 @@ public class EmpresaServiceImpl implements EmpresaService {
return empresaDAO.actualizacion(comEmpConferencia); 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 @Override
public List<Empresa> buscarEmpresaPtoVtaComissao() { public List<Empresa> buscarEmpresaPtoVtaComissao() {
return empresaDAO.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<Empresa> 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;
}
} }

View File

@ -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());
}
}

View File

@ -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);
}
}
}