blob: aca2e055dc0aa90e8c53ddc5f17303b9d653c423 [file] [log] [blame]
// Copyright (C) 2008 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.httpd.rpc.account;
import com.google.gerrit.common.data.GroupAdminService;
import com.google.gerrit.common.data.GroupDetail;
import com.google.gerrit.common.data.GroupList;
import com.google.gerrit.common.data.GroupOptions;
import com.google.gerrit.common.data.GroupReference;
import com.google.gerrit.common.errors.InactiveAccountException;
import com.google.gerrit.common.errors.NameAlreadyUsedException;
import com.google.gerrit.common.errors.NoSuchAccountException;
import com.google.gerrit.common.errors.NoSuchEntityException;
import com.google.gerrit.common.errors.NoSuchGroupException;
import com.google.gerrit.httpd.rpc.BaseServiceImplementation;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.reviewdb.client.AccountGroupInclude;
import com.google.gerrit.reviewdb.client.AccountGroupIncludeAudit;
import com.google.gerrit.reviewdb.client.AccountGroupMember;
import com.google.gerrit.reviewdb.client.AccountGroupMemberAudit;
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.AccountManager;
import com.google.gerrit.server.account.AccountResolver;
import com.google.gerrit.server.account.AuthRequest;
import com.google.gerrit.server.account.GroupBackend;
import com.google.gerrit.server.account.GroupBackends;
import com.google.gerrit.server.account.GroupCache;
import com.google.gerrit.server.account.GroupControl;
import com.google.gerrit.server.account.GroupIncludeCache;
import com.google.gerrit.server.config.AuthConfig;
import com.google.gwtjsonrpc.common.AsyncCallback;
import com.google.gwtjsonrpc.common.VoidResult;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
class GroupAdminServiceImpl extends BaseServiceImplementation implements
GroupAdminService {
private final AccountCache accountCache;
private final AccountResolver accountResolver;
private final AccountManager accountManager;
private final AuthType authType;
private final GroupCache groupCache;
private final GroupBackend groupBackend;
private final GroupIncludeCache groupIncludeCache;
private final GroupControl.Factory groupControlFactory;
private final CreateGroup.Factory createGroupFactory;
private final RenameGroup.Factory renameGroupFactory;
private final GroupDetailHandler.Factory groupDetailFactory;
private final VisibleGroupsHandler.Factory visibleGroupsFactory;
@Inject
GroupAdminServiceImpl(final Provider<ReviewDb> schema,
final Provider<IdentifiedUser> currentUser,
final AccountCache accountCache,
final GroupIncludeCache groupIncludeCache,
final AccountResolver accountResolver,
final AccountManager accountManager,
final AuthConfig authConfig,
final GroupCache groupCache,
final GroupBackend groupBackend,
final GroupControl.Factory groupControlFactory,
final CreateGroup.Factory createGroupFactory,
final RenameGroup.Factory renameGroupFactory,
final GroupDetailHandler.Factory groupDetailFactory,
final VisibleGroupsHandler.Factory visibleGroupsFactory) {
super(schema, currentUser);
this.accountCache = accountCache;
this.groupIncludeCache = groupIncludeCache;
this.accountResolver = accountResolver;
this.accountManager = accountManager;
this.authType = authConfig.getAuthType();
this.groupCache = groupCache;
this.groupBackend = groupBackend;
this.groupControlFactory = groupControlFactory;
this.createGroupFactory = createGroupFactory;
this.renameGroupFactory = renameGroupFactory;
this.groupDetailFactory = groupDetailFactory;
this.visibleGroupsFactory = visibleGroupsFactory;
}
public void visibleGroups(final AsyncCallback<GroupList> callback) {
visibleGroupsFactory.create().to(callback);
}
public void createGroup(final String newName,
final AsyncCallback<AccountGroup.Id> callback) {
createGroupFactory.create(newName).to(callback);
}
public void groupDetail(AccountGroup.Id groupId, AccountGroup.UUID groupUUID,
AsyncCallback<GroupDetail> callback) {
if (groupId == null && groupUUID != null) {
AccountGroup g = groupCache.get(groupUUID);
if (g != null) {
groupId = g.getId();
}
}
groupDetailFactory.create(groupId).to(callback);
}
public void changeGroupDescription(final AccountGroup.Id groupId,
final String description, final AsyncCallback<VoidResult> callback) {
run(callback, new Action<VoidResult>() {
public VoidResult run(final ReviewDb db) throws OrmException, Failure {
final AccountGroup group = db.accountGroups().get(groupId);
assertAmGroupOwner(db, group);
group.setDescription(description);
db.accountGroups().update(Collections.singleton(group));
groupCache.evict(group);
return VoidResult.INSTANCE;
}
});
}
public void changeGroupOptions(final AccountGroup.Id groupId,
final GroupOptions groupOptions, final AsyncCallback<VoidResult> callback) {
run(callback, new Action<VoidResult>() {
public VoidResult run(final ReviewDb db) throws OrmException, Failure {
final AccountGroup group = db.accountGroups().get(groupId);
assertAmGroupOwner(db, group);
group.setVisibleToAll(groupOptions.isVisibleToAll());
db.accountGroups().update(Collections.singleton(group));
groupCache.evict(group);
return VoidResult.INSTANCE;
}
});
}
public void changeGroupOwner(final AccountGroup.Id groupId,
final String newOwnerName, final AsyncCallback<VoidResult> callback) {
run(callback, new Action<VoidResult>() {
public VoidResult run(final ReviewDb db) throws OrmException, Failure {
final AccountGroup group = db.accountGroups().get(groupId);
assertAmGroupOwner(db, group);
GroupReference owner =
GroupBackends.findExactSuggestion(groupBackend, newOwnerName);
if (owner == null) {
throw new Failure(new NoSuchEntityException());
}
group.setOwnerGroupUUID(owner.getUUID());
db.accountGroups().update(Collections.singleton(group));
groupCache.evict(group);
return VoidResult.INSTANCE;
}
});
}
public void renameGroup(final AccountGroup.Id groupId, final String newName,
final AsyncCallback<GroupDetail> callback) {
renameGroupFactory.create(groupId, newName).to(callback);
}
public void changeGroupType(final AccountGroup.Id groupId,
final AccountGroup.Type newType, final AsyncCallback<VoidResult> callback) {
run(callback, new Action<VoidResult>() {
public VoidResult run(final ReviewDb db) throws OrmException, Failure {
final AccountGroup group = db.accountGroups().get(groupId);
assertAmGroupOwner(db, group);
group.setType(newType);
db.accountGroups().update(Collections.singleton(group));
groupCache.evict(group);
return VoidResult.INSTANCE;
}
});
}
public void addGroupMember(final AccountGroup.Id groupId,
final String nameOrEmail, final AsyncCallback<GroupDetail> callback) {
run(callback, new Action<GroupDetail>() {
public GroupDetail run(ReviewDb db) throws OrmException, Failure,
NoSuchGroupException {
final GroupControl control = groupControlFactory.validateFor(groupId);
if (groupCache.get(groupId).getType() != AccountGroup.Type.INTERNAL) {
throw new Failure(new NameAlreadyUsedException());
}
final Account a = findAccount(nameOrEmail);
if (!a.isActive()) {
throw new Failure(new InactiveAccountException(a.getFullName()));
}
if (!control.canAddMember(a.getId())) {
throw new Failure(new NoSuchEntityException());
}
final AccountGroupMember.Key key =
new AccountGroupMember.Key(a.getId(), groupId);
AccountGroupMember m = db.accountGroupMembers().get(key);
if (m == null) {
m = new AccountGroupMember(key);
db.accountGroupMembersAudit().insert(
Collections.singleton(new AccountGroupMemberAudit(m,
getAccountId())));
db.accountGroupMembers().insert(Collections.singleton(m));
accountCache.evict(m.getAccountId());
}
return groupDetailFactory.create(groupId).call();
}
});
}
public void addGroupInclude(final AccountGroup.Id groupId,
final String groupName, final AsyncCallback<GroupDetail> callback) {
run(callback, new Action<GroupDetail>() {
public GroupDetail run(ReviewDb db) throws OrmException, Failure,
NoSuchGroupException {
final GroupControl control = groupControlFactory.validateFor(groupId);
if (groupCache.get(groupId).getType() != AccountGroup.Type.INTERNAL) {
throw new Failure(new NameAlreadyUsedException());
}
final AccountGroup a = findGroup(groupName);
if (!control.canAddGroup(a.getId())) {
throw new Failure(new NoSuchEntityException());
}
final AccountGroupInclude.Key key =
new AccountGroupInclude.Key(groupId, a.getId());
AccountGroupInclude m = db.accountGroupIncludes().get(key);
if (m == null) {
m = new AccountGroupInclude(key);
db.accountGroupIncludesAudit().insert(
Collections.singleton(new AccountGroupIncludeAudit(m,
getAccountId())));
db.accountGroupIncludes().insert(Collections.singleton(m));
groupIncludeCache.evictInclude(a.getGroupUUID());
}
return groupDetailFactory.create(groupId).call();
}
});
}
public void deleteGroupMembers(final AccountGroup.Id groupId,
final Set<AccountGroupMember.Key> keys,
final AsyncCallback<VoidResult> callback) {
run(callback, new Action<VoidResult>() {
public VoidResult run(final ReviewDb db) throws OrmException,
NoSuchGroupException, Failure {
final GroupControl control = groupControlFactory.validateFor(groupId);
if (groupCache.get(groupId).getType() != AccountGroup.Type.INTERNAL) {
throw new Failure(new NameAlreadyUsedException());
}
for (final AccountGroupMember.Key k : keys) {
if (!groupId.equals(k.getAccountGroupId())) {
throw new Failure(new NoSuchEntityException());
}
}
final Account.Id me = getAccountId();
for (final AccountGroupMember.Key k : keys) {
final AccountGroupMember m = db.accountGroupMembers().get(k);
if (m != null) {
if (!control.canRemoveMember(m.getAccountId())) {
throw new Failure(new NoSuchEntityException());
}
AccountGroupMemberAudit audit = null;
for (AccountGroupMemberAudit a : db.accountGroupMembersAudit()
.byGroupAccount(m.getAccountGroupId(), m.getAccountId())) {
if (a.isActive()) {
audit = a;
break;
}
}
if (audit != null) {
audit.removed(me);
db.accountGroupMembersAudit()
.update(Collections.singleton(audit));
} else {
audit = new AccountGroupMemberAudit(m, me);
audit.removedLegacy();
db.accountGroupMembersAudit()
.insert(Collections.singleton(audit));
}
db.accountGroupMembers().delete(Collections.singleton(m));
accountCache.evict(m.getAccountId());
}
}
return VoidResult.INSTANCE;
}
});
}
public void deleteGroupIncludes(final AccountGroup.Id groupId,
final Set<AccountGroupInclude.Key> keys,
final AsyncCallback<VoidResult> callback) {
run(callback, new Action<VoidResult>() {
public VoidResult run(final ReviewDb db) throws OrmException,
NoSuchGroupException, Failure {
final GroupControl control = groupControlFactory.validateFor(groupId);
if (groupCache.get(groupId).getType() != AccountGroup.Type.INTERNAL) {
throw new Failure(new NameAlreadyUsedException());
}
for (final AccountGroupInclude.Key k : keys) {
if (!groupId.equals(k.getGroupId())) {
throw new Failure(new NoSuchEntityException());
}
}
final Account.Id me = getAccountId();
final Set<AccountGroup.Id> groupsToEvict = new HashSet<AccountGroup.Id>();
for (final AccountGroupInclude.Key k : keys) {
final AccountGroupInclude m =
db.accountGroupIncludes().get(k);
if (m != null) {
if (!control.canRemoveGroup(m.getIncludeId())) {
throw new Failure(new NoSuchEntityException());
}
AccountGroupIncludeAudit audit = null;
for (AccountGroupIncludeAudit a : db
.accountGroupIncludesAudit().byGroupInclude(
m.getGroupId(), m.getIncludeId())) {
if (a.isActive()) {
audit = a;
break;
}
}
if (audit != null) {
audit.removed(me);
db.accountGroupIncludesAudit().update(
Collections.singleton(audit));
}
db.accountGroupIncludes().delete(Collections.singleton(m));
groupsToEvict.add(k.getIncludeId());
}
}
for (AccountGroup group : db.accountGroups().get(groupsToEvict)) {
groupIncludeCache.evictInclude(group.getGroupUUID());
}
return VoidResult.INSTANCE;
}
});
}
private void assertAmGroupOwner(final ReviewDb db, final AccountGroup group)
throws Failure {
try {
if (!groupControlFactory.controlFor(group.getId()).isOwner()) {
throw new Failure(new NoSuchGroupException(group.getId()));
}
} catch (NoSuchGroupException e) {
throw new Failure(new NoSuchGroupException(group.getId()));
}
}
private Account findAccount(final String nameOrEmail) throws OrmException,
Failure {
Account r = accountResolver.find(nameOrEmail);
if (r == null) {
switch (authType) {
case HTTP_LDAP:
case CLIENT_SSL_CERT_LDAP:
case LDAP:
r = createAccountByLdap(nameOrEmail);
break;
default:
}
if (r == null) {
throw new Failure(new NoSuchAccountException(nameOrEmail));
}
}
return r;
}
private Account createAccountByLdap(String user) {
if (!user.matches(Account.USER_NAME_PATTERN)) {
return null;
}
try {
final AuthRequest req = AuthRequest.forUser(user);
req.setSkipAuthentication(true);
return accountCache.get(accountManager.authenticate(req).getAccountId())
.getAccount();
} catch (AccountException e) {
return null;
}
}
private AccountGroup findGroup(final String name) throws OrmException,
Failure {
final AccountGroup g = groupCache.get(new AccountGroup.NameKey(name));
if (g == null) {
throw new Failure(new NoSuchGroupException(name));
}
return g;
}
}