Skip to content

Commit

Permalink
Make AuthToken an explicit type
Browse files Browse the repository at this point in the history
  • Loading branch information
henriknyman committed Sep 27, 2016
1 parent df20a2c commit d0d9965
Show file tree
Hide file tree
Showing 23 changed files with 291 additions and 116 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,19 @@ static String safeCast( String key, Map<String,Object> authToken ) throws Invali
return (String) value;
}

static Map<String,Object> safeCastMap( String key, Map<String,Object> authToken )
throws InvalidAuthTokenException
{
Object value = authToken.get( key );
if ( value != null && !(value instanceof Map) )
{
throw new InvalidAuthTokenException(
"The value associated with the key `" + key + "` must be a Map but was: " +
value.getClass().getSimpleName() );
}
return (Map<String,Object>) value;
}

static Map<String,Object> newBasicAuthToken( String username, String password )
{
return map( AuthToken.SCHEME_KEY, BASIC_SCHEME, AuthToken.PRINCIPAL, username, AuthToken.CREDENTIALS,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/*
* 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 Affero 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.neo4j.server.security.enterprise.auth.plugin;

import java.util.Map;

import org.neo4j.kernel.api.security.exception.InvalidAuthTokenException;
import org.neo4j.server.security.enterprise.auth.plugin.api.AuthToken;

import static org.neo4j.kernel.api.security.AuthToken.PRINCIPAL;
import static org.neo4j.kernel.api.security.AuthToken.CREDENTIALS;
import static org.neo4j.kernel.api.security.AuthToken.PARAMETERS;

public class PluginApiAuthToken implements AuthToken
{
private final String principal;
private final char[] credentials;
private final Map<String,Object> parameters;

private PluginApiAuthToken( String principal, char[] credentials, Map<String,Object> parameters )
{
this.principal = principal;
this.credentials = credentials;
this.parameters = parameters;
}

@Override
public String getPrincipal()
{
return principal;
}

@Override
public char[] getCredentials()
{
return credentials;
}

@Override
public Map<String,Object> getParameters()
{
return parameters;
}

public static AuthToken of( String principal, char[] credentials )
{
return new PluginApiAuthToken( principal, credentials, null );
}

public static AuthToken of( String principal, char[] credentials, Map<String,Object> parameters )
{
return new PluginApiAuthToken( principal, credentials, parameters );
}

public static AuthToken createFromMap( Map<String,Object> authTokenMap ) throws InvalidAuthTokenException
{
String principal = org.neo4j.kernel.api.security.AuthToken.safeCast( PRINCIPAL, authTokenMap );
String credentials = org.neo4j.kernel.api.security.AuthToken.safeCast( CREDENTIALS, authTokenMap );
Map<String,Object> parameters = org.neo4j.kernel.api.security.AuthToken.safeCastMap( PARAMETERS, authTokenMap );

return PluginApiAuthToken.of( principal, credentials.toCharArray(), parameters );
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,31 +38,29 @@ public class PluginAuthInfo extends ShiroAuthenticationInfo implements Authoriza
{
Set<String> roles;

public PluginAuthInfo( Object principal, String realmName, Set<String> roles )
private PluginAuthInfo( Object principal, String realmName, Set<String> roles )
{
super( principal, null, realmName, AuthenticationResult.SUCCESS );
this.roles = roles;
}

public PluginAuthInfo( Object principal, Object hashedCredentials, ByteSource credentialsSalt,
private PluginAuthInfo( Object principal, Object hashedCredentials, ByteSource credentialsSalt,
String realmName, Set<String> roles )
{
super( principal, hashedCredentials, credentialsSalt, realmName, AuthenticationResult.SUCCESS );
this.roles = roles;
}

public static PluginAuthInfo create( AuthInfo authInfo, String realmName )
private PluginAuthInfo( AuthInfo authInfo, SimpleHash hashedCredentials, String realmName )
{
return new PluginAuthInfo( authInfo.getPrincipal(), realmName,
this( authInfo.getPrincipal(), hashedCredentials.getBytes(), hashedCredentials.getSalt(), realmName,
authInfo.getRoles().stream().collect( Collectors.toSet() ) );
}

private static PluginAuthInfo create( AuthInfo authInfo, SimpleHash hashedCredentials,
String realmName )
public static PluginAuthInfo create( AuthInfo authInfo, String realmName )
{
return new PluginAuthInfo( authInfo.getPrincipal(),
hashedCredentials.getBytes(), hashedCredentials.getSalt(), realmName,
authInfo.getRoles().stream().collect( Collectors.toSet()) );
return new PluginAuthInfo( authInfo.getPrincipal(), realmName,
authInfo.getRoles().stream().collect( Collectors.toSet() ) );
}

public static PluginAuthInfo createCacheable( AuthInfo authInfo, String realmName, SecureHasher secureHasher )
Expand All @@ -71,7 +69,7 @@ public static PluginAuthInfo createCacheable( AuthInfo authInfo, String realmNam
{
byte[] credentials = ((CacheableAuthInfo) authInfo).getCredentials();
SimpleHash hashedCredentials = secureHasher.hash( credentials );
return PluginAuthInfo.create( authInfo, hashedCredentials, realmName );
return new PluginAuthInfo( authInfo, hashedCredentials, realmName );
}
else
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@

public class PluginAuthorizationInfo extends SimpleAuthorizationInfo
{
public PluginAuthorizationInfo( Set<String> roles )
private PluginAuthorizationInfo( Set<String> roles )
{
super( roles );
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,15 @@

import org.neo4j.graphdb.factory.GraphDatabaseSettings;
import org.neo4j.graphdb.security.AuthExpirationException;
import org.neo4j.kernel.api.security.exception.InvalidAuthTokenException;
import org.neo4j.kernel.configuration.Config;
import org.neo4j.kernel.internal.Version;
import org.neo4j.logging.Log;
import org.neo4j.logging.LogProvider;
import org.neo4j.server.security.enterprise.auth.PredefinedRolesBuilder;
import org.neo4j.server.security.enterprise.auth.SecureHasher;
import org.neo4j.server.security.enterprise.auth.ShiroAuthToken;
import org.neo4j.server.security.enterprise.auth.plugin.api.AuthToken;
import org.neo4j.server.security.enterprise.auth.plugin.api.RealmOperations;
import org.neo4j.server.security.enterprise.auth.plugin.spi.AuthInfo;
import org.neo4j.server.security.enterprise.auth.plugin.spi.AuthPlugin;
Expand Down Expand Up @@ -170,9 +172,11 @@ protected AuthenticationInfo doGetAuthenticationInfo( AuthenticationToken token
{
try
{
AuthToken pluginAuthToken =
PluginApiAuthToken.createFromMap( ((ShiroAuthToken) token).getAuthTokenMap() );
if ( authPlugin != null )
{
AuthInfo authInfo = authPlugin.authenticateAndAuthorize( ((ShiroAuthToken) token).getAuthTokenMap() );
AuthInfo authInfo = authPlugin.authenticateAndAuthorize( pluginAuthToken );
if ( authInfo != null )
{
PluginAuthInfo pluginAuthInfo =
Expand All @@ -186,14 +190,15 @@ protected AuthenticationInfo doGetAuthenticationInfo( AuthenticationToken token
else if ( authenticationPlugin != null )
{
org.neo4j.server.security.enterprise.auth.plugin.spi.AuthenticationInfo authenticationInfo =
authenticationPlugin.authenticate( ((ShiroAuthToken) token).getAuthTokenMap() );
authenticationPlugin.authenticate( pluginAuthToken );
if ( authenticationInfo != null )
{
return PluginAuthenticationInfo.createCacheable( authenticationInfo, getName(), secureHasher );
}
}
}
catch ( org.neo4j.server.security.enterprise.auth.plugin.api.AuthenticationException e )
catch ( org.neo4j.server.security.enterprise.auth.plugin.api.AuthenticationException |
InvalidAuthTokenException e )
{
throw new AuthenticationException( e.getMessage(), e.getCause() );
}
Expand Down Expand Up @@ -328,12 +333,21 @@ public boolean doCredentialsMatch( AuthenticationToken token, AuthenticationInfo
{
// Authentication info is originating from a CustomCacheableAuthenticationInfo
Map<String,Object> authToken = ((ShiroAuthToken) token).getAuthTokenMap();
return customCredentialsMatcher.doCredentialsMatch( authToken );
try
{
AuthToken pluginApiAuthToken = PluginApiAuthToken.createFromMap( authToken );
return customCredentialsMatcher.doCredentialsMatch( pluginApiAuthToken );
}
catch ( InvalidAuthTokenException e )
{
throw new AuthenticationException( e.getMessage() );
}
}
else if ( info.getCredentials() != null )
{
// Authentication info is originating from a CacheableAuthenticationInfo or a CacheableAuthInfo
return secureHasher.getHashedCredentialsMatcher().doCredentialsMatch( token, info );
return secureHasher.getHashedCredentialsMatcher()
.doCredentialsMatch( PluginShiroAuthToken.of( token ), info );
}
else
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
* 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 Affero 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.neo4j.server.security.enterprise.auth.plugin;

import org.apache.shiro.authc.AuthenticationToken;

import java.util.Map;

import org.neo4j.server.security.enterprise.auth.ShiroAuthToken;

/**
* Version of ShiroAuthToken that returns credentials as a char array
* so that it is compatible for credentials matching with the
* cacheable auth info results returned by the plugin API
*/
public class PluginShiroAuthToken extends ShiroAuthToken
{
private PluginShiroAuthToken( Map<String,Object> authTokenMap )
{
super( authTokenMap );
}

@Override
public Object getCredentials()
{
return ((String) super.getCredentials()).toCharArray();
}

public static PluginShiroAuthToken of( ShiroAuthToken shiroAuthToken )
{
return new PluginShiroAuthToken( shiroAuthToken.getAuthTokenMap() );
}

public static PluginShiroAuthToken of( AuthenticationToken authenticationToken )
{
ShiroAuthToken shiroAuthToken = (ShiroAuthToken) authenticationToken;
return PluginShiroAuthToken.of( shiroAuthToken );
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,20 +19,46 @@
*/
package org.neo4j.server.security.enterprise.auth.plugin.api;

import java.util.Map;

/**
* The predefined keys of the auth token <tt>Map&lt;String,Object&gt;</tt>.
* The authentication token provided by the client, which is used to authenticate the subject's identity.
*
* <p>A common scenario is to have principal be a username and credentials be a password.
*
* @see org.neo4j.server.security.enterprise.auth.plugin.spi.AuthenticationPlugin#authenticate(AuthToken)
* @see org.neo4j.server.security.enterprise.auth.plugin.spi.AuthPlugin#authenticateAndAuthorize(AuthToken)
*/
public interface AuthToken
{
String PRINCIPAL = "principal";
String CREDENTIALS = "credentials";
String REALM = "realm";
/**
* Returns the identity to authenticate.
*
* <p>Most commonly this is a username.
*
* @return the identity to authenticate.
*/
String getPrincipal();

/**
* Returns the credentials that verifies the identity.
*
* <p>Most commonly this is a password.
*
* <p>The reason this is a character array and not a <tt>String</tt>, is so that sensitive information
* can be cleared from memory after useage without having to wait for the garbage collector to act.
*
* @return the credentials that verifies the identity.
*/
char[] getCredentials();

/**
* The corresponding value of this key is a <tt>Map<String,Object></tt> of custom parameters
* as provided by the client. This can be used as a vehicle to connect a client application
* with a server-side auth plugin.
* Neo4j will act as a pure transport and will not inspect the contents of this map.
* Returns an optional custom parameter map if provided by the client.
*
* <p>This can be used as a vehicle to send arbitrary auth data from a client application
* to a server-side auth plugin. Neo4j will act as a pure transport and will not touch the contents of this map.
*
* @return a custom parameter map if provided by the client, otherwise <tt>null</tt>
*/
String PARAMETERS = "parameters";
Map<String,Object> getParameters();
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@

import java.io.Serializable;
import java.util.Collection;
import java.util.Map;

import org.neo4j.server.security.enterprise.auth.plugin.api.AuthToken;

/**
* An object that can be returned as the result of successful authentication by an <tt>AuthPlugin</tt>.
Expand All @@ -30,7 +31,7 @@
*
* <p>NOTE: If authentication caching is enabled the result type <tt>CacheableAuthInfo</tt> should be used instead.
*
* @see AuthPlugin#authenticateAndAuthorize(Map)
* @see AuthPlugin#authenticateAndAuthorize(AuthToken)
* @see CacheableAuthInfo
*/
public interface AuthInfo extends Serializable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,7 @@
*/
package org.neo4j.server.security.enterprise.auth.plugin.spi;

import java.util.Map;

import org.neo4j.server.security.enterprise.auth.plugin.api.AuthToken;
import org.neo4j.server.security.enterprise.auth.plugin.api.AuthenticationException;
import org.neo4j.server.security.enterprise.auth.plugin.api.RealmOperations;

Expand All @@ -32,7 +31,7 @@
* all objects that implements this interface that exists in the class path at Neo4j startup, will be
* loaded as services.
*
* @see AuthPlugin
* @see AuthenticationPlugin
* @see AuthorizationPlugin
*/
Expand Down Expand Up @@ -66,7 +65,7 @@ public interface AuthPlugin extends RealmLifecycle
* @see CustomCacheableAuthenticationInfo
* @see RealmOperations#setAuthenticationCachingEnabled(boolean)
*/
AuthInfo authenticateAndAuthorize( Map<String,Object> authToken ) throws AuthenticationException;
AuthInfo authenticateAndAuthorize( AuthToken authToken ) throws AuthenticationException;

abstract class Adapter implements AuthPlugin
{
Expand Down

0 comments on commit d0d9965

Please sign in to comment.