Line data Source code
1 : // Copyright (C) 2009 The Android Open Source Project 2 : // 3 : // Licensed under the Apache License, Version 2.0 (the "License"); 4 : // you may not use this file except in compliance with the License. 5 : // You may obtain a copy of the License at 6 : // 7 : // http://www.apache.org/licenses/LICENSE-2.0 8 : // 9 : // Unless required by applicable law or agreed to in writing, software 10 : // distributed under the License is distributed on an "AS IS" BASIS, 11 : // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 : // See the License for the specific language governing permissions and 13 : // limitations under the License. 14 : 15 : package com.google.gerrit.server.mail.send; 16 : 17 : import static java.nio.charset.StandardCharsets.UTF_8; 18 : 19 : import com.google.common.io.BaseEncoding; 20 : import com.google.gerrit.common.data.ParameterizedString; 21 : import com.google.gerrit.entities.Account; 22 : import com.google.gerrit.entities.Address; 23 : import com.google.gerrit.server.GerritPersonIdent; 24 : import com.google.gerrit.server.account.AccountCache; 25 : import com.google.gerrit.server.account.AccountState; 26 : import com.google.gerrit.server.config.AnonymousCowardName; 27 : import com.google.gerrit.server.config.GerritServerConfig; 28 : import com.google.gerrit.server.mail.MailUtil; 29 : import com.google.inject.Inject; 30 : import com.google.inject.Provider; 31 : import com.google.inject.Singleton; 32 : import java.security.MessageDigest; 33 : import java.security.NoSuchAlgorithmException; 34 : import java.util.Optional; 35 : import java.util.regex.Pattern; 36 : import org.eclipse.jgit.lib.Config; 37 : import org.eclipse.jgit.lib.PersonIdent; 38 : 39 : /** Creates a {@link FromAddressGenerator} from the {@link GerritServerConfig} */ 40 : @Singleton 41 : public class FromAddressGeneratorProvider implements Provider<FromAddressGenerator> { 42 : private final FromAddressGenerator generator; 43 : 44 : @Inject 45 : FromAddressGeneratorProvider( 46 : @GerritServerConfig Config cfg, 47 : @AnonymousCowardName String anonymousCowardName, 48 : @GerritPersonIdent PersonIdent myIdent, 49 146 : AccountCache accountCache) { 50 146 : final String from = cfg.getString("sendemail", null, "from"); 51 146 : final Address srvAddr = toAddress(myIdent); 52 : 53 146 : if (from == null || "MIXED".equalsIgnoreCase(from)) { 54 146 : ParameterizedString name = new ParameterizedString("${user} (Code Review)"); 55 146 : generator = new PatternGen(srvAddr, accountCache, anonymousCowardName, name, srvAddr.email()); 56 146 : } else if ("USER".equalsIgnoreCase(from)) { 57 1 : String[] domains = cfg.getStringList("sendemail", null, "allowedDomain"); 58 1 : Pattern domainPattern = MailUtil.glob(domains); 59 1 : ParameterizedString namePattern = new ParameterizedString("${user} (Code Review)"); 60 1 : generator = 61 : new UserGen(accountCache, domainPattern, anonymousCowardName, namePattern, srvAddr); 62 1 : } else if ("SERVER".equalsIgnoreCase(from)) { 63 1 : generator = new ServerGen(srvAddr); 64 : } else { 65 1 : final Address a = Address.parse(from); 66 1 : final ParameterizedString name = a.name() != null ? new ParameterizedString(a.name()) : null; 67 1 : if (name == null || name.getParameterNames().isEmpty()) { 68 0 : generator = new ServerGen(a); 69 : } else { 70 1 : generator = new PatternGen(srvAddr, accountCache, anonymousCowardName, name, a.email()); 71 : } 72 : } 73 146 : } 74 : 75 : private static Address toAddress(PersonIdent myIdent) { 76 146 : return Address.create(myIdent.getName(), myIdent.getEmailAddress()); 77 : } 78 : 79 : @Override 80 : public FromAddressGenerator get() { 81 146 : return generator; 82 : } 83 : 84 : static final class UserGen implements FromAddressGenerator { 85 : private final AccountCache accountCache; 86 : private final Pattern domainPattern; 87 : private final String anonymousCowardName; 88 : private final ParameterizedString nameRewriteTmpl; 89 : private final Address serverAddress; 90 : 91 : /** 92 : * From address generator for USER mode 93 : * 94 : * @param accountCache get user account from id 95 : * @param domainPattern allowed user domain pattern that Gerrit can send as the user 96 : * @param anonymousCowardName name used when user's full name is missing 97 : * @param nameRewriteTmpl name template used for rewriting the sender's name when Gerrit can not 98 : * send as the user 99 : * @param serverAddress serverAddress.name is used when fromId is null and serverAddress.email 100 : * is used when Gerrit can not send as the user 101 : */ 102 : UserGen( 103 : AccountCache accountCache, 104 : Pattern domainPattern, 105 : String anonymousCowardName, 106 : ParameterizedString nameRewriteTmpl, 107 1 : Address serverAddress) { 108 1 : this.accountCache = accountCache; 109 1 : this.domainPattern = domainPattern; 110 1 : this.anonymousCowardName = anonymousCowardName; 111 1 : this.nameRewriteTmpl = nameRewriteTmpl; 112 1 : this.serverAddress = serverAddress; 113 1 : } 114 : 115 : @Override 116 : public boolean isGenericAddress(Account.Id fromId) { 117 0 : return false; 118 : } 119 : 120 : @Override 121 : public Address from(Account.Id fromId) { 122 : String senderName; 123 1 : if (fromId != null) { 124 1 : Optional<Account> a = accountCache.get(fromId).map(AccountState::account); 125 1 : String fullName = a.map(Account::fullName).orElse(null); 126 1 : String userEmail = a.map(Account::preferredEmail).orElse(null); 127 1 : if (canRelay(userEmail)) { 128 1 : return Address.create(fullName, userEmail); 129 : } 130 : 131 1 : if (fullName == null || "".equals(fullName.trim())) { 132 0 : fullName = anonymousCowardName; 133 : } 134 1 : senderName = nameRewriteTmpl.replace("user", fullName).toString(); 135 1 : } else { 136 1 : senderName = serverAddress.name(); 137 : } 138 : 139 : String senderEmail; 140 1 : ParameterizedString senderEmailPattern = new ParameterizedString(serverAddress.email()); 141 1 : if (senderEmailPattern.getParameterNames().isEmpty()) { 142 1 : senderEmail = senderEmailPattern.getRawPattern(); 143 : } else { 144 0 : senderEmail = senderEmailPattern.replace("userHash", hashOf(senderName)).toString(); 145 : } 146 1 : return Address.create(senderName, senderEmail); 147 : } 148 : 149 : /** check if Gerrit is allowed to send from {@code userEmail}. */ 150 : private boolean canRelay(String userEmail) { 151 1 : if (userEmail != null) { 152 1 : int index = userEmail.indexOf('@'); 153 1 : if (index > 0 && index < userEmail.length() - 1) { 154 1 : return domainPattern.matcher(userEmail.substring(index + 1)).matches(); 155 : } 156 : } 157 1 : return false; 158 : } 159 : } 160 : 161 : static final class ServerGen implements FromAddressGenerator { 162 : private final Address srvAddr; 163 : 164 1 : ServerGen(Address srvAddr) { 165 1 : this.srvAddr = srvAddr; 166 1 : } 167 : 168 : @Override 169 : public boolean isGenericAddress(Account.Id fromId) { 170 0 : return true; 171 : } 172 : 173 : @Override 174 : public Address from(Account.Id fromId) { 175 1 : return srvAddr; 176 : } 177 : } 178 : 179 : static final class PatternGen implements FromAddressGenerator { 180 : private final ParameterizedString senderEmailPattern; 181 : private final Address serverAddress; 182 : private final AccountCache accountCache; 183 : private final String anonymousCowardName; 184 : private final ParameterizedString namePattern; 185 : 186 : PatternGen( 187 : final Address serverAddress, 188 : final AccountCache accountCache, 189 : final String anonymousCowardName, 190 : final ParameterizedString namePattern, 191 146 : final String senderEmail) { 192 146 : this.senderEmailPattern = new ParameterizedString(senderEmail); 193 146 : this.serverAddress = serverAddress; 194 146 : this.accountCache = accountCache; 195 146 : this.anonymousCowardName = anonymousCowardName; 196 146 : this.namePattern = namePattern; 197 146 : } 198 : 199 : @Override 200 : public boolean isGenericAddress(Account.Id fromId) { 201 103 : return false; 202 : } 203 : 204 : @Override 205 : public Address from(Account.Id fromId) { 206 : final String senderName; 207 : 208 106 : if (fromId != null) { 209 103 : String fullName = accountCache.get(fromId).map(a -> a.account().fullName()).orElse(null); 210 103 : if (fullName == null || "".equals(fullName)) { 211 13 : fullName = anonymousCowardName; 212 : } 213 103 : senderName = namePattern.replace("user", fullName).toString(); 214 : 215 103 : } else { 216 13 : senderName = serverAddress.name(); 217 : } 218 : 219 : String senderEmail; 220 106 : if (senderEmailPattern.getParameterNames().isEmpty()) { 221 106 : senderEmail = senderEmailPattern.getRawPattern(); 222 : } else { 223 0 : senderEmail = senderEmailPattern.replace("userHash", hashOf(senderName)).toString(); 224 : } 225 106 : return Address.create(senderName, senderEmail); 226 : } 227 : } 228 : 229 : private static String hashOf(String data) { 230 : try { 231 0 : MessageDigest hash = MessageDigest.getInstance("MD5"); 232 0 : byte[] bytes = hash.digest(data.getBytes(UTF_8)); 233 0 : return BaseEncoding.base64Url().encode(bytes); 234 0 : } catch (NoSuchAlgorithmException e) { 235 0 : throw new IllegalStateException("No MD5 available", e); 236 : } 237 : } 238 : }