PasswordVerifier.java
/*
* GovWay - A customizable API Gateway
* https://govway.org
*
* Copyright (c) 2005-2024 Link.it srl (https://link.it).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3, as published by
* the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package org.openspcoop2.utils.crypt;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Date;
import java.util.Enumeration;
import java.util.List;
import java.util.Properties;
import org.apache.commons.lang.StringUtils;
import org.openspcoop2.utils.Utilities;
import org.openspcoop2.utils.UtilsException;
import org.openspcoop2.utils.date.DateManager;
import org.openspcoop2.utils.regexp.RegularExpressionEngine;
/**
* PasswordVerifier
*
* @author Andrea Poli (apoli@link.it)
* @author $Author$
* @version $Rev$, $Date$
*/
public class PasswordVerifier implements Serializable {
private static final long serialVersionUID = 1L;
private static final String PROPERTY_REGULAR_EXPRESSIONS_PREFIX = "passwordVerifier.regularExpression.";
private static final String PROPERTY_LOGIN_CONTAINS = "passwordVerifier.notContainsLogin";
private static final String PROPERTY_RESTRICTED_WORDS = "passwordVerifier.restrictedWords";
private static final String PROPERTY_MIN_LENGTH = "passwordVerifier.minLength";
private static final String PROPERTY_MAX_LENGTH = "passwordVerifier.maxLength";
private static final String PROPERTY_INCLUDE_LOWER_CASE_LETTER = "passwordVerifier.lowerCaseLetter";
private static final String PROPERTY_INCLUDE_UPPER_CASE_LETTER = "passwordVerifier.upperCaseLetter";
private static final String PROPERTY_INCLUDE_NUMBER = "passwordVerifier.includeNumber";
private static final String PROPERTY_INCLUDE_NOT_ALPHANUMERIC_SYMBOL = "passwordVerifier.includeNotAlphanumericSymbol";
private static final String PROPERTY_ALL_DISTINCT_CHARACTERS = "passwordVerifier.allDistinctCharacters";
private static final String PROPERTY_EXPIRE = "passwordVerifier.expireDays";
private static final String PROPERTY_HISTORY = "passwordVerifier.history";
protected List<String> regulaExpressions = new ArrayList<>();
protected boolean notContainsLogin = false;
protected List<String> restrictedWords = new ArrayList<>();
protected int minLenght = -1;
protected int maxLenght = -1;
protected boolean includeLowerCaseLetter = false;
protected boolean includeUpperCaseLetter = false;
protected boolean includeNumber = false;
protected boolean includeNotAlphanumericSymbol = false;
protected boolean allDistinctCharacters = false;
protected boolean checkPasswordExpire;
protected int expireDays;
protected boolean history;
public PasswordVerifier(){}
public PasswordVerifier(PasswordVerifier pv){
this.regulaExpressions = pv.regulaExpressions;
this.notContainsLogin = pv.notContainsLogin;
this.restrictedWords = pv.restrictedWords;
this.minLenght = pv.minLenght;
this.maxLenght = pv.maxLenght;
this.includeLowerCaseLetter = pv.includeLowerCaseLetter;
this.includeUpperCaseLetter = pv.includeUpperCaseLetter;
this.includeNumber = pv.includeNumber;
this.includeNotAlphanumericSymbol = pv.includeNotAlphanumericSymbol;
this.allDistinctCharacters = pv.allDistinctCharacters;
this.checkPasswordExpire = pv.checkPasswordExpire;
this.expireDays = pv.expireDays;
this.history = pv.history;
}
public PasswordVerifier(String resource) throws UtilsException{
this(resource, true);
}
public PasswordVerifier(String resource, boolean useDefaultIfNotFound) throws UtilsException{
InputStream is = null;
try{
File f = new File(resource);
if(f.exists()){
is = new FileInputStream(f);
}
else{
is = PasswordVerifier.class.getResourceAsStream(resource);
}
if(is==null) {
is = PasswordVerifier.class.getResourceAsStream("/org/openspcoop2/utils/crypt/consolePassword.properties");
}
if(is!=null){
Properties p = new Properties();
p.load(is);
this._init(p);
}
else{
throw new Exception("Resource ["+resource+"] not found");
}
}catch(Exception e){
throw new UtilsException(e.getMessage(),e);
}finally{
try{
if(is!=null){
is.close();
}
}catch(Exception eClose){
// close
}
}
}
public PasswordVerifier(InputStream is) throws UtilsException{
try{
Properties p = new Properties();
p.load(is);
this._init(p);
}catch(Exception e){
throw new UtilsException(e.getMessage(),e);
}
}
public PasswordVerifier(Properties p) throws UtilsException{
this._init(p);
}
private void _init(Properties p) throws UtilsException{
try{
Properties tmpP = Utilities.readProperties(PROPERTY_REGULAR_EXPRESSIONS_PREFIX, p);
if(tmpP!=null && tmpP.size()>0){
Enumeration<?> en =tmpP.keys();
while (en.hasMoreElements()) {
String key = (String) en.nextElement();
String value = tmpP.getProperty(key);
this.regulaExpressions.add(value);
}
}
String tmp = p.getProperty(PROPERTY_LOGIN_CONTAINS);
if(tmp!=null){
tmp=tmp.trim();
try{
this.notContainsLogin = Boolean.parseBoolean(tmp);
}catch(Exception e){
throw new Exception("Property '"+PROPERTY_LOGIN_CONTAINS+"' with wrong value '"+tmp+"': "+e.getMessage(),e);
}
}
tmp = p.getProperty(PROPERTY_RESTRICTED_WORDS);
if(tmp!=null){
tmp=tmp.trim();
if(tmp.contains(",")){
String [] split = tmp.split(",");
for (int i = 0; i < split.length; i++) {
this.restrictedWords.add(split[i].trim());
}
}
else{
this.restrictedWords.add(tmp);
}
}
tmp = p.getProperty(PROPERTY_MIN_LENGTH);
if(tmp!=null){
tmp=tmp.trim();
try{
this.minLenght = Integer.parseInt(tmp);
}catch(Exception e){
throw new Exception("Property '"+PROPERTY_MIN_LENGTH+"' with wrong value '"+tmp+"': "+e.getMessage(),e);
}
}
tmp = p.getProperty(PROPERTY_MAX_LENGTH);
if(tmp!=null){
tmp=tmp.trim();
try{
this.maxLenght = Integer.parseInt(tmp);
}catch(Exception e){
throw new Exception("Property '"+PROPERTY_MAX_LENGTH+"' with wrong value '"+tmp+"': "+e.getMessage(),e);
}
}
tmp = p.getProperty(PROPERTY_INCLUDE_LOWER_CASE_LETTER);
if(tmp!=null){
tmp=tmp.trim();
try{
this.includeLowerCaseLetter = Boolean.parseBoolean(tmp);
}catch(Exception e){
throw new Exception("Property '"+PROPERTY_INCLUDE_LOWER_CASE_LETTER+"' with wrong value '"+tmp+"': "+e.getMessage(),e);
}
}
tmp = p.getProperty(PROPERTY_INCLUDE_UPPER_CASE_LETTER);
if(tmp!=null){
tmp=tmp.trim();
try{
this.includeUpperCaseLetter = Boolean.parseBoolean(tmp);
}catch(Exception e){
throw new Exception("Property '"+PROPERTY_INCLUDE_UPPER_CASE_LETTER+"' with wrong value '"+tmp+"': "+e.getMessage(),e);
}
}
tmp = p.getProperty(PROPERTY_INCLUDE_NUMBER);
if(tmp!=null){
tmp=tmp.trim();
try{
this.includeNumber = Boolean.parseBoolean(tmp);
}catch(Exception e){
throw new Exception("Property '"+PROPERTY_INCLUDE_NUMBER+"' with wrong value '"+tmp+"': "+e.getMessage(),e);
}
}
tmp = p.getProperty(PROPERTY_INCLUDE_NOT_ALPHANUMERIC_SYMBOL);
if(tmp!=null){
tmp=tmp.trim();
try{
this.includeNotAlphanumericSymbol = Boolean.parseBoolean(tmp);
}catch(Exception e){
throw new Exception("Property '"+PROPERTY_INCLUDE_NOT_ALPHANUMERIC_SYMBOL+"' with wrong value '"+tmp+"': "+e.getMessage(),e);
}
}
tmp = p.getProperty(PROPERTY_ALL_DISTINCT_CHARACTERS);
if(tmp!=null){
tmp=tmp.trim();
try{
this.allDistinctCharacters = Boolean.parseBoolean(tmp);
}catch(Exception e){
throw new Exception("Property '"+PROPERTY_ALL_DISTINCT_CHARACTERS+"' with wrong value '"+tmp+"': "+e.getMessage(),e);
}
}
tmp = p.getProperty(PROPERTY_EXPIRE);
if(tmp!=null){
tmp=tmp.trim();
try{
this.expireDays = Integer.parseInt(tmp);
this.checkPasswordExpire = this.expireDays>0;
}catch(Exception e){
throw new Exception("Property '"+PROPERTY_EXPIRE+"' with wrong value '"+tmp+"': "+e.getMessage(),e);
}
}
tmp = p.getProperty(PROPERTY_HISTORY);
if(tmp!=null){
tmp=tmp.trim();
try{
this.history = Boolean.parseBoolean(tmp);
}catch(Exception e){
throw new Exception("Property '"+PROPERTY_HISTORY+"' with wrong value '"+tmp+"': "+e.getMessage(),e);
}
}
}catch(Exception e){
throw new UtilsException(e.getMessage(),e);
}
}
public List<String> getRegulaExpressions() {
return this.regulaExpressions;
}
public void setRegulaExpressions(List<String> regulaExpressions) {
this.regulaExpressions = regulaExpressions;
}
public boolean isNotContainsLogin() {
return this.notContainsLogin;
}
public void setNotContainsLogin(boolean notContainsLogin) {
this.notContainsLogin = notContainsLogin;
}
public List<String> getRestrictedWords() {
return this.restrictedWords;
}
public void setRestrictedWords(List<String> restrictedWords) {
this.restrictedWords = restrictedWords;
}
public int getMinLenght() {
return this.minLenght;
}
public void setMinLenght(int minLenght) {
this.minLenght = minLenght;
}
public int getMaxLenght() {
return this.maxLenght;
}
public void setMaxLenght(int maxLenght) {
this.maxLenght = maxLenght;
}
public boolean isIncludeLowerCaseLetter() {
return this.includeLowerCaseLetter;
}
public void setIncludeLowerCaseLetter(boolean includeLowerCaseLetter) {
this.includeLowerCaseLetter = includeLowerCaseLetter;
}
public boolean isIncludeUpperCaseLetter() {
return this.includeUpperCaseLetter;
}
public void setIncludeUpperCaseLetter(boolean includeUpperCaseLetter) {
this.includeUpperCaseLetter = includeUpperCaseLetter;
}
public boolean isIncludeNumber() {
return this.includeNumber;
}
public void setIncludeNumber(boolean includeNumber) {
this.includeNumber = includeNumber;
}
public boolean isIncludeNotAlphanumericSymbol() {
return this.includeNotAlphanumericSymbol;
}
public void setIncludeNotAlphanumericSymbol(boolean includeNotAlphanumericSymbol) {
this.includeNotAlphanumericSymbol = includeNotAlphanumericSymbol;
}
public boolean isAllDistinctCharacters() {
return this.allDistinctCharacters;
}
public void setAllDistinctCharacters(boolean allDistinctCharacters) {
this.allDistinctCharacters = allDistinctCharacters;
}
public int getExpireDays() {
return this.expireDays;
}
public void setExpireDays(int expireDays) {
this.expireDays = expireDays;
this.checkPasswordExpire = this.expireDays>0;
}
public boolean isCheckPasswordExpire() {
return this.checkPasswordExpire;
}
public boolean isHistory() {
return this.history;
}
public void setHistory(boolean history) {
this.history = history;
}
public boolean validate(String login, String password){
StringBuilder bf = new StringBuilder();
return this.validate(login,password,bf);
}
public boolean validate(String login, String password,StringBuilder bfMotivazioneErrore){
if(password==null) {
bfMotivazioneErrore.append("Password non fornita");
return false;
}
password = password.trim();
if(this.regulaExpressions.size()>0){
for (String regExp : this.regulaExpressions) {
try{
if (RegularExpressionEngine.isMatch(password, regExp) == false){
bfMotivazioneErrore.append("La password non rispetta l'espressione regolare: "+regExp);
return false;
}
}catch(Exception e){
bfMotivazioneErrore.append("Rilevato un errore durante la verifica della password con l'espressione regolare '"+regExp+"': "+e.getMessage());
return false;
}
}
}
if(this.notContainsLogin){
if(password.contains(login)){
bfMotivazioneErrore.append("La password contiene il nome di login");
return false;
}
}
if(this.restrictedWords.size()>0){
for (String word : this.restrictedWords) {
if(password.toLowerCase().equals(word)){
bfMotivazioneErrore.append("La password corrisponde ad una delle parole riservate: "+word);
return false;
}
}
}
if(this.minLenght>0){
if(password.length()<this.minLenght){
bfMotivazioneErrore.append("La password deve essere composta almeno da ").append(this.minLenght).
append(" caratteri mentre quella fornita ha una lunghezza di ").append(password.length()).append(" caratteri");
return false;
}
}
if(this.maxLenght>0){
if(password.length()>this.maxLenght){
bfMotivazioneErrore.append("La password non deve essere composta da più di ").append(this.minLenght).
append(" caratteri mentre quella fornita ha una lunghezza di ").append(password.length()).append(" caratteri");
return false;
}
}
if(this.includeLowerCaseLetter){
boolean found = false;
for (int i = 0; i < password.length(); i++) {
String c = password.charAt(i)+"";
if(StringUtils.isAllLowerCase(c)){
found = true;
break;
}
}
if(!found){
bfMotivazioneErrore.append("La password deve contenere almeno una lettera minuscola (a - z)");
return false;
}
}
if(this.includeUpperCaseLetter){
boolean found = false;
for (int i = 0; i < password.length(); i++) {
String c = password.charAt(i)+"";
if(StringUtils.isAllUpperCase(c)){
found = true;
break;
}
}
if(!found){
bfMotivazioneErrore.append("La password deve contenere almeno una lettera maiuscola (A - Z)");
return false;
}
}
if(this.includeNumber){
boolean found = false;
for (int i = 0; i < password.length(); i++) {
String c = password.charAt(i)+"";
if(StringUtils.isNumeric(c)){
found = true;
break;
}
}
if(!found){
bfMotivazioneErrore.append("La password deve contenere almeno un numero (0 - 9)");
return false;
}
}
if(this.includeNotAlphanumericSymbol){
boolean found = false;
for (int i = 0; i < password.length(); i++) {
String c = password.charAt(i)+"";
if(!StringUtils.isNumeric(c) && !StringUtils.isAlpha(c)){
found = true;
break;
}
}
if(!found){
bfMotivazioneErrore.append("La password deve contenere almeno un carattere non alfanumerico (ad esempio, !, $, #, %, @)");
return false;
}
}
if(this.allDistinctCharacters){
for (int i = 0; i < password.length(); i++) {
String c = password.charAt(i)+"";
int count = 0;
for (int j = 0; j < password.length(); j++) {
String check = password.charAt(j)+"";
if(check.equals(c)){
count++;
}
}
if(count>1){
bfMotivazioneErrore.append("Tutti i caratteri utilizzati devono essere differenti mentre nella password fornita il carattere '"+c+"' appare "+count+" volte");
return false;
}
}
}
return true;
}
public boolean isPasswordExpire(Date lastUpdatePassword){
StringBuilder bf = new StringBuilder();
return this.isPasswordExpire(lastUpdatePassword,bf);
}
public boolean isPasswordExpire(Date lastUpdatePassword, StringBuilder bfMotivazioneErrore) {
if(this.checkPasswordExpire) {
Date now = DateManager.getDate();
long expireMs = ((long) this.expireDays * 24 * 60 * 60 * 1000);
Date expireDate = new Date(lastUpdatePassword.getTime() + expireMs );
if(expireDate.before(now)) {
bfMotivazioneErrore.append("Password impostata da più di "+this.expireDays+" giorni");
return true;
}
}
return false;
}
public boolean existsRestriction(){
String s = this.help("", "", false, false);
return s != null && !"".equals(s);
}
public String help(){
return this.help("\n", "- ", true, false);
}
public String help(String separator){
return this.help(separator, "- ", true, false);
}
public boolean existsRestrictionUpdate(){
String s = this.help("", "", false, true);
return s != null && !"".equals(s);
}
public String helpUpdate(){
return this.help("\n", "- ", true, true);
}
public String helpUpdate(String separator){
return this.help(separator, "- ", true, true);
}
public String help(String separator, String elenco, boolean premessa, boolean update){
StringBuilder bf = new StringBuilder();
if(premessa){
bf.append("La password deve rispettare i seguenti vincoli: ");
}
if(this.regulaExpressions.size()>0){
for (String regExp : this.regulaExpressions) {
bf.append(separator);
bf.append(elenco).append("deve soddisfare l'espressione regolare: "+regExp);
}
}
if(this.notContainsLogin){
bf.append(separator);
bf.append(elenco).append("non deve contenere il nome di login dell'utente");
}
if(this.restrictedWords.size()>0){
bf.append(separator);
bf.append(elenco).append("non deve corrispondere ad una delle seguenti parole riservate: "+this.restrictedWords);
}
if(this.minLenght>0){
bf.append(separator);
bf.append(elenco).append("deve essere composta almeno da ").append(this.minLenght).append(" caratteri");
}
if(this.maxLenght>0){
bf.append(separator);
bf.append(elenco).append("non deve essere composta da più di ").append(this.maxLenght).append(" caratteri");
}
if(this.includeLowerCaseLetter){
bf.append(separator);
bf.append(elenco).append("deve contenere almeno una lettera minuscola (a - z)");
}
if(this.includeUpperCaseLetter){
bf.append(separator);
bf.append(elenco).append("deve contenere almeno una lettera maiuscola (A - Z)");
}
if(this.includeNumber){
bf.append(separator);
bf.append(elenco).append("deve contenere almeno un numero (0 - 9)");
}
if(this.includeNotAlphanumericSymbol){
bf.append(separator);
bf.append(elenco).append("deve contenere almeno un carattere non alfanumerico (ad esempio, !, $, #, %, @)");
}
if(this.allDistinctCharacters){
bf.append(separator);
bf.append(elenco).append("tutti i caratteri utilizzati devono essere differenti");
}
if(this.history){
if(update) {
bf.append(separator);
bf.append(elenco).append("non deve corrispondere ad una precedente password");
}
}
return bf.toString();
}
}