Refactor the SSH session state
We want to split the session state apart from the actual connection
so we can implement a "set uid" feature later, where the user
running a command may not match the original authentication.
Change-Id: I0c9d31b4f5f04849e1c4a171243f0f376056c2c8
Signed-off-by: Shawn O. Pearce <sop@google.com>
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/IdentifiedUser.java b/gerrit-server/src/main/java/com/google/gerrit/server/IdentifiedUser.java
index 00c33a8..dcc055e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/IdentifiedUser.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/IdentifiedUser.java
@@ -69,8 +69,13 @@
}
public IdentifiedUser create(final Account.Id id) {
- return new IdentifiedUser(AccessPath.UNKNOWN, authConfig, canonicalUrl,
- realm, accountCache, null, null, id);
+ return create(AccessPath.UNKNOWN, null, id);
+ }
+
+ public IdentifiedUser create(AccessPath accessPath,
+ Provider<SocketAddress> remotePeerProvider, Account.Id id) {
+ return new IdentifiedUser(accessPath, authConfig, canonicalUrl, realm,
+ accountCache, remotePeerProvider, null, id);
}
}
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/BaseCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/BaseCommand.java
index e8f2c36..99969e1 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/BaseCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/BaseCommand.java
@@ -14,20 +14,22 @@
package com.google.gerrit.sshd;
+import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.RequestCleanup;
import com.google.gerrit.server.git.WorkQueue;
import com.google.gerrit.server.git.WorkQueue.CancelableRunnable;
import com.google.gerrit.server.project.NoSuchChangeException;
import com.google.gerrit.server.project.NoSuchProjectException;
-import com.google.gerrit.sshd.SshScopes.Context;
+import com.google.gerrit.sshd.SshScope.Context;
import com.google.gerrit.util.cli.CmdLineParser;
import com.google.inject.Inject;
+import com.google.inject.Provider;
import org.apache.sshd.common.SshException;
import org.apache.sshd.server.Command;
import org.apache.sshd.server.Environment;
import org.apache.sshd.server.ExitCallback;
-import org.apache.sshd.server.session.ServerSession;
import org.kohsuke.args4j.Argument;
import org.kohsuke.args4j.CmdLineException;
import org.kohsuke.args4j.Option;
@@ -74,6 +76,12 @@
@CommandExecutor
private WorkQueue.Executor executor;
+ @Inject
+ private Provider<CurrentUser> userProvider;
+
+ @Inject
+ private Provider<SshScope.Context> contextProvider;
+
/** The task, as scheduled on a worker thread. */
private Future<?> task;
@@ -324,14 +332,17 @@
if (e instanceof UnloggedFailure) {
} else {
- final ServerSession session = SshScopes.getContext().session;
final StringBuilder m = new StringBuilder();
- m.append("Internal server error (");
- m.append("user ");
- m.append(session.getUsername());
- m.append(" account ");
- m.append(session.getAttribute(SshUtil.CURRENT_ACCOUNT));
- m.append(") during ");
+ m.append("Internal server error");
+ if (userProvider.get() instanceof IdentifiedUser) {
+ final IdentifiedUser u = (IdentifiedUser) userProvider.get();
+ m.append(" (user ");
+ m.append(u.getAccount().getUserName());
+ m.append(" account ");
+ m.append(u.getAccountId());
+ m.append(")");
+ }
+ m.append(" during ");
m.append(getFullCommandLine());
log.error(m.toString(), e);
}
@@ -376,19 +387,28 @@
private final class TaskThunk implements CancelableRunnable {
private final CommandRunnable thunk;
private final Context context;
+ private final String taskName;
private TaskThunk(final CommandRunnable thunk) {
this.thunk = thunk;
- this.context = SshScopes.getContext();
+ this.context = contextProvider.get();
+
+ StringBuilder m = new StringBuilder();
+ m.append(getFullCommandLine());
+ if (userProvider.get() instanceof IdentifiedUser) {
+ IdentifiedUser u = (IdentifiedUser) userProvider.get();
+ m.append(" (" + u.getAccount().getUserName() + ")");
+ }
+ this.taskName = m.toString();
}
@Override
public void cancel() {
+ final Context old = SshScope.set(context);
try {
- SshScopes.current.set(context);
onExit(STATUS_CANCEL);
} finally {
- SshScopes.current.set(null);
+ SshScope.set(old);
}
}
@@ -397,10 +417,10 @@
final Thread thisThread = Thread.currentThread();
final String thisName = thisThread.getName();
int rc = 0;
+ final Context old = SshScope.set(context);
try {
context.started = System.currentTimeMillis();
- thisThread.setName("SSH " + toString());
- SshScopes.current.set(context);
+ thisThread.setName("SSH " + taskName);
try {
thunk.run();
} catch (NoSuchProjectException e) {
@@ -424,7 +444,7 @@
try {
onExit(rc);
} finally {
- SshScopes.current.set(null);
+ SshScope.set(old);
thisThread.setName(thisName);
}
}
@@ -432,9 +452,7 @@
@Override
public String toString() {
- final ServerSession session = context.session;
- final String who = session.getUsername();
- return getFullCommandLine() + " (" + who + ")";
+ return taskName;
}
}
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/CommandFactoryProvider.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/CommandFactoryProvider.java
index cbe0d59..fbe2373 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/CommandFactoryProvider.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/CommandFactoryProvider.java
@@ -14,7 +14,7 @@
package com.google.gerrit.sshd;
-import com.google.gerrit.sshd.SshScopes.Context;
+import com.google.gerrit.sshd.SshScope.Context;
import com.google.inject.Inject;
import com.google.inject.Provider;
@@ -59,7 +59,6 @@
private OutputStream out;
private OutputStream err;
private ExitCallback exit;
- private ServerSession session;
private Context ctx;
private DispatchCommand cmd;
private boolean logged;
@@ -85,16 +84,13 @@
}
public void setSession(final ServerSession session) {
- this.session = session;
+ this.ctx = new Context(session.getAttribute(SshSession.KEY));
}
public void start(final Environment env) throws IOException {
synchronized (this) {
- final Context old = SshScopes.current.get();
+ final Context old = SshScope.set(ctx);
try {
- ctx = new Context(session);
- SshScopes.current.set(ctx);
-
cmd = dispatcher.get();
cmd.setCommandLine(commandLine);
cmd.setInputStream(in);
@@ -115,7 +111,7 @@
});
cmd.start(env);
} finally {
- SshScopes.current.set(old);
+ SshScope.set(old);
}
}
}
@@ -150,15 +146,14 @@
public void destroy() {
synchronized (this) {
if (cmd != null) {
- final Context old = SshScopes.current.get();
+ final Context old = SshScope.set(ctx);
try {
- SshScopes.current.set(ctx);
cmd.destroy();
log(BaseCommand.STATUS_CANCEL);
} finally {
ctx = null;
cmd = null;
- SshScopes.current.set(old);
+ SshScope.set(old);
}
}
}
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/DatabasePasswordAuth.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/DatabasePasswordAuth.java
index d1b6936..5d5572a 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/DatabasePasswordAuth.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/DatabasePasswordAuth.java
@@ -14,15 +14,14 @@
package com.google.gerrit.sshd;
-import static com.google.gerrit.sshd.SshUtil.AUTH_ATTEMPTED_AS;
-import static com.google.gerrit.sshd.SshUtil.AUTH_ERROR;
-import static com.google.gerrit.sshd.SshUtil.CURRENT_ACCOUNT;
-
import com.google.gerrit.reviewdb.AccountExternalId;
+import com.google.gerrit.server.AccessPath;
+import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.AccountCache;
import com.google.gerrit.server.account.AccountState;
-import com.google.gerrit.sshd.SshScopes.Context;
+import com.google.gerrit.sshd.SshScope.Context;
import com.google.inject.Inject;
+import com.google.inject.Provider;
import com.google.inject.Singleton;
import org.apache.mina.core.future.IoFuture;
@@ -30,6 +29,8 @@
import org.apache.sshd.server.PasswordAuthenticator;
import org.apache.sshd.server.session.ServerSession;
+import java.net.SocketAddress;
+
/**
* Authenticates by password through {@link AccountExternalId} entities.
*/
@@ -37,54 +38,63 @@
class DatabasePasswordAuth implements PasswordAuthenticator {
private final AccountCache accountCache;
private final SshLog log;
+ private final IdentifiedUser.GenericFactory userFactory;
@Inject
- DatabasePasswordAuth(final AccountCache ac, final SshLog l) {
+ DatabasePasswordAuth(final AccountCache ac, final SshLog l,
+ final IdentifiedUser.GenericFactory uf) {
accountCache = ac;
log = l;
+ userFactory = uf;
}
@Override
public boolean authenticate(final String username, final String password,
final ServerSession session) {
+ final SshSession sd = session.getAttribute(SshSession.KEY);
+
AccountState state = accountCache.getByUsername(username);
if (state == null) {
- return fail(username, session, "user-not-found");
+ sd.authenticationError(username, "user-not-found");
+ return false;
}
final String p = state.getPassword(username);
if (p == null) {
- return fail(username, session, "no-password");
+ sd.authenticationError(username, "no-password");
+ return false;
}
if (!p.equals(password)) {
- return fail(username, session, "incorrect-password");
+ sd.authenticationError(username, "incorrect-password");
+ return false;
}
- if (session.setAttribute(CURRENT_ACCOUNT, state.getAccount().getId()) == null) {
+ if (sd.getCurrentUser() == null) {
+ sd.authenticationSuccess(username, createUser(sd, state));
+
// If this is the first time we've authenticated this
// session, record a login event in the log and add
// a close listener to record a logout event.
//
- final Context ctx = new Context(session);
- final Context old = SshScopes.current.get();
+ Context ctx = new Context(sd);
+ Context old = SshScope.set(ctx);
try {
- SshScopes.current.set(ctx);
log.onLogin();
} finally {
- SshScopes.current.set(old);
+ SshScope.set(old);
}
session.getIoSession().getCloseFuture().addListener(
new IoFutureListener<IoFuture>() {
@Override
public void operationComplete(IoFuture future) {
- final Context old = SshScopes.current.get();
+ final Context ctx = new Context(sd);
+ final Context old = SshScope.set(ctx);
try {
- SshScopes.current.set(ctx);
log.onLogout();
} finally {
- SshScopes.current.set(old);
+ SshScope.set(old);
}
}
});
@@ -93,10 +103,13 @@
return true;
}
- private static boolean fail(final String username,
- final ServerSession session, final String err) {
- session.setAttribute(AUTH_ATTEMPTED_AS, username);
- session.setAttribute(AUTH_ERROR, err);
- return false;
+ private IdentifiedUser createUser(final SshSession sd,
+ final AccountState state) {
+ return userFactory.create(AccessPath.SSH, new Provider<SocketAddress>() {
+ @Override
+ public SocketAddress get() {
+ return sd.getRemoteAddress();
+ }
+ }, state.getAccount().getId());
}
}
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/DatabasePubKeyAuth.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/DatabasePubKeyAuth.java
index 7dcad4b..9fd136c 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/DatabasePubKeyAuth.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/DatabasePubKeyAuth.java
@@ -14,13 +14,12 @@
package com.google.gerrit.sshd;
-import static com.google.gerrit.sshd.SshUtil.AUTH_ATTEMPTED_AS;
-import static com.google.gerrit.sshd.SshUtil.AUTH_ERROR;
-import static com.google.gerrit.sshd.SshUtil.CURRENT_ACCOUNT;
-
import com.google.gerrit.reviewdb.AccountSshKey;
-import com.google.gerrit.sshd.SshScopes.Context;
+import com.google.gerrit.server.AccessPath;
+import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.sshd.SshScope.Context;
import com.google.inject.Inject;
+import com.google.inject.Provider;
import com.google.inject.Singleton;
import org.apache.mina.core.future.IoFuture;
@@ -28,6 +27,7 @@
import org.apache.sshd.server.PublickeyAuthenticator;
import org.apache.sshd.server.session.ServerSession;
+import java.net.SocketAddress;
import java.security.PublicKey;
/**
@@ -37,15 +37,20 @@
class DatabasePubKeyAuth implements PublickeyAuthenticator {
private final SshKeyCacheImpl sshKeyCache;
private final SshLog log;
+ private final IdentifiedUser.GenericFactory userFactory;
@Inject
- DatabasePubKeyAuth(final SshKeyCacheImpl skc, final SshLog l) {
+ DatabasePubKeyAuth(final SshKeyCacheImpl skc, final SshLog l,
+ final IdentifiedUser.GenericFactory uf) {
sshKeyCache = skc;
log = l;
+ userFactory = uf;
}
public boolean authenticate(final String username,
final PublicKey suppliedKey, final ServerSession session) {
+ final SshSession sd = session.getAttribute(SshSession.KEY);
+
final Iterable<SshKeyCacheEntry> keyList = sshKeyCache.get(username);
final SshKeyCacheEntry key = find(keyList, suppliedKey);
if (key == null) {
@@ -57,7 +62,8 @@
} else {
err = "no-matching-key";
}
- return fail(username, session, err);
+ sd.authenticationError(username, err);
+ return false;
}
// Double check that all of the keys are for the same user account.
@@ -68,34 +74,36 @@
//
for (final SshKeyCacheEntry otherKey : keyList) {
if (!key.getAccount().equals(otherKey.getAccount())) {
- return fail(username, session, "keys-cross-accounts");
+ sd.authenticationError(username, "keys-cross-accounts");
+ return false;
}
}
- if (session.setAttribute(CURRENT_ACCOUNT, key.getAccount()) == null) {
+ if (sd.getCurrentUser() == null) {
+ sd.authenticationSuccess(username, createUser(sd, key));
+
// If this is the first time we've authenticated this
// session, record a login event in the log and add
// a close listener to record a logout event.
//
- final Context ctx = new Context(session);
- final Context old = SshScopes.current.get();
+ Context ctx = new Context(sd);
+ Context old = SshScope.set(ctx);
try {
- SshScopes.current.set(ctx);
log.onLogin();
} finally {
- SshScopes.current.set(old);
+ SshScope.set(old);
}
session.getIoSession().getCloseFuture().addListener(
new IoFutureListener<IoFuture>() {
@Override
public void operationComplete(IoFuture future) {
- final Context old = SshScopes.current.get();
+ final Context ctx = new Context(sd);
+ final Context old = SshScope.set(ctx);
try {
- SshScopes.current.set(ctx);
log.onLogout();
} finally {
- SshScopes.current.set(old);
+ SshScope.set(old);
}
}
});
@@ -104,11 +112,14 @@
return true;
}
- private static boolean fail(final String username,
- final ServerSession session, final String err) {
- session.setAttribute(AUTH_ATTEMPTED_AS, username);
- session.setAttribute(AUTH_ERROR, err);
- return false;
+ private IdentifiedUser createUser(final SshSession sd,
+ final SshKeyCacheEntry key) {
+ return userFactory.create(AccessPath.SSH, new Provider<SocketAddress>() {
+ @Override
+ public SocketAddress get() {
+ return sd.getRemoteAddress();
+ }
+ }, key.getAccount());
}
private SshKeyCacheEntry find(final Iterable<SshKeyCacheEntry> keyList,
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshCurrentUserProvider.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshCurrentUserProvider.java
index 9a0b233..e2797ef 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshCurrentUserProvider.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshCurrentUserProvider.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2009 The Android Open Source Project
+// Copyright (C) 2010 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.
@@ -14,31 +14,22 @@
package com.google.gerrit.sshd;
-import com.google.gerrit.reviewdb.Account;
-import com.google.gerrit.server.AccessPath;
-import com.google.gerrit.server.IdentifiedUser;
-import com.google.gerrit.sshd.SshScopes.Context;
+import com.google.gerrit.server.CurrentUser;
import com.google.inject.Inject;
import com.google.inject.Provider;
-import com.google.inject.ProvisionException;
import com.google.inject.Singleton;
@Singleton
-class SshCurrentUserProvider implements Provider<IdentifiedUser> {
- private final IdentifiedUser.RequestFactory factory;
+class SshCurrentUserProvider implements Provider<CurrentUser> {
+ private final Provider<SshSession> session;
@Inject
- SshCurrentUserProvider(final IdentifiedUser.RequestFactory f) {
- factory = f;
+ SshCurrentUserProvider(final Provider<SshSession> s) {
+ session = s;
}
@Override
- public IdentifiedUser get() {
- final Context ctx = SshScopes.getContext();
- final Account.Id id = ctx.session.getAttribute(SshUtil.CURRENT_ACCOUNT);
- if (id == null) {
- throw new ProvisionException("User not yet authenticated");
- }
- return factory.create(AccessPath.SSH, id);
+ public CurrentUser get() {
+ return session.get().getCurrentUser();
}
}
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshDaemon.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshDaemon.java
index 418150f..d21e6e9e 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshDaemon.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshDaemon.java
@@ -20,7 +20,6 @@
import com.google.gerrit.server.util.IdGenerator;
import com.google.gerrit.server.util.SocketUtil;
import com.google.inject.Inject;
-import com.google.inject.Key;
import com.google.inject.Singleton;
import com.jcraft.jsch.HostKey;
@@ -85,7 +84,6 @@
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
-import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
@@ -157,20 +155,18 @@
}
final ServerSession s = (ServerSession) super.createSession(io);
- s.setAttribute(SshUtil.REMOTE_PEER, io.getRemoteAddress());
- s.setAttribute(SshUtil.SESSION_ID, idGenerator.next());
- s.setAttribute(SshScopes.sessionMap, new HashMap<Key<?>, Object>());
+ final int id = idGenerator.next();
+ final 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>() {
@Override
public void operationComplete(IoFuture future) {
- if (s.getUsername() == null /* not authenticated */) {
- String username = s.getAttribute(SshUtil.AUTH_ATTEMPTED_AS);
- if (username != null) {
- sshLog.onAuthFail(s, username);
- }
+ if (sd.isAuthenticationError()) {
+ sshLog.onAuthFail(sd);
}
}
});
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshIdentifiedUserProvider.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshIdentifiedUserProvider.java
new file mode 100644
index 0000000..f3da92c
--- /dev/null
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshIdentifiedUserProvider.java
@@ -0,0 +1,43 @@
+// Copyright (C) 2010 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 com.google.gerrit.common.errors.NotSignedInException;
+import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.IdentifiedUser;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.ProvisionException;
+import com.google.inject.Singleton;
+
+@Singleton
+class SshIdentifiedUserProvider implements Provider<IdentifiedUser> {
+ private final Provider<SshSession> session;
+
+ @Inject
+ SshIdentifiedUserProvider(final Provider<SshSession> s) {
+ session = s;
+ }
+
+ @Override
+ public IdentifiedUser get() {
+ final CurrentUser user = session.get().getCurrentUser();
+ if (user instanceof IdentifiedUser) {
+ return (IdentifiedUser) user;
+ }
+ throw new ProvisionException(NotSignedInException.MESSAGE,
+ new NotSignedInException());
+ }
+}
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshLog.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshLog.java
index 8495ad5..3d1a3b8 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshLog.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshLog.java
@@ -15,10 +15,11 @@
package com.google.gerrit.sshd;
import com.google.gerrit.lifecycle.LifecycleListener;
+import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.config.SitePaths;
import com.google.gerrit.server.util.IdGenerator;
-import com.google.gerrit.sshd.SshScopes.Context;
+import com.google.gerrit.sshd.SshScope.Context;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
@@ -31,14 +32,10 @@
import org.apache.log4j.Logger;
import org.apache.log4j.spi.ErrorHandler;
import org.apache.log4j.spi.LoggingEvent;
-import org.apache.sshd.server.session.ServerSession;
import org.eclipse.jgit.util.QuotedString;
import java.io.File;
import java.io.IOException;
-import java.net.InetAddress;
-import java.net.InetSocketAddress;
-import java.net.SocketAddress;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
@@ -55,15 +52,12 @@
private static final String P_EXEC = "executionTime";
private static final String P_STATUS = "status";
- private final Provider<ServerSession> session;
- private final Provider<IdentifiedUser> user;
+ private final Provider<SshSession> session;
private final AsyncAppender async;
@Inject
- SshLog(final Provider<ServerSession> session,
- final Provider<IdentifiedUser> user, final SitePaths site) {
+ SshLog(final Provider<SshSession> session, final SitePaths site) {
this.session = session;
- this.user = user;
final DailyRollingFileAppender dst = new DailyRollingFileAppender();
dst.setName(LOG_NAME);
@@ -95,20 +89,16 @@
}
void onLogin() {
- final ServerSession s = session.get();
- final SocketAddress addr = s.getIoSession().getRemoteAddress();
- async.append(log("LOGIN FROM " + format(addr)));
+ async.append(log("LOGIN FROM " + session.get().getRemoteAddressAsString()));
}
- void onAuthFail(final ServerSession s, final String username) {
- final SocketAddress addr = s.getIoSession().getRemoteAddress();
-
+ void onAuthFail(final SshSession sd) {
final LoggingEvent event = new LoggingEvent( //
Logger.class.getName(), // fqnOfCategoryClass
null, // logger (optional)
System.currentTimeMillis(), // when
Level.INFO, // level
- "AUTH FAILURE FROM " + format(addr), // message text
+ "AUTH FAILURE FROM " + sd.getRemoteAddressAsString(), // message text
"SSHD", // thread name
null, // exception information
null, // current NDC string
@@ -116,10 +106,10 @@
null // MDC properties
);
- event.setProperty(P_SESSION, id(s.getAttribute(SshUtil.SESSION_ID)));
- event.setProperty(P_USER_NAME, username);
+ event.setProperty(P_SESSION, id(sd.getSessionId()));
+ event.setProperty(P_USER_NAME, sd.getUsername());
- final String error = s.getAttribute(SshUtil.AUTH_ERROR);
+ final String error = sd.getAuthenticationError();
if (error != null) {
event.setProperty(P_STATUS, error);
}
@@ -165,8 +155,8 @@
}
private LoggingEvent log(final String msg) {
- final ServerSession s = session.get();
- final IdentifiedUser u = user.get();
+ final SshSession sd = session.get();
+ final CurrentUser user = sd.getCurrentUser();
final LoggingEvent event = new LoggingEvent( //
Logger.class.getName(), // fqnOfCategoryClass
@@ -181,32 +171,24 @@
null // MDC properties
);
- event.setProperty(P_SESSION, id(s.getAttribute(SshUtil.SESSION_ID)));
- event.setProperty(P_USER_NAME, u.getUserName());
- event.setProperty(P_ACCOUNT_ID, "a/" + u.getAccountId().toString());
+ event.setProperty(P_SESSION, id(sd.getSessionId()));
+
+ String userName = "-", accountId = "-";
+
+ if (user instanceof IdentifiedUser) {
+ IdentifiedUser u = (IdentifiedUser) user;
+ userName = u.getAccount().getUserName();
+ accountId = "a/" + u.getAccountId().toString();
+ }
+
+ event.setProperty(P_USER_NAME, userName);
+ event.setProperty(P_ACCOUNT_ID, accountId);
return event;
}
- private static String format(final SocketAddress remote) {
- if (remote instanceof InetSocketAddress) {
- final InetSocketAddress sa = (InetSocketAddress) remote;
-
- final InetAddress in = sa.getAddress();
- if (in != null) {
- return in.getHostAddress();
- }
-
- final String hostName = sa.getHostName();
- if (hostName != null) {
- return hostName;
- }
- }
- return remote.toString();
- }
-
- private static String id(final Integer id) {
- return id != null ? IdGenerator.format(id) : "";
+ private static String id(final int id) {
+ return IdGenerator.format(id);
}
private static File resolve(final File logs_dir) {
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshModule.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshModule.java
index 9bd1b03..84e8c61 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshModule.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshModule.java
@@ -38,18 +38,14 @@
import com.google.gerrit.util.cli.OptionHandlerFactory;
import com.google.gerrit.util.cli.OptionHandlerUtil;
import com.google.inject.Key;
-import com.google.inject.Provider;
import com.google.inject.TypeLiteral;
import com.google.inject.assistedinject.FactoryProvider;
import com.google.inject.servlet.RequestScoped;
-import com.google.inject.servlet.SessionScoped;
import org.apache.sshd.common.KeyPairProvider;
-import org.apache.sshd.common.session.AbstractSession;
import org.apache.sshd.server.CommandFactory;
import org.apache.sshd.server.PasswordAuthenticator;
import org.apache.sshd.server.PublickeyAuthenticator;
-import org.apache.sshd.server.session.ServerSession;
import org.kohsuke.args4j.spi.OptionHandler;
import java.net.SocketAddress;
@@ -60,10 +56,8 @@
@Override
protected void configure() {
- bindScope(SessionScoped.class, SshScopes.SESSION);
- bindScope(RequestScoped.class, SshScopes.REQUEST);
+ bindScope(RequestScoped.class, SshScope.REQUEST);
- configureSessionScope();
configureRequestScope();
configureCmdLineParser();
@@ -96,30 +90,20 @@
});
}
- private void configureSessionScope() {
- bind(ServerSession.class).toProvider(new Provider<ServerSession>() {
- @Override
- public ServerSession get() {
- return SshScopes.getContext().session;
- }
- }).in(SshScopes.SESSION);
- bind(AbstractSession.class).to(ServerSession.class).in(SshScopes.SESSION);
-
- bind(SocketAddress.class).annotatedWith(RemotePeer.class).toProvider(
- new Provider<SocketAddress>() {
- @Override
- public SocketAddress get() {
- return SshScopes.getContext().session
- .getAttribute(SshUtil.REMOTE_PEER);
- }
- }).in(SshScopes.SESSION);
- }
-
private void configureRequestScope() {
+ bind(SshScope.Context.class).toProvider(SshScope.ContextProvider.class);
+
+ bind(SshSession.class).toProvider(SshScope.SshSessionProvider.class).in(
+ SshScope.REQUEST);
+ bind(SocketAddress.class).annotatedWith(RemotePeer.class).toProvider(
+ SshRemotePeerProvider.class).in(SshScope.REQUEST);
+
+ bind(CurrentUser.class).toProvider(SshCurrentUserProvider.class).in(
+ SshScope.REQUEST);
+ bind(IdentifiedUser.class).toProvider(SshIdentifiedUserProvider.class).in(
+ SshScope.REQUEST);
+
install(new GerritRequestModule());
- bind(IdentifiedUser.class).toProvider(SshCurrentUserProvider.class).in(
- SshScopes.REQUEST);
- bind(CurrentUser.class).to(IdentifiedUser.class);
}
private void configureCmdLineParser() {
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshRemotePeerProvider.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshRemotePeerProvider.java
new file mode 100644
index 0000000..29ede85
--- /dev/null
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshRemotePeerProvider.java
@@ -0,0 +1,36 @@
+// Copyright (C) 2010 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 com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+
+import java.net.SocketAddress;
+
+@Singleton
+class SshRemotePeerProvider implements Provider<SocketAddress> {
+ private final Provider<SshSession> session;
+
+ @Inject
+ SshRemotePeerProvider(final Provider<SshSession> s) {
+ session = s;
+ }
+
+ @Override
+ public SocketAddress get() {
+ return session.get().getRemoteAddress();
+ }
+}
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshScope.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshScope.java
new file mode 100644
index 0000000..b980c18
--- /dev/null
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshScope.java
@@ -0,0 +1,107 @@
+// 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.sshd;
+
+import com.google.inject.Key;
+import com.google.inject.OutOfScopeException;
+import com.google.inject.Provider;
+import com.google.inject.Scope;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/** Guice scopes for state during an SSH connection. */
+class SshScope {
+ static class Context {
+ private final SshSession session;
+ private final Map<Key<?>, Object> map;
+
+ final long created;
+ volatile long started;
+ volatile long finished;
+
+ Context(final SshSession s) {
+ session = s;
+ map = new HashMap<Key<?>, Object>();
+ created = System.currentTimeMillis();
+ started = created;
+ }
+
+ synchronized <T> T get(Key<T> key, Provider<T> creator) {
+ @SuppressWarnings("unchecked")
+ T t = (T) map.get(key);
+ if (t == null) {
+ t = creator.get();
+ map.put(key, t);
+ }
+ return t;
+ }
+ }
+
+ static class ContextProvider implements Provider<Context> {
+ @Override
+ public Context get() {
+ return getContext();
+ }
+ }
+
+ static class SshSessionProvider implements Provider<SshSession> {
+ @Override
+ public SshSession get() {
+ return getContext().session;
+ }
+ }
+
+ private static final ThreadLocal<Context> current =
+ new ThreadLocal<Context>();
+
+ private static Context getContext() {
+ final Context ctx = current.get();
+ if (ctx == null) {
+ throw new OutOfScopeException("Not in command/request");
+ }
+ return ctx;
+ }
+
+ static Context set(Context ctx) {
+ Context old = current.get();
+ current.set(ctx);
+ return old;
+ }
+
+ /** Returns exactly one instance per command executed. */
+ static final Scope REQUEST = new Scope() {
+ public <T> Provider<T> scope(final Key<T> key, final Provider<T> creator) {
+ return new Provider<T>() {
+ public T get() {
+ return getContext().get(key, creator);
+ }
+
+ @Override
+ public String toString() {
+ return String.format("%s[%s]", creator, REQUEST);
+ }
+ };
+ }
+
+ @Override
+ public String toString() {
+ return "SshScopes.REQUEST";
+ }
+ };
+
+ private SshScope() {
+ }
+}
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshScopes.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshScopes.java
deleted file mode 100644
index 07b13c7..0000000
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshScopes.java
+++ /dev/null
@@ -1,122 +0,0 @@
-// 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.sshd;
-
-import com.google.inject.Key;
-import com.google.inject.OutOfScopeException;
-import com.google.inject.Provider;
-import com.google.inject.Scope;
-
-import org.apache.sshd.common.Session.AttributeKey;
-import org.apache.sshd.server.session.ServerSession;
-
-import java.util.HashMap;
-import java.util.Map;
-
-/** Guice scopes for state during an SSH connection. */
-class SshScopes {
- static class Context {
- final ServerSession session;
- final Map<Key<?>, Object> map;
- final long created;
- volatile long started;
- volatile long finished;
-
- Context(final ServerSession s) {
- session = s;
- map = new HashMap<Key<?>, Object>();
- created = System.currentTimeMillis();
- started = created;
- }
- }
-
- static final AttributeKey<Map<Key<?>, Object>> sessionMap =
- new AttributeKey<Map<Key<?>, Object>>();
-
- static final ThreadLocal<Context> current = new ThreadLocal<Context>();
-
- static Context getContext() {
- final Context ctx = current.get();
- if (ctx == null) {
- throw new OutOfScopeException(
- "Cannot access scoped object; not in request/command.");
- }
- return ctx;
- }
-
- /** Returns exactly one instance for the scope of the SSH connection. */
- static final Scope SESSION = new Scope() {
- public <T> Provider<T> scope(final Key<T> key, final Provider<T> creator) {
- return new Provider<T>() {
- public T get() {
- final Context ctx = getContext();
- final Map<Key<?>, Object> map = ctx.session.getAttribute(sessionMap);
- synchronized (map) {
- @SuppressWarnings("unchecked")
- T t = (T) map.get(key);
- if (t == null) {
- t = creator.get();
- map.put(key, t);
- }
- return t;
- }
- }
-
- @Override
- public String toString() {
- return String.format("%s[%s]", creator, SESSION);
- }
- };
- }
-
- @Override
- public String toString() {
- return "SshScopes.SESSION";
- }
- };
-
- /** Returns exactly one instance per command executed. */
- static final Scope REQUEST = new Scope() {
- public <T> Provider<T> scope(final Key<T> key, final Provider<T> creator) {
- return new Provider<T>() {
- public T get() {
- final Map<Key<?>, Object> map = getContext().map;
- synchronized (map) {
- @SuppressWarnings("unchecked")
- T t = (T) map.get(key);
- if (t == null) {
- t = creator.get();
- map.put(key, t);
- }
- return t;
- }
- }
-
- @Override
- public String toString() {
- return String.format("%s[%s]", creator, REQUEST);
- }
- };
- }
-
- @Override
- public String toString() {
- return "SshScopes.REQUEST";
- }
- };
-
- private SshScopes() {
- }
-}
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshSession.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshSession.java
new file mode 100644
index 0000000..b79db52
--- /dev/null
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshSession.java
@@ -0,0 +1,104 @@
+// Copyright (C) 2010 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 com.google.gerrit.server.CurrentUser;
+
+import org.apache.sshd.common.Session.AttributeKey;
+
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.SocketAddress;
+
+/** Global data related to an active SSH connection. */
+public class SshSession {
+ /** ServerSession attribute key for this object instance. */
+ public static final AttributeKey<SshSession> KEY =
+ new AttributeKey<SshSession>();
+
+ private final int sessionId;
+ private final SocketAddress remoteAddress;
+ private final String remoteAsString;
+
+ private volatile CurrentUser identity;
+ private volatile String username;
+ private volatile String authError;
+
+ SshSession(final int sessionId, SocketAddress peer) {
+ this.sessionId = sessionId;
+ this.remoteAddress = peer;
+ this.remoteAsString = format(remoteAddress);
+ }
+
+ /** Unique session number, assigned during connect. */
+ public int getSessionId() {
+ return sessionId;
+ }
+
+ /** Identity of the authenticated user account on the socket. */
+ public CurrentUser getCurrentUser() {
+ return identity;
+ }
+
+ String getUsername() {
+ return username;
+ }
+
+ String getAuthenticationError() {
+ return authError;
+ }
+
+ void authenticationSuccess(String user, CurrentUser id) {
+ username = user;
+ identity = id;
+ authError = null;
+ }
+
+ void authenticationError(String user, String error) {
+ username = user;
+ identity = null;
+ authError = error;
+ }
+
+ /** @return {@code true} if the authentication did not succeed. */
+ boolean isAuthenticationError() {
+ return authError != null;
+ }
+
+ SocketAddress getRemoteAddress() {
+ return remoteAddress;
+ }
+
+ String getRemoteAddressAsString() {
+ return remoteAsString;
+ }
+
+ private static String format(final SocketAddress remote) {
+ if (remote instanceof InetSocketAddress) {
+ final InetSocketAddress sa = (InetSocketAddress) remote;
+
+ final InetAddress in = sa.getAddress();
+ if (in != null) {
+ return in.getHostAddress();
+ }
+
+ final String hostName = sa.getHostName();
+ if (hostName != null) {
+ return hostName;
+ }
+ }
+ return remote.toString();
+ }
+}
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshUtil.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshUtil.java
index 426030a..65cfcdb 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshUtil.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshUtil.java
@@ -14,20 +14,17 @@
package com.google.gerrit.sshd;
-import com.google.gerrit.reviewdb.Account;
import com.google.gerrit.reviewdb.AccountSshKey;
import org.apache.commons.codec.binary.Base64;
import org.apache.sshd.common.KeyPairProvider;
import org.apache.sshd.common.SshException;
-import org.apache.sshd.common.Session.AttributeKey;
import org.apache.sshd.common.util.Buffer;
import org.eclipse.jgit.lib.Constants;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.StringReader;
-import java.net.SocketAddress;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PublicKey;
@@ -37,25 +34,6 @@
/** Utilities to support SSH operations. */
public class SshUtil {
- /** Server session attribute holding the {@link Account.Id}. */
- public static final AttributeKey<Account.Id> CURRENT_ACCOUNT =
- new AttributeKey<Account.Id>();
-
- /** Server session attribute holding the remote {@link SocketAddress}. */
- public static final AttributeKey<SocketAddress> REMOTE_PEER =
- new AttributeKey<SocketAddress>();
-
- /** Server session attribute holding a unique session id. */
- public static final AttributeKey<Integer> SESSION_ID =
- new AttributeKey<Integer>();
-
- /** Username the last authentication tried to perform as. */
- static final AttributeKey<String> AUTH_ATTEMPTED_AS =
- new AttributeKey<String>();
-
- /** Error message from last authentication attempt. */
- static final AttributeKey<String> AUTH_ERROR = new AttributeKey<String>();
-
/**
* Parse a public key into its Java type.
*
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AdminShowConnections.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AdminShowConnections.java
index ff1b57a..b6c6119 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AdminShowConnections.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AdminShowConnections.java
@@ -14,12 +14,13 @@
package com.google.gerrit.sshd.commands;
-import com.google.gerrit.reviewdb.Account;
+import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.util.IdGenerator;
import com.google.gerrit.sshd.AdminCommand;
import com.google.gerrit.sshd.BaseCommand;
import com.google.gerrit.sshd.SshDaemon;
-import com.google.gerrit.sshd.SshUtil;
+import com.google.gerrit.sshd.SshSession;
import com.google.inject.Inject;
import org.apache.mina.core.service.IoAcceptor;
@@ -89,17 +90,17 @@
p.print("--------------------------------------------------------------\n");
for (final IoSession io : list) {
ServerSession s = (ServerSession) ServerSession.getSession(io, true);
+ 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();
- final Integer id = s != null ? s.getAttribute(SshUtil.SESSION_ID) : null;
p.print(String.format("%8s %8s %8s %-15.15s %.30s\n", //
- id(id), //
+ id(sd), //
time(now, start), //
age(idle), //
- username(s), //
+ username(sd), //
hostname(remoteAddress)));
}
p.print("--\n");
@@ -107,8 +108,8 @@
p.flush();
}
- private static String id(final Integer id) {
- return id != null ? IdGenerator.format(id) : "";
+ private static String id(final SshSession sd) {
+ return sd != null ? IdGenerator.format(sd.getSessionId()) : "";
}
private static String time(final long now, final long time) {
@@ -131,15 +132,26 @@
return String.format("%02d:%02d:%02d", hr, min, sec);
}
- private String username(final ServerSession s) {
- if (s == null) {
+ private String username(final SshSession sd) {
+ if (sd == null) {
return "";
- } else if (numeric) {
- final Account.Id id = s.getAttribute(SshUtil.CURRENT_ACCOUNT);
- return id != null ? "a/" + id.toString() : "";
+ }
+
+ final CurrentUser user = sd.getCurrentUser();
+ if (user instanceof IdentifiedUser) {
+ IdentifiedUser u = (IdentifiedUser) user;
+
+ if (!numeric) {
+ String name = u.getAccount().getUserName();
+ if (name != null && !name.isEmpty()) {
+ return name;
+ }
+ }
+
+ return "a/" + u.getAccountId().toString();
+
} else {
- final String user = s.getUsername();
- return user != null ? user : "";
+ return "";
}
}