Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: allow relying on manually defined user ID when resolving users/email #954

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,16 @@
import hudson.tasks.MailAddressResolver;
import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.apache.commons.lang.StringUtils;
import org.apache.http.HttpEntity;
Expand Down Expand Up @@ -91,44 +96,82 @@
this.mailAddressResolvers = mailAddressResolvers;
}

protected String resolveUserId(User user) {
Optional<String> userId = Optional.ofNullable(mailAddressResolvers)
.map(Collection::stream)
.orElseGet(Stream::empty)
.map(resolver -> {
try {
return resolver.findMailAddressFor(user);
} catch (Exception ex) {
LOGGER.log(Level.WARNING, String.format(
"The email resolver '%s' failed", resolver.getClass().getName()), ex);
return null;
}
})
.filter(StringUtils::isNotEmpty)
.map(this::resolveUserIdForEmailAddress)
.filter(StringUtils::isNotEmpty)
.findAny();
private String resolveUserEmail(User user){
return Optional.ofNullable(mailAddressResolvers)
.map(Collection::stream)
.orElseGet(Stream::empty)
.map(resolver -> {
try {
String email = resolver.findMailAddressFor(user);
LOGGER.log(Level.FINEST, String.format(
"The email resolver '%s' resolved %s as %s", resolver.getClass().getName(), user.getId(), email));
return email;
} catch (Exception ex) {
LOGGER.log(Level.WARNING, String.format(
"The email resolver '%s' failed", resolver.getClass().getName()), ex);
return null;
}
})
.filter(StringUtils::isNotEmpty)
.findAny()
.orElse(null);
}

protected SlackUserProperty fetchUserSlackProperty(User user) {
String userEmail = resolveUserEmail(user);

SlackUserProperty userProperty = null;
if(userEmail != null){
userProperty = resolveSlackPropertyFromEmailViaSlack(userEmail);
}

// Return value can be null, so Optional.orElseGet(Supplier) doesn't work.
if (userId.isPresent()) {
return userId.get();
if (userProperty != null) {
return userProperty;
} else if (defaultMailAddressResolver != null){
return resolveUserIdForEmailAddress(defaultMailAddressResolver.apply(user));
return resolveSlackPropertyFromEmailViaSlack(defaultMailAddressResolver.apply(user));
} else {
return null;
}
}

public String resolveUserIdForEmailAddress(String emailAddress) {
SlackUserProperty userProperty = resolveSlackProperty(emailAddress);
if(userProperty == null){
return null;
}
if(userProperty.getDisableNotifications()){

Check warning on line 143 in src/main/java/jenkins/plugins/slack/user/EmailSlackUserIdResolver.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 143 is only partially covered, one branch is missing
return null;

Check warning on line 144 in src/main/java/jenkins/plugins/slack/user/EmailSlackUserIdResolver.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 144 is not covered by tests
}
return userProperty.getUserId();
}

private SlackUserProperty resolveSlackProperty(String emailAddress) {
if (StringUtils.isEmpty(emailAddress)) {
LOGGER.fine("Email address was empty");
return null;
}

SlackUserProperty userProperty = resolveSlackPropertyFromEmailViaSlack(emailAddress);
if(userProperty == null) {
userProperty = resolveSlackPropertyFromEmailViaInferredUsername(emailAddress);
}
if(userProperty == null) {
userProperty = resolveSlackPropertyFromEmailViaUsers(emailAddress);
}

return userProperty;
}

private SlackUserProperty resolveSlackPropertyFromEmailViaSlack(String emailAddress){
if (StringUtils.isEmpty(authToken)) {
LOGGER.fine("Auth token was empty");
return null;
}
if (StringUtils.isEmpty(emailAddress)) {
LOGGER.fine("Auth token was empty");
return null;
}

String slackUserId = null;
final String url = String.format(LOOKUP_BY_EMAIL_METHOD_URL_FORMAT, emailAddress);
Expand All @@ -149,7 +192,84 @@
} catch (IOException | ParseException | JSONException ex) {
LOGGER.log(Level.WARNING, "Error getting userId from Slack", ex);
}
return slackUserId;

SlackUserProperty userProperty = null;
if(slackUserId != null){
LOGGER.fine(String.format("Found Slack ID '%s' for email '%s'", slackUserId, emailAddress));
userProperty = new SlackUserProperty();
userProperty.setDisableNotifications(false);
userProperty.setUserId(slackUserId);
} else {
LOGGER.log(Level.INFO, String.format("Failed to resolve userId from Slack from email '%s'", emailAddress));
}
return userProperty;
}

private SlackUserProperty resolveSlackPropertyFromEmailViaInferredUsername(String emailAddress){
if (StringUtils.isEmpty(emailAddress)) {

Check warning on line 209 in src/main/java/jenkins/plugins/slack/user/EmailSlackUserIdResolver.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 209 is only partially covered, one branch is missing
return null;

Check warning on line 210 in src/main/java/jenkins/plugins/slack/user/EmailSlackUserIdResolver.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 210 is not covered by tests
}

String baseUsername = emailAddress.split("@")[0];

User user = User.get(baseUsername, false, Collections.emptyMap());
if(user == null){
LOGGER.log(Level.INFO, String.format("Could not find user with name '%s' from email '%s'", baseUsername, emailAddress));
return null;
}
String userEmail = resolveUserEmail(user);
if(userEmail != null && !emailAddress.equals(userEmail)){

Check warning on line 221 in src/main/java/jenkins/plugins/slack/user/EmailSlackUserIdResolver.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 221 is only partially covered, one branch is missing
LOGGER.log(Level.INFO, String.format("User with name '%s' does not match expected email '%s': have '%s'", baseUsername, emailAddress, userEmail));
return null;
}
SlackUserProperty userProperty = user.getProperty(SlackUserProperty.class);
if(userProperty == null){

Check warning on line 226 in src/main/java/jenkins/plugins/slack/user/EmailSlackUserIdResolver.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 226 is only partially covered, one branch is missing
LOGGER.log(Level.INFO, String.format("User with name '%s' does not have slack property", baseUsername));

Check warning on line 227 in src/main/java/jenkins/plugins/slack/user/EmailSlackUserIdResolver.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 227 is not covered by tests
} else {
LOGGER.fine(String.format("Found Slack ID '%s' for user '%s'", userProperty.getUserId(), baseUsername));
}
return userProperty;
}

private SlackUserProperty resolveSlackPropertyFromEmailViaUsers(String emailAddress){
if (StringUtils.isEmpty(emailAddress)) {

Check warning on line 235 in src/main/java/jenkins/plugins/slack/user/EmailSlackUserIdResolver.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 235 is only partially covered, one branch is missing
return null;

Check warning on line 236 in src/main/java/jenkins/plugins/slack/user/EmailSlackUserIdResolver.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 236 is not covered by tests
}

List<User> usersWithEmailAddress = User.getAll().stream()
.filter(user -> emailAddress.equals(resolveUserEmail(user)))
.collect(Collectors.toList());
List<ResolvedUserConfig> usersPerId = usersWithEmailAddress.stream()
.map(user -> new ResolvedUserConfig(user))
.filter(user -> user.slackProperty != null && user.slackProperty.getUserId() != null)

Check warning on line 244 in src/main/java/jenkins/plugins/slack/user/EmailSlackUserIdResolver.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 244 is only partially covered, 2 branches are missing
.filter(distinctSlackUserProperties())
.collect(Collectors.toList());
if(usersPerId.size() > 1) {

Check warning on line 247 in src/main/java/jenkins/plugins/slack/user/EmailSlackUserIdResolver.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 247 is only partially covered, one branch is missing
List<String> conflictingUsersDisplayName = usersPerId.stream()
.map(resolved -> resolved.user.getDisplayName())
.collect(Collectors.toList());
LOGGER.log(Level.WARNING, String.format(
"Multiple users found with email '%s' having different slack IDs or configuration: %s", emailAddress, String.join(",", conflictingUsersDisplayName)));

Check warning on line 252 in src/main/java/jenkins/plugins/slack/user/EmailSlackUserIdResolver.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered lines

Lines 248-252 are not covered by tests
}
Optional<ResolvedUserConfig> pickedUser = usersPerId.stream().findFirst();
if(pickedUser.isPresent()) {
return pickedUser.get().slackProperty;
}
return null;
}

private static class ResolvedUserConfig {
private User user;
private SlackUserProperty slackProperty;
public ResolvedUserConfig(User user) {
this.user = user;
this.slackProperty = user.getProperty(SlackUserProperty.class);
}
}

public static Predicate<ResolvedUserConfig> distinctSlackUserProperties() {
Set<String> seen = ConcurrentHashMap.newKeySet();
return t -> seen.add(String.format("%s-%b", t.slackProperty.getUserId(), t.slackProperty.getDisableNotifications()));
}

@Extension
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public NoSlackUserIdResolver() {
super(null, null);
}

protected String resolveUserId(User user) {
protected SlackUserProperty fetchUserSlackProperty(User user) {
return null;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,18 +58,11 @@
}

public final String findOrResolveUserId(User user) {
String userId = null;
SlackUserProperty userProperty = user.getProperty(SlackUserProperty.class);
if (userProperty != null) {
userId = userProperty.getUserId();
} else {
userProperty = new SlackUserProperty();
}

if (StringUtils.isEmpty(userId)) {
userId = resolveUserId(user);
if (userId != null) {
userProperty.setUserId(userId);
if (userProperty == null || StringUtils.isEmpty(userProperty.getUserId())) {

Check warning on line 63 in src/main/java/jenkins/plugins/slack/user/SlackUserIdResolver.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 63 is only partially covered, one branch is missing
userProperty = fetchUserSlackProperty(user);
if (userProperty.getUserId() != null) {

Check warning on line 65 in src/main/java/jenkins/plugins/slack/user/SlackUserIdResolver.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 65 is only partially covered, one branch is missing
try {
user.addProperty(userProperty);
} catch (IOException ex) {
Expand All @@ -79,10 +72,10 @@
}

final boolean enableNotifications = !userProperty.getDisableNotifications();
return enableNotifications ? userId : null;
return enableNotifications ? userProperty.getUserId() : null;

Check warning on line 75 in src/main/java/jenkins/plugins/slack/user/SlackUserIdResolver.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 75 is only partially covered, one branch is missing
}

protected abstract String resolveUserId(User user);
protected abstract SlackUserProperty fetchUserSlackProperty(User user);

@SuppressWarnings("unchecked")
public List<String> resolveUserIdsForRun(Run run) {
Expand Down
Loading