Unverified 提交 3c10d9c9 authored 作者: Noel Grandin's avatar Noel Grandin 提交者: GitHub

Merge pull request #1154 from mysinmyc/authentication_merge

Support for external authentication
...@@ -83,6 +83,8 @@ Features ...@@ -83,6 +83,8 @@ Features
Compacting a Database</a><br /> Compacting a Database</a><br />
<a href="#cache_settings"> <a href="#cache_settings">
Cache Settings</a><br /> Cache Settings</a><br />
<a href="#external_authentication">
External Authentication (Experimental)</a><br />
<h2 id="feature_list">Feature List</h2> <h2 id="feature_list">Feature List</h2>
<h3>Main Features</h3> <h3>Main Features</h3>
...@@ -1826,6 +1828,79 @@ To get information about page reads and writes, and the current caching algorith ...@@ -1826,6 +1828,79 @@ To get information about page reads and writes, and the current caching algorith
call <code>SELECT * FROM INFORMATION_SCHEMA.SETTINGS</code>. The number of pages read / written call <code>SELECT * FROM INFORMATION_SCHEMA.SETTINGS</code>. The number of pages read / written
is listed. is listed.
</p> </p>
<h2 id="external_authentication">External authentication (Experimental)</h2>
<p>
External authentication allows to optionally validate user credentials externally (JAAS,LDAP,custom classes).
Is also possible to temporary assign roles to to externally authenticated users. <b>This feature is experimental and subject to change</b>
</p>
<p>Master user cannot be externally authenticated</p>
<p>
To enable external authentication on a database execute statement <code>SET AUTHENTICATOR TRUE</code>. This setting in persisted on the database.
</p>
<p>
To connect on a database by using external credentials client must append <code>AUTHREALM=H2</code> to the database URL. <code>H2</code>
is the identifier of the authentication realm (see later).
</p>
<p>External authentication requires to send password to the server. For this reason is works only on local connection or remote over ssl</p>
<p>
By default external authentication is performed through JAAS login interface (configuration name is <code>h2</code>).
To configure JAAS add argument <code>-Djava.security.auth.login.config=jaas.conf</code>
Here an example of <a href="https://docs.oracle.com/javase/7/docs/technotes/guides/security/jgss/tutorials/LoginConfigFile.html">
JAAS login configuration file</a> content:
</p>
<pre>
h2 {
com.sun.security.auth.module.LdapLoginModule REQUIRED \
userProvider="ldap://127.0.0.1:10389" authIdentity="uid={USERNAME},ou=people,dc=example,dc=com" \
debug=true useSSL=false ;
};
</pre>
<p>
Is it possible to specify custom authentication settings by using
JVM argument <code>-Dh2auth.configurationFile={urlOfH2Auth.xml}</code>. Here an example of h2auth.xml file content:
</p>
<pre>
&lt;h2Auth allowUserRegistration="false" createMissingRoles="true"&gt;
&lt;!-- realm: DUMMY authenticate users named DUMMY[0-9] with a static password --&gt;
&lt;realm name="DUMMY"
validatorClass="org.h2.security.auth.impl.FixedPasswordCredentialsValidator"&gt;
&lt;property name="userNamePattern" value="DUMMY[0-9]" /&gt;
&lt;property name="password" value="mock" /&gt;
&lt;/realm&gt;
&lt;!-- realm LDAPEXAMPLE:perform credentials validation on LDAP --&gt;
&lt;realm name="LDAPEXAMPLE"
validatorClass="org.h2.security.auth.impl.LdapCredentialsValidator"&gt;
&lt;property name="bindDnPattern" value="uid=%u,ou=people,dc=example,dc=com" /&gt;
&lt;property name="host" value="127.0.0.1" /&gt;
&lt;property name="port" value="10389" /&gt;
&lt;property name="secure" value="false" /&gt;
&lt;/realm&gt;
&lt;!-- realm JAAS: perform credentials validation by using JAAS api --&gt;
&lt;realm name="JAAS"
validatorClass="org.h2.security.auth.impl.JaasCredentialsValidator"&gt;
&lt;property name="appName" value="H2" /&gt;
&lt;/realm&gt;
&lt;!--Assign to each user role @{REALM} --&gt;
&lt;userToRolesMapper class="org.h2.security.auth.impl.AssignRealmNameRole"/&gt;
&lt;!--Assign to each user role REMOTEUSER --&gt;
&lt;userToRolesMapper class="org.h2.security.auth.impl.StaticRolesMapper"&gt;
&lt;property name="roles" value="REMOTEUSER"/&gt;
&lt;/userToRolesMapper&gt;
&lt;/h2Auth&gt;
</pre>
<p>
Custom credentials validators must implement the interface
<code>org.h2.api.CredentialsValidator</code>
</p>
<p>
Custom criteria for role assignments must implement the interface
<code>org.h2.api.UserToRoleMapper</code>
</p>
<!-- [close] { --></div></td></tr></table><!-- } --><!-- analytics --></body></html> <!-- [close] { --></div></td></tr></table><!-- } --><!-- analytics --></body></html>
/*
* Copyright 2004-2018 H2 Group. Multiple-Licensed under the MPL 2.0,
* and the EPL 1.0 (http://h2database.com/html/license.html).
* Initial Developer: Alessandro Ventura
*/
package org.h2.api;
import org.h2.security.auth.AuthenticationInfo;
import org.h2.security.auth.Configurable;
/**
* A class that implement this interface can be used to validate
* credentials provided by client.
* <p>
* <b>This feature is experimental and subject to change</b>
* </p>
*/
public interface CredentialsValidator extends Configurable {
/**
* Validate user credential
* @param authenticationInfo = authentication info
* @return true if credentials are valid, otherwise false
* @throws Exception any exception occurred (invalid credentials or internal issue) prevent user login
*/
boolean validateCredentials(AuthenticationInfo authenticationInfo) throws Exception;
}
...@@ -1990,7 +1990,20 @@ public class ErrorCode { ...@@ -1990,7 +1990,20 @@ public class ErrorCode {
*/ */
public static final int ROW_NOT_FOUND_IN_PRIMARY_INDEX = 90143; public static final int ROW_NOT_FOUND_IN_PRIMARY_INDEX = 90143;
// next are 90122, 90144 /**
* The error with code <code>90144</code> is thrown when
* user trying to login into a database with AUTHREALM set and
* the target database doesn't have an authenticator defined
* <p>Authenticator experimental feature can be enabled by
* </p>
* <pre>
* SET AUTHENTICATOR TRUE
* </pre>
*/
public static final int AUTHENTICATOR_NOT_AVAILABLE = 90144;
// next are 90122, 90145
private ErrorCode() { private ErrorCode() {
// utility class // utility class
......
/*
* Copyright 2004-2018 H2 Group. Multiple-Licensed under the MPL 2.0,
* and the EPL 1.0 (http://h2database.com/html/license.html).
* Initial Developer: Alessandro Ventura
*/
package org.h2.api;
import java.util.Collection;
import java.util.Set;
import org.h2.security.auth.AuthenticationException;
import org.h2.security.auth.AuthenticationInfo;
import org.h2.security.auth.Configurable;
/**
* A class that implement this interface can be used during
* authentication to map external users to database roles.
* <p>
* <b>This feature is experimental and subject to change</b>
* </p>
*/
public interface UserToRolesMapper extends Configurable {
/**
* Map user identified by authentication info to a set of granted roles
* @param authenticationInfo
* @return list of roles to be assigned to the user temporary
* @throws AuthenticationException
*/
Collection<String> mapUserToRoles(AuthenticationInfo authenticationInfo) throws AuthenticationException;
}
...@@ -18,9 +18,11 @@ import org.h2.engine.Setting; ...@@ -18,9 +18,11 @@ import org.h2.engine.Setting;
import org.h2.expression.Expression; import org.h2.expression.Expression;
import org.h2.expression.ValueExpression; import org.h2.expression.ValueExpression;
import org.h2.message.DbException; import org.h2.message.DbException;
import org.h2.message.Trace;
import org.h2.result.ResultInterface; import org.h2.result.ResultInterface;
import org.h2.result.RowFactory; import org.h2.result.RowFactory;
import org.h2.schema.Schema; import org.h2.schema.Schema;
import org.h2.security.auth.AuthenticatorFactory;
import org.h2.table.Table; import org.h2.table.Table;
import org.h2.tools.CompressTool; import org.h2.tools.CompressTool;
import org.h2.util.JdbcUtils; import org.h2.util.JdbcUtils;
...@@ -540,6 +542,25 @@ public class Set extends Prepared { ...@@ -540,6 +542,25 @@ public class Set extends Prepared {
session.getColumnNamerConfiguration().configure(expression.getColumnName()); session.getColumnNamerConfiguration().configure(expression.getColumnName());
break; break;
} }
case SetTypes.AUTHENTICATOR: {
session.getUser().checkAdmin();
try {
if (expression.getBooleanValue(session)) {
database.setAuthenticator(AuthenticatorFactory.createAuthenticator());
} else {
database.setAuthenticator(null);
}
addOrUpdateSetting(name,expression.getValue(session).getString(),0);
} catch (Exception e) {
//Errors during start are ignored to allow to open the database
if (database.isStarting()) {
database.getTrace(Trace.DATABASE).error(e, "{0}: failed to set authenticator during database start ",expression.toString());
} else {
throw DbException.convert(e);
}
}
break;
}
default: default:
DbException.throwInternalError("type="+type); DbException.throwInternalError("type="+type);
} }
......
...@@ -252,7 +252,12 @@ public class SetTypes { ...@@ -252,7 +252,12 @@ public class SetTypes {
*/ */
public static final int COLUMN_NAME_RULES = 48; public static final int COLUMN_NAME_RULES = 48;
private static final int COUNT = COLUMN_NAME_RULES + 1; /**
* The type of a SET AUTHENTICATOR statement.
*/
public static final int AUTHENTICATOR= 49;
private static final int COUNT = AUTHENTICATOR + 1;
private static final ArrayList<String> TYPES; private static final ArrayList<String> TYPES;
...@@ -311,6 +316,7 @@ public class SetTypes { ...@@ -311,6 +316,7 @@ public class SetTypes {
list.add(LAZY_QUERY_EXECUTION, "LAZY_QUERY_EXECUTION"); list.add(LAZY_QUERY_EXECUTION, "LAZY_QUERY_EXECUTION");
list.add(BUILTIN_ALIAS_OVERRIDE, "BUILTIN_ALIAS_OVERRIDE"); list.add(BUILTIN_ALIAS_OVERRIDE, "BUILTIN_ALIAS_OVERRIDE");
list.add(COLUMN_NAME_RULES, "COLUMN_NAME_RULES"); list.add(COLUMN_NAME_RULES, "COLUMN_NAME_RULES");
list.add(AUTHENTICATOR, "AUTHENTICATOR");
TYPES = list; TYPES = list;
} }
......
...@@ -74,9 +74,9 @@ public class ConnectionInfo implements Cloneable { ...@@ -74,9 +74,9 @@ public class ConnectionInfo implements Cloneable {
readProperties(info); readProperties(info);
readSettingsFromURL(); readSettingsFromURL();
setUserName(removeProperty("USER", "")); setUserName(removeProperty("USER", ""));
convertPasswords();
name = url.substring(Constants.START_URL.length()); name = url.substring(Constants.START_URL.length());
parseName(); parseName();
convertPasswords();
String recoverTest = removeProperty("RECOVER_TEST", null); String recoverTest = removeProperty("RECOVER_TEST", null);
if (recoverTest != null) { if (recoverTest != null) {
FilePathRec.register(); FilePathRec.register();
...@@ -95,7 +95,7 @@ public class ConnectionInfo implements Cloneable { ...@@ -95,7 +95,7 @@ public class ConnectionInfo implements Cloneable {
"IFEXISTS", "INIT", "PASSWORD", "RECOVER", "RECOVER_TEST", "IFEXISTS", "INIT", "PASSWORD", "RECOVER", "RECOVER_TEST",
"USER", "AUTO_SERVER", "AUTO_SERVER_PORT", "NO_UPGRADE", "USER", "AUTO_SERVER", "AUTO_SERVER_PORT", "NO_UPGRADE",
"AUTO_RECONNECT", "OPEN_NEW", "PAGE_SIZE", "PASSWORD_HASH", "JMX", "AUTO_RECONNECT", "OPEN_NEW", "PAGE_SIZE", "PASSWORD_HASH", "JMX",
"SCOPE_GENERATED_KEYS" }; "SCOPE_GENERATED_KEYS", "AUTHREALM", "AUTHZPWD" };
HashSet<String> set = new HashSet<>(128); HashSet<String> set = new HashSet<>(128);
set.addAll(SetTypes.getTypes()); set.addAll(SetTypes.getTypes());
for (String key : connectionTime) { for (String key : connectionTime) {
...@@ -274,8 +274,15 @@ public class ConnectionInfo implements Cloneable { ...@@ -274,8 +274,15 @@ public class ConnectionInfo implements Cloneable {
} }
} }
private void preservePasswordForAuthentication(Object password) {
if ((!isRemote() || isSSL()) && prop.containsKey("AUTHREALM") && password!=null) {
prop.put("AUTHZPWD",password);
}
}
private char[] removePassword() { private char[] removePassword() {
Object p = prop.remove("PASSWORD"); Object p = prop.remove("PASSWORD");
preservePasswordForAuthentication(p);
if (p == null) { if (p == null) {
return new char[0]; return new char[0];
} else if (p instanceof char[]) { } else if (p instanceof char[]) {
...@@ -657,4 +664,8 @@ public class ConnectionInfo implements Cloneable { ...@@ -657,4 +664,8 @@ public class ConnectionInfo implements Cloneable {
return url; return url;
} }
public void cleanAuthenticationInfo() {
removeProperty("AUTHREALM", false);
removeProperty("AUTHZPWD", false);
}
} }
...@@ -20,6 +20,7 @@ import java.util.concurrent.ConcurrentHashMap; ...@@ -20,6 +20,7 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
import org.h2.api.DatabaseEventListener; import org.h2.api.DatabaseEventListener;
import org.h2.api.ErrorCode; import org.h2.api.ErrorCode;
import org.h2.api.JavaObjectSerializer; import org.h2.api.JavaObjectSerializer;
...@@ -43,6 +44,8 @@ import org.h2.schema.Schema; ...@@ -43,6 +44,8 @@ import org.h2.schema.Schema;
import org.h2.schema.SchemaObject; import org.h2.schema.SchemaObject;
import org.h2.schema.Sequence; import org.h2.schema.Sequence;
import org.h2.schema.TriggerObject; import org.h2.schema.TriggerObject;
import org.h2.security.auth.Authenticator;
import org.h2.security.auth.AuthenticatorFactory;
import org.h2.store.DataHandler; import org.h2.store.DataHandler;
import org.h2.store.FileLock; import org.h2.store.FileLock;
import org.h2.store.FileLockMethod; import org.h2.store.FileLockMethod;
...@@ -225,6 +228,8 @@ public class Database implements DataHandler { ...@@ -225,6 +228,8 @@ public class Database implements DataHandler {
private QueryStatisticsData queryStatisticsData; private QueryStatisticsData queryStatisticsData;
private RowFactory rowFactory = RowFactory.DEFAULT; private RowFactory rowFactory = RowFactory.DEFAULT;
private Authenticator authenticator;
public Database(ConnectionInfo ci, String cipher) { public Database(ConnectionInfo ci, String cipher) {
if (ASSERT) { if (ASSERT) {
META_LOCK_DEBUGGING.set(null); META_LOCK_DEBUGGING.set(null);
...@@ -2967,4 +2972,23 @@ public class Database implements DataHandler { ...@@ -2967,4 +2972,23 @@ public class Database implements DataHandler {
return engine; return engine;
} }
/**
* get authenticator for database users
* @return authenticator set for database
*/
public Authenticator getAuthenticator() {
return authenticator;
}
/**
* Set current database authenticator
*
* @param authenticator = authenticator to set, null to revert to the Internal authenticator
*/
public void setAuthenticator(Authenticator authenticator) {
if (authenticator!=null) {
authenticator.init(this);
};
this.authenticator=authenticator;
}
} }
...@@ -14,6 +14,9 @@ import org.h2.command.Parser; ...@@ -14,6 +14,9 @@ import org.h2.command.Parser;
import org.h2.command.dml.SetTypes; import org.h2.command.dml.SetTypes;
import org.h2.message.DbException; import org.h2.message.DbException;
import org.h2.message.Trace; import org.h2.message.Trace;
import org.h2.security.auth.AuthenticationException;
import org.h2.security.auth.AuthenticationInfo;
import org.h2.security.auth.Authenticator;
import org.h2.store.FileLock; import org.h2.store.FileLock;
import org.h2.store.FileLockMethod; import org.h2.store.FileLockMethod;
import org.h2.util.MathUtils; import org.h2.util.MathUtils;
...@@ -90,12 +93,28 @@ public class Engine implements SessionFactory { ...@@ -90,12 +93,28 @@ public class Engine implements SessionFactory {
} }
if (user == null) { if (user == null) {
if (database.validateFilePasswordHash(cipher, ci.getFilePasswordHash())) { if (database.validateFilePasswordHash(cipher, ci.getFilePasswordHash())) {
if (ci.getProperty("AUTHREALM")== null) {
user = database.findUser(ci.getUserName()); user = database.findUser(ci.getUserName());
if (user != null) { if (user != null) {
if (!user.validateUserPasswordHash(ci.getUserPasswordHash())) { if (!user.validateUserPasswordHash(ci.getUserPasswordHash())) {
user = null; user = null;
} }
} }
} else {
Authenticator authenticator = database.getAuthenticator();
if (authenticator==null) {
throw DbException.get(ErrorCode.AUTHENTICATOR_NOT_AVAILABLE, name);
} else {
try {
AuthenticationInfo authenticationInfo=new AuthenticationInfo(ci);
user = database.getAuthenticator().authenticate(authenticationInfo, database);
} catch (AuthenticationException authenticationError) {
database.getTrace(Trace.DATABASE).error(authenticationError,
"an error occurred during authentication; user: \"" +
ci.getUserName() + "\"");
}
}
}
} }
if (opened && (user == null || !user.isAdmin())) { if (opened && (user == null || !user.isAdmin())) {
// reset - because the user is not an admin, and has no // reset - because the user is not an admin, and has no
...@@ -110,6 +129,8 @@ public class Engine implements SessionFactory { ...@@ -110,6 +129,8 @@ public class Engine implements SessionFactory {
database.removeSession(null); database.removeSession(null);
throw er; throw er;
} }
//Prevent to set _PASSWORD
ci.cleanAuthenticationInfo();
checkClustering(ci, database); checkClustering(ci, database);
Session session = database.createSession(user); Session session = database.createSession(user);
if (session == null) { if (session == null) {
......
...@@ -5,7 +5,10 @@ ...@@ -5,7 +5,10 @@
*/ */
package org.h2.engine; package org.h2.engine;
import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List;
import java.util.Map.Entry;
import org.h2.table.Table; import org.h2.table.Table;
...@@ -151,6 +154,26 @@ public abstract class RightOwner extends DbObjectBase { ...@@ -151,6 +154,26 @@ public abstract class RightOwner extends DbObjectBase {
} }
} }
/**
* Remove all the temporary rights granted on roles
*/
public void revokeTemporaryRightsOnRoles() {
if (grantedRoles == null) {
return;
}
List<Role> rolesToRemove= new ArrayList<>();
for (Entry<Role,Right> currentEntry : grantedRoles.entrySet()) {
if ( currentEntry.getValue().isTemporary() || !currentEntry.getValue().isValid()) {
rolesToRemove.add(currentEntry.getKey());
}
}
for (Role currentRoleToRemove : rolesToRemove) {
revokeRole(currentRoleToRemove);
}
}
/** /**
* Get the 'grant schema' right of this object. * Get the 'grant schema' right of this object.
* *
......
...@@ -555,6 +555,16 @@ public class SysProperties { ...@@ -555,6 +555,16 @@ public class SysProperties {
public static final String CUSTOM_DATA_TYPES_HANDLER = public static final String CUSTOM_DATA_TYPES_HANDLER =
Utils.getProperty("h2.customDataTypesHandler", null); Utils.getProperty("h2.customDataTypesHandler", null);
/**
* System property <code>h2.authConfigFile</code>
* (default: null).<br />
* authConfigFile define the URL of configuration file
* of {@link org.h2.security.auth.DefaultAuthenticator}
*
*/
public static final String AUTH_CONFIG_FILE =
Utils.getProperty("h2.authConfigFile", null);
private static final String H2_BASE_DIR = "h2.baseDir"; private static final String H2_BASE_DIR = "h2.baseDir";
private SysProperties() { private SysProperties() {
......
package org.h2.engine;
import org.h2.security.auth.AuthenticationInfo;
import org.h2.util.MathUtils;
public class UserBuilder {
/**
* build the database user starting from authentication informations
* @param authenticationInfo = authentication info
* @param database = target database
* @param persistent = true if the user will be persisted in the database
* @return user bean
*/
public static User buildUser(AuthenticationInfo authenticationInfo, Database database, boolean persistent) {
User user = new User(database, persistent ? database.allocateObjectId() : -1, authenticationInfo.getFullyQualifiedName(), false);
//In case of external authentication fill the password hash with random data
user.setUserPasswordHash( authenticationInfo.getRealm()==null ? authenticationInfo.getConnectionInfo().getUserPasswordHash(): MathUtils.secureRandomBytes(64));
user.setTemporary(!persistent);
return user;
}
}
...@@ -174,6 +174,7 @@ ...@@ -174,6 +174,7 @@
90141=Serializer cannot be changed because there is a data table: {0} 90141=Serializer cannot be changed because there is a data table: {0}
90142=Step size must not be zero 90142=Step size must not be zero
90143=Row {1} not found in primary index {0} 90143=Row {1} not found in primary index {0}
90144=Authenticator not enabled on database {0}
HY000=General error: {0} HY000=General error: {0}
HY004=Unknown data type: {0} HY004=Unknown data type: {0}
HYC00=Feature not supported: {0} HYC00=Feature not supported: {0}
......
/*
* Copyright 2004-2018 H2 Group. Multiple-Licensed under the MPL 2.0,
* and the EPL 1.0 (http://h2database.com/html/license.html).
* Initial Developer: Alessandro Ventura
*/
package org.h2.security.auth;
/**
* Exception thrown when an issue occurs during the authentication configuration
*
*/
public class AuthConfigException extends RuntimeException {
public AuthConfigException() {
super();
}
public AuthConfigException(String message) {
super(message);
}
public AuthConfigException(Throwable cause) {
super(cause);
}
public AuthConfigException(String message, Throwable cause) {
super(message, cause);
}
}
/*
* Copyright 2004-2018 H2 Group. Multiple-Licensed under the MPL 2.0,
* and the EPL 1.0 (http://h2database.com/html/license.html).
* Initial Developer: Alessandro Ventura
*/
package org.h2.security.auth;
/**
* Exception thrown in case of errors during authentication
*/
public class AuthenticationException extends Exception {
public AuthenticationException() {
super();
}
public AuthenticationException(String message) {
super(message);
}
public AuthenticationException(Throwable cause) {
super(cause);
}
public AuthenticationException(String message, Throwable cause) {
super(message, cause);
}
}
/*
* Copyright 2004-2018 H2 Group. Multiple-Licensed under the MPL 2.0,
* and the EPL 1.0 (http://h2database.com/html/license.html).
* Initial Developer: Alessandro Ventura
*/
package org.h2.security.auth;
import org.h2.engine.ConnectionInfo;
import org.h2.util.StringUtils;
/**
* Input data for authenticators; it wraps ConnectionInfo
*/
public class AuthenticationInfo {
private ConnectionInfo connectionInfo;
private String password;
private String realm;
/*
* Can be used by authenticator to hold informations
*/
Object nestedIdentity;
public AuthenticationInfo(ConnectionInfo connectionInfo) {
this.connectionInfo = connectionInfo;
this.realm = connectionInfo.getProperty("AUTHREALM", null);
if (this.realm!=null) {
this.realm=StringUtils.toUpperEnglish(this.realm);
}
this.password = connectionInfo.getProperty("AUTHZPWD", null);
}
public String getUserName() {
return connectionInfo.getUserName();
}
public String getRealm() {
return realm;
}
public String getPassword() {
return password;
}
public ConnectionInfo getConnectionInfo() {
return connectionInfo;
}
public String getFullyQualifiedName() {
if (realm==null) {
return connectionInfo.getUserName();
} else {
return connectionInfo.getUserName()+"@"+realm;
}
}
/**
* get nested identity
* @return
*/
public Object getNestedIdentity() {
return nestedIdentity;
}
/**
* Method used by authenticators to hold informations about authenticated user
* @param nestedIdentity = nested identity object
*/
public void setNestedIdentity(Object nestedIdentity) {
this.nestedIdentity = nestedIdentity;
}
public void clean() {
this.password = null;
this.nestedIdentity = null;
connectionInfo.cleanAuthenticationInfo();
}
}
/*
* Copyright 2004-2018 H2 Group. Multiple-Licensed under the MPL 2.0,
* and the EPL 1.0 (http://h2database.com/html/license.html).
* Initial Developer: Alessandro Ventura
*/
package org.h2.security.auth;
import org.h2.engine.Database;
import org.h2.engine.User;
/**
* Low level interface to implement full authentication process
*/
public interface Authenticator {
/**
* perform user authentication
*
* @param authenticationInfo
* @param database
* @return valid database user or null if user doesn't exists in the database
* @throws AuthenticationException
*/
User authenticate(AuthenticationInfo authenticationInfo, Database database) throws AuthenticationException;
/**
* Initialize the authenticator. This method is invoked by databases when the authenticator is set
* when the authenticator is set.
* @param database = target database
* @throws AuthConfigException
*/
void init(Database database) throws AuthConfigException;
}
/*
* Copyright 2004-2018 H2 Group. Multiple-Licensed under the MPL 2.0,
* and the EPL 1.0 (http://h2database.com/html/license.html).
* Initial Developer: Alessandro Ventura
*/
package org.h2.security.auth;
/**
* Authenticator factory
*/
public class AuthenticatorFactory {
public static Authenticator createAuthenticator() {
return DefaultAuthenticator.getInstance();
}
}
/*
* Copyright 2004-2018 H2 Group. Multiple-Licensed under the MPL 2.0,
* and the EPL 1.0 (http://h2database.com/html/license.html).
* Initial Developer: Alessandro Ventura
*/
package org.h2.security.auth;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import org.h2.util.Utils;
/**
* wrapper for configuration properties
*/
public class ConfigProperties {
private Map<String, String> properties;
public ConfigProperties() {
properties = new HashMap<>();
}
public ConfigProperties(PropertyConfig...configProperties) {
this(configProperties==null?null:Arrays.asList(configProperties));
}
public ConfigProperties(Collection<PropertyConfig> configProperties) {
properties = new HashMap<>();
if (properties != null) {
for (PropertyConfig currentProperty : configProperties) {
if (properties.put(currentProperty.getName(), currentProperty.getValue())!=null) {
throw new AuthConfigException("duplicate property "+currentProperty.getName());
}
}
}
}
public String getStringValue(String name, String defaultValue) {
String result = properties.get(name);
if (result == null) {
return defaultValue;
}
return result;
}
public String getStringValue(String name) {
String result = properties.get(name);
if (result == null) {
throw new AuthConfigException("missing config property "+name);
}
return result;
}
public int getIntValue(String name, int defaultValue) {
String result = properties.get(name);
if (result == null) {
return defaultValue;
}
return Integer.parseInt(result);
}
public int getIntValue(String name) {
String result = properties.get(name);
if (result == null) {
throw new AuthConfigException("missing config property "+name);
}
return Integer.parseInt(result);
}
public boolean getBooleanValue(String name, boolean defaultValue) {
String result = properties.get(name);
if (result == null) {
return defaultValue;
}
return Utils.parseBoolean(result, defaultValue, true);
}
}
/*
* Copyright 2004-2018 H2 Group. Multiple-Licensed under the MPL 2.0,
* and the EPL 1.0 (http://h2database.com/html/license.html).
* Initial Developer: Alessandro Ventura
*/
package org.h2.security.auth;
/**
* describe how to perform objects runtime configuration
*/
public interface Configurable {
/**
* configure the component
* @param configProperties = configuration properties
*/
void configure(ConfigProperties configProperties);
}
/*
* Copyright 2004-2018 H2 Group. Multiple-Licensed under the MPL 2.0,
* and the EPL 1.0 (http://h2database.com/html/license.html).
* Initial Developer: Alessandro Ventura
*/
package org.h2.security.auth;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.xml.bind.JAXB;
import org.h2.api.CredentialsValidator;
import org.h2.api.UserToRolesMapper;
import org.h2.engine.Database;
import org.h2.engine.Right;
import org.h2.engine.Role;
import org.h2.engine.SysProperties;
import org.h2.engine.User;
import org.h2.engine.UserBuilder;
import org.h2.message.Trace;
import org.h2.security.auth.impl.AssignRealmNameRole;
import org.h2.security.auth.impl.JaasCredentialsValidator;
import org.h2.util.StringUtils;
/**
* Default authenticator implementation.
* <p>
* When client connectionInfo contains property AUTHREALM={realName} credentials
* (typically user id and password) are validated by
* by {@link org.h2.api.CredentialsValidator} configured for that realm.
* </p>
* <p>
* When client connectionInfo doesn't contains AUTHREALM property credentials
* are validated internally on the database
* </p>
* <p>
* Rights assignment can be managed through {@link org.h2.api.UserToRolesMapper}
* </p>
* <p>
* Default configuration has a realm H2 that validate credentials through JAAS api (appName=h2).
* To customize configuration set h2.authConfigFile system property to refer a valid h2auth.xml config file
* </p>
*/
public class DefaultAuthenticator implements Authenticator {
public static final String DEFAULT_REALMNAME = "H2";
private Map<String, CredentialsValidator> realms = new HashMap<>();
private List<UserToRolesMapper> userToRolesMappers = new ArrayList<>();
private boolean allowUserRegistration;
private boolean persistUsers;
private boolean createMissingRoles;
private boolean skipDefaultInitialization;
private boolean initialized;
private static DefaultAuthenticator instance;
protected static final DefaultAuthenticator getInstance() {
if (instance==null) {
instance= new DefaultAuthenticator();
}
return instance;
}
/**
* Create the Authenticator with default configurations
*/
public DefaultAuthenticator() {
}
/**
* Create authenticator and optionally skip the default configuration. This
* option is useful when the authenticator is configured at code level
*
* @param skipDefaultInitialization
* if true default initialization is skipped
*/
public DefaultAuthenticator(boolean skipDefaultInitialization) {
this.skipDefaultInitialization = skipDefaultInitialization;
}
/**
* If set save users externals defined during the authentication.
*
* @return
*/
public boolean isPersistUsers() {
return persistUsers;
}
public void setPersistUsers(boolean persistUsers) {
this.persistUsers = persistUsers;
}
/**
* If set create external users in the database if not present.
*
* @return
*/
public boolean isAllowUserRegistration() {
return allowUserRegistration;
}
public void setAllowUserRegistration(boolean allowUserRegistration) {
this.allowUserRegistration = allowUserRegistration;
}
/**
* When set create roles not found in the database. If not set roles not found
* in the database are silently skipped
*
* @return
*/
public boolean isCreateMissingRoles() {
return createMissingRoles;
}
public void setCreateMissingRoles(boolean createMissingRoles) {
this.createMissingRoles = createMissingRoles;
}
/**
* Add an authentication realm. Realms are case insensitive
*
* @param name realm name
* @param credentialsValidator credentials validator for realm
*/
public void addRealm(String name, CredentialsValidator credentialsValidator) {
realms.put(StringUtils.toUpperEnglish(name), credentialsValidator);
}
/**
* UserToRoleMappers assign roles to authenticated users
*
* @return current UserToRoleMappers active
*/
public List<UserToRolesMapper> getUserToRolesMappers() {
return userToRolesMappers;
}
public void setUserToRolesMappers(UserToRolesMapper... userToRolesMappers) {
List<UserToRolesMapper> userToRolesMappersList = new ArrayList<>();
for (UserToRolesMapper current : userToRolesMappers) {
userToRolesMappersList.add(current);
}
this.userToRolesMappers = userToRolesMappersList;
}
/**
* Initializes the authenticator (it is called by AuthententicationManager)
*
* this method is skipped if skipDefaultInitialization is set
* Order of initialization is
* <ol>
* <li>Check h2.authConfigFile system property.</li>
* <li>Use the default configuration hard coded</li>
* </ol>
* @param database where authenticator is initialized
* @throws AuthConfigException
*/
public void init(Database database) throws AuthConfigException {
if (skipDefaultInitialization) {
return;
}
if (initialized) {
return;
}
synchronized (this) {
if (initialized) {
return;
}
Trace trace=database.getTrace(Trace.DATABASE);
URL h2AuthenticatorConfigurationUrl = null;
try {
String configFile = SysProperties.AUTH_CONFIG_FILE;
if (configFile != null) {
if (trace.isDebugEnabled()) {
trace.debug("DefaultAuthenticator.config: configuration read from system property h2auth.configurationfile={0}", configFile);
}
h2AuthenticatorConfigurationUrl = new URL(configFile);
}
if (h2AuthenticatorConfigurationUrl == null) {
if (trace.isDebugEnabled()) {
trace.debug("DefaultAuthenticator.config: default configuration");
}
defaultConfiguration();
} else {
configureFromUrl(h2AuthenticatorConfigurationUrl);
}
} catch (Exception e) {
trace.error(e, "DefaultAuthenticator.config: an error occurred during configuration from {0} ", h2AuthenticatorConfigurationUrl);
throw new AuthConfigException("Failed to configure authentication from " + h2AuthenticatorConfigurationUrl,
e);
}
initialized=true;
}
}
void defaultConfiguration() {
createMissingRoles = false;
allowUserRegistration = true;
realms = new HashMap<>();
CredentialsValidator jaasCredentialsValidator = new JaasCredentialsValidator();
jaasCredentialsValidator.configure(new ConfigProperties());
realms.put(DEFAULT_REALMNAME, jaasCredentialsValidator);
UserToRolesMapper assignRealmNameRole = new AssignRealmNameRole();
assignRealmNameRole.configure(new ConfigProperties());
userToRolesMappers.add(assignRealmNameRole);
}
/**
* Configure the authenticator from a configuration file
*
* @param configUrl URL of configuration file
* @throws Exception
*/
public void configureFromUrl(URL configUrl) throws Exception {
H2AuthConfig config = JAXB.unmarshal(configUrl, H2AuthConfig.class);
configureFrom(config);
}
void configureFrom(H2AuthConfig config) throws Exception {
allowUserRegistration = config.isAllowUserRegistration();
createMissingRoles = config.isCreateMissingRoles();
Map<String, CredentialsValidator> newRealms = new HashMap<>();
for (RealmConfig currentRealmConfig : config.getRealms()) {
String currentRealmName = currentRealmConfig.getName();
if (currentRealmName == null) {
throw new Exception("Missing realm name");
}
currentRealmName = currentRealmName.toUpperCase();
CredentialsValidator currentValidator = null;
try {
currentValidator = (CredentialsValidator) Class.forName(currentRealmConfig.getValidatorClass())
.newInstance();
} catch (Exception e) {
throw new Exception("invalid validator class fo realm " + currentRealmName, e);
}
currentValidator.configure(new ConfigProperties(currentRealmConfig.getProperties()));
if (newRealms.put(currentRealmConfig.getName().toUpperCase(), currentValidator) != null) {
throw new Exception("Duplicate realm " + currentRealmConfig.getName());
}
}
this.realms = newRealms;
List<UserToRolesMapper> newUserToRolesMapper = new ArrayList<>();
for (UserToRolesMapperConfig currentUserToRolesMapperConfig : config.getUserToRolesMappers()) {
UserToRolesMapper currentUserToRolesMapper = null;
try {
currentUserToRolesMapper = (UserToRolesMapper) Class
.forName(currentUserToRolesMapperConfig.getClassName()).newInstance();
} catch (Exception e) {
throw new Exception("Invalid class in UserToRolesMapperConfig", e);
}
currentUserToRolesMapper.configure(new ConfigProperties(currentUserToRolesMapperConfig.getProperties()));
newUserToRolesMapper.add(currentUserToRolesMapper);
}
this.userToRolesMappers = newUserToRolesMapper;
}
boolean updateRoles(AuthenticationInfo authenticationInfo, User user, Database database)
throws AuthenticationException {
boolean updatedDb = false;
Set<String> roles = new HashSet<>();
for (UserToRolesMapper currentUserToRolesMapper : userToRolesMappers) {
Collection<String> currentRoles = currentUserToRolesMapper.mapUserToRoles(authenticationInfo);
if (currentRoles != null && !currentRoles.isEmpty()) {
roles.addAll(currentRoles);
}
}
for (String currentRoleName : roles) {
if (currentRoleName == null || currentRoleName.isEmpty()) {
continue;
}
Role currentRole = database.findRole(currentRoleName);
if (currentRole == null && isCreateMissingRoles()) {
synchronized (database.getSystemSession()) {
currentRole = new Role(database, database.allocateObjectId(), currentRoleName, false);
database.addDatabaseObject(database.getSystemSession(), currentRole);
database.getSystemSession().commit(false);
updatedDb = true;
}
}
if (currentRole == null) {
continue;
}
if (user.getRightForRole(currentRole) == null) {
// NON PERSISTENT
Right currentRight = new Right(database, -1, user, currentRole);
currentRight.setTemporary(true);
user.grantRole(currentRole, currentRight);
}
}
return updatedDb;
}
@Override
public final User authenticate(AuthenticationInfo authenticationInfo, Database database)
throws AuthenticationException {
String userName = authenticationInfo.getFullyQualifiedName();
User user = database.findUser(userName);
if (user == null && !isAllowUserRegistration()) {
throw new AuthenticationException("User " + userName + " not found in db");
}
CredentialsValidator validator = realms.get(authenticationInfo.getRealm());
if (validator == null) {
throw new AuthenticationException("realm " + authenticationInfo.getRealm() + " not configured");
}
try {
if (!validator.validateCredentials(authenticationInfo)) {
return null;
}
} catch (Exception e) {
throw new AuthenticationException(e);
}
if (user == null) {
synchronized (database.getSystemSession()) {
user = UserBuilder.buildUser(authenticationInfo, database, isPersistUsers());
database.addDatabaseObject(database.getSystemSession(), user);
database.getSystemSession().commit(false);
}
}
user.revokeTemporaryRightsOnRoles();
updateRoles(authenticationInfo, user, database);
return user;
}
}
/*
* Copyright 2004-2018 H2 Group. Multiple-Licensed under the MPL 2.0,
* and the EPL 1.0 (http://h2database.com/html/license.html).
* Initial Developer: Alessandro Ventura
*/
package org.h2.security.auth;
import java.util.ArrayList;
import java.util.List;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
/**
* Describe configuration of H2 DefaultAuthenticator
*/
@XmlRootElement(name = "h2Auth")
@XmlAccessorType(XmlAccessType.FIELD)
public class H2AuthConfig {
@XmlAttribute
private boolean allowUserRegistration=true;
public boolean isAllowUserRegistration() {
return allowUserRegistration;
}
public void setAllowUserRegistration(boolean allowUserRegistration) {
this.allowUserRegistration = allowUserRegistration;
}
@XmlAttribute
boolean createMissingRoles=true;
public boolean isCreateMissingRoles() {
return createMissingRoles;
}
public void setCreateMissingRoles(boolean createMissingRoles) {
this.createMissingRoles = createMissingRoles;
}
@XmlElement(name = "realm")
List<RealmConfig> realms;
public List<RealmConfig> getRealms() {
if (realms == null) {
realms = new ArrayList<>();
}
return realms;
}
public void setRealms(List<RealmConfig> realms) {
this.realms = realms;
}
@XmlElement(name = "userToRolesMapper")
List<UserToRolesMapperConfig> userToRolesMappers;
public List<UserToRolesMapperConfig> getUserToRolesMappers() {
if (userToRolesMappers == null) {
userToRolesMappers = new ArrayList<>();
}
return userToRolesMappers;
}
public void setUserToRolesMappers(List<UserToRolesMapperConfig> userToRolesMappers) {
this.userToRolesMappers = userToRolesMappers;
}
}
/*
* Copyright 2004-2018 H2 Group. Multiple-Licensed under the MPL 2.0,
* and the EPL 1.0 (http://h2database.com/html/license.html).
* Initial Developer: Alessandro Ventura
*/
package org.h2.security.auth;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;
/**
* Configuration property
*/
@XmlAccessorType(XmlAccessType.FIELD)
public class PropertyConfig {
@XmlAttribute(required = true)
private String name;
@XmlAttribute
private String value;
public PropertyConfig() {
}
public PropertyConfig(String name, String value) {
this.name=name;
this.value=value;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}
/*
* Copyright 2004-2018 H2 Group. Multiple-Licensed under the MPL 2.0,
* and the EPL 1.0 (http://h2database.com/html/license.html).
* Initial Developer: Alessandro Ventura
*/
package org.h2.security.auth;
import java.util.ArrayList;
import java.util.List;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
@XmlAccessorType(XmlAccessType.FIELD)
public class RealmConfig {
@XmlAttribute(required = true)
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@XmlAttribute(required = true)
String validatorClass;
public String getValidatorClass() {
return validatorClass;
}
public void setValidatorClass(String validatorClass) {
this.validatorClass = validatorClass;
}
@XmlElement(name = "property")
List<PropertyConfig> properties;
public List<PropertyConfig> getProperties() {
if (properties == null) {
properties = new ArrayList<>();
}
return properties;
}
}
/*
* Copyright 2004-2018 H2 Group. Multiple-Licensed under the MPL 2.0,
* and the EPL 1.0 (http://h2database.com/html/license.html).
* Initial Developer: Alessandro Ventura
*/
package org.h2.security.auth;
import java.util.ArrayList;
import java.util.List;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
@XmlAccessorType(XmlAccessType.FIELD)
public class UserToRolesMapperConfig {
@XmlAttribute(required = true, name="class")
private String className;
@XmlElement(name = "property")
private List<PropertyConfig> properties;
public String getClassName() {
return className;
}
public void setClassName(String className) {
this.className = className;
}
public List<PropertyConfig> getProperties() {
if (properties == null) {
properties = new ArrayList<>();
}
return properties;
}
}
package org.h2.security.auth.impl;
import java.util.Arrays;
import java.util.Collection;
import org.h2.api.UserToRolesMapper;
import org.h2.security.auth.AuthenticationException;
import org.h2.security.auth.AuthenticationInfo;
import org.h2.security.auth.ConfigProperties;
/**
* Assign to user a role based on realm name
*
* * <p>
* Configuration parameters:
* </p>
* <ul>
* <li> roleNameFormat, optional by default is @{realm}</li>
* </ul>
*/
public class AssignRealmNameRole implements UserToRolesMapper{
private String roleNameFormat;
public AssignRealmNameRole() {
this("@%s");
}
public AssignRealmNameRole(String roleNameFormat) {
this.roleNameFormat = roleNameFormat;
}
@Override
public void configure(ConfigProperties configProperties) {
roleNameFormat=configProperties.getStringValue("roleNameFormat",roleNameFormat);
}
@Override
public Collection<String> mapUserToRoles(AuthenticationInfo authenticationInfo) throws AuthenticationException {
return Arrays.asList(String.format(roleNameFormat, authenticationInfo.getRealm()));
}
}
/*
* Copyright 2004-2018 H2 Group. Multiple-Licensed under the MPL 2.0,
* and the EPL 1.0 (http://h2database.com/html/license.html).
* Initial Developer: Alessandro Ventura
*/
package org.h2.security.auth.impl;
import java.io.IOException;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.NameCallback;
import javax.security.auth.callback.PasswordCallback;
import javax.security.auth.callback.UnsupportedCallbackException;
import javax.security.auth.login.LoginContext;
import org.h2.api.CredentialsValidator;
import org.h2.security.auth.AuthenticationInfo;
import org.h2.security.auth.ConfigProperties;
/**
* Validate credentials by using standard Java Authentication and Authorization Service
*
* <p>
* Configuration parameters:
* </p>
* <ul>
* <li>appName inside the JAAS configuration (by default h2)</li>
* </ul>
*
*/
public class JaasCredentialsValidator implements CredentialsValidator {
public static final String DEFAULT_APPNAME="h2";
private String appName;
public JaasCredentialsValidator() {
this(DEFAULT_APPNAME);
}
/**
* Create the validator with the given name of JAAS configuration
* @param appName = name of JAAS configuration
*/
public JaasCredentialsValidator(String appName) {
this.appName=appName;
}
@Override
public void configure(ConfigProperties configProperties) {
appName=configProperties.getStringValue("appName",appName);
}
class AuthenticationInfoCallbackHandler implements CallbackHandler {
AuthenticationInfo authenticationInfo;
AuthenticationInfoCallbackHandler(AuthenticationInfo authenticationInfo) {
this.authenticationInfo = authenticationInfo;
}
@Override
public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
for (int i = 0; i < callbacks.length; i++) {
if (callbacks[i] instanceof NameCallback) {
((NameCallback) callbacks[i]).setName(authenticationInfo.getUserName());
} else if (callbacks[i] instanceof PasswordCallback) {
((PasswordCallback) callbacks[i]).setPassword(authenticationInfo.getPassword().toCharArray());
}
}
}
}
@Override
public boolean validateCredentials(AuthenticationInfo authenticationInfo) throws Exception {
LoginContext loginContext = new LoginContext(appName,new AuthenticationInfoCallbackHandler(authenticationInfo));
loginContext.login();
authenticationInfo.setNestedIdentity(loginContext.getSubject());
return true;
}
}
/*
* Copyright 2004-2018 H2 Group. Multiple-Licensed under the MPL 2.0,
* and the EPL 1.0 (http://h2database.com/html/license.html).
* Initial Developer: Alessandro Ventura
*/
package org.h2.security.auth.impl;
import java.util.Hashtable;
import javax.naming.Context;
import javax.naming.directory.DirContext;
import javax.naming.directory.InitialDirContext;
import org.h2.api.CredentialsValidator;
import org.h2.security.auth.AuthenticationInfo;
import org.h2.security.auth.ConfigProperties;
/**
* Validate credentials by performing an LDAP bind
* <p>
* Configuration parameters:
* </p>
* <ul>
* <li>bindDnPattern bind dn pattern with %u instead of username (example: uid=%u,ou=users,dc=example,dc=com)</li>
* <li>host ldap server</li>
* <li>port of ldap service; optional, by default 389 for unsecure, 636 for secure</li>
* <li>secure, optional by default is true (use SSL)</li>
* </ul>
*/
public class LdapCredentialsValidator implements CredentialsValidator {
private String bindDnPattern;
private String host;
private int port;
private boolean secure;
private String url;
@Override
public void configure(ConfigProperties configProperties) {
bindDnPattern = configProperties.getStringValue("bindDnPattern");
host = configProperties.getStringValue("host");
secure = configProperties.getBooleanValue("secure", true);
port = configProperties.getIntValue("port", secure ? 636 : 389);
url = "ldap" + (secure ? "s" : "") + "://" + host + ":" + port;
}
@Override
public boolean validateCredentials(AuthenticationInfo authenticationInfo) throws Exception {
DirContext dirContext = null;
try {
String dn=bindDnPattern.replace("%u", authenticationInfo.getUserName());
Hashtable<String, String> env = new Hashtable<>();
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.PROVIDER_URL, url);
env.put(Context.SECURITY_AUTHENTICATION, "simple");
env.put(Context.SECURITY_PRINCIPAL, dn);
env.put(Context.SECURITY_CREDENTIALS, authenticationInfo.getPassword());
if (secure) {
env.put(Context.SECURITY_PROTOCOL,"ssl");
}
dirContext = new InitialDirContext(env);
authenticationInfo.setNestedIdentity(dn);
return true;
} finally {
if (dirContext != null) {
dirContext.close();
}
}
}
}
/*
* Copyright 2004-2018 H2 Group. Multiple-Licensed under the MPL 2.0,
* and the EPL 1.0 (http://h2database.com/html/license.html).
* Initial Developer: Alessandro Ventura
*/
package org.h2.security.auth.impl;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import org.h2.api.UserToRolesMapper;
import org.h2.security.auth.AuthenticationException;
import org.h2.security.auth.AuthenticationInfo;
import org.h2.security.auth.ConfigProperties;
/**
* Assign static roles to authenticated users
* <p>
* Configuration parameters:
* </p>
* <ul>
* <li>roles role list separated by comma</li>
* </ul>
*
*/
public class StaticRolesMapper implements UserToRolesMapper {
private Collection<String> roles;
public StaticRolesMapper() {
}
public StaticRolesMapper(String... roles) {
this.roles=Arrays.asList(roles);
}
@Override
public void configure(ConfigProperties configProperties) {
String rolesString=configProperties.getStringValue("roles", "");
if (rolesString!=null) {
roles = new HashSet<>(Arrays.asList(rolesString.split(",")));
}
}
@Override
public Collection<String> mapUserToRoles(AuthenticationInfo authenticationInfo) throws AuthenticationException {
return roles;
}
}
/*
* Copyright 2004-2018 H2 Group. Mul tiple-Licensed under the MPL 2.0,
* and the EPL 1.0 (http://h2database.com/html/license.html).
* Initial Developer: Alessandro Ventura
*/
package org.h2.security.auth.impl;
import java.util.regex.Pattern;
import org.h2.api.CredentialsValidator;
import org.h2.security.SHA256;
import org.h2.security.auth.AuthenticationException;
import org.h2.security.auth.AuthenticationInfo;
import org.h2.security.auth.ConfigProperties;
import org.h2.util.MathUtils;
import org.h2.util.StringUtils;
import org.h2.util.Utils;
/**
* This credentials validator matches the user and password with the configured
* Usage should be limited to test purposes
*
*/
public class StaticUserCredentialsValidator implements CredentialsValidator {
private Pattern userNamePattern;
private String password;
private byte[] salt;
private byte[] hashWithSalt;
public StaticUserCredentialsValidator() {
}
public StaticUserCredentialsValidator(String userNamePattern,String password) {
if (userNamePattern!=null) {
this.userNamePattern=Pattern.compile(userNamePattern.toUpperCase());
}
salt=MathUtils.secureRandomBytes(256);
hashWithSalt=SHA256.getHashWithSalt(password.getBytes(), salt);
}
@Override
public boolean validateCredentials(AuthenticationInfo authenticationInfo) throws AuthenticationException {
if (userNamePattern!=null) {
if (!userNamePattern.matcher(authenticationInfo.getUserName()).matches()) {
return false;
}
}
if (password!=null) {
return password.equals(authenticationInfo.getPassword());
}
return Utils.compareSecure(hashWithSalt,SHA256.getHashWithSalt(authenticationInfo.getPassword().getBytes(), salt));
}
@Override
public void configure(ConfigProperties configProperties) {
String userNamePatternString=configProperties.getStringValue("userNamePattern",null);
if (userNamePatternString!=null) {
userNamePattern=userNamePattern.compile(userNamePatternString);
}
password=configProperties.getStringValue("password",password);
String saltString =configProperties.getStringValue("salt",null);
if (saltString!=null) {
salt=StringUtils.convertHexToBytes(saltString);
}
String hashString=configProperties.getStringValue("hash", null);
if (hashString!=null) {
hashWithSalt = SHA256.getHashWithSalt(StringUtils.convertHexToBytes(hashString), salt);
}
}
}
...@@ -15,6 +15,7 @@ import org.h2.Driver; ...@@ -15,6 +15,7 @@ import org.h2.Driver;
import org.h2.engine.Constants; import org.h2.engine.Constants;
import org.h2.store.fs.FilePathRec; import org.h2.store.fs.FilePathRec;
import org.h2.store.fs.FileUtils; import org.h2.store.fs.FileUtils;
import org.h2.test.auth.TestAuthentication;
import org.h2.test.bench.TestPerformance; import org.h2.test.bench.TestPerformance;
import org.h2.test.db.TestAlter; import org.h2.test.db.TestAlter;
import org.h2.test.db.TestAlterSchemaRename; import org.h2.test.db.TestAlterSchemaRename;
...@@ -972,6 +973,7 @@ kill -9 `jps -l | grep "org.h2.test." | cut -d " " -f 1` ...@@ -972,6 +973,7 @@ kill -9 `jps -l | grep "org.h2.test." | cut -d " " -f 1`
addTest(new TestSampleApps()); addTest(new TestSampleApps());
addTest(new TestStringCache()); addTest(new TestStringCache());
addTest(new TestValueMemory()); addTest(new TestValueMemory());
addTest(new TestAuthentication());
runAddedTests(1); runAddedTests(1);
......
/*
* Copyright 2004-2018 H2 Group. Multiple-Licensed under the MPL 2.0,
* and the EPL 1.0 (http://h2database.com/html/license.html).
* Initial Developer: Alessandro Ventura
*/
package org.h2.test.auth;
import java.util.Map;
import javax.security.auth.Subject;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.NameCallback;
import javax.security.auth.callback.PasswordCallback;
import javax.security.auth.login.LoginException;
import javax.security.auth.spi.LoginModule;
/**
* Dummy login module used for test cases
*/
public class MyLoginModule implements LoginModule{
String password;
CallbackHandler callbackHandler;
@Override
public void initialize(Subject subject, CallbackHandler callbackHandler, Map<String, ?> sharedState,
Map<String, ?> options) {
this.callbackHandler=callbackHandler;
password=""+options.get("password");
}
@Override
public boolean login() throws LoginException {
if (callbackHandler==null) {
throw new LoginException("no callbackHandler available");
}
NameCallback nameCallback= new NameCallback("user name");
PasswordCallback passwordCallback= new PasswordCallback("user name",false);
try {
callbackHandler.handle(new Callback[] {nameCallback,passwordCallback});
} catch (Exception exception) {
throw new LoginException("an exception occurred during inquiry of username and password");
}
return password.equals(new String(passwordCallback.getPassword()));
}
@Override
public boolean logout() throws LoginException {
return true;
}
@Override
public boolean abort() throws LoginException {
return true;
}
@Override
public boolean commit() throws LoginException {
return true;
}
}
/*
* Copyright 2004-2018 H2 Group. Multiple-Licensed under the MPL 2.0,
* and the EPL 1.0 (http://h2database.com/html/license.html).
* Initial Developer: Alessandro Ventura
*/
package org.h2.test.auth;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Properties;
import java.util.UUID;
import javax.security.auth.login.AppConfigurationEntry;
import javax.security.auth.login.AppConfigurationEntry.LoginModuleControlFlag;
import javax.security.auth.login.Configuration;
import org.h2.engine.ConnectionInfo;
import org.h2.engine.Database;
import org.h2.engine.Engine;
import org.h2.engine.Role;
import org.h2.engine.Session;
import org.h2.engine.User;
import org.h2.security.auth.DefaultAuthenticator;
import org.h2.security.auth.impl.AssignRealmNameRole;
import org.h2.security.auth.impl.JaasCredentialsValidator;
import org.h2.security.auth.impl.StaticRolesMapper;
import org.h2.security.auth.impl.StaticUserCredentialsValidator;
import org.h2.test.TestBase;
public class TestAuthentication extends TestBase {
public static void main(String... a) throws Exception {
TestBase.createCaller().init().test();
}
String externalUserPassword;
String getExternalUserPassword() {
if (externalUserPassword == null) {
externalUserPassword = UUID.randomUUID().toString();
}
return externalUserPassword;
}
String getRealmName() {
return "testRealm";
}
String getJaasConfigName() {
return "testJaasH2";
}
String getStaticRoleName() {
return "staticRole";
}
DefaultAuthenticator defaultAuthenticator;
void configureAuthentication(Database database) {
defaultAuthenticator = new DefaultAuthenticator(true);
defaultAuthenticator.setAllowUserRegistration(true);
defaultAuthenticator.setCreateMissingRoles(true);
defaultAuthenticator.addRealm(getRealmName(), new JaasCredentialsValidator(getJaasConfigName()));
defaultAuthenticator.addRealm(getRealmName() + "_STATIC",
new StaticUserCredentialsValidator("staticuser[0-9]", "staticpassword"));
defaultAuthenticator.setUserToRolesMappers(new AssignRealmNameRole("@%s"),
new StaticRolesMapper(getStaticRoleName()));
database.setAuthenticator(defaultAuthenticator);
}
void configureJaas() {
final Configuration innerConfiguration = Configuration.getConfiguration();
Configuration.setConfiguration(new Configuration() {
@Override
public AppConfigurationEntry[] getAppConfigurationEntry(String name) {
if (name.equals(getJaasConfigName())) {
HashMap<String, String> options = new HashMap<>();
options.put("password", getExternalUserPassword());
return new AppConfigurationEntry[] { new AppConfigurationEntry(MyLoginModule.class.getName(),
LoginModuleControlFlag.REQUIRED, options) };
}
return innerConfiguration.getAppConfigurationEntry(name);
}
});
}
protected String getDatabaseURL() {
return "jdbc:h2:mem:" + getClass().getSimpleName();
}
protected String getExternalUser() {
return "user";
}
Session session;
Database database;
@Override
public void test() throws Exception {
Configuration oldConfiguration = Configuration.getConfiguration();
try {
configureJaas();
Properties properties = new Properties();
properties.setProperty("USER", "dba");
ConnectionInfo connectionInfo = new ConnectionInfo(getDatabaseURL(), properties);
session = Engine.getInstance().createSession(connectionInfo);
database = session.getDatabase();
configureAuthentication(database);
try {
allTests();
} finally {
session.close();
}
} finally {
Configuration.setConfiguration(oldConfiguration);
}
}
protected void allTests() throws Exception {
testInvalidPassword();
testExternalUserWihoutRealm();
testExternalUser();
testAssignRealNameRole();
testStaticRole();
testStaticUserCredentials();
testUserRegistration();
testSet();
}
protected void testInvalidPassword() throws Exception {
try {
Connection wrongLoginConnection = DriverManager.getConnection(
getDatabaseURL() + ";AUTHREALM=" + getRealmName().toUpperCase(), getExternalUser(), "");
wrongLoginConnection.close();
throw new Exception("user should not be able to login with an invalid password");
} catch (SQLException e) {
}
}
protected void testExternalUserWihoutRealm() throws Exception {
try {
Connection wrongLoginConnection = DriverManager.getConnection(getDatabaseURL(), getExternalUser(),
getExternalUserPassword());
wrongLoginConnection.close();
throw new Exception("user should not be able to login without a realm");
} catch (SQLException e) {
}
}
protected void testExternalUser() throws Exception {
Connection rightConnection = DriverManager.getConnection(
getDatabaseURL() + ";AUTHREALM=" + getRealmName().toUpperCase(), getExternalUser(),
getExternalUserPassword());
try {
User user = session.getDatabase().findUser((getExternalUser() + "@" + getRealmName()).toUpperCase());
assertNotNull(user);
} finally {
rightConnection.close();
}
}
protected void testAssignRealNameRole() throws Exception {
String realmNameRoleName = "@" + getRealmName().toUpperCase();
Role realmNameRole = database.findRole(realmNameRoleName);
if (realmNameRole == null) {
realmNameRole = new Role(database, database.allocateObjectId(), realmNameRoleName, false);
session.getDatabase().addDatabaseObject(session, realmNameRole);
session.commit(false);
}
Connection rightConnection = DriverManager.getConnection(
getDatabaseURL() + ";AUTHREALM=" + getRealmName().toUpperCase(), getExternalUser(),
getExternalUserPassword());
try {
User user = session.getDatabase().findUser((getExternalUser() + "@" + getRealmName()).toUpperCase());
assertNotNull(user);
assertTrue(user.isRoleGranted(realmNameRole));
} finally {
rightConnection.close();
}
}
protected void testStaticRole() throws Exception {
Connection rightConnection = DriverManager.getConnection(
getDatabaseURL() + ";AUTHREALM=" + getRealmName().toUpperCase(), getExternalUser(),
getExternalUserPassword());
try {
User user = session.getDatabase().findUser((getExternalUser() + "@" + getRealmName()).toUpperCase());
assertNotNull(user);
Role staticRole = session.getDatabase().findRole(getStaticRoleName());
if (staticRole != null) {
assertTrue(user.isRoleGranted(staticRole));
}
} finally {
rightConnection.close();
}
}
protected void testUserRegistration() throws Exception {
boolean initialValueAllow = defaultAuthenticator.isAllowUserRegistration();
defaultAuthenticator.setAllowUserRegistration(false);
try {
try {
Connection wrongLoginConnection = DriverManager.getConnection(
getDatabaseURL() + ";AUTHREALM=" + getRealmName().toUpperCase(), "___" + getExternalUser(), "");
wrongLoginConnection.close();
throw new Exception(
"unregistered external users should not be able to login when allowUserRegistration=false");
} catch (SQLException e) {
}
String validUserName = "new_" + getExternalUser();
User validUser = new User(database, database.allocateObjectId(),
(validUserName.toUpperCase() + "@" + getRealmName()).toUpperCase(), false);
validUser.setUserPasswordHash(new byte[] {});
database.addDatabaseObject(session, validUser);
session.commit(false);
Connection connectionWithRegisterUser = DriverManager.getConnection(
getDatabaseURL() + ";AUTHREALM=" + getRealmName().toUpperCase(), validUserName,
getExternalUserPassword());
connectionWithRegisterUser.close();
} finally {
defaultAuthenticator.setAllowUserRegistration(initialValueAllow);
}
}
public void testStaticUserCredentials() throws Exception {
String userName="STATICUSER3";
Connection rightConnection = DriverManager.getConnection(
getDatabaseURL() + ";AUTHREALM=" + getRealmName().toUpperCase()+"_STATIC",userName,
"staticpassword");
try {
User user = session.getDatabase().findUser(userName+ "@" + getRealmName().toUpperCase()+"_STATIC");
assertNotNull(user);
} finally {
rightConnection.close();
}
}
protected void testSet() throws Exception{
Connection rightConnection = DriverManager.getConnection(
getDatabaseURL()+";AUTHENTICATOR=FALSE","DBA","");
try {
try {
testExternalUser();
throw new Exception("External user shouldnt be allowed");
}catch (Exception e) {
}
} finally {
configureAuthentication(database);
rightConnection.close();
}
testExternalUser();
}
}
\ No newline at end of file
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论