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.
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
@@ -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 );
}
}
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
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
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
@@ -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 );
}
}
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();
}
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
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.