Skip to content

Commit

Permalink
Added authentication to Bolt
Browse files Browse the repository at this point in the history
  • Loading branch information
pontusmelke committed Feb 21, 2016
1 parent 4cea1e9 commit 3dfc0d6
Show file tree
Hide file tree
Showing 39 changed files with 834 additions and 132 deletions.
Expand Up @@ -86,7 +86,8 @@ public class BoltKernelExtension extends KernelExtensionFactory<BoltKernelExtens
{
public static class Settings
{
public static final Function<ConfigValues,List<Configuration>> connector_group = Config.groups( "dbms.connector" );
public static final Function<ConfigValues,List<Configuration>> connector_group =
Config.groups( "dbms.connector" );

@Description( "Enable Neo4j Bolt" )
public static final Setting<Boolean> enabled = setting( "enabled", BOOLEAN, "false" );
Expand Down Expand Up @@ -154,7 +155,9 @@ public boolean equals( Object obj )

public enum EncryptionLevel
{
REQUIRED, OPTIONAL, DISABLED
REQUIRED,
OPTIONAL,
DISABLED
}

public interface Dependencies
Expand Down Expand Up @@ -194,14 +197,14 @@ public Lifecycle newInstance( KernelContext context, Dependencies dependencies )

Sessions sessions =
new MonitoredSessions( dependencies.monitors(),
new ThreadedSessions(
life.add( new StandardSessions( api, dependencies.usageData(), logging ) ),
scheduler, logging ), Clock.systemUTC() );
new ThreadedSessions(
life.add( new StandardSessions( api, dependencies.usageData(), logging ) ),
scheduler, logging ), Clock.systemUTC() );

List<NettyServer.ProtocolInitializer> connectors = new ArrayList<>();

List<Configuration> view = config.view( Settings.connector_group );
for( Configuration connector: view )
for ( Configuration connector : view )
{
final HostnamePort socketAddress = connector.get( Settings.socket_address );

Expand All @@ -218,7 +221,8 @@ public Lifecycle newInstance( KernelContext context, Dependencies dependencies )
// no break here
case OPTIONAL:
KeyStoreInformation keyStore = createKeyStore( config, log );
sslCtx = SslContextBuilder.forServer( keyStore.getCertificatePath(), keyStore.getPrivateKeyPath() ).build();
sslCtx = SslContextBuilder.forServer( keyStore.getCertificatePath(), keyStore.getPrivateKeyPath() )
.build();
break;
default:
// case DISABLED:
Expand All @@ -228,11 +232,11 @@ public Lifecycle newInstance( KernelContext context, Dependencies dependencies )

connectors.add( new SocketTransport( socketAddress, sslCtx, logging.getInternalLogProvider(),
newVersions( logging,
requireEncryption ? new EncryptionRequiredSessions( sessions ) : sessions, dependencies.authManager() ) ) );
requireEncryption ? new EncryptionRequiredSessions( sessions ) : sessions) ) );
}
}

if( connectors.size() > 0 )
if ( connectors.size() > 0 )
{
life.add( new NettyServer( scheduler.threadFactory( boltNetworkIO ), connectors ) );
log.info( "Bolt Server extension loaded." );
Expand All @@ -242,12 +246,14 @@ public Lifecycle newInstance( KernelContext context, Dependencies dependencies )
}

private PrimitiveLongObjectMap<BiFunction<Channel,Boolean,BoltProtocol>> newVersions( LogService logging,
Sessions sessions, AuthManager authManager )
Sessions sessions )
{
PrimitiveLongObjectMap<BiFunction<Channel,Boolean,BoltProtocol>> availableVersions = longObjectMap();


availableVersions.put(
BoltProtocolV1.VERSION,
( channel, isEncrypted ) -> new BoltProtocolV1( logging, sessions.newSession( isEncrypted ), channel, authManager )
( channel, isEncrypted ) -> new BoltProtocolV1( logging, sessions.newSession( isEncrypted ), channel )
);
return availableVersions;
}
Expand Down
@@ -0,0 +1,59 @@
/*
* Copyright (c) 2002-2016 "Neo Technology,"
* Network Engine for Objects in Lund AB [http://neotechnology.com]
*
* This file is part of Neo4j.
*
* Neo4j is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.neo4j.bolt.security.auth;

import java.util.Map;

/**
* Authenticate a given token.
* <p>
* The provided token must contain the following items:
* <ul>
* <li><code>scheme</code>: a string defining the authentication scheme.</li>
* <li><code>principal</code>: The security principal, the format of the value depends on the authentication
* scheme.</li>
* <li><code>credentials</code>: The credentials corresponding to the <code>principal</code>, the format of the
* value depends on the authentication scheme.</li>
* </ul>
* <p>
*
* For updating the credentials the new credentials is supplied with the key <code>new-credentials</code>.
*/
public interface Authentication
{
/**
* Authenticate the provided token.
* @param authToken The token to be authenticated.
* @throws AuthenticationException If authentication failed.
*/
void authenticate( Map<String,Object> authToken ) throws AuthenticationException;

/**
* Allows all tokens to authenticate.
*/
Authentication NONE = authToken -> {
//Do nothing
};

String SCHEME_KEY = "scheme";
String PRINCIPAL = "principal";
String CREDENTIALS = "credentials";
String NEW_CREDENTIALS = "new-credentials";
}
@@ -0,0 +1,53 @@
/*
* Copyright (c) 2002-2016 "Neo Technology,"
* Network Engine for Objects in Lund AB [http://neotechnology.com]
*
* This file is part of Neo4j.
*
* Neo4j is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.neo4j.bolt.security.auth;

import java.io.IOException;

import org.neo4j.kernel.api.exceptions.Status;

public class AuthenticationException extends IOException implements Status.HasStatus
{
private final Status status;

public AuthenticationException( Status status )
{
super( status.code().description() );
this.status = status;
}
public AuthenticationException( Status status, String message )
{
super( message );
this.status = status;
}

public AuthenticationException( Status status, String message, Throwable e )
{
super(message, e);
this.status = status;
}


@Override
public Status status()
{
return status;
}
}
@@ -0,0 +1,110 @@
/*
* Copyright (c) 2002-2016 "Neo Technology,"
* Network Engine for Objects in Lund AB [http://neotechnology.com]
*
* This file is part of Neo4j.
*
* Neo4j is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.neo4j.bolt.security.auth;

import java.io.IOException;
import java.util.Map;

import org.neo4j.kernel.api.exceptions.Status;
import org.neo4j.server.security.auth.AuthManager;

/**
* Performs basic authentication with user name and password.
*/
public class BasicAuthentication implements Authentication
{
private final AuthManager authManager;
private final static String SCHEME = "basic";


public BasicAuthentication( AuthManager authManager )
{
this.authManager = authManager;
}

@Override
public void authenticate( Map<String,Object> authToken ) throws AuthenticationException
{
if ( !SCHEME.equals( authToken.get( SCHEME_KEY ) ) )
{
throw new AuthenticationException( Status.Security.AuthenticationFailed,
"Authorization token must contain: '" + SCHEME_KEY + " : " + SCHEME + "'" );
}

String user = safeCast( PRINCIPAL, authToken );
String password = safeCast( CREDENTIALS, authToken );
if ( authToken.containsKey( NEW_CREDENTIALS ) )
{
update( user, password, safeCast( NEW_CREDENTIALS, authToken ) );
}
else
{
authenticate( user, password );
}
}

private void authenticate( String user, String password ) throws AuthenticationException
{
switch ( authManager.authenticate( user, password ) )
{
case SUCCESS:
break;
case PASSWORD_CHANGE_REQUIRED:
throw new AuthenticationException( Status.Security.CredentialsExpired );
case TOO_MANY_ATTEMPTS:
throw new AuthenticationException( Status.Security.AuthenticationRateLimit );
case FAILURE:
throw new AuthenticationException( Status.Security.AuthenticationFailed );
}
}

private void update( String user, String password, String newPassword ) throws AuthenticationException
{
switch ( authManager.authenticate( user, password ) )
{
case SUCCESS:
case PASSWORD_CHANGE_REQUIRED:
try
{
authManager.setPassword( user, newPassword );
}
catch ( IOException e )
{
throw new AuthenticationException( Status.Security.AuthenticationFailed, e.getMessage(), e );
}
break;
default:
throw new AuthenticationException( Status.Security.AuthenticationFailed );
}
}

private String safeCast( String key, Map<String,Object> authToken ) throws AuthenticationException
{
Object value = authToken.get( key );
if ( value == null || !(value instanceof String) )
{
throw new AuthenticationException( Status.Security.AuthenticationFailed,
"The value associated with the key `" + key + "` must be a String but was: " +
(value == null ? "null" : value.getClass().getSimpleName()));
}

return (String) value;
}
}
Expand Up @@ -40,7 +40,7 @@ public interface MessageHandler<E extends Exception>

void handleIgnoredMessage() throws E;

void handleInitMessage( String clientName ) throws E;
void handleInitMessage( String clientName, Map<String,Object> credentials ) throws E;

void handleResetMessage() throws E;

Expand Down Expand Up @@ -89,7 +89,7 @@ public void handleIgnoredMessage() throws E
}

@Override
public void handleInitMessage( String clientName ) throws E
public void handleInitMessage( String clientName, Map<String,Object> credentials ) throws E
{

}
Expand Down
Expand Up @@ -175,10 +175,11 @@ public void handleIgnoredMessage() throws IOException
}

@Override
public void handleInitMessage( String clientName ) throws IOException
public void handleInitMessage( String clientName, Map<String,Object> credentials ) throws IOException
{
packer.packStructHeader( 1, MessageTypes.MSG_INIT );
packer.pack( clientName );
packer.packRawMap( credentials );
onMessageComplete.onMessageComplete();
}

Expand Down Expand Up @@ -337,7 +338,8 @@ private <E extends Exception> void unpackPullAllMessage( MessageHandler<E> outpu
private <E extends Exception> void unpackInitMessage( MessageHandler<E> output ) throws IOException, E
{
String clientName = unpacker.unpackString();
output.handleInitMessage( clientName );
Map<String,Object> credentials = unpacker.unpackMap();
output.handleInitMessage( clientName, credentials );
}

private <E extends Exception> void unpackResetMessage( MessageHandler<E> output ) throws IOException, E
Expand Down
Expand Up @@ -19,15 +19,19 @@
*/
package org.neo4j.bolt.v1.messaging.message;

import java.util.Map;

import org.neo4j.bolt.v1.messaging.MessageHandler;

public class InitMessage implements Message
{
private final String clientName;
private final Map<String, Object> credentials;

public InitMessage( String clientName )
public InitMessage( String clientName, Map<String,Object> credentials )
{
this.clientName = clientName;
this.credentials = credentials;
}

public String clientName()
Expand All @@ -38,7 +42,7 @@ public String clientName()
@Override
public <E extends Exception> void dispatch( MessageHandler<E> consumer ) throws E
{
consumer.handleInitMessage( clientName );
consumer.handleInitMessage( clientName, credentials );
}

@Override
Expand Down
Expand Up @@ -45,9 +45,9 @@ public static Message run( String statement, Map<String,Object> parameters )
return new RunMessage( statement, parameters );
}

public static Message init( String clientName )
public static Message init( String clientName, Map<String, Object> credentials )
{
return new InitMessage( clientName );
return new InitMessage( clientName, credentials );
}

public static Message pullAll()
Expand Down

0 comments on commit 3dfc0d6

Please sign in to comment.