blob: 18f5ee26b45a42ddea34015aebe50e56200bd58b [file] [log] [blame]
// Copyright (C) 2013 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.group;
import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.gerrit.audit.AuditService;
import com.google.gerrit.extensions.common.AccountInfo;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.DefaultInput;
import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.reviewdb.client.AccountGroupMember;
import com.google.gerrit.reviewdb.client.AuthType;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.AccountCache;
import com.google.gerrit.server.account.AccountException;
import com.google.gerrit.server.account.AccountLoader;
import com.google.gerrit.server.account.AccountManager;
import com.google.gerrit.server.account.AccountResolver;
import com.google.gerrit.server.account.AccountsCollection;
import com.google.gerrit.server.account.AuthRequest;
import com.google.gerrit.server.account.GroupControl;
import com.google.gerrit.server.config.AuthConfig;
import com.google.gerrit.server.group.AddMembers.Input;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
@Singleton
public class AddMembers implements RestModifyView<GroupResource, Input> {
public static class Input {
@DefaultInput
String _oneMember;
List<String> members;
public static Input fromMembers(List<String> members) {
Input in = new Input();
in.members = members;
return in;
}
static Input init(Input in) {
if (in == null) {
in = new Input();
}
if (in.members == null) {
in.members = Lists.newArrayListWithCapacity(1);
}
if (!Strings.isNullOrEmpty(in._oneMember)) {
in.members.add(in._oneMember);
}
return in;
}
}
private final Provider<IdentifiedUser> self;
private final AccountManager accountManager;
private final AuthType authType;
private final AccountsCollection accounts;
private final AccountResolver accountResolver;
private final AccountCache accountCache;
private final AccountLoader.Factory infoFactory;
private final Provider<ReviewDb> db;
private final AuditService auditService;
@Inject
AddMembers(Provider<IdentifiedUser> self,
AccountManager accountManager,
AuthConfig authConfig,
AccountsCollection accounts,
AccountResolver accountResolver,
AccountCache accountCache,
AccountLoader.Factory infoFactory,
Provider<ReviewDb> db,
AuditService auditService) {
this.self = self;
this.accountManager = accountManager;
this.auditService = auditService;
this.authType = authConfig.getAuthType();
this.accounts = accounts;
this.accountResolver = accountResolver;
this.accountCache = accountCache;
this.infoFactory = infoFactory;
this.db = db;
}
@Override
public List<AccountInfo> apply(GroupResource resource, Input input)
throws AuthException, MethodNotAllowedException,
UnprocessableEntityException, OrmException {
AccountGroup internalGroup = resource.toAccountGroup();
if (internalGroup == null) {
throw new MethodNotAllowedException();
}
input = Input.init(input);
GroupControl control = resource.getControl();
Set<Account.Id> newMemberIds = new HashSet<>();
for (String nameOrEmail : input.members) {
Account a = findAccount(nameOrEmail);
if (!a.isActive()) {
throw new UnprocessableEntityException(String.format(
"Account Inactive: %s", nameOrEmail));
}
if (!control.canAddMember()) {
throw new AuthException("Cannot add member: " + a.getFullName());
}
newMemberIds.add(a.getId());
}
addMembers(internalGroup.getId(), newMemberIds);
return toAccountInfoList(newMemberIds);
}
private Account findAccount(String nameOrEmail) throws AuthException,
UnprocessableEntityException, OrmException {
try {
return accounts.parse(nameOrEmail).getAccount();
} catch (UnprocessableEntityException e) {
// might be because the account does not exist or because the account is
// not visible
switch (authType) {
case HTTP_LDAP:
case CLIENT_SSL_CERT_LDAP:
case LDAP:
if (accountResolver.find(nameOrEmail) == null) {
// account does not exist, try to create it
Account a = createAccountByLdap(nameOrEmail);
if (a != null) {
return a;
}
}
break;
default:
}
throw e;
}
}
public void addMembers(AccountGroup.Id groupId,
Collection<? extends Account.Id> newMemberIds) throws OrmException {
Map<Account.Id, AccountGroupMember> newAccountGroupMembers = Maps.newHashMap();
for (Account.Id accId : newMemberIds) {
if (!newAccountGroupMembers.containsKey(accId)) {
AccountGroupMember.Key key =
new AccountGroupMember.Key(accId, groupId);
AccountGroupMember m = db.get().accountGroupMembers().get(key);
if (m == null) {
m = new AccountGroupMember(key);
newAccountGroupMembers.put(m.getAccountId(), m);
}
}
}
auditService.dispatchAddAccountsToGroup(self.get().getAccountId(),
newAccountGroupMembers.values());
db.get().accountGroupMembers().insert(newAccountGroupMembers.values());
for (AccountGroupMember m : newAccountGroupMembers.values()) {
accountCache.evict(m.getAccountId());
}
}
private Account createAccountByLdap(String user) {
if (!user.matches(Account.USER_NAME_PATTERN)) {
return null;
}
try {
AuthRequest req = AuthRequest.forUser(user);
req.setSkipAuthentication(true);
return accountCache.get(accountManager.authenticate(req).getAccountId())
.getAccount();
} catch (AccountException e) {
return null;
}
}
private List<AccountInfo> toAccountInfoList(Set<Account.Id> accountIds)
throws OrmException {
List<AccountInfo> result = Lists.newLinkedList();
AccountLoader loader = infoFactory.create(true);
for (Account.Id accId : accountIds) {
result.add(loader.get(accId));
}
loader.fill();
return result;
}
static class PutMember implements RestModifyView<GroupResource, PutMember.Input> {
static class Input {
}
private final AddMembers put;
private final String id;
PutMember(AddMembers put, String id) {
this.put = put;
this.id = id;
}
@Override
public AccountInfo apply(GroupResource resource, PutMember.Input input)
throws AuthException, MethodNotAllowedException,
ResourceNotFoundException, OrmException {
AddMembers.Input in = new AddMembers.Input();
in._oneMember = id;
try {
List<AccountInfo> list = put.apply(resource, in);
if (list.size() == 1) {
return list.get(0);
}
throw new IllegalStateException();
} catch (UnprocessableEntityException e) {
throw new ResourceNotFoundException(id);
}
}
}
@Singleton
static class UpdateMember implements RestModifyView<MemberResource, PutMember.Input> {
static class Input {
}
private final GetMember get;
@Inject
UpdateMember(GetMember get) {
this.get = get;
}
@Override
public AccountInfo apply(MemberResource resource, PutMember.Input input)
throws OrmException {
// Do nothing, the user is already a member.
return get.apply(resource);
}
}
}