Support adding an SSH key via REST
By POST on /accounts/<account-id>/sshkeys it is now possible to add an SSH key for a user. The SSH public key has to be provided as raw content in the request body. The WebUI is adapted to use the new REST endpint to add SSH keys. The old RPC for this is deleted. Change-Id: I9617ccf19f078f9bcee615803755f445ed68bdc9 Signed-off-by: Edwin Kempin <edwin.kempin@sap.com>
This commit is contained in:
committed by
Edwin Kempin
parent
1e85b1ec3f
commit
4e2e5216f9
@@ -509,6 +509,44 @@ describes the SSH key.
|
||||
}
|
||||
----
|
||||
|
||||
[[add-ssh-key]]
|
||||
Add SSH Key
|
||||
~~~~~~~~~~~
|
||||
[verse]
|
||||
'POST /accounts/link:#account-id[\{account-id\}]/sshkeys'
|
||||
|
||||
Adds an SSH key for a user.
|
||||
|
||||
The SSH public key must be provided as raw content in the request body.
|
||||
|
||||
.Request
|
||||
----
|
||||
POST /accounts/self/sshkeys HTTP/1.0
|
||||
Content-Type: plain/text
|
||||
|
||||
AAAAB3NzaC1yc2EAAAABIwAAAQEA0T...YImydZAw\u003d\u003d
|
||||
----
|
||||
|
||||
As response an link:#ssh-key-info[SshKeyInfo] entity is returned that
|
||||
describes the new SSH key.
|
||||
|
||||
.Response
|
||||
----
|
||||
HTTP/1.1 200 OK
|
||||
Content-Disposition: attachment
|
||||
Content-Type: application/json;charset=UTF-8
|
||||
|
||||
)]}'
|
||||
{
|
||||
"seq": 2,
|
||||
"ssh_public_key": "ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA0T...YImydZAw\u003d\u003d john.doe@example.com",
|
||||
"encoded_key": "AAAAB3NzaC1yc2EAAAABIwAAAQEA0T...YImydZAw\u003d\u003d",
|
||||
"algorithm": "ssh-rsa",
|
||||
"comment": "john.doe@example.com",
|
||||
"valid": true
|
||||
}
|
||||
----
|
||||
|
||||
[[list-account-capabilities]]
|
||||
List Account Capabilities
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
@@ -31,10 +31,6 @@ import java.util.Set;
|
||||
|
||||
@RpcImpl(version = Version.V2_0)
|
||||
public interface AccountSecurity extends RemoteJsonService {
|
||||
@Audit
|
||||
@SignInRequired
|
||||
void addSshKey(String keyText, AsyncCallback<AccountSshKey> callback);
|
||||
|
||||
@Audit
|
||||
@SignInRequired
|
||||
void deleteSshKeys(Set<AccountSshKey.Id> ids,
|
||||
|
||||
@@ -40,6 +40,13 @@ public class AccountApi {
|
||||
new RestApi("/accounts/").id(account).view("sshkeys").get(cb);
|
||||
}
|
||||
|
||||
/** Add a new SSH keys */
|
||||
public static void addSshKey(String account, String sshPublicKey,
|
||||
AsyncCallback<SshKeyInfo> cb) {
|
||||
new RestApi("/accounts/").id(account).view("sshkeys")
|
||||
.post(sshPublicKey, cb);
|
||||
}
|
||||
|
||||
/** Generate a new HTTP password */
|
||||
public static void generateHttpPassword(String account,
|
||||
AsyncCallback<NativeString> cb) {
|
||||
|
||||
@@ -24,18 +24,6 @@ public class SshKeyInfo extends JavaScriptObject {
|
||||
public final native String comment() /*-{ return this.comment; }-*/;
|
||||
public final native boolean isValid() /*-{ return this['valid'] ? true : false; }-*/;
|
||||
|
||||
public static native SshKeyInfo create(int seq, String sshPublicKey,
|
||||
String encodedKey, String algorithm, String comment, boolean valid) /*-{
|
||||
return {
|
||||
'seq': seq,
|
||||
'ssh_public_key': sshPublicKey,
|
||||
'encoded_key': encodedKey,
|
||||
'algorithm': algorithm,
|
||||
'comment': comment,
|
||||
'valid': valid
|
||||
};
|
||||
}-*/;
|
||||
|
||||
protected SshKeyInfo() {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -159,13 +159,11 @@ class SshPanel extends Composite {
|
||||
final String txt = addTxt.getText();
|
||||
if (txt != null && txt.length() > 0) {
|
||||
addNew.setEnabled(false);
|
||||
Util.ACCOUNT_SEC.addSshKey(txt, new GerritCallback<AccountSshKey>() {
|
||||
public void onSuccess(final AccountSshKey k) {
|
||||
AccountApi.addSshKey("self", txt, new GerritCallback<SshKeyInfo>() {
|
||||
public void onSuccess(final SshKeyInfo k) {
|
||||
addNew.setEnabled(true);
|
||||
addTxt.setText("");
|
||||
keys.addOneKey(SshKeyInfo.create(k.getKey().get(),
|
||||
k.getSshPublicKey(), k.getEncodedKey(), k.getAlgorithm(),
|
||||
k.getComment(), k.isValid()));
|
||||
keys.addOneKey(k);
|
||||
if (!keys.isVisible()) {
|
||||
showAddKeyBlock(false);
|
||||
setKeyTableVisible(true);
|
||||
|
||||
@@ -304,6 +304,11 @@ public class RestApi {
|
||||
sendJSON(POST, content, cb);
|
||||
}
|
||||
|
||||
public <T extends JavaScriptObject> void post(String content,
|
||||
AsyncCallback<T> cb) {
|
||||
sendRaw(POST, content, cb);
|
||||
}
|
||||
|
||||
public <T extends JavaScriptObject> void put(AsyncCallback<T> cb) {
|
||||
send(PUT, cb);
|
||||
}
|
||||
@@ -329,6 +334,19 @@ public class RestApi {
|
||||
}
|
||||
}
|
||||
|
||||
private <T extends JavaScriptObject> void sendRaw(Method method, String body,
|
||||
AsyncCallback<T> cb) {
|
||||
HttpCallback<T> httpCallback = new HttpCallback<T>(cb);
|
||||
try {
|
||||
RpcStatus.INSTANCE.onRpcStart();
|
||||
RequestBuilder req = request(method);
|
||||
req.setHeader("Content-Type", TEXT_TYPE);
|
||||
req.sendRequest(body, httpCallback);
|
||||
} catch (RequestException e) {
|
||||
httpCallback.onError(null, e);
|
||||
}
|
||||
}
|
||||
|
||||
private RequestBuilder request(Method method) {
|
||||
RequestBuilder req = new RequestBuilder(method, url());
|
||||
if (ifNoneMatch != null) {
|
||||
|
||||
@@ -19,7 +19,6 @@ import com.google.gerrit.common.ChangeHooks;
|
||||
import com.google.gerrit.common.data.AccountSecurity;
|
||||
import com.google.gerrit.common.data.ContributorAgreement;
|
||||
import com.google.gerrit.common.errors.ContactInformationStoreException;
|
||||
import com.google.gerrit.common.errors.InvalidSshKeyException;
|
||||
import com.google.gerrit.common.errors.NoSuchEntityException;
|
||||
import com.google.gerrit.common.errors.PermissionDeniedException;
|
||||
import com.google.gerrit.httpd.rpc.BaseServiceImplementation;
|
||||
@@ -106,29 +105,6 @@ class AccountSecurityImpl extends BaseServiceImplementation implements
|
||||
this.groupCache = groupCache;
|
||||
}
|
||||
|
||||
public void addSshKey(final String keyText,
|
||||
final AsyncCallback<AccountSshKey> callback) {
|
||||
run(callback, new Action<AccountSshKey>() {
|
||||
public AccountSshKey run(final ReviewDb db) throws OrmException, Failure {
|
||||
int max = 0;
|
||||
final Account.Id me = user.get().getAccountId();
|
||||
for (final AccountSshKey k : db.accountSshKeys().byAccount(me)) {
|
||||
max = Math.max(max, k.getKey().get());
|
||||
}
|
||||
|
||||
final AccountSshKey key;
|
||||
try {
|
||||
key = sshKeyCache.create(new AccountSshKey.Id(me, max + 1), keyText);
|
||||
} catch (InvalidSshKeyException e) {
|
||||
throw new Failure(e);
|
||||
}
|
||||
db.accountSshKeys().insert(Collections.singleton(key));
|
||||
uncacheSshKeys();
|
||||
return key;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void deleteSshKeys(final Set<AccountSshKey.Id> ids,
|
||||
final AsyncCallback<VoidResult> callback) {
|
||||
run(callback, new Action<VoidResult>() {
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
// 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.account;
|
||||
|
||||
import com.google.common.base.Charsets;
|
||||
import com.google.common.io.CharStreams;
|
||||
import com.google.common.io.InputSupplier;
|
||||
import com.google.gerrit.common.errors.InvalidSshKeyException;
|
||||
import com.google.gerrit.extensions.restapi.AuthException;
|
||||
import com.google.gerrit.extensions.restapi.BadRequestException;
|
||||
import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
|
||||
import com.google.gerrit.extensions.restapi.RawInput;
|
||||
import com.google.gerrit.extensions.restapi.ResourceConflictException;
|
||||
import com.google.gerrit.extensions.restapi.Response;
|
||||
import com.google.gerrit.extensions.restapi.RestModifyView;
|
||||
import com.google.gerrit.reviewdb.client.AccountSshKey;
|
||||
import com.google.gerrit.reviewdb.server.ReviewDb;
|
||||
import com.google.gerrit.server.CurrentUser;
|
||||
import com.google.gerrit.server.account.AddSshKey.Input;
|
||||
import com.google.gerrit.server.account.GetSshKeys.SshKeyInfo;
|
||||
import com.google.gerrit.server.ssh.SshKeyCache;
|
||||
import com.google.gwtorm.server.OrmException;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Provider;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Collections;
|
||||
|
||||
public class AddSshKey implements RestModifyView<AccountResource, Input> {
|
||||
static class Input {
|
||||
RawInput raw;
|
||||
}
|
||||
|
||||
private final Provider<CurrentUser> self;
|
||||
private final Provider<ReviewDb> dbProvider;
|
||||
private final SshKeyCache sshKeyCache;
|
||||
|
||||
@Inject
|
||||
AddSshKey(Provider<CurrentUser> self, Provider<ReviewDb> dbProvider,
|
||||
SshKeyCache sshKeyCache) {
|
||||
this.self = self;
|
||||
this.dbProvider = dbProvider;
|
||||
this.sshKeyCache = sshKeyCache;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Response<SshKeyInfo> apply(AccountResource rsrc, Input input)
|
||||
throws AuthException, MethodNotAllowedException, BadRequestException,
|
||||
ResourceConflictException, OrmException, IOException {
|
||||
if (self.get() != rsrc.getUser()
|
||||
&& !self.get().getCapabilities().canAdministrateServer()) {
|
||||
throw new AuthException("not allowed to add SSH keys");
|
||||
}
|
||||
if (input == null) {
|
||||
input = new Input();
|
||||
}
|
||||
if (input.raw == null) {
|
||||
throw new BadRequestException("SSH public key missing");
|
||||
}
|
||||
|
||||
int max = 0;
|
||||
for (AccountSshKey k : dbProvider.get().accountSshKeys()
|
||||
.byAccount(rsrc.getUser().getAccountId())) {
|
||||
max = Math.max(max, k.getKey().get());
|
||||
}
|
||||
|
||||
final InputStream in = input.raw.getInputStream();
|
||||
String sshPublicKey =
|
||||
CharStreams.toString(CharStreams.newReaderSupplier(
|
||||
new InputSupplier<InputStream>() {
|
||||
@Override
|
||||
public InputStream getInput() {
|
||||
return in;
|
||||
}
|
||||
}, Charsets.UTF_8));
|
||||
try {
|
||||
AccountSshKey sshKey =
|
||||
sshKeyCache.create(new AccountSshKey.Id(
|
||||
rsrc.getUser().getAccountId(), max + 1), sshPublicKey);
|
||||
dbProvider.get().accountSshKeys().insert(Collections.singleton(sshKey));
|
||||
sshKeyCache.evict(rsrc.getUser().getUserName());
|
||||
return Response.<SshKeyInfo>created(new SshKeyInfo(sshKey));
|
||||
} catch (InvalidSshKeyException e) {
|
||||
throw new BadRequestException(e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -51,6 +51,7 @@ public class Module extends RestApiModule {
|
||||
put(ACCOUNT_KIND, "password.http").to(PutHttpPassword.class);
|
||||
delete(ACCOUNT_KIND, "password.http").to(PutHttpPassword.class);
|
||||
child(ACCOUNT_KIND, "sshkeys").to(SshKeys.class);
|
||||
post(ACCOUNT_KIND, "sshkeys").to(AddSshKey.class);
|
||||
get(SSH_KEY_KIND).to(GetSshKey.class);
|
||||
get(ACCOUNT_KIND, "avatar").to(GetAvatar.class);
|
||||
get(ACCOUNT_KIND, "avatar.change.url").to(GetAvatarChangeUrl.class);
|
||||
|
||||
Reference in New Issue
Block a user