| // Copyright (C) 2009 The Android Open Source Project | |
| // | |
| // Licensed under the Apache License, Version 2.0 (the "License"); | |
| // you may not use this file except in compliance with the License. | |
| // You may obtain a copy of the License at | |
| // | |
| // http://www.apache.org/licenses/LICENSE-2.0 | |
| // | |
| // Unless required by applicable law or agreed to in writing, software | |
| // distributed under the License is distributed on an "AS IS" BASIS, | |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| // See the License for the specific language governing permissions and | |
| // limitations under the License. | |
| package com.google.gerrit.server.mail.send; | |
| import static java.nio.charset.StandardCharsets.UTF_8; | |
| import com.google.common.io.BaseEncoding; | |
| import com.google.gerrit.common.data.ParameterizedString; | |
| import com.google.gerrit.entities.Account; | |
| import com.google.gerrit.entities.Address; | |
| import com.google.gerrit.server.GerritPersonIdent; | |
| import com.google.gerrit.server.account.AccountCache; | |
| import com.google.gerrit.server.account.AccountState; | |
| import com.google.gerrit.server.config.AnonymousCowardName; | |
| import com.google.gerrit.server.config.GerritServerConfig; | |
| import com.google.gerrit.server.mail.MailUtil; | |
| import com.google.inject.Inject; | |
| import com.google.inject.Provider; | |
| import com.google.inject.Singleton; | |
| import java.security.MessageDigest; | |
| import java.security.NoSuchAlgorithmException; | |
| import java.util.Optional; | |
| import java.util.regex.Pattern; | |
| import org.eclipse.jgit.lib.Config; | |
| import org.eclipse.jgit.lib.PersonIdent; | |
| /** Creates a {@link FromAddressGenerator} from the {@link GerritServerConfig} */ | |
| @Singleton | |
| public class FromAddressGeneratorProvider implements Provider<FromAddressGenerator> { | |
| private final FromAddressGenerator generator; | |
| @Inject | |
| FromAddressGeneratorProvider( | |
| @GerritServerConfig Config cfg, | |
| @AnonymousCowardName String anonymousCowardName, | |
| @GerritPersonIdent PersonIdent myIdent, | |
| AccountCache accountCache) { | |
| final String from = cfg.getString("sendemail", null, "from"); | |
| final Address srvAddr = toAddress(myIdent); | |
| if (from == null || "MIXED".equalsIgnoreCase(from)) { | |
| ParameterizedString name = new ParameterizedString("${user} (Code Review)"); | |
| generator = new PatternGen(srvAddr, accountCache, anonymousCowardName, name, srvAddr.email()); | |
| } else if ("USER".equalsIgnoreCase(from)) { | |
| String[] domains = cfg.getStringList("sendemail", null, "allowedDomain"); | |
| Pattern domainPattern = MailUtil.glob(domains); | |
| ParameterizedString namePattern = new ParameterizedString("${user} (Code Review)"); | |
| generator = | |
| new UserGen(accountCache, domainPattern, anonymousCowardName, namePattern, srvAddr); | |
| } else if ("SERVER".equalsIgnoreCase(from)) { | |
| generator = new ServerGen(srvAddr); | |
| } else { | |
| final Address a = Address.parse(from); | |
| final ParameterizedString name = a.name() != null ? new ParameterizedString(a.name()) : null; | |
| if (name == null || name.getParameterNames().isEmpty()) { | |
| generator = new ServerGen(a); | |
| } else { | |
| generator = new PatternGen(srvAddr, accountCache, anonymousCowardName, name, a.email()); | |
| } | |
| } | |
| } | |
| private static Address toAddress(PersonIdent myIdent) { | |
| return Address.create(myIdent.getName(), myIdent.getEmailAddress()); | |
| } | |
| @Override | |
| public FromAddressGenerator get() { | |
| return generator; | |
| } | |
| static final class UserGen implements FromAddressGenerator { | |
| private final AccountCache accountCache; | |
| private final Pattern domainPattern; | |
| private final String anonymousCowardName; | |
| private final ParameterizedString nameRewriteTmpl; | |
| private final Address serverAddress; | |
| /** | |
| * From address generator for USER mode | |
| * | |
| * @param accountCache get user account from id | |
| * @param domainPattern allowed user domain pattern that Gerrit can send as the user | |
| * @param anonymousCowardName name used when user's full name is missing | |
| * @param nameRewriteTmpl name template used for rewriting the sender's name when Gerrit can not | |
| * send as the user | |
| * @param serverAddress serverAddress.name is used when fromId is null and serverAddress.email | |
| * is used when Gerrit can not send as the user | |
| */ | |
| UserGen( | |
| AccountCache accountCache, | |
| Pattern domainPattern, | |
| String anonymousCowardName, | |
| ParameterizedString nameRewriteTmpl, | |
| Address serverAddress) { | |
| this.accountCache = accountCache; | |
| this.domainPattern = domainPattern; | |
| this.anonymousCowardName = anonymousCowardName; | |
| this.nameRewriteTmpl = nameRewriteTmpl; | |
| this.serverAddress = serverAddress; | |
| } | |
| @Override | |
| public boolean isGenericAddress(Account.Id fromId) { | |
| return false; | |
| } | |
| @Override | |
| public Address from(Account.Id fromId) { | |
| String senderName; | |
| if (fromId != null) { | |
| Optional<Account> a = accountCache.get(fromId).map(AccountState::account); | |
| String fullName = a.map(Account::fullName).orElse(null); | |
| String userEmail = a.map(Account::preferredEmail).orElse(null); | |
| if (canRelay(userEmail)) { | |
| return Address.create(fullName, userEmail); | |
| } | |
| if (fullName == null || "".equals(fullName.trim())) { | |
| fullName = anonymousCowardName; | |
| } | |
| senderName = nameRewriteTmpl.replace("user", fullName).toString(); | |
| } else { | |
| senderName = serverAddress.name(); | |
| } | |
| String senderEmail; | |
| ParameterizedString senderEmailPattern = new ParameterizedString(serverAddress.email()); | |
| if (senderEmailPattern.getParameterNames().isEmpty()) { | |
| senderEmail = senderEmailPattern.getRawPattern(); | |
| } else { | |
| senderEmail = senderEmailPattern.replace("userHash", hashOf(senderName)).toString(); | |
| } | |
| return Address.create(senderName, senderEmail); | |
| } | |
| /** check if Gerrit is allowed to send from {@code userEmail}. */ | |
| private boolean canRelay(String userEmail) { | |
| if (userEmail != null) { | |
| int index = userEmail.indexOf('@'); | |
| if (index > 0 && index < userEmail.length() - 1) { | |
| return domainPattern.matcher(userEmail.substring(index + 1)).matches(); | |
| } | |
| } | |
| return false; | |
| } | |
| } | |
| static final class ServerGen implements FromAddressGenerator { | |
| private final Address srvAddr; | |
| ServerGen(Address srvAddr) { | |
| this.srvAddr = srvAddr; | |
| } | |
| @Override | |
| public boolean isGenericAddress(Account.Id fromId) { | |
| return true; | |
| } | |
| @Override | |
| public Address from(Account.Id fromId) { | |
| return srvAddr; | |
| } | |
| } | |
| static final class PatternGen implements FromAddressGenerator { | |
| private final ParameterizedString senderEmailPattern; | |
| private final Address serverAddress; | |
| private final AccountCache accountCache; | |
| private final String anonymousCowardName; | |
| private final ParameterizedString namePattern; | |
| PatternGen( | |
| final Address serverAddress, | |
| final AccountCache accountCache, | |
| final String anonymousCowardName, | |
| final ParameterizedString namePattern, | |
| final String senderEmail) { | |
| this.senderEmailPattern = new ParameterizedString(senderEmail); | |
| this.serverAddress = serverAddress; | |
| this.accountCache = accountCache; | |
| this.anonymousCowardName = anonymousCowardName; | |
| this.namePattern = namePattern; | |
| } | |
| @Override | |
| public boolean isGenericAddress(Account.Id fromId) { | |
| return false; | |
| } | |
| @Override | |
| public Address from(Account.Id fromId) { | |
| final String senderName; | |
| if (fromId != null) { | |
| String fullName = accountCache.get(fromId).map(a -> a.account().fullName()).orElse(null); | |
| if (fullName == null || "".equals(fullName)) { | |
| fullName = anonymousCowardName; | |
| } | |
| senderName = namePattern.replace("user", fullName).toString(); | |
| } else { | |
| senderName = serverAddress.name(); | |
| } | |
| String senderEmail; | |
| if (senderEmailPattern.getParameterNames().isEmpty()) { | |
| senderEmail = senderEmailPattern.getRawPattern(); | |
| } else { | |
| senderEmail = senderEmailPattern.replace("userHash", hashOf(senderName)).toString(); | |
| } | |
| return Address.create(senderName, senderEmail); | |
| } | |
| } | |
| private static String hashOf(String data) { | |
| try { | |
| MessageDigest hash = MessageDigest.getInstance("MD5"); | |
| byte[] bytes = hash.digest(data.getBytes(UTF_8)); | |
| return BaseEncoding.base64Url().encode(bytes); | |
| } catch (NoSuchAlgorithmException e) { | |
| throw new IllegalStateException("No MD5 available", e); | |
| } | |
| } | |
| } |