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 00c33a8c82..dcc055ee44 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 class IdentifiedUser extends CurrentUser { } 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 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 e8f2c36760..99969e11f2 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 @@ public abstract class BaseCommand implements Command { @CommandExecutor private WorkQueue.Executor executor; + @Inject + private Provider userProvider; + + @Inject + private Provider contextProvider; + /** The task, as scheduled on a worker thread. */ private Future task; @@ -324,14 +332,17 @@ public abstract class BaseCommand implements Command { 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 @@ public abstract class BaseCommand implements Command { 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 @@ public abstract class BaseCommand implements Command { 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 @@ public abstract class BaseCommand implements Command { try { onExit(rc); } finally { - SshScopes.current.set(null); + SshScope.set(old); thisThread.setName(thisName); } } @@ -432,9 +452,7 @@ public abstract class BaseCommand implements Command { @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 cbe0d59f8b..fbe2373d03 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 @@ class CommandFactoryProvider implements Provider { 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 @@ class CommandFactoryProvider implements Provider { } 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 @@ class CommandFactoryProvider implements Provider { }); cmd.start(env); } finally { - SshScopes.current.set(old); + SshScope.set(old); } } } @@ -150,15 +146,14 @@ class CommandFactoryProvider implements Provider { 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 d1b6936b45..5d5572a724 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.mina.core.future.IoFutureListener; 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 @@ import org.apache.sshd.server.session.ServerSession; 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() { @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 @@ class DatabasePasswordAuth implements PasswordAuthenticator { 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() { + @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 7dcad4be96..9fd136cafe 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.mina.core.future.IoFutureListener; 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 @@ import java.security.PublicKey; 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 keyList = sshKeyCache.get(username); final SshKeyCacheEntry key = find(keyList, suppliedKey); if (key == null) { @@ -57,7 +62,8 @@ class DatabasePubKeyAuth implements PublickeyAuthenticator { } 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 @@ class DatabasePubKeyAuth implements PublickeyAuthenticator { // 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() { @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 @@ class DatabasePubKeyAuth implements PublickeyAuthenticator { 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() { + @Override + public SocketAddress get() { + return sd.getRemoteAddress(); + } + }, key.getAccount()); } private SshKeyCacheEntry find(final Iterable 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 9a0b233474..e2797ef504 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 { - private final IdentifiedUser.RequestFactory factory; +class SshCurrentUserProvider implements Provider { + private final Provider session; @Inject - SshCurrentUserProvider(final IdentifiedUser.RequestFactory f) { - factory = f; + SshCurrentUserProvider(final Provider 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 418150f7b9..d21e6e9ed6 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.ssh.SshInfo; 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.ArrayList; 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 @@ public class SshDaemon extends SshServer implements SshInfo, LifecycleListener { } 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, 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() { @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 0000000000..f3da92c39e --- /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 { + private final Provider session; + + @Inject + SshIdentifiedUserProvider(final Provider 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 8495ad5100..3d1a3b85e5 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.Level; 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 @@ class SshLog implements LifecycleListener { private static final String P_EXEC = "executionTime"; private static final String P_STATUS = "status"; - private final Provider session; - private final Provider user; + private final Provider session; private final AsyncAppender async; @Inject - SshLog(final Provider session, - final Provider user, final SitePaths site) { + SshLog(final Provider session, final SitePaths site) { this.session = session; - this.user = user; final DailyRollingFileAppender dst = new DailyRollingFileAppender(); dst.setName(LOG_NAME); @@ -95,20 +89,16 @@ class SshLog implements LifecycleListener { } 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 @@ class SshLog implements LifecycleListener { 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 @@ class SshLog implements LifecycleListener { } 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 @@ class SshLog implements LifecycleListener { 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 9bd1b03815..84e8c6146f 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.CmdLineParser; 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 @@ public class SshModule extends FactoryModule { @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 @@ public class SshModule extends FactoryModule { }); } - private void configureSessionScope() { - bind(ServerSession.class).toProvider(new Provider() { - @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() { - @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 0000000000..29ede85ddd --- /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 { + private final Provider session; + + @Inject + SshRemotePeerProvider(final Provider s) { + session = s; + } + + @Override + public SocketAddress get() { + return session.get().getRemoteAddress(); + } +} diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshScopes.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshScope.java similarity index 50% rename from gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshScopes.java rename to gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshScope.java index 07b13c7868..b980c18dbb 100644 --- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshScopes.java +++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshScope.java @@ -19,89 +19,74 @@ 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 { +class SshScope { static class Context { - final ServerSession session; - final Map, Object> map; + private final SshSession session; + private final Map, Object> map; + final long created; volatile long started; volatile long finished; - Context(final ServerSession s) { + Context(final SshSession s) { session = s; map = new HashMap, Object>(); created = System.currentTimeMillis(); started = created; } + + synchronized T get(Key key, Provider creator) { + @SuppressWarnings("unchecked") + T t = (T) map.get(key); + if (t == null) { + t = creator.get(); + map.put(key, t); + } + return t; + } } - static final AttributeKey, Object>> sessionMap = - new AttributeKey, Object>>(); + static class ContextProvider implements Provider { + @Override + public Context get() { + return getContext(); + } + } - static final ThreadLocal current = new ThreadLocal(); + static class SshSessionProvider implements Provider { + @Override + public SshSession get() { + return getContext().session; + } + } - static Context getContext() { + private static final ThreadLocal current = + new ThreadLocal(); + + private static Context getContext() { final Context ctx = current.get(); if (ctx == null) { - throw new OutOfScopeException( - "Cannot access scoped object; not in request/command."); + throw new OutOfScopeException("Not in command/request"); } return ctx; } - /** Returns exactly one instance for the scope of the SSH connection. */ - static final Scope SESSION = new Scope() { - public Provider scope(final Key key, final Provider creator) { - return new Provider() { - public T get() { - final Context ctx = getContext(); - final Map, 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"; - } - }; + 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 Provider scope(final Key key, final Provider creator) { return new Provider() { public T get() { - final Map, 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; - } + return getContext().get(key, creator); } @Override @@ -117,6 +102,6 @@ class SshScopes { } }; - private SshScopes() { + private SshScope() { } } 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 0000000000..b79db521aa --- /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 KEY = + new AttributeKey(); + + 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 426030a1ae..65cfcdbb3f 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 @@ import java.security.spec.InvalidKeySpecException; /** Utilities to support SSH operations. */ public class SshUtil { - /** Server session attribute holding the {@link Account.Id}. */ - public static final AttributeKey CURRENT_ACCOUNT = - new AttributeKey(); - - /** Server session attribute holding the remote {@link SocketAddress}. */ - public static final AttributeKey REMOTE_PEER = - new AttributeKey(); - - /** Server session attribute holding a unique session id. */ - public static final AttributeKey SESSION_ID = - new AttributeKey(); - - /** Username the last authentication tried to perform as. */ - static final AttributeKey AUTH_ATTEMPTED_AS = - new AttributeKey(); - - /** Error message from last authentication attempt. */ - static final AttributeKey AUTH_ERROR = new AttributeKey(); - /** * 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 ff1b57a7c5..b6c6119e74 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 @@ final class AdminShowConnections extends BaseCommand { 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 @@ final class AdminShowConnections extends BaseCommand { 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 @@ final class AdminShowConnections extends BaseCommand { 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 ""; } }