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.control.user.authentication.server.command; 018 019import com.echothree.model.control.contact.common.ContactMechanismPurposes; 020import com.echothree.model.control.contact.common.ContactMechanismTypes; 021import com.echothree.model.control.contact.server.control.ContactControl; 022import static com.echothree.model.control.party.common.PartyTypes.CUSTOMER; 023import static com.echothree.model.control.party.common.PartyTypes.EMPLOYEE; 024import static com.echothree.model.control.party.common.PartyTypes.VENDOR; 025import com.echothree.model.control.party.server.control.PartyControl; 026import com.echothree.model.control.security.common.SecurityRoleGroups; 027import static com.echothree.model.control.security.common.SecurityRoleGroups.Employee; 028import com.echothree.model.control.sequence.common.SequenceTypes; 029import com.echothree.model.control.sequence.server.logic.SequenceGeneratorLogic; 030import com.echothree.model.control.uom.common.UomConstants; 031import com.echothree.model.control.uom.server.control.UomControl; 032import com.echothree.model.control.user.common.UserConstants; 033import com.echothree.model.data.party.server.entity.Party; 034import com.echothree.model.data.party.server.entity.PartyRelationship; 035import com.echothree.model.data.party.server.entity.PartyType; 036import com.echothree.model.data.user.server.entity.UserLoginPasswordString; 037import com.echothree.model.data.user.server.entity.UserLoginStatus; 038import com.echothree.util.common.form.BaseForm; 039import com.echothree.util.common.message.ExecutionErrors; 040import com.echothree.util.common.message.ExecutionWarnings; 041import com.echothree.util.common.validation.FieldDefinition; 042import com.echothree.util.server.control.BaseSimpleCommand; 043import com.echothree.util.server.control.CommandSecurityDefinition; 044import com.echothree.util.server.persistence.Sha1Utils; 045import com.echothree.util.server.string.UnitOfMeasureUtils; 046import java.util.List; 047import javax.inject.Inject; 048 049public abstract class BaseLoginCommand<F extends BaseForm> 050 extends BaseSimpleCommand<F> { 051 052 @Inject 053 ContactControl contactControl; 054 055 @Inject 056 PartyControl partyControl; 057 058 @Inject 059 UomControl uomControl; 060 061 @Inject 062 SequenceGeneratorLogic sequenceGeneratorLogic; 063 064 065 /** Creates a new instance of BaseLoginCommand */ 066 protected BaseLoginCommand(final CommandSecurityDefinition commandSecurityDefinition, 067 final List<FieldDefinition> formFieldDefinition) { 068 super(commandSecurityDefinition, formFieldDefinition, false); 069 } 070 071 protected UserLoginPasswordString checkPassword(final String password, final Party party, final String userLoginPasswordTypeName, 072 final boolean deleteOnSuccess) { 073 var userLoginPasswordType = userControl.getUserLoginPasswordTypeByName(userLoginPasswordTypeName); 074 var userLoginPassword = deleteOnSuccess? userControl.getUserLoginPasswordForUpdate(party, userLoginPasswordType): 075 userControl.getUserLoginPassword(party, userLoginPasswordType); 076 UserLoginPasswordString result = null; 077 078 if(userLoginPassword != null) { 079 var userLoginPasswordEncoderType = userLoginPassword.getUserLoginPasswordType().getUserLoginPasswordEncoderType(); 080 var userLoginPasswordEncoderTypeName = userLoginPasswordEncoderType.getUserLoginPasswordEncoderTypeName(); 081 var userLoginPasswordString = userControl.getUserLoginPasswordString(userLoginPassword); 082 083 if(userLoginPasswordEncoderTypeName.equals(UserConstants.UserLoginPasswordEncoderType_SHA1)) { 084 result = Sha1Utils.getInstance().encode(userLoginPasswordString.getSalt(), password).equals(userLoginPasswordString.getPassword()) ? userLoginPasswordString: null; 085 } else if(userLoginPasswordEncoderTypeName.equals(UserConstants.UserLoginPasswordEncoderType_TEXT)) { 086 result = password.equals(userLoginPasswordString.getPassword())? userLoginPasswordString: null; 087 } 088 089 if(deleteOnSuccess && result != null) { 090 userControl.deleteUserLoginPassword(userLoginPassword, getPartyPK()); 091 } 092 } 093 094 return result; 095 } 096 097 // TODO: Recovered password should become regular password if that ends up being the password that matches, also, 098 // the recovered password should be deleted if the user logs in using their regular one. Changing a password should also 099 // make sure a recovered password does not exist. 100 protected boolean checkPasswords(final UserLoginStatus userLoginStatus, final String password, final Party party, final boolean doStatusChecks) { 101 var result = checkPassword(password, party, UserConstants.UserLoginPasswordType_STRING, false); 102 103 if(result == null) { 104 result = checkPassword(password, party, UserConstants.UserLoginPasswordType_RECOVERED_STRING, true); 105 } 106 107 if(result == null) { 108 addExecutionError(ExecutionErrors.IncorrectPassword.name()); 109 } else if(doStatusChecks) { 110 var partyTypePasswordStringPolicy = partyControl.getPartyTypePasswordStringPolicy(party.getLastDetail().getPartyType()); 111 112 if(partyTypePasswordStringPolicy != null) { 113 var partyTypePasswordStringPolicyDetail = partyTypePasswordStringPolicy.getLastDetail(); 114 var maximumPasswordLifetime = partyTypePasswordStringPolicyDetail.getMaximumPasswordLifetime(); 115 var expiredLoginsPermitted = partyTypePasswordStringPolicyDetail.getExpiredLoginsPermitted(); 116 117 if(maximumPasswordLifetime != null) { 118 var expirationWarningTime = partyTypePasswordStringPolicyDetail.getExpirationWarningTime(); 119 var changedTime = result.getChangedTime(); 120 121 if((session.getStartTime() - changedTime) > maximumPasswordLifetime) { 122 userLoginStatus.setExpiredCount(userLoginStatus.getExpiredCount() + 1); 123 124 addExecutionWarning(ExecutionWarnings.PasswordExpired.name()); 125 } else if(expirationWarningTime != null) { 126 var expirationTime = changedTime + maximumPasswordLifetime; 127 var warningTime = expirationTime - expirationWarningTime; 128 129 if(session.getStartTime() > warningTime) { 130 var timeUnitOfMeasureKind = uomControl.getUnitOfMeasureKindByUnitOfMeasureKindUseTypeUsingNames(UomConstants.UnitOfMeasureKindUseType_TIME); 131 var remainingTime = UnitOfMeasureUtils.getInstance().formatUnitOfMeasure(getUserVisit(), timeUnitOfMeasureKind, Long.valueOf(expirationTime - session.getStartTime())); 132 133 addExecutionWarning(ExecutionWarnings.PasswordExpiration.name(), remainingTime); 134 } 135 } 136 } 137 138 if(expiredLoginsPermitted != null && userLoginStatus.getExpiredCount() > expiredLoginsPermitted) { 139 result = null; 140 addExecutionError(ExecutionErrors.MaximumExpiredLoginsPermittedExceeded.name(), expiredLoginsPermitted); 141 } 142 143 if(userLoginStatus.getForceChange()) { 144 addExecutionWarning(ExecutionWarnings.ForcePasswordChange.name()); 145 } 146 } 147 } 148 149 return result != null; 150 } 151 152 protected String getSecurityRoleGroupName(final PartyType partyType) { 153 String securityRoleGroupName = null; 154 var partyTypeName = partyType.getPartyTypeName(); 155 156 if(partyTypeName.equals(CUSTOMER.name())) { 157 securityRoleGroupName = SecurityRoleGroups.Customer.name(); 158 } else if(partyTypeName.equals(EMPLOYEE.name())) { 159 securityRoleGroupName = Employee.name(); 160 } else if(partyTypeName.equals(VENDOR.name())) { 161 securityRoleGroupName = SecurityRoleGroups.Vendor.name(); 162 } 163 164 return securityRoleGroupName; 165 } 166 167 protected void clearLoginFailures(final UserLoginStatus userLoginStatus) { 168 // Audit trail for callers of this function should be created by the callers. 169 userLoginStatus.setFailureCount(0); 170 userLoginStatus.setFirstFailureTime(null); 171 userLoginStatus.setLastFailureTime(null); 172 } 173 174 protected void addRemoteInet4AddressToParty(final Party party, final Integer remoteInet4Address) { 175 if(remoteInet4Address != null) { 176 var partyPK = party.getPrimaryKey(); 177 var partyContactMechanism = contactControl.getPartyContactMechanismByInet4Address(party, remoteInet4Address); 178 179 if(partyContactMechanism == null) { 180 var contactMechanismName = sequenceGeneratorLogic.getNextSequenceValue(null, SequenceTypes.CONTACT_MECHANISM.name()); 181 var contactMechanismType = contactControl.getContactMechanismTypeByName(ContactMechanismTypes.INET_4.name()); 182 var contactMechanism = contactControl.createContactMechanism(contactMechanismName, contactMechanismType, false, partyPK); 183 184 contactControl.createContactInet4Address(contactMechanism, remoteInet4Address, partyPK); 185 partyContactMechanism = contactControl.createPartyContactMechanism(party, contactMechanism, null, false, 1, partyPK); 186 } 187 188 var contactMechanismPurpose = contactControl.getContactMechanismPurposeByName(ContactMechanismPurposes.INET_4_LOGIN.name()); 189 var partyContactMechanismPurpose = contactControl.getPartyContactMechanismPurpose(partyContactMechanism, contactMechanismPurpose); 190 if(partyContactMechanismPurpose == null) { 191 contactControl.createPartyContactMechanismPurpose(partyContactMechanism, contactMechanismPurpose, false, 1, partyPK); 192 } 193 } 194 } 195 196 protected void successfulLogin(final UserLoginStatus userLoginStatus, final Party party, final PartyRelationship partyRelationship, 197 final Integer remoteInet4Address) { 198 var userVisit = getUserVisitForUpdate(); 199 var userKey = userVisit.getUserKey(); 200 var userKeyDetailValue = userControl.getUserKeyDetailValueByPKForUpdate(userKey.getLastDetail().getPrimaryKey()); 201 202 userControl.associatePartyToUserVisit(userVisit, party, partyRelationship, session.getStartTime()); 203 204 // Only update the UserKeyDetail if the party has changed 205 var partyPK = party.getPrimaryKey(); 206 var partyRelationshipPK = partyRelationship == null? null: partyRelationship.getPrimaryKey(); 207 var userKeyPartyPK = userKeyDetailValue.getPartyPK(); 208 var userKeyPartyRelationshipPK = userKeyDetailValue.getPartyRelationshipPK(); 209 210 if(userKeyPartyPK == null || !userKeyPartyPK.equals(partyPK) 211 || userKeyPartyRelationshipPK == null || !userKeyPartyRelationshipPK.equals(partyRelationshipPK)) { 212 userKeyDetailValue.setPartyPK(partyPK); 213 userKeyDetailValue.setPartyRelationshipPK(partyRelationshipPK); 214 userControl.updateUserKeyFromValue(userKeyDetailValue); 215 } 216 217 clearLoginFailures(userLoginStatus); 218 219 userLoginStatus.setLastLoginTime(session.getStartTime()); 220 221 addRemoteInet4AddressToParty(party, remoteInet4Address); 222 223 // TODO: Create audit trail 224 } 225 226 protected void unsuccessfulLogin(final UserLoginStatus userLoginStatus) { 227 var failureCount = userLoginStatus.getFailureCount(); 228 229 userLoginStatus.setFailureCount(failureCount + 1); 230 if(userLoginStatus.getFirstFailureTime() == null) { 231 userLoginStatus.setFirstFailureTime(session.getStartTime()); 232 } 233 userLoginStatus.setLastFailureTime(session.getStartTime()); 234 235 // TODO: Create audit trail 236 } 237 238}