LCOV - code coverage report
Current view: top level - server/account - InternalAccountDirectory.java (source / functions) Hit Total Coverage
Test: _coverage_report.dat Lines: 95 113 84.1 %
Date: 2022-11-19 15:00:39 Functions: 14 15 93.3 %

          Line data    Source code
       1             : // Copyright (C) 2013 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.Streams.stream;
      18             : import static java.util.stream.Collectors.toList;
      19             : import static java.util.stream.Collectors.toSet;
      20             : import static java.util.stream.Stream.concat;
      21             : 
      22             : import com.google.common.base.Strings;
      23             : import com.google.common.collect.Sets;
      24             : import com.google.gerrit.entities.Account;
      25             : import com.google.gerrit.extensions.common.AccountInfo;
      26             : import com.google.gerrit.extensions.common.AccountInfo.Tags;
      27             : import com.google.gerrit.extensions.common.AvatarInfo;
      28             : import com.google.gerrit.extensions.registration.DynamicItem;
      29             : import com.google.gerrit.extensions.registration.DynamicMap;
      30             : import com.google.gerrit.server.CurrentUser;
      31             : import com.google.gerrit.server.IdentifiedUser;
      32             : import com.google.gerrit.server.account.externalids.ExternalId;
      33             : import com.google.gerrit.server.avatar.AvatarProvider;
      34             : import com.google.gerrit.server.data.AccountAttribute;
      35             : import com.google.gerrit.server.permissions.GlobalPermission;
      36             : import com.google.gerrit.server.permissions.PermissionBackend;
      37             : import com.google.gerrit.server.permissions.PermissionBackendException;
      38             : import com.google.inject.AbstractModule;
      39             : import com.google.inject.Inject;
      40             : import com.google.inject.Provider;
      41             : import com.google.inject.Singleton;
      42             : import java.util.ArrayList;
      43             : import java.util.Collection;
      44             : import java.util.Collections;
      45             : import java.util.EnumSet;
      46             : import java.util.List;
      47             : import java.util.Map;
      48             : import java.util.Objects;
      49             : import java.util.Set;
      50             : import java.util.stream.Stream;
      51             : 
      52             : @Singleton
      53             : public class InternalAccountDirectory extends AccountDirectory {
      54         150 :   static final Set<FillOptions> ID_ONLY = Collections.unmodifiableSet(EnumSet.of(FillOptions.ID));
      55         150 :   static final Set<FillOptions> ALL_ACCOUNT_ATTRIBUTES =
      56         150 :       Collections.unmodifiableSet(
      57         150 :           EnumSet.of(FillOptions.NAME, FillOptions.EMAIL, FillOptions.USERNAME));
      58             : 
      59         138 :   public static class InternalAccountDirectoryModule extends AbstractModule {
      60             :     @Override
      61             :     protected void configure() {
      62         138 :       bind(AccountDirectory.class).to(InternalAccountDirectory.class);
      63         138 :     }
      64             :   }
      65             : 
      66             :   private final AccountCache accountCache;
      67             :   private final DynamicItem<AvatarProvider> avatar;
      68             :   private final IdentifiedUser.GenericFactory userFactory;
      69             :   private final Provider<CurrentUser> self;
      70             :   private final PermissionBackend permissionBackend;
      71             :   private final ServiceUserClassifier serviceUserClassifier;
      72             :   private final DynamicMap<AccountTagProvider> accountTagProviders;
      73             : 
      74             :   @Inject
      75             :   InternalAccountDirectory(
      76             :       AccountCache accountCache,
      77             :       DynamicItem<AvatarProvider> avatar,
      78             :       IdentifiedUser.GenericFactory userFactory,
      79             :       Provider<CurrentUser> self,
      80             :       PermissionBackend permissionBackend,
      81             :       ServiceUserClassifier serviceUserClassifier,
      82         150 :       DynamicMap<AccountTagProvider> accountTagProviders) {
      83         150 :     this.accountCache = accountCache;
      84         150 :     this.avatar = avatar;
      85         150 :     this.userFactory = userFactory;
      86         150 :     this.self = self;
      87         150 :     this.permissionBackend = permissionBackend;
      88         150 :     this.serviceUserClassifier = serviceUserClassifier;
      89         150 :     this.accountTagProviders = accountTagProviders;
      90         150 :   }
      91             : 
      92             :   @Override
      93             :   public void fillAccountInfo(Iterable<? extends AccountInfo> in, Set<FillOptions> options)
      94             :       throws PermissionBackendException {
      95         109 :     if (options.equals(ID_ONLY)) {
      96          74 :       return;
      97             :     }
      98             : 
      99         109 :     boolean canModifyAccount = false;
     100         109 :     Account.Id currentUserId = null;
     101         109 :     if (self.get().isIdentifiedUser()) {
     102         109 :       currentUserId = self.get().getAccountId();
     103         109 :       if (permissionBackend.currentUser().test(GlobalPermission.MODIFY_ACCOUNT)) {
     104         104 :         canModifyAccount = true;
     105             :       }
     106             :     }
     107             : 
     108         109 :     Set<FillOptions> fillOptionsWithoutSecondaryEmails =
     109         109 :         Sets.difference(options, EnumSet.of(FillOptions.SECONDARY_EMAILS));
     110         109 :     Set<Account.Id> ids = stream(in).map(a -> Account.id(a._accountId)).collect(toSet());
     111         109 :     Map<Account.Id, AccountState> accountStates = accountCache.get(ids);
     112         109 :     for (AccountInfo info : in) {
     113         109 :       Account.Id id = Account.id(info._accountId);
     114         109 :       AccountState state = accountStates.get(id);
     115         109 :       if (state != null) {
     116         109 :         if (!options.contains(FillOptions.SECONDARY_EMAILS)
     117           8 :             || Objects.equals(currentUserId, state.account().id())
     118             :             || canModifyAccount) {
     119         109 :           fill(info, accountStates.get(id), options);
     120             :         } else {
     121             :           // user is not allowed to see secondary emails
     122           2 :           fill(info, accountStates.get(id), fillOptionsWithoutSecondaryEmails);
     123             :         }
     124             : 
     125             :       } else {
     126           0 :         info._accountId = options.contains(FillOptions.ID) ? id.get() : null;
     127             :       }
     128         109 :     }
     129         109 :   }
     130             : 
     131             :   @Override
     132             :   public void fillAccountAttributeInfo(Iterable<? extends AccountAttribute> in) {
     133           3 :     Set<Account.Id> ids = stream(in).map(a -> Account.id(a.accountId)).collect(toSet());
     134           3 :     Map<Account.Id, AccountState> accountStates = accountCache.get(ids);
     135           3 :     for (AccountAttribute accountAttribute : in) {
     136           3 :       Account.Id id = Account.id(accountAttribute.accountId);
     137           3 :       AccountState accountState = accountStates.get(id);
     138           3 :       if (accountState != null) {
     139           3 :         fill(accountAttribute, accountState, ALL_ACCOUNT_ATTRIBUTES);
     140             :       } else {
     141           0 :         accountAttribute.accountId = null;
     142             :       }
     143           3 :     }
     144           3 :   }
     145             : 
     146             :   private void fill(
     147             :       AccountAttribute accountAttribute, AccountState accountState, Set<FillOptions> options) {
     148           3 :     Account account = accountState.account();
     149           3 :     if (options.contains(FillOptions.NAME)) {
     150           3 :       accountAttribute.name = Strings.emptyToNull(account.fullName());
     151           3 :       if (accountAttribute.name == null) {
     152           1 :         accountAttribute.name = accountState.userName().orElse(null);
     153             :       }
     154             :     }
     155           3 :     if (options.contains(FillOptions.EMAIL)) {
     156           3 :       accountAttribute.email = account.preferredEmail();
     157             :     }
     158           3 :     if (options.contains(FillOptions.USERNAME)) {
     159           3 :       accountAttribute.username = accountState.userName().orElse(null);
     160             :     }
     161           3 :     if (options.contains(FillOptions.ID)) {
     162           0 :       accountAttribute.accountId = account.id().get();
     163             :     } else {
     164             :       // Was previously set to look up account for filling.
     165           3 :       accountAttribute.accountId = null;
     166             :     }
     167           3 :   }
     168             : 
     169             :   private void fill(AccountInfo info, AccountState accountState, Set<FillOptions> options) {
     170         109 :     Account account = accountState.account();
     171         109 :     if (options.contains(FillOptions.ID)) {
     172         109 :       info._accountId = account.id().get();
     173             :     } else {
     174             :       // Was previously set to look up account for filling.
     175           0 :       info._accountId = null;
     176             :     }
     177         109 :     if (options.contains(FillOptions.NAME)) {
     178         109 :       info.name = Strings.emptyToNull(account.fullName());
     179         109 :       if (info.name == null) {
     180          18 :         info.name = accountState.userName().orElse(null);
     181             :       }
     182             :     }
     183         109 :     if (options.contains(FillOptions.EMAIL)) {
     184         109 :       info.email = account.preferredEmail();
     185             :     }
     186         109 :     if (options.contains(FillOptions.SECONDARY_EMAILS)) {
     187           8 :       info.secondaryEmails = getSecondaryEmails(account, accountState.externalIds());
     188             :     }
     189         109 :     if (options.contains(FillOptions.USERNAME)) {
     190         109 :       info.username = accountState.userName().orElse(null);
     191             :     }
     192             : 
     193         109 :     if (options.contains(FillOptions.DISPLAY_NAME)) {
     194         109 :       info.displayName = account.displayName();
     195             :     }
     196             : 
     197         109 :     if (options.contains(FillOptions.STATUS)) {
     198         109 :       info.status = account.status();
     199             :     }
     200             : 
     201         109 :     if (options.contains(FillOptions.STATE)) {
     202         109 :       info.inactive = account.inactive() ? true : null;
     203             :     }
     204             : 
     205         109 :     if (options.contains(FillOptions.TAGS)) {
     206         109 :       List<String> tags = getTags(account.id());
     207         109 :       if (!tags.isEmpty()) {
     208           2 :         info.tags = tags;
     209             :       }
     210             :     }
     211             : 
     212         109 :     if (options.contains(FillOptions.AVATARS)) {
     213         109 :       AvatarProvider ap = avatar.get();
     214         109 :       if (ap != null) {
     215           0 :         info.avatars = new ArrayList<>();
     216           0 :         IdentifiedUser user = userFactory.create(account.id());
     217             : 
     218             :         // PolyGerrit UI uses the following sizes for avatars:
     219             :         // - 32px for avatars next to names e.g. on the dashboard. This is also Gerrit's default.
     220             :         // - 56px for the user's own avatar in the menu
     221             :         // - 100ox for other user's avatars on dashboards
     222             :         // - 120px for the user's own profile settings page
     223           0 :         addAvatar(ap, info, user, AvatarInfo.DEFAULT_SIZE);
     224           0 :         if (!info.avatars.isEmpty()) {
     225           0 :           addAvatar(ap, info, user, 56);
     226           0 :           addAvatar(ap, info, user, 100);
     227           0 :           addAvatar(ap, info, user, 120);
     228             :         }
     229             :       }
     230             :     }
     231         109 :   }
     232             : 
     233             :   public List<String> getSecondaryEmails(Account account, Collection<ExternalId> externalIds) {
     234           8 :     return ExternalId.getEmails(externalIds)
     235           8 :         .filter(e -> !e.equals(account.preferredEmail()))
     236           8 :         .sorted()
     237           8 :         .collect(toList());
     238             :   }
     239             : 
     240             :   private List<String> getTags(Account.Id id) {
     241         109 :     Stream<String> tagsFromProviders =
     242         109 :         stream(accountTagProviders.iterator())
     243         109 :             .flatMap(accountTagProvider -> accountTagProvider.get().getTags(id).stream());
     244             :     Stream<String> tagsFromServiceUserClassifier =
     245         109 :         serviceUserClassifier.isServiceUser(id) ? Stream.of(Tags.SERVICE_USER) : Stream.empty();
     246         109 :     return concat(tagsFromProviders, tagsFromServiceUserClassifier).collect(toList());
     247             :   }
     248             : 
     249             :   private static void addAvatar(
     250             :       AvatarProvider provider, AccountInfo account, IdentifiedUser user, int size) {
     251           0 :     String url = provider.getUrl(user, size);
     252           0 :     if (url != null) {
     253           0 :       AvatarInfo avatar = new AvatarInfo();
     254           0 :       avatar.url = url;
     255           0 :       avatar.height = size;
     256           0 :       account.avatars.add(avatar);
     257             :     }
     258           0 :   }
     259             : }

Generated by: LCOV version 1.16+git.20220603.dfeb750