/* * The MIT License * * Copyright (c) 2014 Stellar Science Ltd Co, K. R. Walker * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package hudson.plugins.emailext.plugins.recipients; import com.google.common.base.Predicates; import com.google.common.collect.Iterables; import hudson.EnvVars; import hudson.model.AbstractBuild; import hudson.model.Cause; import hudson.model.Item; import hudson.model.Run; import hudson.model.TaskListener; import hudson.model.User; import hudson.plugins.emailext.EmailRecipientUtils; import hudson.plugins.emailext.ExtendedEmailPublisherContext; import hudson.scm.ChangeLogSet; import hudson.tasks.MailSender; import javax.mail.internet.InternetAddress; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.logging.Logger; import javax.annotation.CheckForNull; import jenkins.model.Jenkins; import org.acegisecurity.Authentication; import org.acegisecurity.userdetails.UsernameNotFoundException; public final class RecipientProviderUtilities { private static final Logger LOGGER = Logger.getLogger(RecipientProviderUtilities.class.getName()); private RecipientProviderUtilities() { } public interface IDebug { void send(final String format, final Object... args); } public static Set<User> getChangeSetAuthors(final Collection<Run<?, ?>> runs, final IDebug debug) { debug.send(" Collecting change authors..."); final Set<User> users = new HashSet<>(); for (final Run<?, ?> run : runs) { debug.send(" build: %d", run.getNumber()); if (run instanceof AbstractBuild<?,?>) { final ChangeLogSet<?> changeLogSet = ((AbstractBuild<?,?>)run).getChangeSet(); if (changeLogSet == null) { debug.send(" changeLogSet was null"); } else { addChangeSetUsers(changeLogSet, users, debug); } } else { try { Method getChangeSets = run.getClass().getMethod("getChangeSets"); if (List.class.isAssignableFrom(getChangeSets.getReturnType())) { @SuppressWarnings("unchecked") List<ChangeLogSet<ChangeLogSet.Entry>> sets = (List<ChangeLogSet<ChangeLogSet.Entry>>) getChangeSets.invoke(run); if (Iterables.all(sets, Predicates.instanceOf(ChangeLogSet.class))) { for (ChangeLogSet<ChangeLogSet.Entry> set : sets) { addChangeSetUsers(set, users, debug); } } } } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) { debug.send("Exception getting changesets for %s: %s", run, e); } } } return users; } private static void addChangeSetUsers(ChangeLogSet<?> changeLogSet, Set<User> users, IDebug debug) { final Set<User> changeAuthors = new HashSet<User>(); for (final ChangeLogSet.Entry change : changeLogSet) { final User changeAuthor = change.getAuthor(); if (changeAuthors.add(changeAuthor)) { debug.send(" adding author: %s", changeAuthor.getFullName()); } } users.addAll(changeAuthors); } public static Set<User> getUsersTriggeringTheBuilds(final Collection<Run<?, ?>> runs, final IDebug debug) { debug.send(" Collecting build requestors..."); final Set<User> users = new HashSet<>(); for (final Run<?, ?> run : runs) { debug.send(" build: %d", run.getNumber()); final User buildRequestor = getUserTriggeringTheBuild(run); if (buildRequestor != null) { debug.send(" adding requestor: %s", buildRequestor.getFullName()); users.add(buildRequestor); } else { debug.send(" buildRequestor was null"); } } return users; } private static User getByUserIdCause(Run<?, ?> run) { try { Cause.UserIdCause cause = run.getCause(Cause.UserIdCause.class); if (cause != null) { String id = cause.getUserId(); return User.get(id, false, null); } } catch (Exception e) { LOGGER.info(e.getMessage()); } return null; } @SuppressWarnings("deprecated") private static User getByLegacyUserCause(Run<?, ?> run) { try { Cause.UserCause userCause = run.getCause(Cause.UserCause.class); // userCause.getUserName() returns displayName which may be different from authentication name // Therefore use reflection to access the real authenticationName if (userCause != null) { Field authenticationName = Cause.UserCause.class.getDeclaredField("authenticationName"); authenticationName.setAccessible(true); String name = (String) authenticationName.get(userCause); return User.get(name, false, null); } } catch (Exception e) { LOGGER.info(e.getMessage()); } return null; } public static User getUserTriggeringTheBuild(final Run<?, ?> run) { User user = getByUserIdCause(run); if (user == null) { user = getByLegacyUserCause(run); } return user; } @Deprecated public static void addUsers(final Set<User> users, final TaskListener listener, final EnvVars env, final Set<InternetAddress> to, final Set<InternetAddress> cc, final Set<InternetAddress> bcc, final IDebug debug) { addUsers(users, listener, null, env, to, cc, bcc, debug); } public static void addUsers(final Set<User> users, final ExtendedEmailPublisherContext context, final EnvVars env, final Set<InternetAddress> to, final Set<InternetAddress> cc, final Set<InternetAddress> bcc, final IDebug debug) { addUsers(users, context.getListener(), context.getRun(), env, to, cc, bcc, debug); } /** If set, send to known users who lack {@link Item#READ} access to the job. */ static /* not final */ boolean SEND_TO_USERS_WITHOUT_READ = Boolean.getBoolean(MailSender.class.getName() + ".SEND_TO_USERS_WITHOUT_READ"); /** If set, send to unknown users. */ static /* not final */ boolean SEND_TO_UNKNOWN_USERS = Boolean.getBoolean(MailSender.class.getName() + ".SEND_TO_UNKNOWN_USERS"); private static void addUsers(final Set<User> users, final TaskListener listener, @CheckForNull Run<?,?> run, final EnvVars env, final Set<InternetAddress> to, final Set<InternetAddress> cc, final Set<InternetAddress> bcc, final IDebug debug) { for (final User user : users) { if (EmailRecipientUtils.isExcludedRecipient(user, listener)) { debug.send("User %s is an excluded recipient.", user.getFullName()); } else { final String userAddress = EmailRecipientUtils.getUserConfiguredEmail(user); if (userAddress != null) { if (Jenkins.getActiveInstance().isUseSecurity()) { try { Authentication auth = user.impersonate(); if (run != null && !run.getACL().hasPermission(auth, Item.READ)) { if (SEND_TO_USERS_WITHOUT_READ) { listener.getLogger().printf("Warning: user %s has no permission to view %s, but sending mail anyway%n", userAddress, run.getFullDisplayName()); } else { listener.getLogger().printf("Not sending mail to user %s with no permission to view %s", userAddress, run.getFullDisplayName()); continue; } } } catch (UsernameNotFoundException x) { if (SEND_TO_UNKNOWN_USERS) { listener.getLogger().printf("Warning: %s is not a recognized user, but sending mail anyway%n", userAddress); } else { listener.getLogger().printf("Not sending mail to unregistered user %s%n", userAddress); continue; } } } debug.send("Adding %s with address %s", user.getFullName(), userAddress); EmailRecipientUtils.addAddressesFromRecipientList(to, cc, bcc, userAddress, env, listener); } else { listener.getLogger().println("Failed to send e-mail to " + user.getFullName() + " because no e-mail address is known, and no default e-mail domain is configured"); } } } } }