Add REST endpoint to get account external IDs
Add a new endpoint /accounts/{id}/external.ids which will return a list of the external IDs for the given account. It is only allowed to get this list for a user's own account. To determine whether or not the user can delete an external ID, we need to know if that ID was used to log into the current session. The WebSession is not available outside of gerrit-httpd, so pass the external ID through a property on the current user. Change-Id: Iab7ef2ffa06c3a8d9f22472308051b4b73668f82
This commit is contained in:
@@ -1675,6 +1675,39 @@ can contain a list of link:#project-watch-info[ProjectWatchInfo] entities.
|
||||
HTTP/1.1 204 No Content
|
||||
----
|
||||
|
||||
[[get-account-external-ids]]
|
||||
=== Get Account External IDs
|
||||
--
|
||||
'GET /accounts/link:#account-id[\{account-id\}]/external.ids'
|
||||
--
|
||||
|
||||
Retrieves the external ids of a user account.
|
||||
|
||||
.Request
|
||||
----
|
||||
GET /a/accounts/self/external.ids HTTP/1.0
|
||||
----
|
||||
|
||||
As result the external ids of the user are returned as a list of
|
||||
link:#account-external-id-info[AccountExternalIdInfo] entities.
|
||||
|
||||
.Response
|
||||
----
|
||||
HTTP/1.1 200 OK
|
||||
Content-Disposition: attachment
|
||||
Content-Type: application/json; charset=UTF-8
|
||||
|
||||
)]}'
|
||||
[
|
||||
{
|
||||
"identity": "username:john",
|
||||
"email": "john.doe@example.com",
|
||||
"trusted": true,
|
||||
"_can_delete": false
|
||||
}
|
||||
]
|
||||
----
|
||||
|
||||
[[default-star-endpoints]]
|
||||
== Default Star Endpoints
|
||||
|
||||
@@ -2099,6 +2132,20 @@ for an account.
|
||||
If not set or if set to an empty string, the account name is deleted.
|
||||
|=============================
|
||||
|
||||
[[account-external-id-info]]
|
||||
=== AccountExternalIdInfo
|
||||
The `AccountExternalIdInfo` entity contains information for an external id of
|
||||
an account.
|
||||
|
||||
[options="header",cols="1,^1,5"]
|
||||
|============================
|
||||
|Field Name ||Description
|
||||
|`identity` ||The account external id.
|
||||
|`email` |optional|The email address for the external id.
|
||||
|`trusted` ||True if the external id is trusted.
|
||||
|`_can_delete` ||True if the external id can be deleted.
|
||||
|============================
|
||||
|
||||
[[capability-info]]
|
||||
=== CapabilityInfo
|
||||
The `CapabilityInfo` entity contains information about the global
|
||||
|
@@ -38,6 +38,7 @@ import com.google.common.io.BaseEncoding;
|
||||
import com.google.gerrit.acceptance.AbstractDaemonTest;
|
||||
import com.google.gerrit.acceptance.AccountCreator;
|
||||
import com.google.gerrit.acceptance.PushOneCommit;
|
||||
import com.google.gerrit.acceptance.RestResponse;
|
||||
import com.google.gerrit.acceptance.TestAccount;
|
||||
import com.google.gerrit.acceptance.UseSsh;
|
||||
import com.google.gerrit.common.data.Permission;
|
||||
@@ -46,6 +47,7 @@ import com.google.gerrit.extensions.api.changes.AddReviewerInput;
|
||||
import com.google.gerrit.extensions.api.changes.ReviewInput;
|
||||
import com.google.gerrit.extensions.api.changes.StarsInput;
|
||||
import com.google.gerrit.extensions.client.GeneralPreferencesInfo;
|
||||
import com.google.gerrit.extensions.common.AccountExternalIdInfo;
|
||||
import com.google.gerrit.extensions.common.AccountInfo;
|
||||
import com.google.gerrit.extensions.common.ChangeInfo;
|
||||
import com.google.gerrit.extensions.common.GpgKeyInfo;
|
||||
@@ -70,6 +72,7 @@ import com.google.gerrit.server.project.RefPattern;
|
||||
import com.google.gerrit.server.util.MagicBranch;
|
||||
import com.google.gerrit.testutil.ConfigSuite;
|
||||
import com.google.gerrit.testutil.FakeEmailSender.Message;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Provider;
|
||||
|
||||
@@ -98,6 +101,7 @@ import java.util.EnumSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class AccountIT extends AbstractDaemonTest {
|
||||
@ConfigSuite.Default
|
||||
@@ -743,6 +747,33 @@ public class AccountIT extends AbstractDaemonTest {
|
||||
gApi.accounts().id(admin.username).index();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getAccountExternalIds() throws Exception {
|
||||
List<AccountExternalIdInfo> expectedIdInfoList = getExternalIds(user)
|
||||
.stream().map(AccountIT::toInfo).sorted().collect(Collectors.toList());
|
||||
|
||||
RestResponse response = userRestSession.get("/accounts/self/external.ids");
|
||||
response.assertOK();
|
||||
List<AccountExternalIdInfo> externalIdInfoList =
|
||||
newGson().fromJson(response.getReader(),
|
||||
new TypeToken<List<AccountExternalIdInfo>>() {}.getType());
|
||||
|
||||
// 'canDelete' field will be all false. It will be better if we can find
|
||||
// a way to test it. But it looks a little difficult.
|
||||
externalIdInfoList.stream().sorted();
|
||||
assertThat(expectedIdInfoList)
|
||||
.containsExactlyElementsIn(expectedIdInfoList);
|
||||
}
|
||||
|
||||
private static AccountExternalIdInfo toInfo(AccountExternalId id) {
|
||||
AccountExternalIdInfo info = new AccountExternalIdInfo();
|
||||
info.identity = id.getExternalId();
|
||||
info.emailAddress = id.getEmailAddress();
|
||||
info.trusted = id.isTrusted();
|
||||
info.canDelete = id.canDelete();
|
||||
return info;
|
||||
}
|
||||
|
||||
private void assertSequenceNumbers(List<SshKeyInfo> sshKeys) {
|
||||
int seq = 1;
|
||||
for (SshKeyInfo key : sshKeys) {
|
||||
|
@@ -19,6 +19,7 @@ import com.google.gerrit.extensions.client.DiffPreferencesInfo;
|
||||
import com.google.gerrit.extensions.client.EditPreferencesInfo;
|
||||
import com.google.gerrit.extensions.client.GeneralPreferencesInfo;
|
||||
import com.google.gerrit.extensions.client.ProjectWatchInfo;
|
||||
import com.google.gerrit.extensions.common.AccountExternalIdInfo;
|
||||
import com.google.gerrit.extensions.common.AccountInfo;
|
||||
import com.google.gerrit.extensions.common.AgreementInfo;
|
||||
import com.google.gerrit.extensions.common.ChangeInfo;
|
||||
@@ -79,6 +80,8 @@ public interface AccountApi {
|
||||
|
||||
void index() throws RestApiException;
|
||||
|
||||
List<AccountExternalIdInfo> getExternalIds() throws RestApiException;
|
||||
|
||||
/**
|
||||
* A default implementation which allows source compatibility
|
||||
* when adding new methods to the interface.
|
||||
@@ -225,5 +228,10 @@ public interface AccountApi {
|
||||
public void index() {
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<AccountExternalIdInfo> getExternalIds() {
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,33 @@
|
||||
// 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.google.gerrit.extensions.common;
|
||||
|
||||
import com.google.common.collect.ComparisonChain;
|
||||
|
||||
public class AccountExternalIdInfo
|
||||
implements Comparable<AccountExternalIdInfo> {
|
||||
public String identity;
|
||||
public String emailAddress;
|
||||
public Boolean trusted;
|
||||
public Boolean canDelete;
|
||||
|
||||
@Override
|
||||
public int compareTo(AccountExternalIdInfo a) {
|
||||
return ComparisonChain.start()
|
||||
.compare(a.identity, identity)
|
||||
.compare(a.emailAddress, emailAddress)
|
||||
.result();
|
||||
}
|
||||
}
|
@@ -90,6 +90,7 @@ import com.google.gerrit.extensions.restapi.RestView;
|
||||
import com.google.gerrit.extensions.restapi.TopLevelResource;
|
||||
import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
|
||||
import com.google.gerrit.httpd.WebSession;
|
||||
import com.google.gerrit.reviewdb.client.AccountExternalId;
|
||||
import com.google.gerrit.server.AccessPath;
|
||||
import com.google.gerrit.server.AnonymousUser;
|
||||
import com.google.gerrit.server.CurrentUser;
|
||||
@@ -1077,6 +1078,9 @@ public class RestApiServlet extends HttpServlet {
|
||||
CurrentUser user = globals.currentUser.get();
|
||||
if (isRead(req)) {
|
||||
user.setAccessPath(AccessPath.REST_API);
|
||||
CurrentUser.PropertyKey<AccountExternalId.Key> k =
|
||||
CurrentUser.PropertyKey.create();
|
||||
user.put(k, globals.webSession.get().getLastLoginExternalId());
|
||||
} else if (user instanceof AnonymousUser) {
|
||||
throw new AuthException("Authentication required");
|
||||
} else if (!globals.webSession.get().isAccessPathOk(AccessPath.REST_API)) {
|
||||
|
@@ -0,0 +1,87 @@
|
||||
// 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.google.gerrit.server.account;
|
||||
|
||||
import static com.google.gerrit.reviewdb.client.AccountExternalId.SCHEME_USERNAME;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.gerrit.extensions.common.AccountExternalIdInfo;
|
||||
import com.google.gerrit.extensions.restapi.AuthException;
|
||||
import com.google.gerrit.extensions.restapi.RestApiException;
|
||||
import com.google.gerrit.extensions.restapi.RestReadView;
|
||||
import com.google.gerrit.reviewdb.client.AccountExternalId;
|
||||
import com.google.gerrit.server.CurrentUser;
|
||||
import com.google.gerrit.server.config.AuthConfig;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Provider;
|
||||
import com.google.inject.Singleton;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
@Singleton
|
||||
public class GetExternalIds implements RestReadView<AccountResource> {
|
||||
private final ExternalIdCache externalIdCache;
|
||||
private final Provider<CurrentUser> self;
|
||||
private final AuthConfig authConfig;
|
||||
|
||||
@Inject
|
||||
GetExternalIds(ExternalIdCache externalIdCache,
|
||||
Provider<CurrentUser> self,
|
||||
AuthConfig authConfig) {
|
||||
this.externalIdCache = externalIdCache;
|
||||
this.self = self;
|
||||
this.authConfig = authConfig;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<AccountExternalIdInfo> apply(AccountResource resource)
|
||||
throws RestApiException {
|
||||
if (self.get() != resource.getUser()) {
|
||||
throw new AuthException("not allowed to get external IDs");
|
||||
}
|
||||
|
||||
Collection<AccountExternalId> ids = externalIdCache.byAccount(
|
||||
resource.getUser().getAccountId());
|
||||
if (ids.isEmpty()) {
|
||||
return ImmutableList.of();
|
||||
}
|
||||
List<AccountExternalIdInfo> result =
|
||||
Lists.newArrayListWithCapacity(ids.size());
|
||||
for (AccountExternalId id : ids) {
|
||||
AccountExternalIdInfo info = new AccountExternalIdInfo();
|
||||
info.identity = id.getExternalId();
|
||||
info.emailAddress = id.getEmailAddress();
|
||||
info.trusted = authConfig.isIdentityTrustable(
|
||||
Collections.singleton(id));
|
||||
// The identity can be deleted only if its not the one used to
|
||||
// establish this web session, and if only if an identity was
|
||||
// actually used to establish this web session.
|
||||
if (id.isScheme(SCHEME_USERNAME)) {
|
||||
info.canDelete = false;
|
||||
} else {
|
||||
CurrentUser.PropertyKey<AccountExternalId.Key> k =
|
||||
CurrentUser.PropertyKey.create();
|
||||
AccountExternalId.Key last = resource.getUser().get(k);
|
||||
info.canDelete = (last != null) && (!last.get().equals(info.identity));
|
||||
}
|
||||
result.add(info);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
@@ -95,6 +95,8 @@ public class Module extends RestApiModule {
|
||||
get(STAR_KIND).to(Stars.Get.class);
|
||||
post(STAR_KIND).to(Stars.Post.class);
|
||||
|
||||
get(ACCOUNT_KIND, "external.ids").to(GetExternalIds.class);
|
||||
|
||||
factory(CreateAccount.Factory.class);
|
||||
factory(CreateEmail.Factory.class);
|
||||
}
|
||||
|
@@ -26,6 +26,7 @@ import com.google.gerrit.extensions.client.DiffPreferencesInfo;
|
||||
import com.google.gerrit.extensions.client.EditPreferencesInfo;
|
||||
import com.google.gerrit.extensions.client.GeneralPreferencesInfo;
|
||||
import com.google.gerrit.extensions.client.ProjectWatchInfo;
|
||||
import com.google.gerrit.extensions.common.AccountExternalIdInfo;
|
||||
import com.google.gerrit.extensions.common.AccountInfo;
|
||||
import com.google.gerrit.extensions.common.AgreementInfo;
|
||||
import com.google.gerrit.extensions.common.AgreementInput;
|
||||
@@ -49,6 +50,7 @@ import com.google.gerrit.server.account.GetAgreements;
|
||||
import com.google.gerrit.server.account.GetAvatar;
|
||||
import com.google.gerrit.server.account.GetDiffPreferences;
|
||||
import com.google.gerrit.server.account.GetEditPreferences;
|
||||
import com.google.gerrit.server.account.GetExternalIds;
|
||||
import com.google.gerrit.server.account.GetPreferences;
|
||||
import com.google.gerrit.server.account.GetSshKeys;
|
||||
import com.google.gerrit.server.account.GetWatchedProjects;
|
||||
@@ -110,6 +112,7 @@ public class AccountApiImpl implements AccountApi {
|
||||
private final PutActive putActive;
|
||||
private final DeleteActive deleteActive;
|
||||
private final Index index;
|
||||
private final GetExternalIds getExternalIds;
|
||||
|
||||
@Inject
|
||||
AccountApiImpl(AccountLoader.Factory ailf,
|
||||
@@ -141,6 +144,7 @@ public class AccountApiImpl implements AccountApi {
|
||||
PutActive putActive,
|
||||
DeleteActive deleteActive,
|
||||
Index index,
|
||||
GetExternalIds getExternalIds,
|
||||
@Assisted AccountResource account) {
|
||||
this.account = account;
|
||||
this.accountLoaderFactory = ailf;
|
||||
@@ -172,6 +176,7 @@ public class AccountApiImpl implements AccountApi {
|
||||
this.putActive = putActive;
|
||||
this.deleteActive = deleteActive;
|
||||
this.index = index;
|
||||
this.getExternalIds = getExternalIds;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -447,4 +452,9 @@ public class AccountApiImpl implements AccountApi {
|
||||
throw new RestApiException("Cannot index account", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<AccountExternalIdInfo> getExternalIds() throws RestApiException {
|
||||
return getExternalIds.apply(account);
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user