Refactor SSH command permission checks to use CurrentUser

This is a step in the direction of whacking the static decision
logic used by BaseServiceImplementation and making it based on
the project and current user concepts instead.

Signed-off-by: Shawn O. Pearce <sop@google.com>
This commit is contained in:
Shawn O. Pearce
2009-08-03 14:43:35 -07:00
parent b1563835fe
commit 22483737d8
24 changed files with 407 additions and 249 deletions

View File

@@ -29,6 +29,7 @@ import com.google.gerrit.client.reviewdb.ReviewDb;
import com.google.gerrit.client.rpc.Common;
import com.google.gerrit.server.ChangeUtil;
import com.google.gerrit.server.GerritServer;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.config.CanonicalWebUrl;
import com.google.gerrit.server.config.Nullable;
import com.google.gerrit.server.mail.EmailException;
@@ -116,6 +117,7 @@ public class MergeOp {
private final Provider<String> urlProvider;
private final GerritConfig gerritConfig;
private final PatchSetInfoFactory patchSetInfoFactory;
private final IdentifiedUser.Factory identifiedUserFactory;
private final PersonIdent myIdent;
private final Branch.NameKey destBranch;
@@ -137,7 +139,7 @@ public class MergeOp {
final MergeFailSender.Factory mfsf,
@CanonicalWebUrl @Nullable final Provider<String> cwu,
final GerritConfig gc, final PatchSetInfoFactory psif,
@Assisted final Branch.NameKey branch) {
final IdentifiedUser.Factory iuf, @Assisted final Branch.NameKey branch) {
server = gs;
schemaFactory = sf;
replication = rq;
@@ -146,6 +148,7 @@ public class MergeOp {
urlProvider = cwu;
gerritConfig = gc;
patchSetInfoFactory = psif;
identifiedUserFactory = iuf;
myIdent = server.newGerritPersonIdent();
destBranch = branch;
@@ -524,16 +527,10 @@ public class MergeOp {
}
private void setRefLogIdent(final ChangeApproval submitAudit) {
if (submitAudit == null) {
return;
if (submitAudit != null) {
branchUpdate.setRefLogIdent(identifiedUserFactory.create(
submitAudit.getAccountId()).toPersonIdent());
}
final Account a = Common.getAccountCache().get(submitAudit.getAccountId());
if (a == null) {
return;
}
branchUpdate.setRefLogIdent(ChangeUtil.toReflogIdent(a, null));
}
private void cherryPickChanges() throws MergeException {

View File

@@ -0,0 +1,45 @@
// Copyright (C) 2009 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;
import com.google.gerrit.client.reviewdb.AccountGroup;
import com.google.gerrit.client.reviewdb.SystemConfig;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import java.util.Collections;
import java.util.Set;
/** An anonymous user who has not yet authenticated. */
@Singleton
public class AnonymousUser extends CurrentUser {
private final Set<AccountGroup.Id> effectiveGroups;
@Inject
AnonymousUser(final SystemConfig cfg) {
super(cfg);
effectiveGroups = Collections.singleton(cfg.anonymousGroupId);
}
@Override
public Set<AccountGroup.Id> getEffectiveGroups() {
return effectiveGroups;
}
@Override
public String toString() {
return "ANONYMOUS";
}
}

View File

@@ -14,26 +14,20 @@
package com.google.gerrit.server;
import com.google.gerrit.client.reviewdb.Account;
import com.google.gerrit.client.reviewdb.Change;
import com.google.gerrit.client.reviewdb.ReviewDb;
import com.google.gwtorm.client.OrmException;
import org.spearce.jgit.lib.PersonIdent;
import org.spearce.jgit.util.Base64;
import org.spearce.jgit.util.NB;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
public class ChangeUtil {
private static int uuidPrefix;
private static int uuidSeq;
/**
* Generate a new unique identifier for change message entities.
*
*
* @param db the database connection, used to increment the change message
* allocation sequence.
* @return the new unique identifier.
@@ -45,55 +39,6 @@ public class ChangeUtil {
return Base64.encodeBytes(raw);
}
public static PersonIdent toPersonIdent(final Account userAccount) {
String name = userAccount.getFullName();
if (name == null) {
name = userAccount.getPreferredEmail();
}
if (name == null) {
name = "Anonymous Coward";
}
String user = "account-" + userAccount.getId().toString();
String host = "unknown";
return new PersonIdent(name, user + "@" + host);
}
public static PersonIdent toReflogIdent(final Account userAccount,
final SocketAddress remotePeer) {
String name = userAccount.getFullName();
if (name == null) {
name = userAccount.getPreferredEmail();
}
if (name == null) {
name = "Anonymous Coward";
}
final String userId = "account-" + userAccount.getId().toString();
final String user;
if (userAccount.getSshUserName() != null) {
user = userAccount.getSshUserName() + "|" + userId;
} else {
user = userId;
}
String host = null;
if (remotePeer instanceof InetSocketAddress) {
final InetSocketAddress sa = (InetSocketAddress) remotePeer;
final InetAddress in = sa.getAddress();
if (in != null) {
host = in.getCanonicalHostName();
} else {
host = sa.getHostName();
}
}
if (host == null) {
host = "unknown";
}
return new PersonIdent(name, user + "@" + host);
}
private static synchronized void fill(byte[] raw, ReviewDb db)
throws OrmException {
if (uuidSeq == 0) {
@@ -123,7 +68,7 @@ public class ChangeUtil {
}
private static final char[] hexchar =
{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', //
{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', //
'a', 'b', 'c', 'd', 'e', 'f'};
private static void formatHexInt(final StringBuilder dst, final int p, int w) {

View File

@@ -14,26 +14,83 @@
package com.google.gerrit.server;
import com.google.gerrit.client.data.ProjectCache;
import com.google.gerrit.client.reviewdb.AccountGroup;
import com.google.gerrit.client.reviewdb.ApprovalCategory;
import com.google.gerrit.client.reviewdb.ProjectRight;
import com.google.gerrit.client.reviewdb.SystemConfig;
import com.google.gerrit.client.rpc.Common;
import com.google.inject.servlet.RequestScoped;
import java.util.Set;
/**
* Information about the currently logged in user.
* <p>
* This is a {@link RequestScoped} property managed by Guice.
*
* @see AnonymousUser
* @see IdentifiedUser
*/
public abstract class CurrentUser {
/** An anonymous user, the identity is not known. */
public static final CurrentUser ANONYMOUS = new Anonymous();
protected final SystemConfig systemConfig;
private static class Anonymous extends CurrentUser {
private Anonymous() {
protected CurrentUser(final SystemConfig cfg) {
systemConfig = cfg;
}
/**
* Get the set of groups the user is currently a member of.
* <p>
* The returned set may be a subset of the user's actual groups; if the user's
* account is currently deemed to be untrusted then the effective group set is
* only the anonymous and registered user groups. To enable additional groups
* (and gain their granted permissions) the user must update their account to
* use only trusted authentication providers.
*
* @return active groups for this user.
*/
public abstract Set<AccountGroup.Id> getEffectiveGroups();
@Deprecated
public final boolean isAdministrator() {
return getEffectiveGroups().contains(systemConfig.adminGroupId);
}
@Deprecated
public boolean canPerform(final ProjectCache.Entry e,
final ApprovalCategory.Id actionId, final short requireValue) {
if (e == null) {
return false;
}
@Override
public String toString() {
return "ANONYMOUS";
int val = Integer.MIN_VALUE;
for (final ProjectRight pr : e.getRights()) {
if (actionId.equals(pr.getApprovalCategoryId())
&& getEffectiveGroups().contains(pr.getAccountGroupId())) {
if (val < 0 && pr.getMaxValue() > 0) {
// If one of the user's groups had denied them access, but
// this group grants them access, prefer the grant over
// the denial. We have to break the tie somehow and we
// prefer being "more open" to being "more closed".
//
val = pr.getMaxValue();
} else {
// Otherwise we use the largest value we can get.
//
val = Math.max(pr.getMaxValue(), val);
}
}
}
if (val == Integer.MIN_VALUE && actionId.canInheritFromWildProject()) {
for (final ProjectRight pr : Common.getProjectCache().getWildcardRights()) {
if (actionId.equals(pr.getApprovalCategoryId())
&& getEffectiveGroups().contains(pr.getAccountGroupId())) {
val = Math.max(pr.getMaxValue(), val);
}
}
}
return val >= requireValue;
}
}

View File

@@ -15,26 +15,41 @@
package com.google.gerrit.server;
import com.google.gerrit.client.reviewdb.Account;
import com.google.gerrit.client.reviewdb.AccountGroup;
import com.google.gerrit.client.reviewdb.SystemConfig;
import com.google.gerrit.client.rpc.Common;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.assistedinject.Assisted;
import com.google.inject.servlet.RequestScoped;
import org.spearce.jgit.lib.PersonIdent;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.Set;
/** An authenticated user. */
@RequestScoped
public class IdentifiedUser extends CurrentUser {
public static final class Factory {
public IdentifiedUser create(final Account.Id id) {
return new IdentifiedUser(id);
}
public interface Factory {
IdentifiedUser create(Account.Id id);
}
private final Account.Id accountId;
@Inject(optional = true)
@RemotePeer
private Provider<SocketAddress> remotePeerProvider;
private Account account;
private Set<AccountGroup.Id> effectiveGroups;
@Inject
IdentifiedUser(@Assisted final Account.Id i) {
accountId = i;
IdentifiedUser(final SystemConfig cfg, @Assisted final Account.Id id) {
super(cfg);
accountId = id;
}
/** The account identity for the user. */
@@ -49,8 +64,54 @@ public class IdentifiedUser extends CurrentUser {
return account;
}
@Override
public Set<AccountGroup.Id> getEffectiveGroups() {
if (effectiveGroups == null) {
effectiveGroups =
Common.getGroupCache().getEffectiveGroups(getAccountId());
}
return effectiveGroups;
}
public PersonIdent toPersonIdent() {
final Account ua = getAccount();
String name = ua.getFullName();
if (name == null) {
name = ua.getPreferredEmail();
}
if (name == null) {
name = "Anonymous Coward";
}
final String userId = "account-" + ua.getId().toString();
final String user;
if (ua.getSshUserName() != null) {
user = ua.getSshUserName() + "|" + userId;
} else {
user = userId;
}
String host = null;
final SocketAddress remotePeer =
remotePeerProvider != null ? remotePeerProvider.get() : null;
if (remotePeer instanceof InetSocketAddress) {
final InetSocketAddress sa = (InetSocketAddress) remotePeer;
final InetAddress in = sa.getAddress();
if (in != null) {
host = in.getCanonicalHostName();
} else {
host = sa.getHostName();
}
}
if (host == null) {
host = "unknown";
}
return new PersonIdent(name, user + "@" + host);
}
@Override
public String toString() {
return "CurrentUser[" + getAccountId() + "]";
return "IdentifiedUser[account " + getAccountId() + "]";
}
}

View File

@@ -25,6 +25,7 @@ import com.google.gerrit.git.PushReplication;
import com.google.gerrit.git.ReloadSubmitQueueOp;
import com.google.gerrit.git.ReplicationQueue;
import com.google.gerrit.git.WorkQueue;
import com.google.gerrit.server.AnonymousUser;
import com.google.gerrit.server.ContactStore;
import com.google.gerrit.server.EncryptedContactStoreProvider;
import com.google.gerrit.server.FileTypeRegistry;
@@ -61,6 +62,7 @@ public class GerritServerModule extends FactoryModule {
bind(Config.class).annotatedWith(GerritServerConfig.class).toProvider(
GerritServerConfigProvider.class).in(SINGLETON);
bind(AuthConfig.class).in(SINGLETON);
bind(AnonymousUser.class);
// Note that the CanonicalWebUrl itself must not be a singleton, but its
// provider must be.
@@ -95,7 +97,7 @@ public class GerritServerModule extends FactoryModule {
bind(EmailSender.class).to(SmtpEmailSender.class).in(SINGLETON);
factory(PatchSetImporter.Factory.class);
bind(PatchSetInfoFactory.class);
bind(IdentifiedUser.Factory.class);
factory(IdentifiedUser.Factory.class);
factory(AbandonedSender.Factory.class);
factory(AddReviewerSender.Factory.class);

View File

@@ -22,6 +22,7 @@ import com.google.gerrit.client.rpc.Common.CurrentAccountImpl;
import com.google.gerrit.git.PushAllProjectsOp;
import com.google.gerrit.git.ReloadSubmitQueueOp;
import com.google.gerrit.git.WorkQueue;
import com.google.gerrit.server.AnonymousUser;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.config.AuthConfig;
@@ -172,7 +173,7 @@ public class GerritServletConfig extends GuiceServletContextListener {
CurrentUser u = webUser.get();
if (u instanceof IdentifiedUser) {
return ((IdentifiedUser) u).getAccountId();
} else if (u == CurrentUser.ANONYMOUS) {
} else if (u instanceof AnonymousUser) {
return null;
} else {
throw new OutOfScopeException("Cannot determine current user");

View File

@@ -15,6 +15,7 @@
package com.google.gerrit.server.http;
import com.google.gerrit.client.reviewdb.Account;
import com.google.gerrit.server.AnonymousUser;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
import com.google.inject.Inject;
@@ -24,17 +25,20 @@ import com.google.inject.servlet.RequestScoped;
@RequestScoped
class HttpCurrentUserProvider implements Provider<CurrentUser> {
private final GerritCall call;
private final IdentifiedUser.Factory factory;
private final AnonymousUser anonymous;
private final IdentifiedUser.Factory identified;
@Inject
HttpCurrentUserProvider(final GerritCall c, final IdentifiedUser.Factory f) {
HttpCurrentUserProvider(final GerritCall c, final AnonymousUser a,
final IdentifiedUser.Factory f) {
call = c;
factory = f;
anonymous = a;
identified = f;
}
@Override
public CurrentUser get() {
final Account.Id id = call.getAccountId();
return id != null ? factory.create(id) : CurrentUser.ANONYMOUS;
return id != null ? identified.create(id) : anonymous;
}
}

View File

@@ -34,7 +34,7 @@ import com.google.gerrit.git.ReplicationQueue;
import com.google.gerrit.server.BaseServiceImplementation;
import com.google.gerrit.server.ChangeUtil;
import com.google.gerrit.server.GerritServer;
import com.google.gerrit.server.RemotePeer;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwtjsonrpc.client.VoidResult;
import com.google.gwtorm.client.OrmException;
@@ -58,7 +58,6 @@ import org.spearce.jgit.revwalk.RevCommit;
import java.io.File;
import java.io.IOException;
import java.net.SocketAddress;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@@ -72,16 +71,16 @@ class ProjectAdminServiceImpl extends BaseServiceImplementation implements
private final Logger log = LoggerFactory.getLogger(getClass());
private final GerritServer server;
private final ReplicationQueue replication;
private final Provider<SocketAddress> remotePeer;
private final Provider<IdentifiedUser> identifiedUser;
@Inject
ProjectAdminServiceImpl(final SchemaFactory<ReviewDb> sf,
final GerritServer gs, final ReplicationQueue rq,
@RemotePeer final Provider<SocketAddress> rp) {
final Provider<IdentifiedUser> iu) {
super(sf);
server = gs;
replication = rq;
remotePeer = rp;
identifiedUser = iu;
}
public void ownedProjects(final AsyncCallback<List<Project>> callback) {
@@ -423,7 +422,7 @@ class ProjectAdminServiceImpl extends BaseServiceImplementation implements
final RefUpdate u = repo.updateRef(refname);
u.setExpectedOldObjectId(ObjectId.zeroId());
u.setNewObjectId(revid);
u.setRefLogIdent(ChangeUtil.toReflogIdent(me, remotePeer.get()));
u.setRefLogIdent(identifiedUser.get().toPersonIdent());
u.setRefLogMessage("created via web from " + startingRevision,
false);
final RefUpdate.Result result = u.update(rw);

View File

@@ -1,83 +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.server.ssh;
import com.google.gerrit.client.data.ProjectCache;
import com.google.gerrit.client.reviewdb.Account;
import com.google.gerrit.client.reviewdb.AccountGroup;
import com.google.gerrit.client.reviewdb.ApprovalCategory;
import com.google.gerrit.client.rpc.Common;
import com.google.gerrit.server.BaseServiceImplementation;
import com.google.gerrit.server.IdentifiedUser;
import com.google.inject.Inject;
import java.io.BufferedWriter;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.util.Set;
public abstract class AbstractCommand extends BaseCommand {
private static final String ENC = "UTF-8";
@Inject
private IdentifiedUser currentUser;
private Set<AccountGroup.Id> userGroups;
protected PrintWriter toPrintWriter(final OutputStream o)
throws UnsupportedEncodingException {
return new PrintWriter(new BufferedWriter(new OutputStreamWriter(o, ENC)));
}
protected Account.Id getAccountId() {
return currentUser.getAccountId();
}
protected Set<AccountGroup.Id> getGroups() {
if (userGroups == null) {
userGroups = Common.getGroupCache().getEffectiveGroups(getAccountId());
}
return userGroups;
}
protected boolean canRead(final ProjectCache.Entry project) {
return canPerform(project, ApprovalCategory.READ, (short) 1);
}
protected boolean canPerform(final ProjectCache.Entry project,
final ApprovalCategory.Id actionId, final short val) {
return BaseServiceImplementation.canPerform(getGroups(), project, actionId,
val);
}
protected void assertIsAdministrator() throws Failure {
if (!Common.getGroupCache().isAdministrator(getAccountId())) {
throw new Failure(1, "fatal: Not a Gerrit administrator");
}
}
public void start() {
startThread(new CommandRunnable() {
public void run() throws Exception {
parseCommandLine();
AbstractCommand.this.run();
}
});
}
protected abstract void run() throws Exception;
}

View File

@@ -0,0 +1,32 @@
// Copyright (C) 2009 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.ssh;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
/**
* Annotation tagged on a concrete Command that requires administrator access.
* <p>
* Currently this annotation is only enforced by DispatchCommand after it has
* created the command object, but before it populates it or starts execution.
*/
@Target( {ElementType.TYPE})
@Retention(RUNTIME)
public @interface AdminCommand {
}

View File

@@ -30,15 +30,20 @@ import org.kohsuke.args4j.Option;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List;
public abstract class BaseCommand implements Command {
private static final Logger log = LoggerFactory.getLogger(BaseCommand.class);
public static final String ENC = "UTF-8";
@Option(name = "--help", usage = "display this help text", aliases = {"-h"})
private boolean help;
@@ -267,6 +272,18 @@ public abstract class BaseCommand implements Command {
cleanup.run();
}
/** Wrap the supplied output stream in a UTF-8 encoded PrintWriter. */
protected static PrintWriter toPrintWriter(final OutputStream o) {
try {
return new PrintWriter(new BufferedWriter(new OutputStreamWriter(o, ENC)));
} catch (UnsupportedEncodingException e) {
// Our default encoding is required by the specifications for the
// runtime APIs, this should never, ever happen.
//
throw new RuntimeException("JVM lacks " + ENC + " encoding", e);
}
}
private String threadName() {
final ServerSession session = SshScopes.getContext().session;
final String who = session.getUsername();
@@ -310,7 +327,7 @@ public abstract class BaseCommand implements Command {
if (e instanceof Failure) {
final Failure f = (Failure) e;
try {
err.write((f.getMessage() + "\n").getBytes("UTF-8"));
err.write((f.getMessage() + "\n").getBytes(ENC));
err.flush();
} catch (IOException e2) {
} catch (Throwable e2) {
@@ -320,7 +337,7 @@ public abstract class BaseCommand implements Command {
} else {
try {
err.write("fatal: internal server error\n".getBytes("UTF-8"));
err.write("fatal: internal server error\n".getBytes(ENC));
err.flush();
} catch (IOException e2) {
} catch (Throwable e2) {

View File

@@ -14,6 +14,7 @@
package com.google.gerrit.server.ssh;
import com.google.gerrit.server.CurrentUser;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.assistedinject.Assisted;
@@ -27,17 +28,19 @@ import java.util.Map;
/**
* Command that dispatches to a subcommand from its command table.
*/
class DispatchCommand extends BaseCommand {
final class DispatchCommand extends BaseCommand {
interface Factory {
DispatchCommand create(String prefix, Map<String, Provider<Command>> map);
}
private final Provider<CurrentUser> currentUser;
private final String prefix;
private final Map<String, Provider<Command>> commands;
@Inject
DispatchCommand(@Assisted final String pfx,
DispatchCommand(final Provider<CurrentUser> cu, @Assisted final String pfx,
@Assisted final Map<String, Provider<Command>> all) {
currentUser = cu;
prefix = pfx;
commands = all;
}
@@ -65,6 +68,16 @@ class DispatchCommand extends BaseCommand {
final Provider<Command> p = commands.get(name);
if (p != null) {
final Command cmd = p.get();
if (cmd.getClass().getAnnotation(AdminCommand.class) != null) {
final CurrentUser u = currentUser.get();
if (!u.isAdministrator()) {
err.write("fatal: Not a Gerrit administrator\n".getBytes(ENC));
err.flush();
onExit(1);
return;
}
}
provideStateTo(cmd);
if (cmd instanceof BaseCommand) {
final BaseCommand bc = (BaseCommand) cmd;
@@ -77,7 +90,7 @@ class DispatchCommand extends BaseCommand {
cmd.start();
} else {
final String msg = prefix + ": " + name + ": not found\n";
err.write(msg.getBytes("UTF-8"));
err.write(msg.getBytes(ENC));
err.flush();
onExit(127);
}

View File

@@ -15,13 +15,13 @@
package com.google.gerrit.server.ssh.commands;
import com.google.gerrit.client.data.ProjectCache;
import com.google.gerrit.client.reviewdb.Account;
import com.google.gerrit.client.reviewdb.ApprovalCategory;
import com.google.gerrit.client.reviewdb.Project;
import com.google.gerrit.client.reviewdb.ProjectRight;
import com.google.gerrit.client.rpc.Common;
import com.google.gerrit.server.GerritServer;
import com.google.gerrit.server.ssh.AbstractCommand;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.ssh.BaseCommand;
import com.google.inject.Inject;
import org.kohsuke.args4j.Argument;
@@ -30,20 +30,32 @@ import org.spearce.jgit.lib.Repository;
import java.io.IOException;
abstract class AbstractGitCommand extends AbstractCommand {
abstract class AbstractGitCommand extends BaseCommand {
@Argument(index = 0, metaVar = "PROJECT.git", required = true, usage = "project name")
private String reqProjName;
@Inject
protected GerritServer server;
@Inject
private IdentifiedUser currentUser;
protected Repository repo;
protected ProjectCache.Entry cachedProj;
protected Project proj;
protected Account userAccount;
@Override
protected final void run() throws IOException, Failure {
public void start() {
startThread(new CommandRunnable() {
@Override
public void run() throws Exception {
parseCommandLine();
AbstractGitCommand.this.service();
}
});
}
private void service() throws IOException, Failure {
String projectName = reqProjName;
if (projectName.endsWith(".git")) {
// Be nice and drop the trailing ".git" suffix, which we never keep
@@ -70,17 +82,11 @@ abstract class AbstractGitCommand extends AbstractCommand {
throw new Failure(1, "fatal: '" + reqProjName + "': not a valid project",
new IllegalArgumentException("Cannot access the wildcard project"));
}
if (!canRead(cachedProj)) {
if (!canPerform(ApprovalCategory.READ, (short) 1)) {
throw new Failure(1, "fatal: '" + reqProjName + "': unknown project",
new SecurityException("Account lacks Read permission"));
}
userAccount = Common.getAccountCache().get(getAccountId());
if (userAccount == null) {
throw new Failure(1, "fatal: cannot query user database",
new IllegalStateException("Account record no longer in database"));
}
try {
repo = server.openRepository(proj.getName());
} catch (RepositoryNotFoundException e) {
@@ -95,7 +101,7 @@ abstract class AbstractGitCommand extends AbstractCommand {
protected boolean canPerform(final ApprovalCategory.Id actionId,
final short val) {
return canPerform(cachedProj, actionId, val);
return currentUser.canPerform(cachedProj, actionId, val);
}
protected abstract void runImpl() throws IOException, Failure;

View File

@@ -16,6 +16,7 @@ package com.google.gerrit.server.ssh.commands;
import com.google.gerrit.client.rpc.Common;
import com.google.gerrit.server.patch.DiffCache;
import com.google.gerrit.server.ssh.AdminCommand;
import com.google.inject.Inject;
import net.sf.ehcache.Ehcache;
@@ -23,13 +24,13 @@ import net.sf.ehcache.Ehcache;
import org.kohsuke.args4j.Option;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List;
import java.util.SortedSet;
/** Causes the caches to purge all entries and reload. */
class AdminFlushCaches extends AbstractAdminCacheCommand {
@AdminCommand
final class AdminFlushCaches extends CacheCommand {
@Option(name = "--cache", usage = "flush named cache", metaVar = "NAME")
private List<String> caches = new ArrayList<String>();
@@ -42,12 +43,20 @@ class AdminFlushCaches extends AbstractAdminCacheCommand {
@Inject
private DiffCache diffCache;
PrintWriter p;
private PrintWriter p;
@Override
protected void run() throws Failure, UnsupportedEncodingException {
assertIsAdministrator();
public void start() {
startThread(new CommandRunnable() {
@Override
public void run() throws Exception {
parseCommandLine();
flush();
}
});
}
private void flush() throws Failure {
p = toPrintWriter(err);
if (list) {
if (all || caches.size() > 0) {

View File

@@ -18,7 +18,8 @@ import com.google.gerrit.client.reviewdb.Project;
import com.google.gerrit.client.rpc.Common;
import com.google.gerrit.git.PushAllProjectsOp;
import com.google.gerrit.git.ReplicationQueue;
import com.google.gerrit.server.ssh.AbstractCommand;
import com.google.gerrit.server.ssh.AdminCommand;
import com.google.gerrit.server.ssh.BaseCommand;
import com.google.inject.Inject;
import org.kohsuke.args4j.Argument;
@@ -29,7 +30,8 @@ import java.util.List;
import java.util.concurrent.TimeUnit;
/** Force a project to replicate, again. */
class AdminReplicate extends AbstractCommand {
@AdminCommand
final class AdminReplicate extends BaseCommand {
@Option(name = "--all", usage = "push all known projects")
private boolean all;
@@ -46,9 +48,17 @@ class AdminReplicate extends AbstractCommand {
private ReplicationQueue replication;
@Override
protected void run() throws Failure {
assertIsAdministrator();
public void start() {
startThread(new CommandRunnable() {
@Override
public void run() throws Exception {
parseCommandLine();
AdminReplicate.this.schedule();
}
});
}
private void schedule() throws Failure {
if (all && projectNames.size() > 0) {
throw new Failure(1, "error: cannot combine --all and PROJECT");
}

View File

@@ -14,6 +14,8 @@
package com.google.gerrit.server.ssh.commands;
import com.google.gerrit.server.ssh.AdminCommand;
import net.sf.ehcache.Ehcache;
import net.sf.ehcache.Statistics;
import net.sf.ehcache.config.CacheConfiguration;
@@ -21,15 +23,24 @@ import net.sf.ehcache.config.CacheConfiguration;
import org.spearce.jgit.lib.WindowCacheStatAccessor;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
/** Show the current cache states. */
class AdminShowCaches extends AbstractAdminCacheCommand {
PrintWriter p;
@AdminCommand
final class AdminShowCaches extends CacheCommand {
private PrintWriter p;
@Override
protected void run() throws Failure, UnsupportedEncodingException {
assertIsAdministrator();
public void start() {
startThread(new CommandRunnable() {
@Override
public void run() throws Exception {
parseCommandLine();
display();
}
});
}
private void display() {
p = toPrintWriter(out);
for (final Ehcache cache : getAllCaches()) {

View File

@@ -16,7 +16,8 @@ package com.google.gerrit.server.ssh.commands;
import com.google.gerrit.client.reviewdb.Account;
import com.google.gerrit.client.rpc.Common;
import com.google.gerrit.server.ssh.AbstractCommand;
import com.google.gerrit.server.ssh.AdminCommand;
import com.google.gerrit.server.ssh.BaseCommand;
import com.google.gerrit.server.ssh.SshDaemon;
import com.google.gerrit.server.ssh.SshUtil;
import com.google.inject.Inject;
@@ -28,7 +29,6 @@ import org.apache.sshd.server.CommandFactory.Command;
import org.kohsuke.args4j.Option;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
@@ -40,18 +40,28 @@ import java.util.Date;
import java.util.List;
/** Show the current SSH connections. */
class AdminShowConnections extends AbstractCommand {
@AdminCommand
final class AdminShowConnections extends BaseCommand {
@Option(name = "--numeric", aliases = {"-n"}, usage = "don't resolve names")
private boolean numeric;
PrintWriter p;
private PrintWriter p;
@Inject
private SshDaemon daemon;
@Override
protected void run() throws Failure, UnsupportedEncodingException {
assertIsAdministrator();
public void start() {
startThread(new CommandRunnable() {
@Override
public void run() throws Exception {
parseCommandLine();
AdminShowConnections.this.display();
}
});
}
private void display() throws Failure {
p = toPrintWriter(out);
final IoAcceptor acceptor = daemon.getIoAcceptor();

View File

@@ -16,11 +16,11 @@ package com.google.gerrit.server.ssh.commands;
import com.google.gerrit.git.WorkQueue;
import com.google.gerrit.git.WorkQueue.Task;
import com.google.gerrit.server.ssh.AbstractCommand;
import com.google.gerrit.server.ssh.AdminCommand;
import com.google.gerrit.server.ssh.BaseCommand;
import com.google.inject.Inject;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.text.SimpleDateFormat;
import java.util.Collections;
import java.util.Comparator;
@@ -29,15 +29,25 @@ import java.util.List;
import java.util.concurrent.TimeUnit;
/** Display the current work queue. */
class AdminShowQueue extends AbstractCommand {
@AdminCommand
final class AdminShowQueue extends BaseCommand {
@Inject
private WorkQueue workQueue;
PrintWriter p;
private PrintWriter p;
@Override
protected void run() throws Failure, UnsupportedEncodingException {
assertIsAdministrator();
public void start() {
startThread(new CommandRunnable() {
@Override
public void run() throws Exception {
parseCommandLine();
AdminShowQueue.this.display();
}
});
}
private void display() {
p = toPrintWriter(out);
final List<Task<?>> pending = workQueue.getTasks();

View File

@@ -14,7 +14,7 @@
package com.google.gerrit.server.ssh.commands;
import com.google.gerrit.server.ssh.AbstractCommand;
import com.google.gerrit.server.ssh.BaseCommand;
import com.google.inject.Inject;
import net.sf.ehcache.CacheManager;
@@ -24,7 +24,7 @@ import java.util.Arrays;
import java.util.SortedSet;
import java.util.TreeSet;
abstract class AbstractAdminCacheCommand extends AbstractCommand {
abstract class CacheCommand extends BaseCommand {
@Inject
protected CacheManager cacheMgr;

View File

@@ -14,24 +14,39 @@
package com.google.gerrit.server.ssh.commands;
import static com.google.gerrit.client.reviewdb.ApprovalCategory.READ;
import com.google.gerrit.client.data.ProjectCache;
import com.google.gerrit.client.reviewdb.Project;
import com.google.gerrit.client.reviewdb.ProjectRight;
import com.google.gerrit.client.reviewdb.ReviewDb;
import com.google.gerrit.client.rpc.Common;
import com.google.gerrit.server.ssh.AbstractCommand;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.ssh.BaseCommand;
import com.google.gwtorm.client.OrmException;
import com.google.inject.Inject;
import java.io.IOException;
import java.io.PrintWriter;
class ListProjects extends AbstractCommand {
final class ListProjects extends BaseCommand {
@Inject
private ReviewDb db;
@Inject
private IdentifiedUser currentUser;
@Override
protected void run() throws IOException, Failure {
public void start() {
startThread(new CommandRunnable() {
@Override
public void run() throws Exception {
parseCommandLine();
ListProjects.this.display();
}
});
}
private void display() throws Failure {
final PrintWriter stdout = toPrintWriter(out);
try {
final ProjectCache cache = Common.getProjectCache();
@@ -43,7 +58,7 @@ class ListProjects extends AbstractCommand {
}
final ProjectCache.Entry e = cache.get(p.getId());
if (e != null && canRead(e)) {
if (e != null && currentUser.canPerform(e, READ, (short) 1)) {
stdout.print(p.getName());
stdout.println();
}

View File

@@ -43,7 +43,7 @@ import com.google.gerrit.client.reviewdb.ReviewDb;
import com.google.gerrit.git.PatchSetImporter;
import com.google.gerrit.git.ReplicationQueue;
import com.google.gerrit.server.ChangeUtil;
import com.google.gerrit.server.RemotePeer;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.config.AuthConfig;
import com.google.gerrit.server.config.CanonicalWebUrl;
import com.google.gerrit.server.config.Nullable;
@@ -81,7 +81,6 @@ import org.spearce.jgit.transport.ReceiveCommand.Type;
import java.io.IOException;
import java.io.PrintWriter;
import java.net.SocketAddress;
import java.sql.Timestamp;
import java.text.MessageFormat;
import java.util.ArrayList;
@@ -98,7 +97,7 @@ import java.util.regex.Pattern;
import javax.security.auth.login.AccountNotFoundException;
/** Receives change upload over SSH using the Git receive-pack protocol. */
class Receive extends AbstractGitCommand {
final class Receive extends AbstractGitCommand {
private static final Logger log = LoggerFactory.getLogger(Receive.class);
private static final String NEW_CHANGE = "refs/for/";
@@ -134,8 +133,7 @@ class Receive extends AbstractGitCommand {
}
@Inject
@RemotePeer
private SocketAddress remoteAddress;
private IdentifiedUser currentUser;
@Inject
private ReviewDb db;
@@ -190,7 +188,7 @@ class Receive extends AbstractGitCommand {
verifyActiveContributorAgreement();
}
loadMyEmails();
refLogIdent = ChangeUtil.toReflogIdent(userAccount, remoteAddress);
refLogIdent = currentUser.toPersonIdent();
rp = new ReceivePack(repo);
rp.setAllowCreates(true);
@@ -261,7 +259,7 @@ class Receive extends AbstractGitCommand {
AbstractAgreement bestAgreement = null;
ContributorAgreement bestCla = null;
try {
OUTER: for (final AccountGroup.Id groupId : getGroups()) {
OUTER: for (AccountGroup.Id groupId : currentUser.getEffectiveGroups()) {
for (final AccountGroupAgreement a : db.accountGroupAgreements()
.byGroup(groupId)) {
final ContributorAgreement cla =
@@ -278,7 +276,7 @@ class Receive extends AbstractGitCommand {
if (bestAgreement == null) {
for (final AccountAgreement a : db.accountAgreements().byAccount(
userAccount.getId()).toList()) {
currentUser.getAccountId()).toList()) {
final ContributorAgreement cla =
db.contributorAgreements().get(a.getAgreementId());
if (cla == null) {
@@ -313,9 +311,9 @@ class Receive extends AbstractGitCommand {
if (bestCla != null && bestCla.isRequireContactInformation()) {
boolean fail = false;
fail |= missing(userAccount.getFullName());
fail |= missing(userAccount.getPreferredEmail());
fail |= !userAccount.isContactFiled();
fail |= missing(currentUser.getAccount().getFullName());
fail |= missing(currentUser.getAccount().getPreferredEmail());
fail |= !currentUser.getAccount().isContactFiled();
if (fail) {
final StringBuilder msg = new StringBuilder();
@@ -372,10 +370,10 @@ class Receive extends AbstractGitCommand {
}
private void loadMyEmails() throws Failure {
addEmail(userAccount.getPreferredEmail());
addEmail(currentUser.getAccount().getPreferredEmail());
try {
for (final AccountExternalId id : db.accountExternalIds().byAccount(
userAccount.getId())) {
currentUser.getAccountId())) {
addEmail(id.getEmailAddress());
}
} catch (OrmException e) {
@@ -718,7 +716,7 @@ class Receive extends AbstractGitCommand {
walk.parseBody(c);
final Transaction txn = db.beginTransaction();
final Account.Id me = userAccount.getId();
final Account.Id me = currentUser.getAccountId();
final Change change =
new Change(new Change.Id(db.nextChangeId()), me, destBranch
.getNameKey());
@@ -850,7 +848,7 @@ class Receive extends AbstractGitCommand {
return;
}
final Account.Id me = userAccount.getId();
final Account.Id me = currentUser.getAccountId();
final Set<Account.Id> reviewers = new HashSet<Account.Id>(reviewerId);
final Set<Account.Id> cc = new HashSet<Account.Id>(ccId);
for (final FooterLine footerLine : c.getFooterLines()) {
@@ -938,7 +936,7 @@ class Receive extends AbstractGitCommand {
final PatchSet ps = new PatchSet(change.newPatchSetId());
ps.setCreatedOn(new Timestamp(System.currentTimeMillis()));
ps.setUploader(userAccount.getId());
ps.setUploader(currentUser.getAccountId());
final PatchSetImporter imp =
importFactory.create(db, proj.getNameKey(), repo, c, ps, true);
@@ -1313,7 +1311,7 @@ class Receive extends AbstractGitCommand {
msgBuf.append(".");
final ChangeMessage msg =
new ChangeMessage(new ChangeMessage.Key(change.getId(), ChangeUtil
.messageUUID(db)), getAccountId());
.messageUUID(db)), currentUser.getAccountId());
msg.setMessage(msgBuf.toString());
db.changeApprovals().update(result.approvals, txn);
@@ -1324,9 +1322,8 @@ class Receive extends AbstractGitCommand {
private void sendMergedEmail(final ReplaceResult result) {
if (result != null && result.mergedIntoRef != null) {
try {
final MergedSender cm;
cm = mergedSenderFactory.create(result.change);
cm.setFrom(getAccountId());
final MergedSender cm = mergedSenderFactory.create(result.change);
cm.setFrom(currentUser.getAccountId());
cm.setReviewDb(db);
cm.setPatchSet(result.patchSet, result.info);
cm.setDest(new Branch.NameKey(proj.getNameKey(), result.mergedIntoRef));

View File

@@ -39,7 +39,7 @@ import java.util.ArrayList;
import java.util.List;
import java.util.TreeMap;
class ScpCommand extends BaseCommand {
final class ScpCommand extends BaseCommand {
private static final String TYPE_DIR = "D";
private static final String TYPE_FILE = "C";
private static final Logger log = LoggerFactory.getLogger(ScpCommand.class);

View File

@@ -19,7 +19,7 @@ import org.spearce.jgit.transport.UploadPack;
import java.io.IOException;
/** Publishes Git repositories over SSH using the Git upload-pack protocol. */
class Upload extends AbstractGitCommand {
final class Upload extends AbstractGitCommand {
@Override
protected void runImpl() throws IOException {
final UploadPack up = new UploadPack(repo);