Merge branch 'stable-2.8'

* stable-2.8:
  Bump SSHD version to 0.9.0.201311081
  Add REST API to toggle starred change state

Change-Id: Ic8d7a1802f6d6319f8f1012f9fe0c7f0c030f60a
This commit is contained in:
Shawn Pearce
2013-11-08 21:30:05 -08:00
22 changed files with 616 additions and 230 deletions

View File

@@ -2507,6 +2507,14 @@ namespace. To alias `replication start` to `gerrit replicate`:
[[sshd]] Section sshd
~~~~~~~~~~~~~~~~~~~~~
[[sshd.backend]]sshd.backend::
+
Starting from version 0.9.0 Apache SSHD project added support for NIO2
IoSession. To use the new NIO2 session the `backend` option must be set
to `NIO2`.
+
By default, `MINA`.
[[sshd.listenAddress]]sshd.listenAddress::
+
Specifies the local addresses the internal SSHD should listen
@@ -2545,20 +2553,13 @@ of them.
+
By default, sshd.listenAddress.
[[sshd.reuseAddress]]sshd.reuseAddress::
+
If true, permits the daemon to bind to the port even if the port
is already in use. If false, the daemon ensures the port is not
in use before starting. Busy sites may need to set this to true
to permit fast restarts.
+
By default, true.
[[sshd.tcpKeepAlive]]sshd.tcpKeepAlive::
+
If true, enables TCP keepalive messages to the other side, so
the daemon can terminate connections if the peer disappears.
+
Only effective when `sshd.backend` is set to `MINA`.
+
By default, true.
[[sshd.threads]]sshd.threads::

View File

@@ -924,6 +924,84 @@ link:#diff-preferences-info[DiffPreferencesInfo] entity.
}
----
Get Starred Changes
~~~~~~~~~~~~~~~~~~~
[verse]
'GET /accounts/link:#account-id[\{account-id\}]/starred.changes'
Gets the changes starred by the identified user account. This
URL endpoint is functionally identical to the changes query
`GET /changes/?q=is:starred`. The result is a list of
link:rest-api-changes.html#change-info[ChangeInfo] entities.
.Request
----
GET /a/accounts/self/starred.changes
----
.Response
----
HTTP/1.1 200 OK
Content-Disposition: attachment
Content-Type: application/json;charset=UTF-8
)]}'
[
{
"kind": "gerritcodereview#change",
"id": "myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940",
"project": "myProject",
"branch": "master",
"change_id": "I8473b95934b5732ac55d26311a706c9c2bde9940",
"subject": "Implementing Feature X",
"status": "NEW",
"created": "2013-02-01 09:59:32.126000000",
"updated": "2013-02-21 11:16:36.775000000",
"mergeable": true,
"_sortkey": "0023412400000f7d",
"_number": 3965,
"owner": {
"name": "John Doe"
}
}
]
----
Star Change
~~~~~~~~~~~
[verse]
'PUT /accounts/link:#account-id[\{account-id\}]/starred.changes/link:rest-api-changes.html#change-id[\{change-id\}]'
Star a change. Starred changes are returned for the search query
`is:starred` or `starredby:USER` and automatically notify the user
whenever updates are made to the change.
.Request
----
PUT /a/accounts/self/starred.changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940 HTTP/1.0
----
.Response
----
HTTP/1.1 204 No Content
----
Unstar Change
~~~~~~~~~~~~~
[verse]
'DELETE /accounts/link:#account-id[\{account-id\}]/starred.changes/link:rest-api-changes#change-id[\{change-id\}]'
Unstar a change. Removes the starred flag, stopping notifications.
.Request
----
DELETE /a/accounts/self/starred.changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940 HTTP/1.0
----
.Response
----
HTTP/1.1 204 No Content
----
[[ids]]
IDs

View File

@@ -2,7 +2,11 @@ include_defs('//gerrit-acceptance-tests/tests.defs')
acceptance_tests(
srcs = glob(['*IT.java']),
deps = [':util'],
deps = [
':util',
'//gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git:util',
'//gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change:util',
],
)
java_library(

View File

@@ -0,0 +1,122 @@
// 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.acceptance.rest.account;
import static com.google.gerrit.acceptance.git.GitUtil.cloneProject;
import static com.google.gerrit.acceptance.git.GitUtil.createProject;
import static com.google.gerrit.acceptance.git.GitUtil.initSsh;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.AccountCreator;
import com.google.gerrit.acceptance.RestResponse;
import com.google.gerrit.acceptance.RestSession;
import com.google.gerrit.acceptance.SshSession;
import com.google.gerrit.acceptance.TestAccount;
import com.google.gerrit.acceptance.git.PushOneCommit;
import com.google.gerrit.acceptance.git.PushOneCommit.Result;
import com.google.gerrit.acceptance.rest.change.ChangeInfo;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import com.google.gwtorm.server.OrmException;
import com.google.gwtorm.server.SchemaFactory;
import com.google.inject.Inject;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
import java.util.List;
public class StarredChangesIT extends AbstractDaemonTest {
@Inject
private AccountCreator accounts;
@Inject
private SchemaFactory<ReviewDb> reviewDbProvider;
private TestAccount admin;
private RestSession session;
private Git git;
private ReviewDb db;
@Before
public void setUp() throws Exception {
admin = accounts.admin();
session = new RestSession(server, admin);
initSsh(admin);
Project.NameKey project = new Project.NameKey("p");
SshSession sshSession = new SshSession(server, admin);
createProject(sshSession, project.get());
git = cloneProject(sshSession.getUrl() + "/" + project.get());
sshSession.close();
db = reviewDbProvider.open();
}
@After
public void cleanup() {
db.close();
}
@Test
public void starredChangeState() throws GitAPIException, IOException,
OrmException {
Result c1 = createChange();
Result c2 = createChange();
assertNull(getChange(c1.getChangeId()).starred);
assertNull(getChange(c2.getChangeId()).starred);
starChange(true, c1.getPatchSetId().getParentKey());
starChange(true, c2.getPatchSetId().getParentKey());
assertTrue(getChange(c1.getChangeId()).starred);
assertTrue(getChange(c2.getChangeId()).starred);
starChange(false, c1.getPatchSetId().getParentKey());
starChange(false, c2.getPatchSetId().getParentKey());
assertNull(getChange(c1.getChangeId()).starred);
assertNull(getChange(c2.getChangeId()).starred);
}
private ChangeInfo getChange(String changeId) throws IOException {
RestResponse r = session.get("/changes/?q=" + changeId);
List<ChangeInfo> c = (new Gson()).fromJson(r.getReader(),
new TypeToken<List<ChangeInfo>>() {}.getType());
return c.get(0);
}
private void starChange(boolean on, Change.Id id) throws IOException {
String url = "/accounts/self/starred.changes/" + id.get();
if (on) {
RestResponse r = session.put(url);
assertEquals(204, r.getStatusCode());
} else {
RestResponse r = session.delete(url);
assertEquals(204, r.getStatusCode());
}
}
private Result createChange() throws GitAPIException, IOException {
PushOneCommit push = new PushOneCommit(db, admin.getIdent());
return push.to(git, "refs/for/master");
}
}

View File

@@ -38,4 +38,5 @@ java_library(
'//lib:guava',
'//gerrit-reviewdb:server',
],
visibility = ['//gerrit-acceptance-tests/...'],
)

View File

@@ -24,4 +24,5 @@ public class ChangeInfo {
String branch;
List<ChangeMessageInfo> messages;
Change.Status status;
public Boolean starred;
}

View File

@@ -1,35 +0,0 @@
// 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.common.data;
import com.google.gerrit.common.audit.Audit;
import com.google.gerrit.common.auth.SignInRequired;
import com.google.gwtjsonrpc.common.AsyncCallback;
import com.google.gwtjsonrpc.common.RemoteJsonService;
import com.google.gwtjsonrpc.common.RpcImpl;
import com.google.gwtjsonrpc.common.VoidResult;
import com.google.gwtjsonrpc.common.RpcImpl.Version;
@RpcImpl(version = Version.V2_0)
public interface ChangeListService extends RemoteJsonService {
/**
* Add and/or remove changes from the set of starred changes of the caller.
*
* @param req the add and remove cluster.
*/
@Audit
@SignInRequired
void toggleStars(ToggleStarRequest req, AsyncCallback<VoidResult> callback);
}

View File

@@ -15,19 +15,23 @@
package com.google.gerrit.client.changes;
import com.google.gerrit.client.Gerrit;
import com.google.gerrit.client.rpc.GerritCallback;
import com.google.gerrit.common.data.ToggleStarRequest;
import com.google.gerrit.client.account.AccountApi;
import com.google.gerrit.client.rpc.RestApi;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.KeyPressEvent;
import com.google.gwt.resources.client.ImageResource;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.Image;
import com.google.gwtexpui.globalkey.client.KeyCommand;
import com.google.gwtjsonrpc.common.VoidResult;
import com.google.web.bindery.event.shared.Event;
import com.google.web.bindery.event.shared.HandlerRegistration;
import java.util.LinkedHashMap;
import java.util.Map;
/** Supports the star icon displayed on changes and tracking the status. */
public class StarredChanges {
private static final Event.Type<ChangeStarHandler> TYPE =
@@ -105,57 +109,52 @@ public class StarredChanges {
public static void toggleStar(
final Change.Id changeId,
final boolean newValue) {
if (next == null) {
next = new ToggleStarRequest();
}
next.toggle(changeId, newValue);
pending.put(changeId, newValue);
fireChangeStarEvent(changeId, newValue);
if (!busy) {
start();
startRequest();
}
}
private static ToggleStarRequest next;
private static boolean busy;
private static final Map<Change.Id, Boolean> pending =
new LinkedHashMap<Change.Id, Boolean>(4);
private static void start() {
final ToggleStarRequest req = next;
next = null;
private static void startRequest() {
busy = true;
Util.LIST_SVC.toggleStars(req, new GerritCallback<VoidResult>() {
final Change.Id id = pending.keySet().iterator().next();
final boolean starred = pending.remove(id);
RestApi call = AccountApi.self().view("starred.changes").id(id.get());
AsyncCallback<JavaScriptObject> cb = new AsyncCallback<JavaScriptObject>() {
@Override
public void onSuccess(VoidResult result) {
if (next != null) {
start();
} else {
public void onSuccess(JavaScriptObject none) {
if (pending.isEmpty()) {
busy = false;
} else {
startRequest();
}
}
@Override
public void onFailure(Throwable caught) {
rollback(req);
if (next != null) {
rollback(next);
next = null;
}
busy = false;
super.onFailure(caught);
}
});
if (!starred && RestApi.isStatus(caught, 404)) {
onSuccess(null);
return;
}
private static void rollback(ToggleStarRequest req) {
if (req.getAddSet() != null) {
for (Change.Id id : req.getAddSet()) {
fireChangeStarEvent(id, false);
fireChangeStarEvent(id, !starred);
for (Map.Entry<Change.Id, Boolean> e : pending.entrySet()) {
fireChangeStarEvent(e.getKey(), !e.getValue());
}
pending.clear();
busy = false;
}
if (req.getRemoveSet() != null) {
for (Change.Id id : req.getRemoveSet()) {
fireChangeStarEvent(id, true);
}
};
if (starred) {
call.put(cb);
} else {
call.delete(cb);
}
}

View File

@@ -15,7 +15,6 @@
package com.google.gerrit.client.changes;
import com.google.gerrit.common.data.ChangeDetailService;
import com.google.gerrit.common.data.ChangeListService;
import com.google.gerrit.common.data.ChangeManageService;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gwt.core.client.GWT;
@@ -27,7 +26,6 @@ public class Util {
public static final ChangeResources R = GWT.create(ChangeResources.class);
public static final ChangeDetailService DETAIL_SVC;
public static final ChangeListService LIST_SVC;
public static final ChangeManageService MANAGE_SVC;
private static final int SUBJECT_MAX_LENGTH = 80;
@@ -38,9 +36,6 @@ public class Util {
DETAIL_SVC = GWT.create(ChangeDetailService.class);
JsonUtil.bind(DETAIL_SVC, "rpc/ChangeDetailService");
LIST_SVC = GWT.create(ChangeListService.class);
JsonUtil.bind(LIST_SVC, "rpc/ChangeListService");
MANAGE_SVC = GWT.create(ChangeManageService.class);
JsonUtil.bind(MANAGE_SVC, "rpc/ChangeManageService");
}

View File

@@ -1,74 +0,0 @@
// 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;
import com.google.gerrit.common.data.ChangeListService;
import com.google.gerrit.common.data.ToggleStarRequest;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.StarredChange;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.CurrentUser;
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.ArrayList;
import java.util.List;
import java.util.Set;
public class ChangeListServiceImpl extends BaseServiceImplementation implements
ChangeListService {
private final Provider<CurrentUser> currentUser;
@Inject
ChangeListServiceImpl(final Provider<ReviewDb> schema,
final Provider<CurrentUser> currentUser) {
super(schema, currentUser);
this.currentUser = currentUser;
}
public void toggleStars(final ToggleStarRequest req,
final AsyncCallback<VoidResult> callback) {
run(callback, new Action<VoidResult>() {
public VoidResult run(final ReviewDb db) throws OrmException {
final Account.Id me = getAccountId();
final Set<Change.Id> existing = currentUser.get().getStarredChanges();
List<StarredChange> add = new ArrayList<StarredChange>();
List<StarredChange.Key> remove = new ArrayList<StarredChange.Key>();
if (req.getAddSet() != null) {
for (final Change.Id id : req.getAddSet()) {
if (!existing.contains(id)) {
add.add(new StarredChange(new StarredChange.Key(me, id)));
}
}
}
if (req.getRemoveSet() != null) {
for (final Change.Id id : req.getRemoveSet()) {
remove.add(new StarredChange.Key(me, id));
}
}
db.starredChanges().insert(add);
db.starredChanges().deleteKeys(remove);
return VoidResult.INSTANCE;
}
});
}
}

View File

@@ -27,7 +27,6 @@ public class UiRpcModule extends RpcServletModule {
@Override
protected void configureServlets() {
rpc(ChangeListServiceImpl.class);
rpc(SuggestServiceImpl.class);
rpc(SystemInfoServiceImpl.class);

View File

@@ -17,7 +17,9 @@ package com.google.gerrit.server.account;
import com.google.gerrit.extensions.restapi.RestResource;
import com.google.gerrit.extensions.restapi.RestView;
import com.google.gerrit.reviewdb.client.AccountSshKey;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.change.ChangeResource;
import com.google.inject.TypeLiteral;
public class AccountResource implements RestResource {
@@ -33,6 +35,9 @@ public class AccountResource implements RestResource {
public static final TypeLiteral<RestView<SshKey>> SSH_KEY_KIND =
new TypeLiteral<RestView<SshKey>>() {};
public static final TypeLiteral<RestView<StarredChange>> STARRED_CHANGE_KIND =
new TypeLiteral<RestView<StarredChange>>() {};
private final IdentifiedUser user;
public AccountResource(IdentifiedUser user) {
@@ -90,4 +95,17 @@ public class AccountResource implements RestResource {
return sshKey;
}
}
public static class StarredChange extends AccountResource {
private final ChangeResource change;
public StarredChange(IdentifiedUser user, ChangeResource change) {
super(user);
this.change = change;
}
public Change getChange() {
return change.getChange();
}
}
}

View File

@@ -18,6 +18,7 @@ import static com.google.gerrit.server.account.AccountResource.ACCOUNT_KIND;
import static com.google.gerrit.server.account.AccountResource.CAPABILITY_KIND;
import static com.google.gerrit.server.account.AccountResource.EMAIL_KIND;
import static com.google.gerrit.server.account.AccountResource.SSH_KEY_KIND;
import static com.google.gerrit.server.account.AccountResource.STARRED_CHANGE_KIND;
import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.extensions.restapi.RestApiModule;
@@ -30,9 +31,10 @@ public class Module extends RestApiModule {
bind(Capabilities.class);
DynamicMap.mapOf(binder(), ACCOUNT_KIND);
DynamicMap.mapOf(binder(), CAPABILITY_KIND);
DynamicMap.mapOf(binder(), EMAIL_KIND);
DynamicMap.mapOf(binder(), SSH_KEY_KIND);
DynamicMap.mapOf(binder(), CAPABILITY_KIND);
DynamicMap.mapOf(binder(), STARRED_CHANGE_KIND);
put(ACCOUNT_KIND).to(PutAccount.class);
get(ACCOUNT_KIND).to(GetAccount.class);
@@ -65,6 +67,11 @@ public class Module extends RestApiModule {
put(ACCOUNT_KIND, "preferences.diff").to(SetDiffPreferences.class);
get(CAPABILITY_KIND).to(GetCapabilities.CheckOne.class);
child(ACCOUNT_KIND, "starred.changes").to(StarredChanges.class);
put(STARRED_CHANGE_KIND).to(StarredChanges.Put.class);
delete(STARRED_CHANGE_KIND).to(StarredChanges.Delete.class);
bind(StarredChanges.Create.class);
install(new FactoryModuleBuilder().build(CreateAccount.Factory.class));
install(new FactoryModuleBuilder().build(CreateEmail.Factory.class));
}

View File

@@ -0,0 +1,195 @@
// 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.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.extensions.restapi.AcceptsCreate;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.ChildCollection;
import com.google.gerrit.extensions.restapi.IdString;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.extensions.restapi.RestReadView;
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.reviewdb.client.StarredChange;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.change.ChangeResource;
import com.google.gerrit.server.change.ChangesCollection;
import com.google.gerrit.server.query.change.QueryChanges;
import com.google.gwtorm.server.OrmDuplicateKeyException;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Collections;
class StarredChanges implements
ChildCollection<AccountResource, AccountResource.StarredChange>,
AcceptsCreate<AccountResource> {
private static final Logger log = LoggerFactory.getLogger(StarredChanges.class);
private final ChangesCollection changes;
private final DynamicMap<RestView<AccountResource.StarredChange>> views;
private final Provider<Create> createProvider;
@Inject
StarredChanges(ChangesCollection changes,
DynamicMap<RestView<AccountResource.StarredChange>> views,
Provider<Create> createProvider) {
this.changes = changes;
this.views = views;
this.createProvider = createProvider;
}
@Override
public AccountResource.StarredChange parse(AccountResource parent, IdString id)
throws ResourceNotFoundException, OrmException {
IdentifiedUser user = parent.getUser();
try {
user.asyncStarredChanges();
ChangeResource change = changes.parse(TopLevelResource.INSTANCE, id);
if (user.getStarredChanges().contains(change.getChange().getId())) {
return new AccountResource.StarredChange(user, change);
}
throw new ResourceNotFoundException(id);
} finally {
user.abortStarredChanges();
}
}
@Override
public DynamicMap<RestView<AccountResource.StarredChange>> views() {
return views;
}
@Override
public RestView<AccountResource> list() throws ResourceNotFoundException {
return new RestReadView<AccountResource>() {
@Override
public Object apply(AccountResource self) throws BadRequestException,
AuthException, OrmException {
QueryChanges query = changes.list();
query.addQuery("starredby:" + self.getUser().getAccountId().get());
return query.apply(TopLevelResource.INSTANCE);
}
};
}
@SuppressWarnings("unchecked")
@Override
public RestModifyView<AccountResource, EmptyInput> create(
AccountResource parent, IdString id) throws UnprocessableEntityException{
try {
return createProvider.get()
.setChange(changes.parse(TopLevelResource.INSTANCE, id));
} catch (ResourceNotFoundException e) {
throw new UnprocessableEntityException(String.format("change %s not found", id.get()));
} catch (OrmException e) {
log.error("cannot resolve change", e);
throw new UnprocessableEntityException("internal server error");
}
}
static class Create implements RestModifyView<AccountResource, EmptyInput> {
private final Provider<CurrentUser> self;
private final Provider<ReviewDb> dbProvider;
private ChangeResource change;
@Inject
Create(Provider<CurrentUser> self, Provider<ReviewDb> dbProvider) {
this.self = self;
this.dbProvider = dbProvider;
}
Create setChange(ChangeResource change) {
this.change = change;
return this;
}
@Override
public Response<?> apply(AccountResource rsrc, EmptyInput in)
throws AuthException, OrmException {
if (self.get() != rsrc.getUser()) {
throw new AuthException("not allowed to add starred change");
}
try {
dbProvider.get().starredChanges().insert(Collections.singleton(
new StarredChange(new StarredChange.Key(
rsrc.getUser().getAccountId(),
change.getChange().getId()))));
} catch (OrmDuplicateKeyException e) {
return Response.none();
}
return Response.none();
}
}
static class Put implements
RestModifyView<AccountResource.StarredChange, EmptyInput> {
private final Provider<CurrentUser> self;
@Inject
Put(Provider<CurrentUser> self) {
this.self = self;
}
@Override
public Response<?> apply(AccountResource.StarredChange rsrc, EmptyInput in)
throws AuthException, OrmException {
if (self.get() != rsrc.getUser()) {
throw new AuthException("not allowed update starred changes");
}
return Response.none();
}
}
static class Delete implements
RestModifyView<AccountResource.StarredChange, EmptyInput> {
private final Provider<CurrentUser> self;
private final Provider<ReviewDb> dbProvider;
@Inject
Delete(Provider<CurrentUser> self, Provider<ReviewDb> dbProvider) {
this.self = self;
this.dbProvider = dbProvider;
}
@Override
public Response<?> apply(AccountResource.StarredChange rsrc,
EmptyInput in) throws AuthException, OrmException {
if (self.get() != rsrc.getUser()) {
throw new AuthException("not allowed remove starred change");
}
dbProvider.get().starredChanges().delete(Collections.singleton(
new StarredChange(new StarredChange.Key(
rsrc.getUser().getAccountId(),
rsrc.getChange().getId()))));
return Response.none();
}
}
static class EmptyInput {
}
}

View File

@@ -57,7 +57,7 @@ public class ChangesCollection implements
}
@Override
public RestView<TopLevelResource> list() {
public QueryChanges list() {
return queryFactory.get();
}

View File

@@ -0,0 +1,34 @@
// 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.sshd;
import org.apache.sshd.common.future.CloseFuture;
import org.apache.sshd.common.future.SshFutureListener;
import org.apache.sshd.common.io.IoSession;
import org.apache.sshd.server.ServerFactoryManager;
import org.apache.sshd.server.session.ServerSession;
/* Expose addition of close session listeners */
class GerritServerSession extends ServerSession {
GerritServerSession(ServerFactoryManager server,
IoSession ioSession) throws Exception {
super(server, ioSession);
}
void addCloseSessionListener(SshFutureListener<CloseFuture> l) {
closeFuture.addListener(l);
}
}

View File

@@ -15,7 +15,6 @@
package com.google.gerrit.sshd;
import static com.google.gerrit.server.ssh.SshAddressesModule.IANA_SSH_PORT;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.concurrent.TimeUnit.SECONDS;
@@ -35,20 +34,18 @@ import com.google.inject.Singleton;
import com.jcraft.jsch.HostKey;
import com.jcraft.jsch.JSchException;
import org.apache.mina.core.future.IoFuture;
import org.apache.mina.core.future.IoFutureListener;
import org.apache.mina.core.service.IoAcceptor;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.transport.socket.SocketSessionConfig;
import org.apache.sshd.SshServer;
import org.apache.sshd.common.Channel;
import org.apache.sshd.common.Cipher;
import org.apache.sshd.common.Compression;
import org.apache.sshd.common.ForwardingFilter;
import org.apache.sshd.common.KeyExchange;
import org.apache.sshd.common.KeyPairProvider;
import org.apache.sshd.common.NamedFactory;
import org.apache.sshd.common.Session;
import org.apache.sshd.common.Signature;
import org.apache.sshd.common.SshdSocketAddress;
import org.apache.sshd.common.cipher.AES128CBC;
import org.apache.sshd.common.cipher.AES192CBC;
import org.apache.sshd.common.cipher.AES256CBC;
@@ -56,6 +53,19 @@ import org.apache.sshd.common.cipher.BlowfishCBC;
import org.apache.sshd.common.cipher.CipherNone;
import org.apache.sshd.common.cipher.TripleDESCBC;
import org.apache.sshd.common.compression.CompressionNone;
import org.apache.sshd.common.file.FileSystemFactory;
import org.apache.sshd.common.file.FileSystemView;
import org.apache.sshd.common.file.SshFile;
import org.apache.sshd.common.forward.DefaultTcpipForwarderFactory;
import org.apache.sshd.common.forward.TcpipServerChannel;
import org.apache.sshd.common.future.CloseFuture;
import org.apache.sshd.common.future.SshFutureListener;
import org.apache.sshd.common.io.IoAcceptor;
import org.apache.sshd.common.io.IoServiceFactory;
import org.apache.sshd.common.io.IoSession;
import org.apache.sshd.common.io.mina.MinaServiceFactory;
import org.apache.sshd.common.io.mina.MinaSession;
import org.apache.sshd.common.io.nio2.Nio2ServiceFactory;
import org.apache.sshd.common.mac.HMACMD5;
import org.apache.sshd.common.mac.HMACMD596;
import org.apache.sshd.common.mac.HMACSHA1;
@@ -63,26 +73,21 @@ import org.apache.sshd.common.mac.HMACSHA196;
import org.apache.sshd.common.random.BouncyCastleRandom;
import org.apache.sshd.common.random.JceRandom;
import org.apache.sshd.common.random.SingletonRandomFactory;
import org.apache.sshd.common.session.AbstractSession;
import org.apache.sshd.common.signature.SignatureDSA;
import org.apache.sshd.common.signature.SignatureRSA;
import org.apache.sshd.common.util.Buffer;
import org.apache.sshd.common.util.SecurityUtils;
import org.apache.sshd.server.Command;
import org.apache.sshd.server.CommandFactory;
import org.apache.sshd.server.FileSystemFactory;
import org.apache.sshd.server.FileSystemView;
import org.apache.sshd.server.ForwardingFilter;
import org.apache.sshd.server.PublickeyAuthenticator;
import org.apache.sshd.server.SshFile;
import org.apache.sshd.server.UserAuth;
import org.apache.sshd.server.auth.UserAuthPublicKey;
import org.apache.sshd.server.auth.gss.GSSAuthenticator;
import org.apache.sshd.server.auth.gss.UserAuthGSS;
import org.apache.sshd.server.channel.ChannelDirectTcpip;
import org.apache.sshd.server.channel.ChannelSession;
import org.apache.sshd.server.kex.DHG1;
import org.apache.sshd.server.kex.DHG14;
import org.apache.sshd.server.session.ServerSession;
import org.apache.sshd.server.session.SessionFactory;
import org.eclipse.jgit.lib.Config;
import org.slf4j.Logger;
@@ -91,7 +96,6 @@ import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.net.UnknownHostException;
import java.security.InvalidKeyException;
@@ -126,6 +130,11 @@ import java.util.List;
public class SshDaemon extends SshServer implements SshInfo, LifecycleListener {
private static final Logger log = LoggerFactory.getLogger(SshDaemon.class);
public static enum SshSessionBackend {
MINA,
NIO2
}
private final List<SocketAddress> listen;
private final List<String> advertised;
private final boolean keepAlive;
@@ -144,7 +153,6 @@ public class SshDaemon extends SshServer implements SshInfo, LifecycleListener {
this.listen = listen;
this.advertised = advertised;
reuseAddress = cfg.getBoolean("sshd", "reuseaddress", true);
keepAlive = cfg.getBoolean("sshd", "tcpkeepalive", true);
getProperties().put(SERVER_IDENTIFICATION,
@@ -161,12 +169,6 @@ public class SshDaemon extends SshServer implements SshInfo, LifecycleListener {
long idleTimeoutSeconds = ConfigUtil.getTimeUnit(cfg, "sshd", null,
"idleTimeout", 0, SECONDS);
if (idleTimeoutSeconds == 0) {
// Since Apache SSHD does not allow to turn off closing idle connections,
// we fake it by using the highest timeout allowed by Apache SSHD, which
// amounts to ~24 days.
idleTimeoutSeconds = MILLISECONDS.toSeconds(Integer.MAX_VALUE);
}
getProperties().put(
IDLE_TIMEOUT,
String.valueOf(SECONDS.toMillis(idleTimeoutSeconds)));
@@ -183,6 +185,14 @@ public class SshDaemon extends SshServer implements SshInfo, LifecycleListener {
final String kerberosPrincipal = cfg.getString(
"sshd", null, "kerberosPrincipal");
SshSessionBackend backend = cfg.getEnum(
"sshd", null, "backend", SshSessionBackend.MINA);
System.setProperty(IoServiceFactory.class.getName(),
backend == SshSessionBackend.MINA
? MinaServiceFactory.class.getName()
: Nio2ServiceFactory.class.getName());
if (SecurityUtils.isBouncyCastleRegistered()) {
initProviderBouncyCastle();
} else {
@@ -192,7 +202,7 @@ public class SshDaemon extends SshServer implements SshInfo, LifecycleListener {
initMacs(cfg);
initSignatures();
initChannels();
initForwardingFilter();
initForwarding();
initFileSystemFactory();
initSubsystems();
initCompression();
@@ -202,24 +212,28 @@ public class SshDaemon extends SshServer implements SshInfo, LifecycleListener {
setShellFactory(noShell);
setSessionFactory(new SessionFactory() {
@Override
protected ServerSession createSession(final IoSession io)
protected AbstractSession createSession(final IoSession io)
throws Exception {
if (io.getConfig() instanceof SocketSessionConfig) {
final SocketSessionConfig c = (SocketSessionConfig) io.getConfig();
c.setKeepAlive(keepAlive);
if (io instanceof MinaSession) {
if (((MinaSession) io).getSession()
.getConfig() instanceof SocketSessionConfig) {
((SocketSessionConfig) ((MinaSession) io).getSession()
.getConfig())
.setKeepAlive(keepAlive);
}
}
final ServerSession s = (ServerSession) super.createSession(io);
final int id = idGenerator.next();
final SocketAddress peer = io.getRemoteAddress();
GerritServerSession s = (GerritServerSession)super.createSession(io);
int id = idGenerator.next();
SocketAddress peer = io.getRemoteAddress();
final SshSession sd = new SshSession(id, peer);
s.setAttribute(SshSession.KEY, sd);
// Log a session close without authentication as a failure.
//
io.getCloseFuture().addListener(new IoFutureListener<IoFuture>() {
s.addCloseSessionListener(new SshFutureListener<CloseFuture>() {
@Override
public void operationComplete(IoFuture future) {
public void operationComplete(CloseFuture future) {
if (sd.isAuthenticationError()) {
sshLog.onAuthFail(sd);
}
@@ -227,6 +241,12 @@ public class SshDaemon extends SshServer implements SshInfo, LifecycleListener {
});
return s;
}
@Override
protected AbstractSession doCreateSession(IoSession ioSession)
throws Exception {
return new GerritServerSession(server, ioSession);
}
});
hostKeys = computeHostKeys();
@@ -245,13 +265,11 @@ public class SshDaemon extends SshServer implements SshInfo, LifecycleListener {
public synchronized void start() {
if (acceptor == null && !listen.isEmpty()) {
checkConfig();
if (sessionFactory == null) {
sessionFactory = createSessionFactory();
}
sessionFactory.setServer(this);
acceptor = createAcceptor();
configure(acceptor);
final SessionFactory handler = getSessionFactory();
handler.setServer(this);
acceptor.setHandler(handler);
try {
acceptor.bind(listen);
@@ -259,7 +277,8 @@ public class SshDaemon extends SshServer implements SshInfo, LifecycleListener {
throw new IllegalStateException("Cannot bind to " + addressList(), e);
}
log.info("Started Gerrit SSHD on " + addressList());
log.info(String.format("Started Gerrit %s on %s",
version, addressList()));
}
}
@@ -473,7 +492,7 @@ public class SshDaemon extends SshServer implements SshInfo, LifecycleListener {
private void initChannels() {
setChannelFactories(Arrays.<NamedFactory<Channel>> asList(
new ChannelSession.Factory(), //
new ChannelDirectTcpip.Factory() //
new TcpipServerChannel.DirectTcpipFactory() //
));
}
@@ -514,28 +533,29 @@ public class SshDaemon extends SshServer implements SshInfo, LifecycleListener {
setPublickeyAuthenticator(pubkey);
}
private void initForwardingFilter() {
setForwardingFilter(new ForwardingFilter() {
private void initForwarding() {
setTcpipForwardingFilter(new ForwardingFilter() {
@Override
public boolean canForwardAgent(ServerSession session) {
public boolean canForwardAgent(Session session) {
return false;
}
@Override
public boolean canForwardX11(ServerSession session) {
public boolean canForwardX11(Session session) {
return false;
}
@Override
public boolean canConnect(InetSocketAddress address, ServerSession session) {
public boolean canListen(SshdSocketAddress address, Session session) {
return false;
}
@Override
public boolean canListen(InetSocketAddress address, ServerSession session) {
public boolean canConnect(SshdSocketAddress address, Session session) {
return false;
}
});
setTcpipForwarderFactory(new DefaultTcpipForwarderFactory());
}
private void initFileSystemFactory() {

View File

@@ -21,8 +21,8 @@ import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.sshd.SshScope.Context;
import org.apache.commons.codec.binary.Base64;
import org.apache.mina.core.future.IoFuture;
import org.apache.mina.core.future.IoFutureListener;
import org.apache.sshd.common.future.CloseFuture;
import org.apache.sshd.common.future.SshFutureListener;
import org.apache.sshd.common.KeyPairProvider;
import org.apache.sshd.common.SshException;
import org.apache.sshd.common.util.Buffer;
@@ -138,10 +138,11 @@ public class SshUtil {
sshScope.set(old);
}
session.getIoSession().getCloseFuture().addListener(
new IoFutureListener<IoFuture>() {
GerritServerSession s = (GerritServerSession) session;
s.addCloseSessionListener(
new SshFutureListener<CloseFuture>() {
@Override
public void operationComplete(IoFuture future) {
public void operationComplete(CloseFuture future) {
final Context ctx = sshScope.newContext(null, sd, null);
final Context old = sshScope.set(ctx);
try {

View File

@@ -32,8 +32,9 @@ import com.google.gerrit.sshd.SshDaemon;
import com.google.inject.Inject;
import com.google.inject.Provider;
import org.apache.mina.core.service.IoAcceptor;
import org.apache.mina.core.session.IoSession;
import org.apache.sshd.common.io.IoAcceptor;
import org.apache.sshd.common.io.IoSession;
import org.apache.sshd.common.io.mina.MinaSession;
import org.apache.sshd.server.Environment;
import org.eclipse.jgit.internal.storage.file.WindowCacheStatAccessor;
import org.kohsuke.args4j.Option;
@@ -274,8 +275,12 @@ final class ShowCaches extends CacheCommand {
long now = TimeUtil.nowMs();
Collection<IoSession> list = acceptor.getManagedSessions().values();
long oldest = now;
for (IoSession s : list) {
oldest = Math.min(oldest, s.getCreationTime());
if (s instanceof MinaSession) {
MinaSession minaSession = (MinaSession)s;
oldest = Math.min(oldest, minaSession.getSession().getCreationTime());
}
}
stdout.format(

View File

@@ -26,8 +26,9 @@ import com.google.gerrit.sshd.SshDaemon;
import com.google.gerrit.sshd.SshSession;
import com.google.inject.Inject;
import org.apache.mina.core.service.IoAcceptor;
import org.apache.mina.core.session.IoSession;
import org.apache.sshd.common.io.IoAcceptor;
import org.apache.sshd.common.io.IoSession;
import org.apache.sshd.common.io.mina.MinaSession;
import org.apache.sshd.server.Environment;
import org.apache.sshd.server.session.ServerSession;
import org.kohsuke.args4j.Option;
@@ -84,11 +85,17 @@ final class ShowConnections extends SshCommand {
Collections.sort(list, new Comparator<IoSession>() {
@Override
public int compare(IoSession arg0, IoSession arg1) {
if (arg0.getCreationTime() < arg1.getCreationTime()) {
if (arg0 instanceof MinaSession) {
MinaSession mArg0 = (MinaSession) arg0;
MinaSession mArg1 = (MinaSession) arg1;
if (mArg0.getSession().getCreationTime() < mArg1.getSession()
.getCreationTime()) {
return -1;
} else if (arg0.getCreationTime() > arg1.getCreationTime()) {
} else if (mArg0.getSession().getCreationTime() > mArg1.getSession()
.getCreationTime()) {
return 1;
}
}
return (int) (arg0.getId() - arg1.getId());
}
});
@@ -104,8 +111,15 @@ final class ShowConnections extends SshCommand {
SshSession sd = s != null ? s.getAttribute(SshSession.KEY) : null;
final SocketAddress remoteAddress = io.getRemoteAddress();
final long start = io.getCreationTime();
final long idle = now - io.getLastIoTime();
MinaSession minaSession = io instanceof MinaSession
? (MinaSession) io
: null;
final long start = minaSession == null
? 0
: minaSession.getSession().getCreationTime();
final long idle = minaSession == null
? now
: now - minaSession.getSession().getLastIoTime();
stdout.print(String.format("%8s %8s %8s %-15.15s %s\n", //
id(sd), //

View File

@@ -26,7 +26,7 @@ log4j.logger.org.apache.mina=WARN
log4j.logger.org.apache.sshd.common=WARN
log4j.logger.org.apache.sshd.server=WARN
log4j.logger.org.apache.sshd.common.keyprovider.FileKeyPairProvider=INFO
log4j.logger.com.google.gerrit.server.ssh.GerritServerSession=WARN
log4j.logger.com.google.gerrit.sshd.GerritServerSession=WARN
# Silence non-critical messages from Jetty.
#

View File

@@ -8,17 +8,18 @@ EXCLUDE = [
maven_jar(
name = 'core',
id = 'org.apache.mina:mina-core:2.0.5',
sha1 = '0e134a3761833a3c28c79331e806f64f985a9eec',
id = 'org.apache.mina:mina-core:2.0.7',
sha1 = 'c878e2aa82de748474a624ec3933e4604e446dec',
license = 'Apache2.0',
exclude = EXCLUDE,
)
maven_jar(
name = 'sshd',
id = 'org.apache.sshd:sshd-core:0.6.0',
sha1 = '2b9a119dd77a1decec78b0c511ba400c8655e96e',
id = 'org.apache.sshd:sshd-core:0.9.0.201311081',
sha1 = '38f7ac8602e70fa05fdc6147d204198e9cefe5bc',
license = 'Apache2.0',
deps = [':core'],
exclude = EXCLUDE,
repository = GERRIT,
)