| // Copyright (C) 2017 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.vmware.gerrit.owners.common; |
| |
| import static com.google.gerrit.server.account.ExternalId.SCHEME_GERRIT; |
| import static com.google.gerrit.server.account.ExternalId.SCHEME_MAILTO; |
| |
| import com.google.gerrit.common.errors.NoSuchGroupException; |
| import com.google.gerrit.reviewdb.client.Account; |
| import com.google.gerrit.reviewdb.client.Account.Id; |
| import com.google.gerrit.reviewdb.client.AccountGroup; |
| import com.google.gerrit.server.IdentifiedUser; |
| import com.google.gerrit.server.account.AccountCache; |
| import com.google.gerrit.server.account.AccountResolver; |
| import com.google.gerrit.server.account.AccountState; |
| import com.google.gerrit.server.account.ExternalId; |
| import com.google.gerrit.server.account.GroupCache; |
| import com.google.gerrit.server.account.GroupMembers; |
| import com.google.gerrit.server.project.NoSuchProjectException; |
| import com.google.gerrit.server.util.ManualRequestContext; |
| import com.google.gerrit.server.util.OneOffRequestContext; |
| import com.google.gwtorm.server.OrmException; |
| import com.google.inject.Inject; |
| import java.io.IOException; |
| import java.util.Collections; |
| import java.util.Optional; |
| import java.util.Set; |
| import java.util.stream.Collectors; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| public class AccountsImpl implements Accounts { |
| private static final Logger log = LoggerFactory.getLogger(AccountsImpl.class); |
| |
| private final AccountResolver resolver; |
| private final AccountCache byId; |
| private final GroupCache groupCache; |
| private final GroupMembers.Factory groupMembers; |
| private final IdentifiedUser adminUser; |
| private final OneOffRequestContext oneOffRequestContext; |
| |
| @Inject |
| public AccountsImpl( |
| AccountResolver resolver, |
| AccountCache byId, |
| GroupCache groupCache, |
| GroupMembers.Factory groupMembersFactory, |
| OneOffRequestContext oneOffRequestContext, |
| IdentifiedUser.GenericFactory userFactory) { |
| this.resolver = resolver; |
| this.byId = byId; |
| this.groupCache = groupCache; |
| this.groupMembers = groupMembersFactory; |
| this.adminUser = userFactory.create(new Account.Id(1000000)); |
| this.oneOffRequestContext = oneOffRequestContext; |
| } |
| |
| @Override |
| public Set<Account.Id> find(String nameOrEmailOrGroup) { |
| if (nameOrEmailOrGroup.startsWith(GROUP_PREFIX)) { |
| return findAccountsInGroup(nameOrEmailOrGroup.substring(GROUP_PREFIX.length())); |
| } |
| return findUserOrEmail(nameOrEmailOrGroup); |
| } |
| |
| private Set<Id> findAccountsInGroup(String groupNameOrUUID) { |
| AccountGroup group = |
| Optional.ofNullable(groupCache.get(new AccountGroup.NameKey(groupNameOrUUID))) |
| .orElse(groupCache.get(new AccountGroup.UUID(groupNameOrUUID))); |
| |
| if (group == null) { |
| log.warn("Group {} was not found", groupNameOrUUID); |
| } |
| |
| try (ManualRequestContext ctx = oneOffRequestContext.openAs(adminUser.getAccountId())) { |
| |
| return groupMembers |
| .create(adminUser) |
| .listAccounts(group.getGroupUUID(), null) |
| .stream() |
| .map(Account::getId) |
| .collect(Collectors.toSet()); |
| } catch (NoSuchGroupException | NoSuchProjectException | OrmException | IOException e) { |
| log.error("Unable to list accounts in group " + group, e); |
| return Collections.emptySet(); |
| } |
| } |
| |
| private Set<Account.Id> findUserOrEmail(String nameOrEmail) { |
| try (ManualRequestContext ctx = oneOffRequestContext.open()) { |
| Set<Id> accountIds = resolver.findAll(ctx.getReviewDbProvider().get(), nameOrEmail); |
| if (accountIds.isEmpty()) { |
| log.warn("User '{}' does not resolve to any account.", nameOrEmail); |
| return accountIds; |
| } |
| |
| Set<Id> activeAccountIds = |
| accountIds.stream().filter(this::isActive).collect(Collectors.toSet()); |
| if (activeAccountIds.isEmpty()) { |
| log.warn( |
| "User '{}' resolves to {} accounts {}, but none of them are active", |
| nameOrEmail, |
| accountIds.size(), |
| accountIds); |
| return activeAccountIds; |
| } |
| |
| Set<Id> fulllyMatchedAccountIds = |
| activeAccountIds |
| .stream() |
| .filter(id -> isFullMatch(id, nameOrEmail)) |
| .collect(Collectors.toSet()); |
| if (fulllyMatchedAccountIds.isEmpty()) { |
| log.warn( |
| "User '{}' resolves to {} accounts {}, but does not correspond to any them", |
| nameOrEmail, |
| accountIds.size(), |
| accountIds); |
| return fulllyMatchedAccountIds; |
| } |
| |
| return accountIds; |
| } catch (OrmException e) { |
| log.error("Error trying to resolve user " + nameOrEmail, e); |
| return Collections.emptySet(); |
| } |
| } |
| |
| private boolean isFullMatch(Account.Id id, String nameOrEmail) { |
| AccountState account = byId.get(id); |
| return isFullNameMatch(account, nameOrEmail) |
| || account |
| .getExternalIds() |
| .stream() |
| .anyMatch(eid -> isEMailMatch(eid, nameOrEmail) || isUsernameMatch(eid, nameOrEmail)); |
| } |
| |
| private boolean isFullNameMatch(AccountState account, String fullName) { |
| return Optional.ofNullable(account.getAccount().getFullName()) |
| .filter(n -> n.trim().equalsIgnoreCase(fullName)) |
| .isPresent(); |
| } |
| |
| private boolean isUsernameMatch(ExternalId externalId, String username) { |
| return keySchemeRest(SCHEME_GERRIT, externalId.key()) |
| .filter(name -> name.equals(username)) |
| .isPresent(); |
| } |
| |
| private boolean isEMailMatch(ExternalId externalId, String email) { |
| ExternalId.Key externalKey = externalId.key(); |
| return OptionalUtils.combine( |
| Optional.ofNullable(externalId.email()).filter(mail -> mail.equalsIgnoreCase(email)), |
| keySchemeRest(SCHEME_MAILTO, externalKey).filter(mail -> mail.equalsIgnoreCase(email))) |
| .isPresent(); |
| } |
| |
| private boolean isActive(Account.Id accountId) { |
| return byId.get(accountId).getAccount().isActive(); |
| } |
| |
| private Optional<String> keySchemeRest(String scheme, ExternalId.Key key) { |
| if (scheme != null && key.isScheme(scheme)) { |
| return Optional.of(key.get().substring(scheme.length() + 1)); |
| } |
| return Optional.empty(); |
| } |
| } |