Add validation listener for account activation
Change-Id: Ib238701b2a9563157f37834fa0944b8e40db3701 Signed-off-by: Edwin Kempin <ekempin@google.com>
This commit is contained in:
parent
6d8a2d0ff5
commit
f0d71a822d
@ -114,6 +114,14 @@ This interface provides a low-level e-mail filtering API for plugins.
|
|||||||
Plugins implementing the `OutgoingEmailValidationListener` interface can perform
|
Plugins implementing the `OutgoingEmailValidationListener` interface can perform
|
||||||
filtering of outgoing e-mails just before they are sent.
|
filtering of outgoing e-mails just before they are sent.
|
||||||
|
|
||||||
|
[[account-activation-validation]]
|
||||||
|
== Account activation validation
|
||||||
|
|
||||||
|
|
||||||
|
Plugins implementing the `AccountActivationValidationListener` interface can
|
||||||
|
perform validation when an account is activated or deactivated via the Gerrit
|
||||||
|
REST API or the Java extension API.
|
||||||
|
|
||||||
|
|
||||||
GERRIT
|
GERRIT
|
||||||
------
|
------
|
||||||
|
@ -14,33 +14,43 @@
|
|||||||
|
|
||||||
package com.google.gerrit.server.account;
|
package com.google.gerrit.server.account;
|
||||||
|
|
||||||
|
import com.google.gerrit.extensions.registration.DynamicSet;
|
||||||
import com.google.gerrit.extensions.restapi.ResourceConflictException;
|
import com.google.gerrit.extensions.restapi.ResourceConflictException;
|
||||||
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
|
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
|
||||||
import com.google.gerrit.extensions.restapi.Response;
|
import com.google.gerrit.extensions.restapi.Response;
|
||||||
import com.google.gerrit.extensions.restapi.RestApiException;
|
import com.google.gerrit.extensions.restapi.RestApiException;
|
||||||
import com.google.gerrit.reviewdb.client.Account;
|
import com.google.gerrit.reviewdb.client.Account;
|
||||||
import com.google.gerrit.server.ServerInitiated;
|
import com.google.gerrit.server.ServerInitiated;
|
||||||
|
import com.google.gerrit.server.validators.AccountActivationValidationListener;
|
||||||
|
import com.google.gerrit.server.validators.ValidationException;
|
||||||
import com.google.gwtorm.server.OrmException;
|
import com.google.gwtorm.server.OrmException;
|
||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
import com.google.inject.Provider;
|
import com.google.inject.Provider;
|
||||||
import com.google.inject.Singleton;
|
import com.google.inject.Singleton;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.Optional;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
import org.eclipse.jgit.errors.ConfigInvalidException;
|
import org.eclipse.jgit.errors.ConfigInvalidException;
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
public class SetInactiveFlag {
|
public class SetInactiveFlag {
|
||||||
|
private final DynamicSet<AccountActivationValidationListener>
|
||||||
|
accountActivationValidationListeners;
|
||||||
private final Provider<AccountsUpdate> accountsUpdateProvider;
|
private final Provider<AccountsUpdate> accountsUpdateProvider;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
SetInactiveFlag(@ServerInitiated Provider<AccountsUpdate> accountsUpdateProvider) {
|
SetInactiveFlag(
|
||||||
|
DynamicSet<AccountActivationValidationListener> accountActivationValidationListeners,
|
||||||
|
@ServerInitiated Provider<AccountsUpdate> accountsUpdateProvider) {
|
||||||
|
this.accountActivationValidationListeners = accountActivationValidationListeners;
|
||||||
this.accountsUpdateProvider = accountsUpdateProvider;
|
this.accountsUpdateProvider = accountsUpdateProvider;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Response<?> deactivate(Account.Id accountId)
|
public Response<?> deactivate(Account.Id accountId)
|
||||||
throws RestApiException, IOException, ConfigInvalidException, OrmException {
|
throws RestApiException, IOException, ConfigInvalidException, OrmException {
|
||||||
AtomicBoolean alreadyInactive = new AtomicBoolean(false);
|
AtomicBoolean alreadyInactive = new AtomicBoolean(false);
|
||||||
|
AtomicReference<Optional<RestApiException>> exception = new AtomicReference<>(Optional.empty());
|
||||||
accountsUpdateProvider
|
accountsUpdateProvider
|
||||||
.get()
|
.get()
|
||||||
.update(
|
.update(
|
||||||
@ -50,10 +60,21 @@ public class SetInactiveFlag {
|
|||||||
if (!a.getAccount().isActive()) {
|
if (!a.getAccount().isActive()) {
|
||||||
alreadyInactive.set(true);
|
alreadyInactive.set(true);
|
||||||
} else {
|
} else {
|
||||||
|
for (AccountActivationValidationListener l : accountActivationValidationListeners) {
|
||||||
|
try {
|
||||||
|
l.validateDeactivation(a);
|
||||||
|
} catch (ValidationException e) {
|
||||||
|
exception.set(Optional.of(new ResourceConflictException(e.getMessage(), e)));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
u.setActive(false);
|
u.setActive(false);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.orElseThrow(() -> new ResourceNotFoundException("account not found"));
|
.orElseThrow(() -> new ResourceNotFoundException("account not found"));
|
||||||
|
if (exception.get().isPresent()) {
|
||||||
|
throw exception.get().get();
|
||||||
|
}
|
||||||
if (alreadyInactive.get()) {
|
if (alreadyInactive.get()) {
|
||||||
throw new ResourceConflictException("account not active");
|
throw new ResourceConflictException("account not active");
|
||||||
}
|
}
|
||||||
@ -61,8 +82,9 @@ public class SetInactiveFlag {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Response<String> activate(Account.Id accountId)
|
public Response<String> activate(Account.Id accountId)
|
||||||
throws ResourceNotFoundException, IOException, ConfigInvalidException, OrmException {
|
throws RestApiException, IOException, ConfigInvalidException, OrmException {
|
||||||
AtomicBoolean alreadyActive = new AtomicBoolean(false);
|
AtomicBoolean alreadyActive = new AtomicBoolean(false);
|
||||||
|
AtomicReference<Optional<RestApiException>> exception = new AtomicReference<>(Optional.empty());
|
||||||
accountsUpdateProvider
|
accountsUpdateProvider
|
||||||
.get()
|
.get()
|
||||||
.update(
|
.update(
|
||||||
@ -72,10 +94,21 @@ public class SetInactiveFlag {
|
|||||||
if (a.getAccount().isActive()) {
|
if (a.getAccount().isActive()) {
|
||||||
alreadyActive.set(true);
|
alreadyActive.set(true);
|
||||||
} else {
|
} else {
|
||||||
|
for (AccountActivationValidationListener l : accountActivationValidationListeners) {
|
||||||
|
try {
|
||||||
|
l.validateActivation(a);
|
||||||
|
} catch (ValidationException e) {
|
||||||
|
exception.set(Optional.of(new ResourceConflictException(e.getMessage(), e)));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
u.setActive(true);
|
u.setActive(true);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.orElseThrow(() -> new ResourceNotFoundException("account not found"));
|
.orElseThrow(() -> new ResourceNotFoundException("account not found"));
|
||||||
|
if (exception.get().isPresent()) {
|
||||||
|
throw exception.get().get();
|
||||||
|
}
|
||||||
return alreadyActive.get() ? Response.ok("") : Response.created("");
|
return alreadyActive.get() ? Response.ok("") : Response.created("");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -184,6 +184,7 @@ import com.google.gerrit.server.tools.ToolsCatalog;
|
|||||||
import com.google.gerrit.server.update.BatchUpdate;
|
import com.google.gerrit.server.update.BatchUpdate;
|
||||||
import com.google.gerrit.server.util.IdGenerator;
|
import com.google.gerrit.server.util.IdGenerator;
|
||||||
import com.google.gerrit.server.util.ThreadLocalRequestContext;
|
import com.google.gerrit.server.util.ThreadLocalRequestContext;
|
||||||
|
import com.google.gerrit.server.validators.AccountActivationValidationListener;
|
||||||
import com.google.gerrit.server.validators.AssigneeValidationListener;
|
import com.google.gerrit.server.validators.AssigneeValidationListener;
|
||||||
import com.google.gerrit.server.validators.GroupCreationValidationListener;
|
import com.google.gerrit.server.validators.GroupCreationValidationListener;
|
||||||
import com.google.gerrit.server.validators.HashtagValidationListener;
|
import com.google.gerrit.server.validators.HashtagValidationListener;
|
||||||
@ -365,6 +366,7 @@ public class GerritGlobalModule extends FactoryModule {
|
|||||||
DynamicSet.setOf(binder(), GroupCreationValidationListener.class);
|
DynamicSet.setOf(binder(), GroupCreationValidationListener.class);
|
||||||
DynamicSet.setOf(binder(), HashtagValidationListener.class);
|
DynamicSet.setOf(binder(), HashtagValidationListener.class);
|
||||||
DynamicSet.setOf(binder(), OutgoingEmailValidationListener.class);
|
DynamicSet.setOf(binder(), OutgoingEmailValidationListener.class);
|
||||||
|
DynamicSet.setOf(binder(), AccountActivationValidationListener.class);
|
||||||
DynamicItem.itemOf(binder(), AvatarProvider.class);
|
DynamicItem.itemOf(binder(), AvatarProvider.class);
|
||||||
DynamicSet.setOf(binder(), LifecycleListener.class);
|
DynamicSet.setOf(binder(), LifecycleListener.class);
|
||||||
DynamicSet.setOf(binder(), TopMenu.class);
|
DynamicSet.setOf(binder(), TopMenu.class);
|
||||||
|
@ -17,8 +17,8 @@ package com.google.gerrit.server.restapi.account;
|
|||||||
import com.google.gerrit.common.data.GlobalCapability;
|
import com.google.gerrit.common.data.GlobalCapability;
|
||||||
import com.google.gerrit.extensions.annotations.RequiresCapability;
|
import com.google.gerrit.extensions.annotations.RequiresCapability;
|
||||||
import com.google.gerrit.extensions.common.Input;
|
import com.google.gerrit.extensions.common.Input;
|
||||||
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
|
|
||||||
import com.google.gerrit.extensions.restapi.Response;
|
import com.google.gerrit.extensions.restapi.Response;
|
||||||
|
import com.google.gerrit.extensions.restapi.RestApiException;
|
||||||
import com.google.gerrit.extensions.restapi.RestModifyView;
|
import com.google.gerrit.extensions.restapi.RestModifyView;
|
||||||
import com.google.gerrit.server.account.AccountResource;
|
import com.google.gerrit.server.account.AccountResource;
|
||||||
import com.google.gerrit.server.account.SetInactiveFlag;
|
import com.google.gerrit.server.account.SetInactiveFlag;
|
||||||
@ -41,7 +41,7 @@ public class PutActive implements RestModifyView<AccountResource, Input> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Response<String> apply(AccountResource rsrc, Input input)
|
public Response<String> apply(AccountResource rsrc, Input input)
|
||||||
throws ResourceNotFoundException, OrmException, IOException, ConfigInvalidException {
|
throws RestApiException, OrmException, IOException, ConfigInvalidException {
|
||||||
return setInactiveFlag.activate(rsrc.getUser().getAccountId());
|
return setInactiveFlag.activate(rsrc.getUser().getAccountId());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,41 @@
|
|||||||
|
// Copyright (C) 2018 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.validators;
|
||||||
|
|
||||||
|
import com.google.gerrit.extensions.annotations.ExtensionPoint;
|
||||||
|
import com.google.gerrit.server.account.AccountState;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validator that is invoked when an account activated or deactivated via the Gerrit REST API or the
|
||||||
|
* Java extension API.
|
||||||
|
*/
|
||||||
|
@ExtensionPoint
|
||||||
|
public interface AccountActivationValidationListener {
|
||||||
|
/**
|
||||||
|
* Called when an account should be activated to allow validation of the account activation.
|
||||||
|
*
|
||||||
|
* @param account the account that should be activated
|
||||||
|
* @throws ValidationException if validation fails
|
||||||
|
*/
|
||||||
|
void validateActivation(AccountState account) throws ValidationException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when an account should be deactivated to allow validation of the account deactivation.
|
||||||
|
*
|
||||||
|
* @param account the account that should be deactivated
|
||||||
|
* @throws ValidationException if validation fails
|
||||||
|
*/
|
||||||
|
void validateDeactivation(AccountState account) throws ValidationException;
|
||||||
|
}
|
@ -117,6 +117,8 @@ import com.google.gerrit.server.project.RefPattern;
|
|||||||
import com.google.gerrit.server.query.account.InternalAccountQuery;
|
import com.google.gerrit.server.query.account.InternalAccountQuery;
|
||||||
import com.google.gerrit.server.update.RetryHelper;
|
import com.google.gerrit.server.update.RetryHelper;
|
||||||
import com.google.gerrit.server.util.MagicBranch;
|
import com.google.gerrit.server.util.MagicBranch;
|
||||||
|
import com.google.gerrit.server.validators.AccountActivationValidationListener;
|
||||||
|
import com.google.gerrit.server.validators.ValidationException;
|
||||||
import com.google.gerrit.testing.ConfigSuite;
|
import com.google.gerrit.testing.ConfigSuite;
|
||||||
import com.google.gerrit.testing.FakeEmailSender.Message;
|
import com.google.gerrit.testing.FakeEmailSender.Message;
|
||||||
import com.google.gerrit.testing.TestTimeUtil;
|
import com.google.gerrit.testing.TestTimeUtil;
|
||||||
@ -216,6 +218,9 @@ public class AccountIT extends AbstractDaemonTest {
|
|||||||
|
|
||||||
@Inject private AccountOperations accountOperations;
|
@Inject private AccountOperations accountOperations;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
private DynamicSet<AccountActivationValidationListener> accountActivationValidationListeners;
|
||||||
|
|
||||||
private AccountIndexedCounter accountIndexedCounter;
|
private AccountIndexedCounter accountIndexedCounter;
|
||||||
private RegistrationHandle accountIndexEventCounterHandle;
|
private RegistrationHandle accountIndexEventCounterHandle;
|
||||||
private RefUpdateCounter refUpdateCounter;
|
private RefUpdateCounter refUpdateCounter;
|
||||||
@ -518,6 +523,95 @@ public class AccountIT extends AbstractDaemonTest {
|
|||||||
accountIndexedCounter.assertReindexOf(user);
|
accountIndexedCounter.assertReindexOf(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void validateAccountActivation() throws Exception {
|
||||||
|
com.google.gerrit.acceptance.testsuite.account.TestAccount activatableAccount =
|
||||||
|
accountOperations.newAccount().inactive().preferredEmail("foo@activatable.com").create();
|
||||||
|
com.google.gerrit.acceptance.testsuite.account.TestAccount deactivatableAccount =
|
||||||
|
accountOperations.newAccount().preferredEmail("foo@deactivatable.com").create();
|
||||||
|
RegistrationHandle registrationHandle =
|
||||||
|
accountActivationValidationListeners.add(
|
||||||
|
new AccountActivationValidationListener() {
|
||||||
|
@Override
|
||||||
|
public void validateActivation(AccountState account) throws ValidationException {
|
||||||
|
String preferredEmail = account.getAccount().getPreferredEmail();
|
||||||
|
if (preferredEmail == null || !preferredEmail.endsWith("@activatable.com")) {
|
||||||
|
throw new ValidationException("not allowed to active account");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void validateDeactivation(AccountState account) throws ValidationException {
|
||||||
|
String preferredEmail = account.getAccount().getPreferredEmail();
|
||||||
|
if (preferredEmail == null || !preferredEmail.endsWith("@deactivatable.com")) {
|
||||||
|
throw new ValidationException("not allowed to deactive account");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
/* Test account that can be activated, but not deactivated */
|
||||||
|
// Deactivate account that is already inactive
|
||||||
|
try {
|
||||||
|
gApi.accounts().id(activatableAccount.accountId().get()).setActive(false);
|
||||||
|
fail("Expected exception");
|
||||||
|
} catch (ResourceConflictException e) {
|
||||||
|
assertThat(e.getMessage()).isEqualTo("account not active");
|
||||||
|
}
|
||||||
|
assertThat(accountOperations.account(activatableAccount.accountId()).get().active())
|
||||||
|
.isFalse();
|
||||||
|
|
||||||
|
// Activate account that can be activated
|
||||||
|
gApi.accounts().id(activatableAccount.accountId().get()).setActive(true);
|
||||||
|
assertThat(accountOperations.account(activatableAccount.accountId()).get().active()).isTrue();
|
||||||
|
|
||||||
|
// Activate account that is already active
|
||||||
|
gApi.accounts().id(activatableAccount.accountId().get()).setActive(true);
|
||||||
|
assertThat(accountOperations.account(activatableAccount.accountId()).get().active()).isTrue();
|
||||||
|
|
||||||
|
// Try deactivating account that cannot be deactivated
|
||||||
|
try {
|
||||||
|
gApi.accounts().id(activatableAccount.accountId().get()).setActive(false);
|
||||||
|
fail("Expected exception");
|
||||||
|
} catch (ResourceConflictException e) {
|
||||||
|
assertThat(e.getMessage()).isEqualTo("not allowed to deactive account");
|
||||||
|
}
|
||||||
|
assertThat(accountOperations.account(activatableAccount.accountId()).get().active()).isTrue();
|
||||||
|
|
||||||
|
/* Test account that can be deactivated, but not activated */
|
||||||
|
// Activate account that is already inactive
|
||||||
|
gApi.accounts().id(deactivatableAccount.accountId().get()).setActive(true);
|
||||||
|
assertThat(accountOperations.account(deactivatableAccount.accountId()).get().active())
|
||||||
|
.isTrue();
|
||||||
|
|
||||||
|
// Deactivate account that can be deactivated
|
||||||
|
gApi.accounts().id(deactivatableAccount.accountId().get()).setActive(false);
|
||||||
|
assertThat(accountOperations.account(deactivatableAccount.accountId()).get().active())
|
||||||
|
.isFalse();
|
||||||
|
|
||||||
|
// Deactivate account that is already inactive
|
||||||
|
try {
|
||||||
|
gApi.accounts().id(deactivatableAccount.accountId().get()).setActive(false);
|
||||||
|
fail("Expected exception");
|
||||||
|
} catch (ResourceConflictException e) {
|
||||||
|
assertThat(e.getMessage()).isEqualTo("account not active");
|
||||||
|
}
|
||||||
|
assertThat(accountOperations.account(deactivatableAccount.accountId()).get().active())
|
||||||
|
.isFalse();
|
||||||
|
|
||||||
|
// Try activating account that cannot be activated
|
||||||
|
try {
|
||||||
|
gApi.accounts().id(deactivatableAccount.accountId().get()).setActive(true);
|
||||||
|
fail("Expected exception");
|
||||||
|
} catch (ResourceConflictException e) {
|
||||||
|
assertThat(e.getMessage()).isEqualTo("not allowed to active account");
|
||||||
|
}
|
||||||
|
assertThat(accountOperations.account(deactivatableAccount.accountId()).get().active())
|
||||||
|
.isFalse();
|
||||||
|
} finally {
|
||||||
|
registrationHandle.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void deactivateSelf() throws Exception {
|
public void deactivateSelf() throws Exception {
|
||||||
exception.expect(ResourceConflictException.class);
|
exception.expect(ResourceConflictException.class);
|
||||||
|
Loading…
Reference in New Issue
Block a user