From a1aa2d77ae62d392b2ec3d27bfc647fa00b5ad8b Mon Sep 17 00:00:00 2001 From: Michael Bayne Date: Sat, 20 Jun 2009 00:56:01 +0000 Subject: [PATCH] Revamping/cleanup of how we handle authentication usernames as well as chained authenticators and chained session factories. We can share a lot more code this way and the implicit requirement that the default authenticator/factory had to be configured before anyone else configured a chanied author/factory is gone. The other big change is that Credentials doesn't require a username (UsernamePasswordCreds inherits that username so most derived classes don't notice any difference). Instead we require that a canonical authentication username be determined and configured in AuthingConnection during the authentication process. This canonical username is then used to resolve the client session and map everything in the client manager. This is pretty much exactly what was going on before except that we were doing it all in an ad hoc way by jamming a new name into Credentials during the authentication process and also doing jiggery pokery in PresentsSession.assignStartingUsername. That all goes away and/or becomes cleaner and more explicit. This is going to impact some Yohoho jiggery pokery, which I will shortly commit a patch for, but we're going to need to test it. Omelets, eggs, etc. git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@5828 542714f4-19e9-0310-aa3c-eee0fc999fb1 --- etc/aslib-config.xml.in | 1 + etc/libs-incl.xml | 1 + .../threerings/bureau/client/BureauClient.as | 11 ++- .../threerings/bureau/data/BureauAuthName.as | 36 ++++++++ .../bureau/data/BureauCredentials.as | 48 +--------- .../threerings/presents/net/Credentials.as | 40 +------- .../threerings/presents/net/ServiceCreds.as | 87 ++++++++++++++++++ .../presents/net/UsernamePasswordCreds.as | 30 ++++-- .../bureau/client/BureauClient.java | 6 +- .../bureau/data/BureauAuthName.java | 40 ++++++++ .../bureau/data/BureauCredentials.java | 59 +----------- .../bureau/server/BureauAuthenticator.java | 53 ----------- .../bureau/server/BureauRegistry.java | 77 ++++++++-------- .../bureau/server/BureauSessionFactory.java | 68 -------------- .../threerings/crowd/server/CrowdServer.java | 2 +- .../threerings/presents/net/Credentials.java | 65 ------------- .../threerings/presents/net/ServiceCreds.java | 75 +++++++++++++++ .../presents/net/UsernamePasswordCreds.java | 35 ++++--- .../presents/peer/data/PeerAuthName.java | 40 ++++++++ .../presents/peer/net/PeerCreds.java | 35 +------ .../peer/server/PeerAuthenticator.java | 71 -------------- .../presents/peer/server/PeerManager.java | 31 ++++--- .../presents/peer/server/PeerSession.java | 3 +- .../peer/server/PeerSessionFactory.java | 64 ------------- .../presents/server/Authenticator.java | 4 + .../presents/server/ChainedAuthenticator.java | 30 +----- .../presents/server/ClientManager.java | 58 +++++++----- .../presents/server/DummyAuthenticator.java | 13 +++ .../presents/server/PresentsSession.java | 17 +--- .../presents/server/ServiceAuthenticator.java | 92 +++++++++++++++++++ .../presents/server/SessionFactory.java | 34 +++++-- .../server/net/AuthingConnection.java | 21 +++++ .../server/net/ConnectionManager.java | 20 +++- .../bureau/server/RegistryTester.java | 2 +- .../threerings/bureau/server/TestServer.java | 9 -- 35 files changed, 622 insertions(+), 656 deletions(-) create mode 100644 src/as/com/threerings/bureau/data/BureauAuthName.as create mode 100644 src/as/com/threerings/presents/net/ServiceCreds.as create mode 100644 src/java/com/threerings/bureau/data/BureauAuthName.java delete mode 100644 src/java/com/threerings/bureau/server/BureauAuthenticator.java delete mode 100644 src/java/com/threerings/bureau/server/BureauSessionFactory.java create mode 100644 src/java/com/threerings/presents/net/ServiceCreds.java create mode 100644 src/java/com/threerings/presents/peer/data/PeerAuthName.java delete mode 100644 src/java/com/threerings/presents/peer/server/PeerAuthenticator.java delete mode 100644 src/java/com/threerings/presents/peer/server/PeerSessionFactory.java create mode 100644 src/java/com/threerings/presents/server/ServiceAuthenticator.java diff --git a/etc/aslib-config.xml.in b/etc/aslib-config.xml.in index 6d27e5f15..ac8b1f584 100644 --- a/etc/aslib-config.xml.in +++ b/etc/aslib-config.xml.in @@ -105,6 +105,7 @@ false + ../dist/lib/corelib.swc diff --git a/etc/libs-incl.xml b/etc/libs-incl.xml index 0e7a282d9..63edc142f 100644 --- a/etc/libs-incl.xml +++ b/etc/libs-incl.xml @@ -19,5 +19,6 @@ + diff --git a/src/as/com/threerings/bureau/client/BureauClient.as b/src/as/com/threerings/bureau/client/BureauClient.as index 29191897b..d174b1bc4 100644 --- a/src/as/com/threerings/bureau/client/BureauClient.as +++ b/src/as/com/threerings/bureau/client/BureauClient.as @@ -22,15 +22,20 @@ package com.threerings.bureau.client { import com.threerings.presents.client.Client; +import com.threerings.presents.dobj.DObjectManager; + +import com.threerings.bureau.data.BureauAuthName; import com.threerings.bureau.data.BureauCredentials; import com.threerings.bureau.util.BureauContext; -import com.threerings.presents.dobj.DObjectManager; /** * Represents a client embedded in a bureau. */ public class BureauClient extends Client { + // statically reference classes we require + BureauAuthName + /** * Creates a new client. * @param creds the credentials supplied during connection @@ -40,9 +45,7 @@ public class BureauClient extends Client { super(null); _bureauId = bureauId; - var creds :BureauCredentials = new BureauCredentials(_bureauId); - creds.sessionToken = token; - _creds = creds; + _creds = new BureauCredentials(_bureauId, token); _ctx = createContext(); _director = createDirector(); } diff --git a/src/as/com/threerings/bureau/data/BureauAuthName.as b/src/as/com/threerings/bureau/data/BureauAuthName.as new file mode 100644 index 000000000..63327f046 --- /dev/null +++ b/src/as/com/threerings/bureau/data/BureauAuthName.as @@ -0,0 +1,36 @@ +// +// $Id$ +// +// Narya library - tools for developing networked games +// Copyright (C) 2002-2009 Three Rings Design, Inc., All Rights Reserved +// http://www.threerings.net/code/narya/ +// +// This library is free software; you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published +// by the Free Software Foundation; either version 2.1 of the License, or +// (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +package com.threerings.bureau.data { + +import com.threerings.util.Name; + +/** + * Represents an authenticated bureau client. + */ +public class BureauAuthName extends Name +{ + public function BureauAuthName (bureauId :String = "") + { + super(bureauId); + } +} +} diff --git a/src/as/com/threerings/bureau/data/BureauCredentials.as b/src/as/com/threerings/bureau/data/BureauCredentials.as index f32f3f1ce..5537a4364 100644 --- a/src/as/com/threerings/bureau/data/BureauCredentials.as +++ b/src/as/com/threerings/bureau/data/BureauCredentials.as @@ -21,59 +21,19 @@ package com.threerings.bureau.data { -import com.threerings.presents.net.Credentials; -import com.threerings.util.Name; -import com.threerings.io.ObjectInputStream; -import com.threerings.io.ObjectOutputStream; -import com.threerings.util.StringBuilder; +import com.threerings.presents.net.ServiceCreds; /** * Extends the basic credentials to provide bureau-specific fields. */ -public class BureauCredentials extends Credentials +public class BureauCredentials extends ServiceCreds { - /** - * The token to pass to the server when logging in. This is usually just passed to the bureau - * on the command line to guard against outside connections being established. - */ - public var sessionToken :String; - - /** - * The id of the bureau logging in. - */ - public var bureauId :String; - /** * Creates new credentials for a specific bureau. */ - public function BureauCredentials (bureauId :String) + public function BureauCredentials (bureauId :String = null, sharedSecret :String = null) { - super(new Name("@@bureau:" + bureauId + "@@")); - this.bureauId = bureauId; - } - - /** @inheritDoc */ - override protected function toStringBuf (buf :StringBuilder) :void - { - super.toStringBuf(buf); - buf.append(" bureauId=").append(bureauId). - append(" token=").append(sessionToken); - } - - // from interface Streamable - override public function readObject (ins :ObjectInputStream) :void - { - super.readObject(ins); - sessionToken = (ins.readField(String) as String); - bureauId = (ins.readField(String) as String); - } - - // from interface Streamable - override public function writeObject (out :ObjectOutputStream) :void - { - super.writeObject(out); - out.writeField(sessionToken); - out.writeField(bureauId); + super(bureauId, sharedSecret); } } } diff --git a/src/as/com/threerings/presents/net/Credentials.as b/src/as/com/threerings/presents/net/Credentials.as index 9884b5113..ad25b61d4 100644 --- a/src/as/com/threerings/presents/net/Credentials.as +++ b/src/as/com/threerings/presents/net/Credentials.as @@ -21,9 +21,6 @@ package com.threerings.presents.net { -import com.threerings.util.Name; -import com.threerings.util.StringBuilder; - import com.threerings.io.ObjectInputStream; import com.threerings.io.ObjectOutputStream; import com.threerings.io.Streamable; @@ -31,49 +28,22 @@ import com.threerings.io.Streamable; public /* abstract */ class Credentials implements Streamable { - public function Credentials (username :Name) + public function Credentials () { - _username = username; } - public function getUsername () :Name - { - return _username; - } - - public function setUsername (name :Name) :void - { - _username = name; - } - - // documentation inherited from interface Streamable + // from interface Streamable public function writeObject (out :ObjectOutputStream) :void //throws IOError { - out.writeObject(_username); + // nada } - // documentation inherited from interface Streamable + // from interface Streamable public function readObject (ins :ObjectInputStream) :void //throws IOError { - _username = Name(ins.readObject()); + // nada } - - public function toString () :String - { - var buf :StringBuilder = new StringBuilder("["); - toStringBuf(buf); - buf.append("]"); - return buf.toString(); - } - - protected function toStringBuf (buf :StringBuilder) :void - { - buf.append("username=", _username); - } - - /** The username. */ - protected var _username :Name; } } diff --git a/src/as/com/threerings/presents/net/ServiceCreds.as b/src/as/com/threerings/presents/net/ServiceCreds.as new file mode 100644 index 000000000..9c2e35afe --- /dev/null +++ b/src/as/com/threerings/presents/net/ServiceCreds.as @@ -0,0 +1,87 @@ +// +// $Id$ +// +// Narya library - tools for developing networked games +// Copyright (C) 2002-2009 Three Rings Design, Inc., All Rights Reserved +// http://www.threerings.net/code/narya/ +// +// This library is free software; you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published +// by the Free Software Foundation; either version 2.1 of the License, or +// (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +package com.threerings.presents.net { + +import com.adobe.crypto.MD5; + +import com.threerings.io.ObjectInputStream; +import com.threerings.io.ObjectOutputStream; + +/** + * Extends the basic credentials to provide bureau-specific fields. + */ +public class ServiceCreds extends Credentials +{ + /** The id of the service client that is authenticating. */ + public var clientId :String; + + /** + * Creates credentials for the specified client. + */ + public function ServiceCreds (clientId :String = null, sharedSecret :String = null) + { + this.clientId = clientId; + _authToken = createAuthToken(clientId, sharedSecret); + } + + /** + * Validates that these credentials were created with the supplied shared secret. + */ + public function areValid (sharedSecret :String) :Boolean + { + return createAuthToken(clientId, sharedSecret) == _authToken; + } + + // from interface Streamable + override public function readObject (ins :ObjectInputStream) :void + { + super.readObject(ins); + clientId = (ins.readField(String) as String); + _authToken = (ins.readField(String) as String); + } + + // from interface Streamable + override public function writeObject (out :ObjectOutputStream) :void + { + super.writeObject(out); + out.writeField(clientId); + out.writeField(_authToken); + } + + // from Object + public function toString () :String + { + return "[id=" + clientId + ", token=" + _authToken + "]"; + } + + /** + * Creates a unique password for the specified node using the supplied shared secret. + */ + protected static function createAuthToken (clientId :String, sharedSecret :String) :String + { + return MD5.hash(clientId + sharedSecret); + } + + /** A token created by a call to {@link #createAuthToken}. */ + protected var _authToken :String; +} +} diff --git a/src/as/com/threerings/presents/net/UsernamePasswordCreds.as b/src/as/com/threerings/presents/net/UsernamePasswordCreds.as index 6643a7850..e16243d47 100644 --- a/src/as/com/threerings/presents/net/UsernamePasswordCreds.as +++ b/src/as/com/threerings/presents/net/UsernamePasswordCreds.as @@ -32,36 +32,52 @@ public class UsernamePasswordCreds extends Credentials /** * Construct credentials with the supplied username and password. */ - public function UsernamePasswordCreds (username :Name, password :String) + public function UsernamePasswordCreds (username :Name = null, password :String = null) { - super(username); + _username = username; _password = password; } + public function getUsername () :Name + { + return _username; + } + public function getPassword () :String { return _password; } + // from Credentials override public function writeObject (out :ObjectOutputStream) :void { - super.writeObject(out); + out.writeObject(_username); out.writeField(_password); } + // from Credentials override public function readObject (ins :ObjectInputStream) :void { - super.readObject(ins); + _username = Name(ins.readObject()); _password = (ins.readField(String) as String); } - // documentation inherited - override protected function toStringBuf (buf :StringBuilder) :void + // from Object + public function toString () :String { - super.toStringBuf(buf); + var buf :StringBuilder = new StringBuilder("["); + toStringBuf(buf); + buf.append("]"); + return buf.toString(); + } + + protected function toStringBuf (buf :StringBuilder) :void + { + buf.append("username=", _username); buf.append(", password=", _password); } + protected var _username :Name; protected var _password :String; } } diff --git a/src/java/com/threerings/bureau/client/BureauClient.java b/src/java/com/threerings/bureau/client/BureauClient.java index 343803ef7..2c4742022 100644 --- a/src/java/com/threerings/bureau/client/BureauClient.java +++ b/src/java/com/threerings/bureau/client/BureauClient.java @@ -39,13 +39,11 @@ public abstract class BureauClient extends Client * Creates a new client. * @param runQueue the place to post tasks required by clients */ - public BureauClient (String token, String bureauId, RunQueue runQueue) + public BureauClient (String bureauId, String sharedSecret, RunQueue runQueue) { super(null, runQueue); _bureauId = bureauId; - BureauCredentials creds = new BureauCredentials(_bureauId); - creds.sessionToken = token; - _creds = creds; + _creds = new BureauCredentials(_bureauId, sharedSecret); _ctx = createContext(); _director = createDirector(); } diff --git a/src/java/com/threerings/bureau/data/BureauAuthName.java b/src/java/com/threerings/bureau/data/BureauAuthName.java new file mode 100644 index 000000000..41e6c058f --- /dev/null +++ b/src/java/com/threerings/bureau/data/BureauAuthName.java @@ -0,0 +1,40 @@ +// +// $Id$ +// +// Narya library - tools for developing networked games +// Copyright (C) 2002-2008 Three Rings Design, Inc., All Rights Reserved +// http://www.threerings.net/code/narya/ +// +// This library is free software; you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published +// by the Free Software Foundation; either version 2.1 of the License, or +// (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +package com.threerings.bureau.data; + +import com.threerings.util.Name; + +/** + * Represents an authenticated bureau client. + */ +public class BureauAuthName extends Name +{ + public BureauAuthName (String bureauId) + { + super(bureauId); + } + + // used when unserializing + public BureauAuthName () + { + } +} diff --git a/src/java/com/threerings/bureau/data/BureauCredentials.java b/src/java/com/threerings/bureau/data/BureauCredentials.java index 9e936aeb0..218e68bdd 100644 --- a/src/java/com/threerings/bureau/data/BureauCredentials.java +++ b/src/java/com/threerings/bureau/data/BureauCredentials.java @@ -21,51 +21,19 @@ package com.threerings.bureau.data; -import com.threerings.util.Name; - -import com.threerings.presents.net.Credentials; +import com.threerings.presents.net.ServiceCreds; /** * Extends the basic credentials to provide bureau-specific fields. */ -public class BureauCredentials extends Credentials +public class BureauCredentials extends ServiceCreds { - public static final String PREFIX = "@@bureau:"; - public static final String SUFFIX = "@@"; - /** - * The token to pass to the server when logging in. This is usually just passed to the bureau - * on the command line to guard against outside connections being established. + * Creates new credentials for a specific bureau. */ - public String sessionToken; - - /** - * The id of the bureau logging in. - */ - public String bureauId; - - /** - * Test if a given name object matches the name that we generate. - */ - public static boolean isBureau (Name name) + public BureauCredentials (String bureauId, String sharedSecret) { - String normal = name.getNormal(); - return normal.startsWith(PREFIX) && normal.endsWith(SUFFIX); - } - - /** - * Extract the buerauId from the name that we generate. - */ - public static String extractBureauId (Name name) - { - String normal = name.getNormal(); - int prefixPos = normal.indexOf(PREFIX); - int suffixPos = normal.lastIndexOf(SUFFIX); - if (prefixPos != 0 || suffixPos != normal.length() - SUFFIX.length()) { - return null; - } - - return normal.substring(prefixPos + PREFIX.length(), suffixPos); + super(bureauId, sharedSecret); } /** @@ -74,21 +42,4 @@ public class BureauCredentials extends Credentials public BureauCredentials () { } - - /** - * Creates new credentials for a specific bureau. - */ - public BureauCredentials (String bureauId) - { - super(new Name("@@bureau:" + bureauId + "@@")); - this.bureauId = bureauId; - } - - @Override // inherit documentation - protected void toString (StringBuilder buf) - { - super.toString(buf); - buf.append(" id=").append(bureauId). - append(" token=").append(sessionToken); - } } diff --git a/src/java/com/threerings/bureau/server/BureauAuthenticator.java b/src/java/com/threerings/bureau/server/BureauAuthenticator.java deleted file mode 100644 index fab9e8f6b..000000000 --- a/src/java/com/threerings/bureau/server/BureauAuthenticator.java +++ /dev/null @@ -1,53 +0,0 @@ -package com.threerings.bureau.server; - -import com.threerings.presents.data.AuthCodes; -import com.threerings.presents.net.AuthRequest; -import com.threerings.presents.net.AuthResponse; -import com.threerings.presents.net.AuthResponseData; -import com.threerings.presents.server.ChainedAuthenticator; -import com.threerings.presents.server.net.AuthingConnection; - -import com.threerings.bureau.data.BureauCredentials; - -import static com.threerings.bureau.Log.log; - -/** - * Authenticates bureaus only. - */ -public class BureauAuthenticator extends ChainedAuthenticator -{ - /** - * Creates a new bureau authenticator. - */ - public BureauAuthenticator (BureauRegistry registry) - { - _registry = registry; - } - - @Override // from abstract ChainedAuthenticator - protected boolean shouldHandleConnection (AuthingConnection conn) - { - return (conn.getAuthRequest().getCredentials() instanceof BureauCredentials); - } - - @Override // from Authenticator - protected void processAuthentication ( - AuthingConnection conn, - AuthResponse rsp) - { - AuthRequest req = conn.getAuthRequest(); - BureauCredentials creds = (BureauCredentials)req.getCredentials(); - String problem = _registry.checkToken(creds); - - if (problem == null) { - rsp.getData().code = AuthResponseData.SUCCESS; - - } else { - log.warning("Received invalid bureau auth request", - "creds", creds + "], problem: " + problem); - rsp.getData().code = AuthCodes.SERVER_ERROR; - } - } - - protected BureauRegistry _registry; -} diff --git a/src/java/com/threerings/bureau/server/BureauRegistry.java b/src/java/com/threerings/bureau/server/BureauRegistry.java index 5c987a325..fcb5acd93 100644 --- a/src/java/com/threerings/bureau/server/BureauRegistry.java +++ b/src/java/com/threerings/bureau/server/BureauRegistry.java @@ -42,8 +42,12 @@ import com.threerings.presents.dobj.RootDObjectManager; import com.threerings.presents.server.ClientManager; import com.threerings.presents.server.InvocationManager; import com.threerings.presents.server.PresentsSession; +import com.threerings.presents.server.ServiceAuthenticator; +import com.threerings.presents.server.SessionFactory; +import com.threerings.presents.server.net.ConnectionManager; import com.threerings.bureau.data.AgentObject; +import com.threerings.bureau.data.BureauAuthName; import com.threerings.bureau.data.BureauCodes; import com.threerings.bureau.data.BureauCredentials; import com.threerings.bureau.util.BureauLogRedirector; @@ -95,7 +99,8 @@ public class BureauRegistry /** * Creates an uninitialized registry. */ - @Inject public BureauRegistry (InvocationManager invmgr) + @Inject public BureauRegistry ( + InvocationManager invmgr, ConnectionManager conmgr, ClientManager clmgr) { invmgr.registerDispatcher(new BureauDispatcher(new BureauProvider() { public void bureauInitialized (ClientObject client, String bureauId) { @@ -114,59 +119,44 @@ public class BureauRegistry BureauRegistry.this.agentDestroyed(client, agentId); } }), BureauCodes.BUREAU_GROUP); - } - - /** - * Provides the Bureau registry with necessary runtime configuration. Inserts the bureau - * client factory into the client manager and registers observers to track the progress - * of launched bureaus. - * @see BureauSessionFactory - */ - public void init () - { - _clmgr.addClientObserver(new ClientManager.ClientObserver() { + conmgr.addChainedAuthenticator(new ServiceAuthenticator( + BureauCredentials.class, BureauAuthName.class) { + protected boolean areValid (BureauCredentials creds) { + return checkToken(creds) == null; + } + }); + clmgr.addSessionFactory( + SessionFactory.newSessionFactory(BureauCredentials.class, getSessionClass(), + BureauAuthName.class, getClientResolverClass())); + clmgr.addClientObserver(new ClientManager.ClientObserver() { public void clientSessionDidStart (PresentsSession client) { - String id = BureauCredentials.extractBureauId(client.getUsername()); - if (id != null) { - sessionDidStart(client, id); + if (client.getCredentials() instanceof BureauCredentials) { + sessionDidStart(client, ((BureauCredentials)client.getCredentials()).clientId); } } public void clientSessionDidEnd (PresentsSession client) { - String id = BureauCredentials.extractBureauId(client.getUsername()); - if (id != null) { - sessionDidEnd(client, id); + if (client.getCredentials() instanceof BureauCredentials) { + sessionDidEnd(client, ((BureauCredentials)client.getCredentials()).clientId); } } }); } - /** - * Adds the delegating client factory. Note this should be called after all application - * client factories are in place. The installed factory will simply create presents clients and - * client objects for bureaus. - */ - public void setDefaultSessionFactory () - { - // add the client factory, but later, after all the other modules have been initialized - _clmgr.setSessionFactory(new BureauSessionFactory(_clmgr.getSessionFactory())); - } - /** * Check the credentials to make sure this is one of our bureaus. * @return null if all's well, otherwise a string describing the authentication failure */ public String checkToken (BureauCredentials creds) { - Bureau bureau = _bureaus.get(creds.bureauId); + Bureau bureau = _bureaus.get(creds.clientId); if (bureau == null) { - return "Bureau " + creds.bureauId + " not found"; + return "Bureau " + creds.clientId + " not found"; } if (bureau.clientObj != null) { - return "Bureau " + creds.bureauId + " already logged in"; + return "Bureau " + creds.clientId + " already logged in"; } - if (!bureau.token.equals(creds.sessionToken)) { - return "Bureau " + creds.bureauId + - " does not match credentials token"; + if (!creds.areValid(bureau.token)) { + return "Bureau " + creds.clientId + " does not match credentials token"; } return null; } @@ -617,6 +607,22 @@ public class BureauRegistry _bureaus.remove(bureau.bureauId); } + /** + * Returns the class used to handle bureau sessions. + */ + protected Class getSessionClass () + { + return BureauSession.class; + } + + /** + * Returns the class used to resolve bureau client data. + */ + protected Class getClientResolverClass () + { + return BureauClientResolver.class; + } + /** * Invoker unit to launch a bureau's process, then assign the result on the main thread. */ @@ -795,5 +801,4 @@ public class BureauRegistry @Inject protected RootDObjectManager _omgr; @Inject protected @MainInvoker Invoker _invoker; - @Inject protected ClientManager _clmgr; } diff --git a/src/java/com/threerings/bureau/server/BureauSessionFactory.java b/src/java/com/threerings/bureau/server/BureauSessionFactory.java deleted file mode 100644 index e41bc7bb6..000000000 --- a/src/java/com/threerings/bureau/server/BureauSessionFactory.java +++ /dev/null @@ -1,68 +0,0 @@ -// -// $Id$ -// -// Narya library - tools for developing networked games -// Copyright (C) 2002-2007 Three Rings Design, Inc., All Rights Reserved -// http://www.threerings.net/code/narya/ -// -// This library is free software; you can redistribute it and/or modify it -// under the terms of the GNU Lesser General Public License as published -// by the Free Software Foundation; either version 2.1 of the License, or -// (at your option) any later version. -// -// This library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public -// License along with this library; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - -package com.threerings.bureau.server; - -import com.threerings.util.Name; - -import com.threerings.presents.net.AuthRequest; -import com.threerings.presents.server.SessionFactory; -import com.threerings.presents.server.ClientResolver; -import com.threerings.presents.server.PresentsSession; - -import com.threerings.bureau.data.BureauCredentials; - -/** - * Handles resolution of bureaus and passes non-bureau resolution requests through to a normal - * factory. For bureaus, creates base class instances {@link PresentsSession} and - * {@link ClientResolver}. - * @see BureauRegistry#setDefaultSessionFactory() - */ -public class BureauSessionFactory implements SessionFactory -{ - public BureauSessionFactory (SessionFactory delegate) - { - _delegate = delegate; - } - - // from interface SessionFactory - public Class getSessionClass (AuthRequest areq) - { - // Just give bureaus a vanilla PresentsSession client for now. - if (areq.getCredentials() instanceof BureauCredentials) { - return BureauSession.class; - } else { - return _delegate.getSessionClass(areq); - } - } - - // from interface SessionFactory - public Class getClientResolverClass (Name username) - { - if (BureauCredentials.isBureau(username)) { - return BureauClientResolver.class; - } else { - return _delegate.getClientResolverClass(username); - } - } - - protected SessionFactory _delegate; -} diff --git a/src/java/com/threerings/crowd/server/CrowdServer.java b/src/java/com/threerings/crowd/server/CrowdServer.java index 95f35d60e..c28e040d3 100644 --- a/src/java/com/threerings/crowd/server/CrowdServer.java +++ b/src/java/com/threerings/crowd/server/CrowdServer.java @@ -63,7 +63,7 @@ public class CrowdServer extends PresentsServer super.init(injector); // configure the client manager to use our bits - _clmgr.setSessionFactory(new SessionFactory() { + _clmgr.setDefaultSessionFactory(new SessionFactory() { public Class getSessionClass (AuthRequest areq) { return CrowdSession.class; } diff --git a/src/java/com/threerings/presents/net/Credentials.java b/src/java/com/threerings/presents/net/Credentials.java index af87bdaf8..076d2ac12 100644 --- a/src/java/com/threerings/presents/net/Credentials.java +++ b/src/java/com/threerings/presents/net/Credentials.java @@ -23,47 +23,17 @@ package com.threerings.presents.net; import com.threerings.io.Streamable; -import com.threerings.util.Name; - /** * Credentials are supplied by the client implementation and sent along to the server during the * authentication process. To provide support for a variety of authentication methods, the * credentials class is meant to be subclassed for the particular method (ie. password, auth * digest, etc.) in use in a given system. * - *

All credentials must provide a username as the username is used to associate network - * connections with sessions. - * *

All derived classes should provide a no argument constructor so that they can be * instantiated prior to reconstruction from a data input stream. */ public abstract class Credentials implements Streamable { - /** - * Constructs a credentials instance with the specified username. - */ - public Credentials (Name username) - { - _username = username; - } - - /** - * Constructs a blank credentials instance in preparation for unserializing from the network. - */ - public Credentials () - { - } - - public Name getUsername () - { - return _username; - } - - public void setUsername (Name name) - { - _username = name; - } - /** * Returns a string to use in a hash on the datagram contents to authenticate client datagrams. */ @@ -71,39 +41,4 @@ public abstract class Credentials implements Streamable { return ""; } - - @Override - public int hashCode () - { - return _username.hashCode(); - } - - @Override - public boolean equals (Object other) - { - if (other instanceof Credentials) { - return _username.equals(((Credentials)other)._username); - } else { - return false; - } - } - - @Override - public String toString () - { - StringBuilder buf = new StringBuilder("["); - toString(buf); - return buf.append("]").toString(); - } - - /** - * An easily extensible method via which derived classes can add to {@link #toString()}'s - * output. - */ - protected void toString (StringBuilder buf) - { - buf.append("username=").append(_username); - } - - protected Name _username; } diff --git a/src/java/com/threerings/presents/net/ServiceCreds.java b/src/java/com/threerings/presents/net/ServiceCreds.java new file mode 100644 index 000000000..98aeb93be --- /dev/null +++ b/src/java/com/threerings/presents/net/ServiceCreds.java @@ -0,0 +1,75 @@ +// +// $Id$ +// +// Narya library - tools for developing networked games +// Copyright (C) 2002-2009 Three Rings Design, Inc., All Rights Reserved +// http://www.threerings.net/code/narya/ +// +// This library is free software; you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published +// by the Free Software Foundation; either version 2.1 of the License, or +// (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +package com.threerings.presents.net; + +import com.samskivert.util.StringUtil; + +/** + * Credentials used by service clients (peers, bureaus, etc.). A service would extend this class + * (so that their service clients can be identified by class name). + */ +public abstract class ServiceCreds extends Credentials +{ + /** The id of the service client that is authenticating. */ + public String clientId; + + /** + * Creates credentials for the specified client. + */ + public ServiceCreds (String clientId, String sharedSecret) + { + this.clientId = clientId; + _authToken = createAuthToken(clientId, sharedSecret); + } + + /** + * Used when unserializing an instance from the network. + */ + public ServiceCreds () + { + } + + /** + * Validates that these credentials were created with the supplied shared secret. + */ + public boolean areValid (String sharedSecret) + { + return createAuthToken(clientId, sharedSecret).equals(_authToken); + } + + @Override // from Object + public String toString () + { + return getClass().getSimpleName() + "[id=" + clientId + ", token=" + _authToken + "]"; + } + + /** + * Creates a unique password for the specified node using the supplied shared secret. + */ + protected static String createAuthToken (String clientId, String sharedSecret) + { + return StringUtil.md5hex(clientId + sharedSecret); + } + + /** A token created by a call to {@link #createAuthToken}. */ + protected String _authToken; +} diff --git a/src/java/com/threerings/presents/net/UsernamePasswordCreds.java b/src/java/com/threerings/presents/net/UsernamePasswordCreds.java index 46dcf434f..a1ca84cf8 100644 --- a/src/java/com/threerings/presents/net/UsernamePasswordCreds.java +++ b/src/java/com/threerings/presents/net/UsernamePasswordCreds.java @@ -24,7 +24,7 @@ package com.threerings.presents.net; import com.threerings.util.Name; /** - * Extends the basic credentials with a password. + * Credentials that use a username and (hashed) password. */ public class UsernamePasswordCreds extends Credentials { @@ -33,7 +33,6 @@ public class UsernamePasswordCreds extends Credentials */ public UsernamePasswordCreds () { - super(); } /** @@ -41,10 +40,15 @@ public class UsernamePasswordCreds extends Credentials */ public UsernamePasswordCreds (Name username, String password) { - super(username); + _username = username; _password = password; } + public Name getUsername () + { + return _username; + } + public String getPassword () { return _password; @@ -57,29 +61,22 @@ public class UsernamePasswordCreds extends Credentials } @Override - public int hashCode () + public String toString () { - return super.hashCode() ^ _password.hashCode(); + StringBuilder buf = new StringBuilder("["); + toString(buf); + return buf.append("]").toString(); } - @Override - public boolean equals (Object other) - { - if (other instanceof UsernamePasswordCreds) { - UsernamePasswordCreds upcreds = (UsernamePasswordCreds)other; - return super.equals(other) && - _password.equals(upcreds._password); - } else { - return false; - } - } - - @Override + /** + * An easily extensible method via which derived classes can add to {@link #toString}'s output. + */ protected void toString (StringBuilder buf) { - super.toString(buf); + buf.append("username=").append(_username); buf.append(", password=").append(_password); } + protected Name _username; protected String _password; } diff --git a/src/java/com/threerings/presents/peer/data/PeerAuthName.java b/src/java/com/threerings/presents/peer/data/PeerAuthName.java new file mode 100644 index 000000000..dba8ddf2c --- /dev/null +++ b/src/java/com/threerings/presents/peer/data/PeerAuthName.java @@ -0,0 +1,40 @@ +// +// $Id$ +// +// Narya library - tools for developing networked games +// Copyright (C) 2002-2009 Three Rings Design, Inc., All Rights Reserved +// http://www.threerings.net/code/narya/ +// +// This library is free software; you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published +// by the Free Software Foundation; either version 2.1 of the License, or +// (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +package com.threerings.presents.peer.data; + +import com.threerings.util.Name; + +/** + * Represents an authenticated peer client. + */ +public class PeerAuthName extends Name +{ + public PeerAuthName (String nodeName) + { + super(nodeName); + } + + // used when unserializing + public PeerAuthName () + { + } +} diff --git a/src/java/com/threerings/presents/peer/net/PeerCreds.java b/src/java/com/threerings/presents/peer/net/PeerCreds.java index 3fc16aa88..f0095a0ec 100644 --- a/src/java/com/threerings/presents/peer/net/PeerCreds.java +++ b/src/java/com/threerings/presents/peer/net/PeerCreds.java @@ -21,36 +21,16 @@ package com.threerings.presents.peer.net; -import com.samskivert.util.StringUtil; - -import com.threerings.util.Name; - -import com.threerings.presents.net.UsernamePasswordCreds; +import com.threerings.presents.net.ServiceCreds; /** * Used by peer servers in a cluster installation to authenticate with one another. */ -public class PeerCreds extends UsernamePasswordCreds +public class PeerCreds extends ServiceCreds { - /** A prefix prepended to the node name used as a peer's username to prevent the username from - * colliding with a normal authenticating user's username. We assume that colons are not - * allowed in a normal username. */ - public static final String PEER_PREFIX = "peer:"; - - /** - * Creates a unique password for the specified node using the supplied shared secret. - */ - public static String createPassword (String nodeName, String sharedSecret) - { - return StringUtil.md5hex(nodeName + sharedSecret); - } - - /** - * Creates credentials for the specified peer. - */ public PeerCreds (String nodeName, String sharedSecret) { - super(new Name(PEER_PREFIX + nodeName), createPassword(nodeName, sharedSecret)); + super(nodeName, sharedSecret); } /** @@ -59,13 +39,4 @@ public class PeerCreds extends UsernamePasswordCreds public PeerCreds () { } - - /** - * Returns the node name of this authenticating peer (which does not include the {@link - * #PEER_PREFIX}. - */ - public String getNodeName () - { - return getUsername().toString().substring(PEER_PREFIX.length()); - } } diff --git a/src/java/com/threerings/presents/peer/server/PeerAuthenticator.java b/src/java/com/threerings/presents/peer/server/PeerAuthenticator.java deleted file mode 100644 index fc557a34b..000000000 --- a/src/java/com/threerings/presents/peer/server/PeerAuthenticator.java +++ /dev/null @@ -1,71 +0,0 @@ -// -// $Id$ -// -// Narya library - tools for developing networked games -// Copyright (C) 2002-2007 Three Rings Design, Inc., All Rights Reserved -// http://www.threerings.net/code/narya/ -// -// This library is free software; you can redistribute it and/or modify it -// under the terms of the GNU Lesser General Public License as published -// by the Free Software Foundation; either version 2.1 of the License, or -// (at your option) any later version. -// -// This library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public -// License along with this library; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - -package com.threerings.presents.peer.server; - -import com.samskivert.io.PersistenceException; - -import com.threerings.presents.data.AuthCodes; -import com.threerings.presents.net.AuthRequest; -import com.threerings.presents.net.AuthResponse; -import com.threerings.presents.net.AuthResponseData; -import com.threerings.presents.peer.net.PeerCreds; -import com.threerings.presents.server.ChainedAuthenticator; -import com.threerings.presents.server.net.AuthingConnection; - -import static com.threerings.presents.Log.log; - -/** - * Handles authentication of peer servers and passes non-peer authentication requests through to a - * normal authenticator. - */ -public class PeerAuthenticator extends ChainedAuthenticator -{ - public PeerAuthenticator (PeerManager nodemgr) - { - _peermgr = nodemgr; - } - - @Override // from abstract ChainedAuthenticator - protected boolean shouldHandleConnection (AuthingConnection conn) - { - return (conn.getAuthRequest().getCredentials() instanceof PeerCreds); - } - - @Override // from abstract Authenticator - protected void processAuthentication (AuthingConnection conn, AuthResponse rsp) - throws PersistenceException - { - // here, we are ONLY authenticating peers - AuthRequest req = conn.getAuthRequest(); - PeerCreds pcreds = (PeerCreds) req.getCredentials(); - - if (_peermgr.isAuthenticPeer(pcreds)) { - rsp.getData().code = AuthResponseData.SUCCESS; - - } else { - log.warning("Received invalid peer auth request?", "creds", pcreds); - rsp.getData().code = AuthCodes.SERVER_ERROR; - } - } - - protected PeerManager _peermgr; -} diff --git a/src/java/com/threerings/presents/peer/server/PeerManager.java b/src/java/com/threerings/presents/peer/server/PeerManager.java index 21bc65544..0902d8cbd 100644 --- a/src/java/com/threerings/presents/peer/server/PeerManager.java +++ b/src/java/com/threerings/presents/peer/server/PeerManager.java @@ -69,11 +69,14 @@ import com.threerings.presents.server.InvocationManager; import com.threerings.presents.server.PresentsDObjectMgr; import com.threerings.presents.server.PresentsSession; import com.threerings.presents.server.ReportManager; +import com.threerings.presents.server.ServiceAuthenticator; +import com.threerings.presents.server.SessionFactory; import com.threerings.presents.server.net.ConnectionManager; import com.threerings.presents.peer.client.PeerService; import com.threerings.presents.peer.data.ClientInfo; import com.threerings.presents.peer.data.NodeObject; +import com.threerings.presents.peer.data.PeerAuthName; import com.threerings.presents.peer.net.PeerCreds; import com.threerings.presents.peer.server.persist.NodeRecord; import com.threerings.presents.peer.server.persist.NodeRepository; @@ -234,8 +237,15 @@ public abstract class PeerManager _sharedSecret = sharedSecret; // wire ourselves into the server - _conmgr.addChainedAuthenticator(new PeerAuthenticator(this)); - _clmgr.setSessionFactory(new PeerSessionFactory(_clmgr.getSessionFactory())); + _conmgr.addChainedAuthenticator( + new ServiceAuthenticator(PeerCreds.class, PeerAuthName.class) { + protected boolean areValid (PeerCreds creds) { + return creds.areValid(_sharedSecret); + } + }); + _clmgr.addSessionFactory( + SessionFactory.newSessionFactory(PeerCreds.class, PeerSession.class, + PeerAuthName.class, PeerClientResolver.class)); // create our node object _nodeobj = _omgr.registerObject(createNodeObject()); @@ -269,8 +279,7 @@ public abstract class PeerManager */ public boolean isAuthenticPeer (PeerCreds creds) { - return PeerCreds.createPassword(creds.getNodeName(), _sharedSecret).equals( - creds.getPassword()); + return creds.areValid(_sharedSecret); } /** @@ -855,7 +864,7 @@ public abstract class PeerManager // we scan through the list instead of relying on ClientInfo.getKey() because we want // derived classes to be able to override that for lookups that happen way more frequently // than logging off - Name username = client.getCredentials().getUsername(); + Name username = client.getUsername(); for (ClientInfo clinfo : _nodeobj.clients) { if (clinfo.username.equals(username)) { _nodeobj.startTransaction(); @@ -1015,7 +1024,7 @@ public abstract class PeerManager */ protected void initClientInfo (PresentsSession client, ClientInfo info) { - info.username = client.getCredentials().getUsername(); + info.username = client.getUsername(); } /** @@ -1391,13 +1400,13 @@ public abstract class PeerManager protected Stats _stats = new Stats(); // our service dependencies - @Inject protected ConnectionManager _conmgr; - @Inject protected ClientManager _clmgr; - @Inject protected ReportManager _repmgr; - @Inject protected PresentsDObjectMgr _omgr; - @Inject protected InvocationManager _invmgr; @Inject protected @MainInvoker Invoker _invoker; + @Inject protected ClientManager _clmgr; + @Inject protected ConnectionManager _conmgr; + @Inject protected InvocationManager _invmgr; @Inject protected NodeRepository _noderepo; + @Inject protected PresentsDObjectMgr _omgr; + @Inject protected ReportManager _repmgr; /** We wait this long for peer ratification to complete before acquiring/releasing the lock. */ protected static final long LOCK_TIMEOUT = 5000L; diff --git a/src/java/com/threerings/presents/peer/server/PeerSession.java b/src/java/com/threerings/presents/peer/server/PeerSession.java index 893a69dd2..fccfcaeab 100644 --- a/src/java/com/threerings/presents/peer/server/PeerSession.java +++ b/src/java/com/threerings/presents/peer/server/PeerSession.java @@ -41,8 +41,7 @@ import static com.threerings.presents.Log.log; public class PeerSession extends PresentsSession { /** - * Creates a peer session and provides it with a reference to the peer manager. This is only - * done by the {@link PeerSessionFactory}. + * Creates a peer session and provides it with a reference to the peer manager. */ @Inject public PeerSession (PeerManager peermgr) { diff --git a/src/java/com/threerings/presents/peer/server/PeerSessionFactory.java b/src/java/com/threerings/presents/peer/server/PeerSessionFactory.java deleted file mode 100644 index 540612b25..000000000 --- a/src/java/com/threerings/presents/peer/server/PeerSessionFactory.java +++ /dev/null @@ -1,64 +0,0 @@ -// -// $Id$ -// -// Narya library - tools for developing networked games -// Copyright (C) 2002-2007 Three Rings Design, Inc., All Rights Reserved -// http://www.threerings.net/code/narya/ -// -// This library is free software; you can redistribute it and/or modify it -// under the terms of the GNU Lesser General Public License as published -// by the Free Software Foundation; either version 2.1 of the License, or -// (at your option) any later version. -// -// This library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public -// License along with this library; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - -package com.threerings.presents.peer.server; - -import com.threerings.util.Name; - -import com.threerings.presents.net.AuthRequest; -import com.threerings.presents.peer.net.PeerCreds; -import com.threerings.presents.server.SessionFactory; -import com.threerings.presents.server.ClientResolver; -import com.threerings.presents.server.PresentsSession; - -/** - * Handles resolution of peer servers and passes non-peer resolution requests through to a normal - * factory. - */ -public class PeerSessionFactory implements SessionFactory -{ - public PeerSessionFactory (SessionFactory delegate) - { - _delegate = delegate; - } - - // documentation inherited from interface SessionFactory - public Class getSessionClass (AuthRequest areq) - { - if (areq.getCredentials() instanceof PeerCreds) { - return PeerSession.class; - } else { - return _delegate.getSessionClass(areq); - } - } - - // documentation inherited from interface SessionFactory - public Class getClientResolverClass (Name username) - { - if (username.toString().startsWith(PeerCreds.PEER_PREFIX)) { - return PeerClientResolver.class; - } else { - return _delegate.getClientResolverClass(username); - } - } - - protected SessionFactory _delegate; -} diff --git a/src/java/com/threerings/presents/server/Authenticator.java b/src/java/com/threerings/presents/server/Authenticator.java index f57b0b24b..c5d4208f5 100644 --- a/src/java/com/threerings/presents/server/Authenticator.java +++ b/src/java/com/threerings/presents/server/Authenticator.java @@ -55,6 +55,10 @@ public abstract class Authenticator public boolean invoke () { try { processAuthentication(conn, rsp); + if (AuthResponseData.SUCCESS.equals(rdata.code) && + conn.getAuthName() == null) { // fail early, fail (less) often + throw new IllegalStateException("Authenticator failed to provide authname"); + } } catch (Exception e) { log.warning("Error authenticating user", "areq", req, e); rdata.code = AuthCodes.SERVER_ERROR; diff --git a/src/java/com/threerings/presents/server/ChainedAuthenticator.java b/src/java/com/threerings/presents/server/ChainedAuthenticator.java index bdd455531..61b4271a3 100644 --- a/src/java/com/threerings/presents/server/ChainedAuthenticator.java +++ b/src/java/com/threerings/presents/server/ChainedAuthenticator.java @@ -21,11 +21,7 @@ package com.threerings.presents.server; -import com.samskivert.util.Invoker; -import com.samskivert.util.ResultListener; - import com.threerings.presents.server.net.AuthingConnection; -import com.threerings.presents.server.net.ConnectionManager; /** * Handles certain special kinds of authentications and passes the remainder through to the default @@ -33,33 +29,9 @@ import com.threerings.presents.server.net.ConnectionManager; */ public abstract class ChainedAuthenticator extends Authenticator { - /** - * Called by the {@link ConnectionManager} to initialize our delegate. - */ - public void setChainedAuthenticator (Authenticator author) - { - _delegate = author; - } - - @Override // from Authenticator - public void authenticateConnection (Invoker invoker, AuthingConnection conn, - ResultListener onComplete) - { - // if we handle this sort of authentication, then do so - if (shouldHandleConnection(conn)) { - super.authenticateConnection(invoker, conn, onComplete); - - } else { - // otherwise pass the request on to our delegate - _delegate.authenticateConnection(invoker, conn, onComplete); - } - } - /** * Derived classes should implement this method and return true if the supplied connection is * one that they should authenticate. */ - protected abstract boolean shouldHandleConnection (AuthingConnection conn); - - protected Authenticator _delegate; + public abstract boolean shouldHandleConnection (AuthingConnection conn); } diff --git a/src/java/com/threerings/presents/server/ClientManager.java b/src/java/com/threerings/presents/server/ClientManager.java index fc50b6aec..0c325ca96 100644 --- a/src/java/com/threerings/presents/server/ClientManager.java +++ b/src/java/com/threerings/presents/server/ClientManager.java @@ -117,20 +117,23 @@ public class ClientManager } /** - * Configures the client manager with a factory for creating {@link PresentsSession} and {@link - * ClientResolver} classes for authenticated client connections. + * Configures the default factory for creating {@link PresentsSession} and {@link + * ClientResolver} classes for authenticated client connections. All factories added via {@link + * #addSessionFactory} will be offered a chance to handle sessions before this factory of last + * resort. */ - public void setSessionFactory (SessionFactory factory) + public void setDefaultSessionFactory (SessionFactory factory) { - _factory = factory; + _factories.set(_factories.size()-1, factory); } /** - * Returns the {@link SessionFactory} currently in use. + * Adds a session factory to the chain. This factory will be offered a chance to resolve + * sessions before passing the buck to the next factory in the chain. */ - public SessionFactory getSessionFactory () + public void addSessionFactory (SessionFactory factory) { - return _factory; + _factories.add(0, factory); } /** @@ -271,10 +274,18 @@ public class ClientManager return; } + // figure out our client resolver class + Class resolverClass = null; + for (SessionFactory factory : _factories) { + if ((resolverClass = factory.getClientResolverClass(username)) != null) { + break; + } + } + try { // create a client resolver instance which will create our client object, populate it // and notify the listeners - clr = _injector.getInstance(_factory.getClientResolverClass(username)); + clr = _injector.getInstance(resolverClass); clr.init(username); clr.addResolutionListener(this); clr.addResolutionListener(listener); @@ -400,28 +411,34 @@ public class ClientManager * Called by the connection manager to let us know when a new connection has been established. */ public synchronized void connectionEstablished ( - Connection conn, AuthRequest req, AuthResponse rsp) + Connection conn, Name authname, AuthRequest req, AuthResponse rsp) { Credentials creds = req.getCredentials(); - Name username = creds.getUsername(); - String type = username.getClass().getSimpleName(); + String type = authname.getClass().getSimpleName(); - // see if a client is already registered with these credentials - PresentsSession client = getClient(username); + // see if a client is already registered with this name + PresentsSession client = getClient(authname); if (client != null) { - log.info("Resuming session", "type", type, "who", username, "conn", conn); + log.info("Resuming session", "type", type, "who", authname, "conn", conn); client.resumeSession(req, conn); } else { - log.info("Session initiated", "type", type, "who", username, "conn", conn); + log.info("Session initiated", "type", type, "who", authname, "conn", conn); + // figure out our session class + Class sessionClass = null; + for (SessionFactory factory : _factories) { + if ((sessionClass = factory.getSessionClass(req)) != null) { + break; + } + } // create a new client and stick'em in the table - client = _injector.getInstance(_factory.getSessionClass(req)); - client.startSession(req, conn, rsp.authdata); + client = _injector.getInstance(sessionClass); + client.startSession(authname, req, conn, rsp.authdata); // map their client instance synchronized (_usermap) { - _usermap.put(username, client); + _usermap.put(authname, client); } } @@ -515,10 +532,9 @@ public class ClientManager protected void clearSession (PresentsSession session) { // remove the client from the username map - Name username = session.getCredentials().getUsername(); PresentsSession rc; synchronized (_usermap) { - rc = _usermap.remove(username); + rc = _usermap.remove(session.getUsername()); } // sanity check just because we can @@ -604,7 +620,7 @@ public class ClientManager protected Map _penders = Maps.newHashMap(); /** Lets us know what sort of client classes to use. */ - protected SessionFactory _factory = SessionFactory.DEFAULT; + protected List _factories = Lists.newArrayList(SessionFactory.DEFAULT); /** Tracks registered {@link ClientObserver}s. */ protected ObserverList _clobservers = ObserverList.newSafeInOrder(); diff --git a/src/java/com/threerings/presents/server/DummyAuthenticator.java b/src/java/com/threerings/presents/server/DummyAuthenticator.java index 9dbe45323..33e62d185 100644 --- a/src/java/com/threerings/presents/server/DummyAuthenticator.java +++ b/src/java/com/threerings/presents/server/DummyAuthenticator.java @@ -23,8 +23,12 @@ package com.threerings.presents.server; import com.samskivert.io.PersistenceException; +import com.threerings.util.Name; + import com.threerings.presents.net.AuthResponse; import com.threerings.presents.net.AuthResponseData; +import com.threerings.presents.net.Credentials; +import com.threerings.presents.net.UsernamePasswordCreds; import com.threerings.presents.server.net.AuthingConnection; import static com.threerings.presents.Log.log; @@ -39,6 +43,15 @@ public class DummyAuthenticator extends Authenticator throws PersistenceException { log.info("Accepting request: " + conn.getAuthRequest()); + + // we need to provide some sort of authentication username + Credentials creds = conn.getAuthRequest().getCredentials(); + if (creds instanceof UsernamePasswordCreds) { + conn.setAuthName(((UsernamePasswordCreds)creds).getUsername()); + } else { + conn.setAuthName(new Name(conn.getInetAddress().getHostAddress())); + } + rsp.getData().code = AuthResponseData.SUCCESS; } } diff --git a/src/java/com/threerings/presents/server/PresentsSession.java b/src/java/com/threerings/presents/server/PresentsSession.java index 411d2d271..3944ae02e 100644 --- a/src/java/com/threerings/presents/server/PresentsSession.java +++ b/src/java/com/threerings/presents/server/PresentsSession.java @@ -465,15 +465,13 @@ public class PresentsSession * Initializes this client instance with the specified username, connection instance and client * object and begins a client session. */ - protected void startSession (AuthRequest req, Connection conn, Object authdata) + protected void startSession (Name authname, AuthRequest req, Connection conn, Object authdata) { + _username = authname; _areq = req; _authdata = authdata; setConnection(conn); - // obtain our starting username - assignStartingUsername(); - // resolve our client object before we get fully underway _clmgr.resolveClientObject(_username, this); @@ -481,17 +479,6 @@ public class PresentsSession _sessionStamp = System.currentTimeMillis(); } - /** - * This is factored out to allow derived classes to use a different starting username than the - * one supplied in the user's credentials. Generally one only wants to munge the starting - * username if the user will subsequently choose a "screen name" and it is desirable to avoid - * collision between the authentication user namespace and the screen namespace. - */ - protected void assignStartingUsername () - { - _username = getCredentials().getUsername(); - } - /** * Called by the client manager when a new connection arrives that authenticates as this * already established client. This must only be called from the congmr thread. diff --git a/src/java/com/threerings/presents/server/ServiceAuthenticator.java b/src/java/com/threerings/presents/server/ServiceAuthenticator.java new file mode 100644 index 000000000..2db28389e --- /dev/null +++ b/src/java/com/threerings/presents/server/ServiceAuthenticator.java @@ -0,0 +1,92 @@ +// +// $Id$ +// +// Narya library - tools for developing networked games +// Copyright (C) 2002-2009 Three Rings Design, Inc., All Rights Reserved +// http://www.threerings.net/code/narya/ +// +// This library is free software; you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published +// by the Free Software Foundation; either version 2.1 of the License, or +// (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +package com.threerings.presents.server; + +import java.lang.reflect.Constructor; + +import com.samskivert.io.PersistenceException; + +import com.threerings.util.Name; + +import com.threerings.presents.data.AuthCodes; +import com.threerings.presents.net.AuthResponse; +import com.threerings.presents.net.AuthResponseData; +import com.threerings.presents.net.ServiceCreds; +import com.threerings.presents.server.net.AuthingConnection; + +import static com.threerings.presents.Log.log; + +/** + * Works in conjunction with {@link ServiceCreds} to handle the authentication of service clients + * (bureaus, peers, etc.). + */ +public abstract class ServiceAuthenticator extends ChainedAuthenticator +{ + /** + * Creates an authenticator that will handle requests using the supplied credentials class and + * which will create instances of the supplied auth name class to identify those clients. Note + * that the auth name class must have a public constructor that takes a single string. + */ + public ServiceAuthenticator (Class credsClass, Class authNameClass) + { + _credsClass = credsClass; + try { + _authNamer = authNameClass.getConstructor(String.class); + } catch (NoSuchMethodException nsme) { + throw new IllegalArgumentException("AuthName must have AuthName(String) constructor."); + } + } + + @Override // from abstract ChainedAuthenticator + public boolean shouldHandleConnection (AuthingConnection conn) + { + return _credsClass.isInstance(conn.getAuthRequest().getCredentials()); + } + + @Override // from abstract Authenticator + protected void processAuthentication (AuthingConnection conn, AuthResponse rsp) + throws PersistenceException + { + T creds = _credsClass.cast(conn.getAuthRequest().getCredentials()); + if (!areValid(creds)) { + log.warning("Received invalid service auth request?", "creds", creds); + rsp.getData().code = AuthCodes.SERVER_ERROR; + return; + } + + try { + conn.setAuthName(_authNamer.newInstance(creds.clientId)); + rsp.getData().code = AuthResponseData.SUCCESS; + } catch (Exception e) { + log.warning("Failed to construct auth name", "namer", _authNamer, e); + rsp.getData().code = AuthCodes.SERVER_ERROR; + } + } + + /** + * Returns true if the creds in question are valid. + */ + protected abstract boolean areValid (T creds); + + protected Class _credsClass; + protected Constructor _authNamer; +} diff --git a/src/java/com/threerings/presents/server/SessionFactory.java b/src/java/com/threerings/presents/server/SessionFactory.java index 407535c6a..f51a690d3 100644 --- a/src/java/com/threerings/presents/server/SessionFactory.java +++ b/src/java/com/threerings/presents/server/SessionFactory.java @@ -24,12 +24,13 @@ package com.threerings.presents.server; import com.threerings.util.Name; import com.threerings.presents.net.AuthRequest; +import com.threerings.presents.net.Credentials; /** * Used to determine what type of {@link PresentsSession} to use to manage an authenticated client * as well the type of {@link ClientResolver} to use when resolving clients' runtime data. */ -public interface SessionFactory +public abstract class SessionFactory { /** The default client factory. */ public static SessionFactory DEFAULT = new SessionFactory() { @@ -42,14 +43,35 @@ public interface SessionFactory }; /** - * Returns the {@link PresentsSession} derived class to use for the session that authenticated - * with the supplied request. + * Creates a session factory that handles clients with the supplied credentials and + * authentication name. */ - Class getSessionClass (AuthRequest areq); + public static SessionFactory newSessionFactory ( + final Class credsClass, + final Class sessionClass, + final Class nameClass, + final Class resolverClass) + { + return new SessionFactory() { + public Class getSessionClass (AuthRequest areq) { + return credsClass.isInstance(areq.getCredentials()) ? sessionClass : null; + } + public Class getClientResolverClass (Name username) { + return nameClass.isInstance(username) ? resolverClass : null; + } + }; + } + + /** + * Returns the {@link PresentsSession} derived class to use for the session that authenticated + * with the supplied request or null if this factory does not handle sessions of the supplied + * type. + */ + public abstract Class getSessionClass (AuthRequest areq); /** * Returns the {@link ClientResolver} derived class to use to resolve a client with the - * specified username. + * specified username or null if this factory does not handle clients of the supplied type. */ - Class getClientResolverClass (Name username); + public abstract Class getClientResolverClass (Name username); } diff --git a/src/java/com/threerings/presents/server/net/AuthingConnection.java b/src/java/com/threerings/presents/server/net/AuthingConnection.java index dccd653e2..260cc60f4 100644 --- a/src/java/com/threerings/presents/server/net/AuthingConnection.java +++ b/src/java/com/threerings/presents/server/net/AuthingConnection.java @@ -21,6 +21,8 @@ package com.threerings.presents.server.net; +import com.threerings.util.Name; + import com.threerings.presents.net.AuthRequest; import com.threerings.presents.net.AuthResponse; import com.threerings.presents.net.Message; @@ -76,6 +78,24 @@ public class AuthingConnection extends Connection _authrsp = authrsp; } + /** + * Returns the username that uniquely identifies this authenticated session. This will be used + * to map Name -> PresentsSession in the ClientManager and used elsewhere. + */ + public Name getAuthName () + { + return _authname; + } + + /** + * During the authentication process, the authenticator must establish the client's + * authentication username and configure it via this method. + */ + public void setAuthName (Name authname) + { + _authname = authname; + } + @Override public String toString () { @@ -84,4 +104,5 @@ public class AuthingConnection extends Connection protected AuthRequest _authreq; protected AuthResponse _authrsp; + protected Name _authname; } diff --git a/src/java/com/threerings/presents/server/net/ConnectionManager.java b/src/java/com/threerings/presents/server/net/ConnectionManager.java index f2a55d504..fadd4fc3b 100644 --- a/src/java/com/threerings/presents/server/net/ConnectionManager.java +++ b/src/java/com/threerings/presents/server/net/ConnectionManager.java @@ -24,6 +24,7 @@ package com.threerings.presents.server.net; import java.net.InetSocketAddress; import java.util.ArrayList; +import java.util.List; import java.util.Map; import java.util.Set; @@ -39,6 +40,7 @@ import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; import java.nio.channels.spi.SelectorProvider; +import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.inject.Inject; import com.google.inject.Singleton; @@ -134,12 +136,11 @@ public class ConnectionManager extends LoopingThread /** * Adds an authenticator to the authentication chain. This authenticator will be offered a - * chance to authenticate incoming connections in lieu of the main autuenticator. + * chance to authenticate incoming connections before falling back to the main authenticator. */ public void addChainedAuthenticator (ChainedAuthenticator author) { - author.setChainedAuthenticator(_author); - _author = author; + _authors.add(author); } /** @@ -266,7 +267,14 @@ public class ConnectionManager extends LoopingThread */ protected void authenticateConnection (AuthingConnection conn) { - _author.authenticateConnection(_authInvoker, conn, new ResultListener() { + Authenticator author = _author; + for (ChainedAuthenticator cauthor : _authors) { + if (cauthor.shouldHandleConnection(conn)) { + author = cauthor; + break; + } + } + author.authenticateConnection(_authInvoker, conn, new ResultListener() { public void requestCompleted (AuthingConnection conn) { _authq.append(conn); } @@ -547,7 +555,8 @@ public class ConnectionManager extends LoopingThread } // and let the client manager know about our new connection - _clmgr.connectionEstablished(rconn, conn.getAuthRequest(), conn.getAuthResponse()); + _clmgr.connectionEstablished(rconn, conn.getAuthName(), conn.getAuthRequest(), + conn.getAuthResponse()); } catch (IOException ioe) { log.warning("Failure upgrading authing connection to running.", ioe); @@ -1187,6 +1196,7 @@ public class ConnectionManager extends LoopingThread * like the PeerManager may replace this authenticator with one that intercepts certain types * of authentication and then passes normal authentications through. */ @Inject(optional=true) protected Authenticator _author = new DummyAuthenticator(); + protected List _authors = Lists.newArrayList(); protected int[] _ports, _datagramPorts; protected String _datagramHostname; diff --git a/tests/src/java/com/threerings/bureau/server/RegistryTester.java b/tests/src/java/com/threerings/bureau/server/RegistryTester.java index 42e737c2a..fdee98d02 100644 --- a/tests/src/java/com/threerings/bureau/server/RegistryTester.java +++ b/tests/src/java/com/threerings/bureau/server/RegistryTester.java @@ -205,7 +205,7 @@ public class RegistryTester return; } - String id = BureauCredentials.extractBureauId(bureau.getUsername()); + String id = ((BureauCredentials)bureau.getCredentials()).clientId; log.info("Killing bureau " + id); bureau.endSession(); diff --git a/tests/src/java/com/threerings/bureau/server/TestServer.java b/tests/src/java/com/threerings/bureau/server/TestServer.java index 1cabc3d59..30ee8879c 100644 --- a/tests/src/java/com/threerings/bureau/server/TestServer.java +++ b/tests/src/java/com/threerings/bureau/server/TestServer.java @@ -68,15 +68,6 @@ public class TestServer extends PresentsServer }; } - @Override // from PresentsServer - public void init (Injector injector) - throws Exception - { - super.init(injector); - _bureauReg.init(); - _bureauReg.setDefaultSessionFactory(); - } - public void setClientTarget (String target) { _bureauReg.setCommandGenerator("test", antCommandGenerator(target));