Line data Source code
1 : // Copyright (C) 2017 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.account; 16 : 17 : import static com.google.common.collect.ImmutableList.toImmutableList; 18 : import static com.google.common.collect.ImmutableSet.toImmutableSet; 19 : 20 : import com.google.common.collect.ImmutableSet; 21 : import com.google.common.collect.ImmutableSetMultimap; 22 : import com.google.common.collect.MultimapBuilder; 23 : import com.google.common.collect.SetMultimap; 24 : import com.google.gerrit.entities.Account; 25 : import com.google.gerrit.entities.UserIdentity; 26 : import com.google.gerrit.server.account.externalids.ExternalId; 27 : import com.google.gerrit.server.account.externalids.ExternalIds; 28 : import com.google.gerrit.server.update.RetryHelper; 29 : import com.google.inject.Inject; 30 : import com.google.inject.Singleton; 31 : import java.io.IOException; 32 : import java.util.Arrays; 33 : import java.util.List; 34 : import java.util.Set; 35 : import org.eclipse.jgit.lib.PersonIdent; 36 : 37 : /** Class to access accounts by email. */ 38 : @Singleton 39 : public class Emails { 40 : private final ExternalIds externalIds; 41 : private final RetryHelper retryHelper; 42 : 43 : @Inject 44 150 : public Emails(ExternalIds externalIds, RetryHelper retryHelper) { 45 150 : this.externalIds = externalIds; 46 150 : this.retryHelper = retryHelper; 47 150 : } 48 : 49 : /** 50 : * Returns the accounts with the given email. 51 : * 52 : * <p>Each email should belong to a single account only. This means if more than one account is 53 : * returned there is an inconsistency in the external IDs. 54 : * 55 : * <p>The accounts are retrieved via the external ID cache. Each access to the external ID cache 56 : * requires reading the SHA1 of the refs/meta/external-ids branch. If accounts for multiple emails 57 : * are needed it is more efficient to use {@link #getAccountsFor(String...)} as this method reads 58 : * the SHA1 of the refs/meta/external-ids branch only once (and not once per email). 59 : * 60 : * <p>In addition accounts are included that have the given email as preferred email even if they 61 : * have no external ID for the preferred email. Having accounts with a preferred email that does 62 : * not exist as external ID is an inconsistency, but existing functionality relies on still 63 : * getting those accounts, which is why they are included. Accounts by preferred email are fetched 64 : * from the account index as a fallback for email addresses that could not be resolved using 65 : * {@link ExternalIds}. 66 : * 67 : * @see #getAccountsFor(String...) 68 : */ 69 : public ImmutableSet<Account.Id> getAccountFor(String email) throws IOException { 70 106 : ImmutableSet<Account.Id> accounts = 71 106 : externalIds.byEmail(email).stream().map(ExternalId::accountId).collect(toImmutableSet()); 72 106 : if (!accounts.isEmpty()) { 73 104 : return accounts; 74 : } 75 : 76 36 : return retryHelper 77 36 : .accountIndexQuery("queryAccountsByPreferredEmail", q -> q.byPreferredEmail(email)).call() 78 36 : .stream() 79 36 : .map(a -> a.account().id()) 80 36 : .collect(toImmutableSet()); 81 : } 82 : 83 : /** 84 : * Returns the accounts for the given emails. 85 : * 86 : * @see #getAccountFor(String) 87 : */ 88 : public ImmutableSetMultimap<String, Account.Id> getAccountsFor(String... emails) 89 : throws IOException { 90 1 : SetMultimap<String, Account.Id> result = 91 1 : MultimapBuilder.hashKeys(emails.length).hashSetValues(1).build(); 92 1 : externalIds.byEmails(emails).entries().stream() 93 1 : .forEach(e -> result.put(e.getKey(), e.getValue().accountId())); 94 1 : List<String> emailsToBackfill = 95 1 : Arrays.stream(emails).filter(e -> !result.containsKey(e)).collect(toImmutableList()); 96 1 : if (!emailsToBackfill.isEmpty()) { 97 0 : retryHelper 98 0 : .accountIndexQuery( 99 0 : "queryAccountsByPreferredEmails", q -> q.byPreferredEmail(emailsToBackfill)) 100 0 : .call().entries().stream() 101 0 : .forEach(e -> result.put(e.getKey(), e.getValue().account().id())); 102 : } 103 1 : return ImmutableSetMultimap.copyOf(result); 104 : } 105 : 106 : /** 107 : * Returns the accounts with the given email. 108 : * 109 : * <p>This method behaves just like {@link #getAccountFor(String)}, except that accounts are not 110 : * looked up by their preferred email. Thus, this method does not rely on the accounts index. 111 : */ 112 : public ImmutableSet<Account.Id> getAccountForExternal(String email) throws IOException { 113 3 : return externalIds.byEmail(email).stream().map(ExternalId::accountId).collect(toImmutableSet()); 114 : } 115 : 116 : public UserIdentity toUserIdentity(PersonIdent who) throws IOException { 117 103 : UserIdentity u = new UserIdentity(); 118 103 : u.setName(who.getName()); 119 103 : u.setEmail(who.getEmailAddress()); 120 103 : u.setDate(who.getWhenAsInstant()); 121 103 : u.setTimeZone(who.getTimeZoneOffset()); 122 : 123 : // If only one account has access to this email address, select it 124 : // as the identity of the user. 125 : // 126 103 : Set<Account.Id> a = getAccountFor(u.getEmail()); 127 103 : if (a.size() == 1) { 128 101 : u.setAccount(a.iterator().next()); 129 : } 130 : 131 103 : return u; 132 : } 133 : }