Skip to content

Commit

Permalink
test
Browse files Browse the repository at this point in the history
  • Loading branch information
nkonev committed Sep 19, 2024
1 parent d0c249e commit 35f99aa
Show file tree
Hide file tree
Showing 7 changed files with 82 additions and 42 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,8 @@
import name.nkonev.aaa.dto.UserAccountDetailsDTO;
import name.nkonev.aaa.entity.jdbc.UserAccount;
import name.nkonev.aaa.entity.ldap.LdapEntity;
import name.nkonev.aaa.exception.UserAlreadyPresentException;
import name.nkonev.aaa.repository.jdbc.UserAccountRepository;
import name.nkonev.aaa.services.CheckService;
import name.nkonev.aaa.services.ConflictResolver;
import name.nkonev.aaa.services.ConflictResolingActions;
import name.nkonev.aaa.services.ConflictService;
import name.nkonev.aaa.services.EventService;
import org.slf4j.Logger;
Expand All @@ -35,7 +33,7 @@

// https://spring.io/guides/gs/authenticating-ldap
@Component
public class LdapAuthenticationProvider implements AuthenticationProvider, ConflictResolver {
public class LdapAuthenticationProvider implements AuthenticationProvider, ConflictResolingActions {

@Autowired
private LdapOperations ldapOperations;
Expand All @@ -55,9 +53,6 @@ public class LdapAuthenticationProvider implements AuthenticationProvider, Confl
@Autowired
private UserAccountConverter userAccountConverter;

@Autowired
private CheckService userService;

@Autowired
private ConflictService conflictService;

Expand Down Expand Up @@ -103,16 +98,17 @@ public Authentication authenticate(Authentication authentication) throws Authent
}
var mappedRoles = RoleMapper.map(aaaProperties.roleMappings().ldap(), rawRoles);

// check for conflicts by username or email and create the user if conflict resolution is not "ignore"
conflictService.process(UserAccountConverter.buildUserAccountEntityForLdapInsert(
var userToInsert = UserAccountConverter.buildUserAccountEntityForLdapInsert(
userName,
ldapUserId,
mappedRoles,
email,
false,
true,
getNowUTC()
), this);
);
// check for conflicts by username or email and create the user if conflict resolution is not "ignore"
conflictService.process(userToInsert, this);
// due to conflict we can ignore the user and not to save him
// so we try to lookup him
var foundNewUser = userAccountRepository.findByUsername(userName);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import name.nkonev.aaa.entity.jdbc.UserAccount;

public interface ConflictResolver {
public interface ConflictResolingActions {

void saveUser(UserAccount userAccount);

Expand Down
28 changes: 14 additions & 14 deletions aaa/src/main/java/name/nkonev/aaa/services/ConflictService.java
Original file line number Diff line number Diff line change
Expand Up @@ -37,68 +37,68 @@ private Map<ConflictBy, UserAccount> checkForConflicts(String username, String e
return conflicts;
}

public void process(UserAccount newUser, ConflictResolver conflictResolver) {
public void process(UserAccount newUser, ConflictResolingActions conflictResolingActions) {
var conflicts = checkForConflicts(newUser.username(), newUser.email());

if (conflicts.isEmpty()) {
conflictResolver.saveUser(newUser);
conflictResolingActions.saveUser(newUser);
return;
}

LOGGER.info("For new user {} found conflicting old users: {}", newUser, conflicts);

if (conflicts.containsKey(ConflictBy.USERNAME)) {
solveUsernameConflict(conflictResolver, conflicts.get(ConflictBy.USERNAME), newUser);
solveUsernameConflict(conflictResolingActions, conflicts.get(ConflictBy.USERNAME), newUser);
}

if (conflicts.containsKey(ConflictBy.EMAIL)) {
solveEmailConflict(conflictResolver, conflicts.get(ConflictBy.EMAIL), newUser);
solveEmailConflict(conflictResolingActions, conflicts.get(ConflictBy.EMAIL), newUser);
}
}


private void solveUsernameConflict(ConflictResolver conflictResolver, UserAccount oldUser, UserAccount newUser) {
private void solveUsernameConflict(ConflictResolingActions conflictResolingActions, UserAccount oldUser, UserAccount newUser) {
switch (aaaProperties.ldap().resolveConflictsStrategy()) {
case IGNORE:
LOGGER.info("Skipping importing an user with conflicting username {}", newUser.username());
return;
case WRITE_NEW_AND_REMOVE_OLD:
LOGGER.info("Removing old conflicting user {}", oldUser);
conflictResolver.removeUser(oldUser);
conflictResolingActions.removeUser(oldUser);
LOGGER.info("Saving new user {}", newUser);
conflictResolver.saveUser(newUser);
conflictResolingActions.saveUser(newUser);
return;
case WRITE_NEW_AND_RENAME_OLD:
var rl = LDAP_CONFLICT_PREFIX + oldUser.username();
LOGGER.info("Saving old conflicting user {} with renamed login {}", oldUser, rl);
oldUser = oldUser.withUsername(rl);
conflictResolver.saveUser(oldUser);
conflictResolingActions.saveUser(oldUser);
LOGGER.info("Saving new user {}", newUser);
conflictResolver.saveUser(newUser);
conflictResolingActions.saveUser(newUser);
return;
default:
throw new IllegalStateException("Missed action for conflict strategy: " + aaaProperties.ldap().resolveConflictsStrategy());
}
}

private void solveEmailConflict(ConflictResolver conflictResolver, UserAccount oldUser, UserAccount newUser) {
private void solveEmailConflict(ConflictResolingActions conflictResolingActions, UserAccount oldUser, UserAccount newUser) {
switch (aaaProperties.ldap().resolveConflictsStrategy()) {
case IGNORE:
LOGGER.info("Skipping importing an user with conflicting email {}", newUser.email());
return;
case WRITE_NEW_AND_REMOVE_OLD:
LOGGER.info("Removing old conflicting user {}", oldUser);
conflictResolver.removeUser(oldUser);
conflictResolingActions.removeUser(oldUser);
LOGGER.info("Saving new user {}", newUser);
conflictResolver.saveUser(newUser);
conflictResolingActions.saveUser(newUser);
return;
case WRITE_NEW_AND_RENAME_OLD:
var re = LDAP_CONFLICT_PREFIX + oldUser.email();
LOGGER.info("Saving old conflicting user {} with renamed email {}", oldUser, re);
oldUser = oldUser.withEmail(re);
conflictResolver.saveUser(oldUser);
conflictResolingActions.saveUser(oldUser);
LOGGER.info("Saving new user {}", newUser);
conflictResolver.saveUser(newUser);
conflictResolingActions.saveUser(newUser);
return;
default:
throw new IllegalStateException("Missed action for conflict strategy: " + aaaProperties.ldap().resolveConflictsStrategy());
Expand Down
46 changes: 30 additions & 16 deletions aaa/src/main/java/name/nkonev/aaa/tasks/SyncLdapTask.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,13 @@
import name.nkonev.aaa.converter.UserAccountConverter;
import name.nkonev.aaa.dto.ForceKillSessionsReasonType;
import name.nkonev.aaa.dto.UserRole;
import name.nkonev.aaa.entity.jdbc.UserAccount;
import name.nkonev.aaa.entity.ldap.LdapEntity;
import name.nkonev.aaa.repository.jdbc.UserAccountRepository;
import name.nkonev.aaa.security.AaaUserDetailsService;
import name.nkonev.aaa.security.RoleMapper;
import name.nkonev.aaa.services.ConflictResolingActions;
import name.nkonev.aaa.services.ConflictService;
import net.javacrumbs.shedlock.spring.annotation.SchedulerLock;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand Down Expand Up @@ -35,7 +38,7 @@
import static name.nkonev.aaa.utils.TimeUtil.getNowUTC;

@Service
public class SyncLdapTask {
public class SyncLdapTask implements ConflictResolingActions {
@Autowired
private AaaProperties aaaProperties;

Expand All @@ -51,6 +54,9 @@ public class SyncLdapTask {
@Autowired
private TransactionTemplate transactionTemplate;

@Autowired
private ConflictService conflictService;

private static final Logger LOGGER = LoggerFactory.getLogger(SyncLdapTask.class);

private LocalDateTime currTime;
Expand Down Expand Up @@ -92,9 +98,9 @@ public void doWork() {
ldapOperations.search(se, handler);
handler.processLeftovers();

LOGGER.info("Deleting entries from database");
LOGGER.info("Deleting entries from database which were removed from LDAP");
var deleted = transactionTemplate.execute(s -> userAccountRepository.deleteWithLdapIdElderThan(currTime));
LOGGER.info("Deleted {} entries from database", deleted);
LOGGER.info("Deleted {} entries from database which were removed from LDAP", deleted);

LOGGER.info("Sync ldap task finish");
}
Expand All @@ -120,7 +126,7 @@ private void processUserBatch(List<LdapEntity> attributes) {

if (StringUtils.hasLength(ldapUserId)) {
var o = chunk.stream().filter(ua -> ua.ldapId().equals(ldapUserId)).findAny();
if (o.isPresent()) {
if (o.isPresent()) { // update the existing user
LOGGER.info("User with ldapId={} is existing in database, deciding to update him or not", ldapUserId);
var userAccount = o.get();
var shouldUpdateInDb = false;
Expand Down Expand Up @@ -200,7 +206,7 @@ private void processUserBatch(List<LdapEntity> attributes) {
} else {
toUpdateSyncLdapTime.add(ldapUserId);
}
} else {
} else { // add the user to insert list
LOGGER.info("User with ldapId = {} does not exist in database, adding him to insert list", ldapUserId);
toInsert.add(ldapEntry);
}
Expand All @@ -214,24 +220,22 @@ private void processUserBatch(List<LdapEntity> attributes) {

LOGGER.info("Inserting {} users to database", toInsert.size());
if (!toInsert.isEmpty()) {
var usersToInsert = toInsert.stream().map(ldapEntry -> {
Set<String> rawRoles = ldapEntry.roles();
var mappedRoles = RoleMapper.map(aaaProperties.roleMappings().ldap(), rawRoles);
boolean locked = ldapEntry.locked() == null ? false : ldapEntry.locked();
boolean enabled = ldapEntry.enabled() == null ? true : ldapEntry.enabled();
return UserAccountConverter.buildUserAccountEntityForLdapInsert(
for (var ldapEntry : toInsert) {
Set<String> rawRoles = ldapEntry.roles();
var mappedRoles = RoleMapper.map(aaaProperties.roleMappings().ldap(), rawRoles);
boolean locked = ldapEntry.locked() == null ? false : ldapEntry.locked();
boolean enabled = ldapEntry.enabled() == null ? true : ldapEntry.enabled();
var userToInsert = UserAccountConverter.buildUserAccountEntityForLdapInsert(
ldapEntry.username(),
ldapEntry.id(),
mappedRoles,
ldapEntry.email(),
locked,
enabled,
currTime
);
}
).toList();
var inserted = userAccountRepository.saveAll(usersToInsert);
LOGGER.debug("Inserted {} to database", inserted);
);
conflictService.process(userToInsert, this);
}
}

if (!toUpdateSyncLdapTime.isEmpty()) {
Expand All @@ -240,6 +244,16 @@ private void processUserBatch(List<LdapEntity> attributes) {
}
});
}

@Override
public void saveUser(UserAccount userAccount) {
userAccountRepository.save(userAccount);
}

@Override
public void removeUser(UserAccount userAccount) {
userAccountRepository.deleteById(userAccount.id());
}
}

class MappingConsumingCallbackHandler<T> implements NameClassPairCallbackHandler {
Expand Down
4 changes: 3 additions & 1 deletion aaa/src/test/java/name/nkonev/aaa/TestConstants.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ public class TestConstants {
public static final String USER_BOB_LDAP_PASSWORD = "bobspassword"; // see in src/test/resources/test-server.ldif
public static final String USER_BOB_LDAP_ID = "2"; // see in src/test/resources/test-server.ldif
public static final String USER_BOB_LDAP_EMAIL = "bham@sf.org";
public static final String USER_NIKITA = "nikita";

public static final String USER_BEN_LDAP = "ben"; // conflicting
public static final String USER_BEN_LDAP_EMAIL = "ben@sf.org";

public static final String USER_LOCKED = "generated_user_66";
public static final String COMMON_PASSWORD = "generated_user_password";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -604,6 +604,33 @@ public void ldapSyncCreatesUsers() {
Assertions.assertEquals(4L, ldapUsersAfter);
}

@Test
public void ldapSyncOverridesUsers() {
var conflictingLogin = USER_BEN_LDAP;
var conflictingEmail = conflictingLogin+"@example.com";
UserAccount userAccount = new UserAccount(
null,
CreationType.REGISTRATION,
conflictingLogin, null, null, null, null,false, false, true, true,
new UserRole[]{UserRole.ROLE_USER}, conflictingEmail, null, null, null, null, null, null, null, null);
userAccountRepository.save(userAccount);
var before = userAccountRepository.findByUsername(conflictingLogin).get();
Assertions.assertEquals(conflictingEmail, before.email());

var ldapUsersBefore = jdbcTemplate.queryForObject("select count (*) from user_account where ldap_id is not null", Long.class);
Assertions.assertEquals(0L, ldapUsersBefore);

syncLdapTask.doWork();

var ldapUsersAfter = jdbcTemplate.queryForObject("select count (*) from user_account where ldap_id is not null", Long.class);
Assertions.assertEquals(4L, ldapUsersAfter);

var after = userAccountRepository.findByUsername(conflictingLogin).get();
Assertions.assertNotEquals(conflictingEmail, after.email());
Assertions.assertEquals(USER_BEN_LDAP_EMAIL, after.email());
Assertions.assertNotEquals(before.id(), after.id());
}

final String userForChangeEmail0 = "generated_user_20";
@WithUserDetails(userForChangeEmail0)
@Test
Expand Down
1 change: 1 addition & 0 deletions aaa/src/test/resources/config/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,7 @@ custom.ldap:
password:
encodingType: ""
strength: 10
resolve-conflicts-strategy: WRITE_NEW_AND_REMOVE_OLD

custom.http-client:
connect-timeout: 3s
Expand Down

0 comments on commit 35f99aa

Please sign in to comment.