Add REST endpoint to confirm emails
Use the new REST endpoint in the UI instead of the old AccountSecurity.validateEmail(...) RPC. AccountSecurity.validateEmail(...) is removed since it is no longer used. Change-Id: I561224e9d9ea31875df2bba838ee53f77f24c55b Signed-off-by: Edwin Kempin <edwin.kempin@sap.com>
This commit is contained in:
@@ -126,6 +126,32 @@ As result a link:#server-info[ServerInfo] entity is returned.
|
|||||||
}
|
}
|
||||||
----
|
----
|
||||||
|
|
||||||
|
[[confirm-email]]
|
||||||
|
=== Confirm Email
|
||||||
|
--
|
||||||
|
'PUT /config/server/email.confirm'
|
||||||
|
--
|
||||||
|
|
||||||
|
Confirms that the user owns an email address.
|
||||||
|
|
||||||
|
The email token must be provided in the request body inside
|
||||||
|
an link:#email-confirmation-input[EmailConfirmationInput] entity.
|
||||||
|
|
||||||
|
.Request
|
||||||
|
----
|
||||||
|
PUT /config/server/email.confirm HTTP/1.0
|
||||||
|
Content-Type: application/json; charset=UTF-8
|
||||||
|
|
||||||
|
{
|
||||||
|
"token": "Enim+QNbAo6TV8Hur8WwoUypI6apG7qBPvF+bw==$MTAwMDAwNDp0ZXN0QHRlc3QuZGU="
|
||||||
|
}
|
||||||
|
----
|
||||||
|
|
||||||
|
The response is "`204 No Content`".
|
||||||
|
|
||||||
|
If the token is invalid or if it's the token of another user the
|
||||||
|
request fails and the response is "`422 Unprocessable Entity`".
|
||||||
|
|
||||||
|
|
||||||
[[list-caches]]
|
[[list-caches]]
|
||||||
=== List Caches
|
=== List Caches
|
||||||
@@ -1132,6 +1158,18 @@ Empty, if accessed anonymously and the download scheme requires
|
|||||||
authentication.
|
authentication.
|
||||||
|=================================
|
|=================================
|
||||||
|
|
||||||
|
[[email-confirmation-input]]
|
||||||
|
=== EmailConfirmationInput
|
||||||
|
The `EmailConfirmationInput` entity contains information for confirming
|
||||||
|
an email address.
|
||||||
|
|
||||||
|
[options="header",cols="1,6"]
|
||||||
|
|=======================
|
||||||
|
|Field Name |Description
|
||||||
|
|`token` |
|
||||||
|
The token that was sent by mail to a newly registered email address.
|
||||||
|
|=======================
|
||||||
|
|
||||||
[[entries-info]]
|
[[entries-info]]
|
||||||
=== EntriesInfo
|
=== EntriesInfo
|
||||||
The `EntriesInfo` entity contains information about the entries in a
|
The `EntriesInfo` entity contains information about the entries in a
|
||||||
|
@@ -0,0 +1,66 @@
|
|||||||
|
// Copyright (C) 2015 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.acceptance.rest.config;
|
||||||
|
|
||||||
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
|
|
||||||
|
import com.google.gerrit.acceptance.AbstractDaemonTest;
|
||||||
|
import com.google.gerrit.acceptance.RestResponse;
|
||||||
|
import com.google.gerrit.server.config.ConfirmEmail;
|
||||||
|
import com.google.gerrit.server.mail.EmailTokenVerifier;
|
||||||
|
import com.google.gerrit.testutil.ConfigSuite;
|
||||||
|
import com.google.gwtjsonrpc.server.SignedToken;
|
||||||
|
import com.google.inject.Inject;
|
||||||
|
|
||||||
|
import org.apache.http.HttpStatus;
|
||||||
|
import org.eclipse.jgit.lib.Config;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
public class ConfirmEmailIT extends AbstractDaemonTest {
|
||||||
|
@ConfigSuite.Default
|
||||||
|
public static Config defaultConfig() {
|
||||||
|
Config cfg = new Config();
|
||||||
|
cfg.setString("auth", null, "registerEmailPrivateKey",
|
||||||
|
SignedToken.generateRandomKey());
|
||||||
|
return cfg;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
private EmailTokenVerifier emailTokenVerifier;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void confirm() throws Exception {
|
||||||
|
ConfirmEmail.Input in = new ConfirmEmail.Input();
|
||||||
|
in.token = emailTokenVerifier.encode(admin.getId(), "new.mail@example.com");
|
||||||
|
RestResponse r = adminSession.put("/config/server/email.confirm", in);
|
||||||
|
assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_NO_CONTENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void confirmForOtherUser_UnprocessableEntity() throws Exception {
|
||||||
|
ConfirmEmail.Input in = new ConfirmEmail.Input();
|
||||||
|
in.token = emailTokenVerifier.encode(user.getId(), "new.mail@example.com");
|
||||||
|
RestResponse r = adminSession.put("/config/server/email.confirm", in);
|
||||||
|
assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_UNPROCESSABLE_ENTITY);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void confirmInvalidToken_UnprocessableEntity() throws Exception {
|
||||||
|
ConfirmEmail.Input in = new ConfirmEmail.Input();
|
||||||
|
in.token = "invalidToken";
|
||||||
|
RestResponse r = adminSession.put("/config/server/email.confirm", in);
|
||||||
|
assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_UNPROCESSABLE_ENTITY);
|
||||||
|
}
|
||||||
|
}
|
@@ -47,8 +47,4 @@ public interface AccountSecurity extends RemoteJsonService {
|
|||||||
@SignInRequired
|
@SignInRequired
|
||||||
void enterAgreement(String agreementName,
|
void enterAgreement(String agreementName,
|
||||||
AsyncCallback<VoidResult> callback);
|
AsyncCallback<VoidResult> callback);
|
||||||
|
|
||||||
@Audit
|
|
||||||
@SignInRequired
|
|
||||||
void validateEmail(String token, AsyncCallback<VoidResult> callback);
|
|
||||||
}
|
}
|
||||||
|
@@ -15,10 +15,11 @@
|
|||||||
package com.google.gerrit.client.account;
|
package com.google.gerrit.client.account;
|
||||||
|
|
||||||
import com.google.gerrit.client.Gerrit;
|
import com.google.gerrit.client.Gerrit;
|
||||||
|
import com.google.gerrit.client.VoidResult;
|
||||||
|
import com.google.gerrit.client.config.ConfigServerApi;
|
||||||
import com.google.gerrit.client.rpc.ScreenLoadCallback;
|
import com.google.gerrit.client.rpc.ScreenLoadCallback;
|
||||||
import com.google.gerrit.client.ui.AccountScreen;
|
import com.google.gerrit.client.ui.AccountScreen;
|
||||||
import com.google.gerrit.common.PageLinks;
|
import com.google.gerrit.common.PageLinks;
|
||||||
import com.google.gwtjsonrpc.common.VoidResult;
|
|
||||||
|
|
||||||
public class ValidateEmailScreen extends AccountScreen {
|
public class ValidateEmailScreen extends AccountScreen {
|
||||||
private final String magicToken;
|
private final String magicToken;
|
||||||
@@ -36,7 +37,7 @@ public class ValidateEmailScreen extends AccountScreen {
|
|||||||
@Override
|
@Override
|
||||||
protected void onLoad() {
|
protected void onLoad() {
|
||||||
super.onLoad();
|
super.onLoad();
|
||||||
Util.ACCOUNT_SEC.validateEmail(magicToken,
|
ConfigServerApi.confirmEmail(magicToken,
|
||||||
new ScreenLoadCallback<VoidResult>(this) {
|
new ScreenLoadCallback<VoidResult>(this) {
|
||||||
@Override
|
@Override
|
||||||
protected void preDisplay(final VoidResult result) {
|
protected void preDisplay(final VoidResult result) {
|
||||||
|
@@ -14,11 +14,13 @@
|
|||||||
|
|
||||||
package com.google.gerrit.client.config;
|
package com.google.gerrit.client.config;
|
||||||
|
|
||||||
|
import com.google.gerrit.client.VoidResult;
|
||||||
import com.google.gerrit.client.info.AccountPreferencesInfo;
|
import com.google.gerrit.client.info.AccountPreferencesInfo;
|
||||||
import com.google.gerrit.client.info.ServerInfo;
|
import com.google.gerrit.client.info.ServerInfo;
|
||||||
import com.google.gerrit.client.info.TopMenuList;
|
import com.google.gerrit.client.info.TopMenuList;
|
||||||
import com.google.gerrit.client.rpc.NativeMap;
|
import com.google.gerrit.client.rpc.NativeMap;
|
||||||
import com.google.gerrit.client.rpc.RestApi;
|
import com.google.gerrit.client.rpc.RestApi;
|
||||||
|
import com.google.gwt.core.client.JavaScriptObject;
|
||||||
import com.google.gwt.user.client.rpc.AsyncCallback;
|
import com.google.gwt.user.client.rpc.AsyncCallback;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -42,4 +44,21 @@ public class ConfigServerApi {
|
|||||||
public static void serverInfo(AsyncCallback<ServerInfo> cb) {
|
public static void serverInfo(AsyncCallback<ServerInfo> cb) {
|
||||||
new RestApi("/config/server/info").get(cb);
|
new RestApi("/config/server/info").get(cb);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void confirmEmail(String token, AsyncCallback<VoidResult> cb) {
|
||||||
|
EmailConfirmationInput input = EmailConfirmationInput.create();
|
||||||
|
input.setToken(token);
|
||||||
|
new RestApi("/config/server/email.confirm").put(input, cb);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class EmailConfirmationInput extends JavaScriptObject {
|
||||||
|
final native void setToken(String t) /*-{ this.t = t; }-*/;
|
||||||
|
|
||||||
|
static EmailConfirmationInput create() {
|
||||||
|
return createObject().cast();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected EmailConfirmationInput() {
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -34,12 +34,9 @@ import com.google.gerrit.server.CurrentUser;
|
|||||||
import com.google.gerrit.server.IdentifiedUser;
|
import com.google.gerrit.server.IdentifiedUser;
|
||||||
import com.google.gerrit.server.account.AccountByEmailCache;
|
import com.google.gerrit.server.account.AccountByEmailCache;
|
||||||
import com.google.gerrit.server.account.AccountCache;
|
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.GroupCache;
|
import com.google.gerrit.server.account.GroupCache;
|
||||||
import com.google.gerrit.server.account.Realm;
|
import com.google.gerrit.server.account.Realm;
|
||||||
import com.google.gerrit.server.contact.ContactStore;
|
import com.google.gerrit.server.contact.ContactStore;
|
||||||
import com.google.gerrit.server.mail.EmailTokenVerifier;
|
|
||||||
import com.google.gerrit.server.project.ProjectCache;
|
import com.google.gerrit.server.project.ProjectCache;
|
||||||
import com.google.gwtjsonrpc.common.AsyncCallback;
|
import com.google.gwtjsonrpc.common.AsyncCallback;
|
||||||
import com.google.gwtjsonrpc.common.VoidResult;
|
import com.google.gwtjsonrpc.common.VoidResult;
|
||||||
@@ -57,10 +54,8 @@ class AccountSecurityImpl extends BaseServiceImplementation implements
|
|||||||
private final Realm realm;
|
private final Realm realm;
|
||||||
private final ProjectCache projectCache;
|
private final ProjectCache projectCache;
|
||||||
private final Provider<IdentifiedUser> user;
|
private final Provider<IdentifiedUser> user;
|
||||||
private final EmailTokenVerifier emailTokenVerifier;
|
|
||||||
private final AccountByEmailCache byEmailCache;
|
private final AccountByEmailCache byEmailCache;
|
||||||
private final AccountCache accountCache;
|
private final AccountCache accountCache;
|
||||||
private final AccountManager accountManager;
|
|
||||||
private final boolean useContactInfo;
|
private final boolean useContactInfo;
|
||||||
|
|
||||||
private final DeleteExternalIds.Factory deleteExternalIdsFactory;
|
private final DeleteExternalIds.Factory deleteExternalIdsFactory;
|
||||||
@@ -74,9 +69,8 @@ class AccountSecurityImpl extends BaseServiceImplementation implements
|
|||||||
AccountSecurityImpl(final Provider<ReviewDb> schema,
|
AccountSecurityImpl(final Provider<ReviewDb> schema,
|
||||||
final Provider<CurrentUser> currentUser, final ContactStore cs,
|
final Provider<CurrentUser> currentUser, final ContactStore cs,
|
||||||
final Realm r, final Provider<IdentifiedUser> u,
|
final Realm r, final Provider<IdentifiedUser> u,
|
||||||
final EmailTokenVerifier etv, final ProjectCache pc,
|
final ProjectCache pc,
|
||||||
final AccountByEmailCache abec, final AccountCache uac,
|
final AccountByEmailCache abec, final AccountCache uac,
|
||||||
final AccountManager am,
|
|
||||||
final DeleteExternalIds.Factory deleteExternalIdsFactory,
|
final DeleteExternalIds.Factory deleteExternalIdsFactory,
|
||||||
final ExternalIdDetailFactory.Factory externalIdDetailFactory,
|
final ExternalIdDetailFactory.Factory externalIdDetailFactory,
|
||||||
final ChangeHooks hooks, final GroupCache groupCache,
|
final ChangeHooks hooks, final GroupCache groupCache,
|
||||||
@@ -85,11 +79,9 @@ class AccountSecurityImpl extends BaseServiceImplementation implements
|
|||||||
contactStore = cs;
|
contactStore = cs;
|
||||||
realm = r;
|
realm = r;
|
||||||
user = u;
|
user = u;
|
||||||
emailTokenVerifier = etv;
|
|
||||||
projectCache = pc;
|
projectCache = pc;
|
||||||
byEmailCache = abec;
|
byEmailCache = abec;
|
||||||
accountCache = uac;
|
accountCache = uac;
|
||||||
accountManager = am;
|
|
||||||
this.auditService = auditService;
|
this.auditService = auditService;
|
||||||
|
|
||||||
useContactInfo = contactStore != null && contactStore.isEnabled();
|
useContactInfo = contactStore != null && contactStore.isEnabled();
|
||||||
@@ -201,22 +193,4 @@ class AccountSecurityImpl extends BaseServiceImplementation implements
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void validateEmail(final String tokenString,
|
|
||||||
final AsyncCallback<VoidResult> callback) {
|
|
||||||
try {
|
|
||||||
EmailTokenVerifier.ParsedToken token = emailTokenVerifier.decode(tokenString);
|
|
||||||
Account.Id currentUser = user.get().getAccountId();
|
|
||||||
if (currentUser.equals(token.getAccountId())) {
|
|
||||||
accountManager.link(currentUser, token.toAuthRequest());
|
|
||||||
callback.onSuccess(VoidResult.INSTANCE);
|
|
||||||
} else {
|
|
||||||
throw new EmailTokenVerifier.InvalidTokenException();
|
|
||||||
}
|
|
||||||
} catch (EmailTokenVerifier.InvalidTokenException | OrmException
|
|
||||||
| AccountException e) {
|
|
||||||
callback.onFailure(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,80 @@
|
|||||||
|
// Copyright (C) 2015 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.config;
|
||||||
|
|
||||||
|
import com.google.gerrit.extensions.restapi.AuthException;
|
||||||
|
import com.google.gerrit.extensions.restapi.DefaultInput;
|
||||||
|
import com.google.gerrit.extensions.restapi.Response;
|
||||||
|
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.server.CurrentUser;
|
||||||
|
import com.google.gerrit.server.IdentifiedUser;
|
||||||
|
import com.google.gerrit.server.account.AccountException;
|
||||||
|
import com.google.gerrit.server.account.AccountManager;
|
||||||
|
import com.google.gerrit.server.config.ConfirmEmail.Input;
|
||||||
|
import com.google.gerrit.server.mail.EmailTokenVerifier;
|
||||||
|
import com.google.gwtorm.server.OrmException;
|
||||||
|
import com.google.inject.Inject;
|
||||||
|
import com.google.inject.Provider;
|
||||||
|
import com.google.inject.Singleton;
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
public class ConfirmEmail implements RestModifyView<ConfigResource, Input> {
|
||||||
|
public static class Input {
|
||||||
|
@DefaultInput
|
||||||
|
public String token;
|
||||||
|
}
|
||||||
|
|
||||||
|
private final Provider<CurrentUser> self;
|
||||||
|
private final EmailTokenVerifier emailTokenVerifier;
|
||||||
|
private final AccountManager accountManager;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public ConfirmEmail(Provider<CurrentUser> self,
|
||||||
|
EmailTokenVerifier emailTokenVerifier,
|
||||||
|
AccountManager accountManager) {
|
||||||
|
this.self = self;
|
||||||
|
this.emailTokenVerifier = emailTokenVerifier;
|
||||||
|
this.accountManager = accountManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Response<?> apply(ConfigResource rsrc, Input input)
|
||||||
|
throws AuthException, UnprocessableEntityException, AccountException,
|
||||||
|
OrmException {
|
||||||
|
CurrentUser user = self.get();
|
||||||
|
if (!user.isIdentifiedUser()) {
|
||||||
|
throw new AuthException("Authentication required");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (input == null) {
|
||||||
|
input = new Input();
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
EmailTokenVerifier.ParsedToken token = emailTokenVerifier.decode(input.token);
|
||||||
|
Account.Id accId = ((IdentifiedUser)user).getAccountId();
|
||||||
|
if (accId.equals(token.getAccountId())) {
|
||||||
|
accountManager.link(accId, token.toAuthRequest());
|
||||||
|
return Response.none();
|
||||||
|
} else {
|
||||||
|
throw new UnprocessableEntityException("invalid token");
|
||||||
|
}
|
||||||
|
} catch (EmailTokenVerifier.InvalidTokenException e) {
|
||||||
|
throw new UnprocessableEntityException("invalid token");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -38,5 +38,6 @@ public class Module extends RestApiModule {
|
|||||||
get(CONFIG_KIND, "info").to(GetServerInfo.class);
|
get(CONFIG_KIND, "info").to(GetServerInfo.class);
|
||||||
get(CONFIG_KIND, "preferences").to(GetPreferences.class);
|
get(CONFIG_KIND, "preferences").to(GetPreferences.class);
|
||||||
put(CONFIG_KIND, "preferences").to(SetPreferences.class);
|
put(CONFIG_KIND, "preferences").to(SetPreferences.class);
|
||||||
|
put(CONFIG_KIND, "email.confirm").to(ConfirmEmail.class);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user