001// -------------------------------------------------------------------------------- 002// Copyright 2002-2026 Echo Three, LLC 003// 004// Licensed under the Apache License, Version 2.0 (the "License"); 005// you may not use this file except in compliance with the License. 006// You may obtain a copy of the License at 007// 008// http://www.apache.org/licenses/LICENSE-2.0 009// 010// Unless required by applicable law or agreed to in writing, software 011// distributed under the License is distributed on an "AS IS" BASIS, 012// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013// See the License for the specific language governing permissions and 014// limitations under the License. 015// -------------------------------------------------------------------------------- 016 017package com.echothree.model.control.party.server.logic; 018 019import com.echothree.model.control.party.server.control.PartyControl; 020import com.echothree.model.control.uom.common.UomConstants; 021import com.echothree.model.control.uom.server.control.UomControl; 022import com.echothree.model.control.user.server.control.UserControl; 023import com.echothree.model.data.party.server.entity.Party; 024import com.echothree.model.data.party.server.entity.PartyType; 025import com.echothree.model.data.party.server.entity.PartyTypePasswordStringPolicy; 026import com.echothree.model.data.party.server.entity.PartyTypePasswordStringPolicyDetail; 027import com.echothree.model.data.user.server.entity.UserLoginPassword; 028import com.echothree.model.data.user.server.entity.UserVisit; 029import com.echothree.model.data.user.server.value.UserLoginPasswordStringValue; 030import com.echothree.util.common.message.ExecutionErrors; 031import com.echothree.util.common.string.StringUtils; 032import com.echothree.util.server.message.ExecutionErrorAccumulator; 033import com.echothree.util.server.persistence.Session; 034import com.echothree.util.server.persistence.Sha1Utils; 035import com.echothree.util.server.string.UnitOfMeasureUtils; 036import java.util.HashMap; 037import java.util.Map; 038import javax.enterprise.context.ApplicationScoped; 039import javax.inject.Inject; 040 041@ApplicationScoped 042public class PasswordStringPolicyLogic { 043 044 @Inject 045 PartyControl partyControl; 046 047 @Inject 048 UomControl uomControl; 049 050 @Inject 051 UserControl userControl; 052 053 @Inject 054 Sha1Utils sha1Utils; 055 056 protected PasswordStringPolicyLogic() { 057 super(); 058 } 059 060 private void checkAllowChange(final ExecutionErrorAccumulator ema, final PartyTypePasswordStringPolicyDetail policyDetail) { 061 if(!policyDetail.getAllowChange()) { 062 ema.addExecutionError(ExecutionErrors.PasswordChangeNotAllowed.name()); 063 } 064 } 065 066 private void checkPasswordHistory(final ExecutionErrorAccumulator ema, 067 final PartyTypePasswordStringPolicyDetail policyDetail, final UserLoginPassword ulp, final String password) { 068 var passwordHistory = policyDetail.getPasswordHistory(); 069 070 if(passwordHistory != null) { 071 for(var userLoginPasswordString: userControl.getUserLoginPasswordStringHistory(ulp, passwordHistory)) { 072 var salt = userLoginPasswordString.getSalt(); 073 074 if(sha1Utils.encode(salt, password).equals(userLoginPasswordString.getPassword())) { 075 ema.addExecutionError(ExecutionErrors.PasswordInRecentHistory.name(), passwordHistory); 076 break; 077 } 078 } 079 } 080 } 081 082 private void checkMinimumPasswordLifetime(final Session session, final UserVisit userVisit, 083 final ExecutionErrorAccumulator ema, final PartyTypePasswordStringPolicyDetail policyDetail, final UserLoginPasswordStringValue ulpsv) { 084 var minimumPasswordLifetime = policyDetail.getMinimumPasswordLifetime(); 085 086 if(minimumPasswordLifetime != null) { 087 var currentPasswordLifetime = session.getStartTime() - ulpsv.getChangedTime(); 088 089 if(currentPasswordLifetime < minimumPasswordLifetime) { 090 var timeUnitOfMeasureKind = uomControl.getUnitOfMeasureKindByUnitOfMeasureKindUseTypeUsingNames(UomConstants.UnitOfMeasureKindUseType_TIME); 091 var fmtMinimumPasswordLifetime = UnitOfMeasureUtils.getInstance().formatUnitOfMeasure(userVisit, 092 timeUnitOfMeasureKind, minimumPasswordLifetime); 093 var fmtCurrentPasswordLifetime = UnitOfMeasureUtils.getInstance().formatUnitOfMeasure(userVisit, 094 timeUnitOfMeasureKind, Long.valueOf(currentPasswordLifetime)); 095 096 ema.addExecutionError(ExecutionErrors.PasswordMinimumLifetimeNotMet.name(), fmtMinimumPasswordLifetime, fmtCurrentPasswordLifetime); 097 } 098 } 099 } 100 101 private void checkLength(final ExecutionErrorAccumulator ema, final PartyTypePasswordStringPolicyDetail policyDetail, 102 final String password) { 103 var length = password.length(); 104 var minimumLegnth = policyDetail.getMinimumLength(); 105 var maximumLength = policyDetail.getMaximumLength(); 106 107 if(minimumLegnth != null && length < minimumLegnth) { 108 ema.addExecutionError(ExecutionErrors.PasswordLessThanMinimumLength.name(), minimumLegnth); 109 } 110 111 if(maximumLength != null && length < maximumLength) { 112 ema.addExecutionError(ExecutionErrors.PasswordGreaterThanMaximumLength.name(), maximumLength); 113 } 114 } 115 116 private int getTypeCount(final Map<Integer, Integer> types, final byte type) { 117 var count = types.get(Integer.valueOf(type)); 118 119 return count == null? 0: count; 120 } 121 122 private void checkCharacterTypes(final ExecutionErrorAccumulator ema, final PartyTypePasswordStringPolicyDetail policyDetail, 123 final String password) { 124 var requiredDigitCount = policyDetail.getRequiredDigitCount(); 125 var requiredLetterCount = policyDetail.getRequiredLetterCount(); 126 var requiredUpperCaseCount = policyDetail.getRequiredUpperCaseCount(); 127 var requiredLowerCaseCount = policyDetail.getRequiredLowerCaseCount(); 128 var maximumRepeated = policyDetail.getMaximumRepeated(); 129 var minimumCharacterTypes = policyDetail.getMinimumCharacterTypes(); 130 Map<Integer, Integer> types = new HashMap<>(); 131 var lastCh = 0; 132 var repeat = 0; 133 var maxRepeat = 0; 134 135 for(int ch : StringUtils.getInstance().codePoints(password)) { 136 Integer type = Character.getType(ch); 137 var count = types.get(type); 138 139 if(count == null) { 140 types.put(type, 1); 141 } else { 142 types.put(type, count + 1); 143 } 144 145 if(ch == lastCh) { 146 repeat++; 147 } else { 148 lastCh = ch; 149 150 if(repeat > maxRepeat) { 151 maxRepeat = repeat; 152 } 153 154 repeat = 1; 155 } 156 } 157 158 if(repeat > maxRepeat) { 159 maxRepeat = repeat; 160 } 161 162 var upperCaseCount = getTypeCount(types, Character.UPPERCASE_LETTER); 163 var lowerCaseCount = getTypeCount(types, Character.LOWERCASE_LETTER); 164 var letterCount = upperCaseCount + lowerCaseCount; 165 166 if(requiredDigitCount != null) { 167 var digitCount = getTypeCount(types, Character.DECIMAL_DIGIT_NUMBER); 168 169 if(digitCount < requiredDigitCount) { 170 ema.addExecutionError(ExecutionErrors.PasswordRequiredDigitCountNotMet.name(), requiredDigitCount); 171 } 172 } 173 174 if(requiredLetterCount != null) { 175 if(letterCount < requiredLetterCount) { 176 ema.addExecutionError(ExecutionErrors.PasswordRequiredLetterCountNotMet.name(), requiredLetterCount); 177 } 178 } 179 180 if(requiredUpperCaseCount != null) { 181 if(upperCaseCount < requiredUpperCaseCount) { 182 ema.addExecutionError(ExecutionErrors.PasswordRequiredUpperCaseCountNotMet.name(), requiredUpperCaseCount); 183 } 184 } 185 186 if(requiredLowerCaseCount != null) { 187 if(lowerCaseCount < requiredLowerCaseCount) { 188 ema.addExecutionError(ExecutionErrors.PasswordRequiredLowerCaseCountNotMet.name(), requiredLowerCaseCount); 189 } 190 } 191 192 if(maximumRepeated != null) { 193 if(maxRepeat < maximumRepeated) { 194 ema.addExecutionError(ExecutionErrors.PasswordMaximumRepeatedExceeded.name(), maximumRepeated); 195 } 196 } 197 198 if(minimumCharacterTypes != null) { 199 var characterTypes = types.size(); 200 201 if(characterTypes < minimumCharacterTypes) { 202 ema.addExecutionError(ExecutionErrors.PasswordMinimumCharacterTypesNotMet.name(), minimumCharacterTypes); 203 } 204 } 205 } 206 207 public PartyTypePasswordStringPolicy checkStringPassword(final Session session, final UserVisit userVisit, final ExecutionErrorAccumulator ema, 208 final PartyType partyType, final UserLoginPassword ulp, final UserLoginPasswordStringValue ulpsv, final String password) { 209 var policy = partyControl.getPartyTypePasswordStringPolicy(partyType); 210 211 if(policy != null) { 212 var policyDetail = policy.getLastDetail(); 213 214 if(ulp != null) { 215 checkPasswordHistory(ema, policyDetail, ulp, password); 216 } 217 218 if(ulpsv != null) { 219 checkAllowChange(ema, policyDetail); 220 checkMinimumPasswordLifetime(session, userVisit, ema, policyDetail, ulpsv); 221 } 222 223 checkLength(ema, policyDetail, password); 224 checkCharacterTypes(ema, policyDetail, password); 225 } 226 227 return policy; 228 } 229 230 public PartyTypePasswordStringPolicy checkStringPassword(final Session session, final UserVisit userVisit, final ExecutionErrorAccumulator ema, 231 final Party party, final UserLoginPassword ulp, final UserLoginPasswordStringValue ulpsv, final String password) { 232 var partyType = party.getLastDetail().getPartyType(); 233 234 return checkStringPassword(session, userVisit, ema, partyType, ulp, ulpsv, password); 235 } 236 237}