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