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