Unverified 提交 78b5a2dc authored 作者: Alessandro Ventura's avatar Alessandro Ventura 提交者: GitHub

External authentication

Added support for custom authentication at database level
上级 2bb8ab6a
......@@ -83,6 +83,8 @@ Features
Compacting a Database</a><br />
<a href="#cache_settings">
Cache Settings</a><br />
<a href="#external_authentication">
External Authentication</a><br />
<h2 id="feature_list">Feature List</h2>
<h3>Main Features</h3>
......@@ -1826,6 +1828,80 @@ 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
is listed.
</p>
<h2 id="external_authentication">External authentication</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
</p>
<p>Master user cannot be externally authenticated</p>
<p>
To enable external authentication on a database execute statement <code>SET AUTHENTICATOR='on'</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 trough 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 putting <code>h2auth.xml</code> file in the classpath or 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>
/*
* 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.engine.Database;
import org.h2.engine.User;
import org.h2.security.auth.AuthConfigException;
import org.h2.security.auth.AuthenticationException;
import org.h2.security.auth.AuthenticationInfo;
/**
* 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.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.
* It is users by DefaultAuthenticator
*/
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;
}
/*
* 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.
* It is used by DefaultAuthenticator
*/
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;
import org.h2.expression.Expression;
import org.h2.expression.ValueExpression;
import org.h2.message.DbException;
import org.h2.message.Trace;
import org.h2.result.ResultInterface;
import org.h2.result.RowFactory;
import org.h2.schema.Schema;
import org.h2.security.auth.AuthenticatorBuilder;
import org.h2.table.Table;
import org.h2.tools.CompressTool;
import org.h2.util.JdbcUtils;
......@@ -540,6 +542,22 @@ public class Set extends Prepared {
session.getColumnNamerConfiguration().configure(expression.getColumnName());
break;
}
case SetTypes.AUTHENTICATOR: {
session.getUser().checkAdmin();
String authenticatorString=expression.getValue(session).getString();
try {
database.setAuthenticator(AuthenticatorBuilder.buildAuthenticator(authenticatorString));
addOrUpdateSetting(name,"'"+authenticatorString+"'",0);
} catch (Exception e) {
//Errors during start are ignored to allow to open the database
if (database.isStarting()) {
database.getTrace(Trace.DATABASE).error(e, "SET AUTHENTICATOR: failed to set authenticator {0} during database start",authenticatorString);
} else {
throw DbException.convert(e);
}
}
break;
}
default:
DbException.throwInternalError("type="+type);
}
......
......@@ -252,7 +252,12 @@ public class SetTypes {
*/
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;
......@@ -311,6 +316,7 @@ public class SetTypes {
list.add(LAZY_QUERY_EXECUTION, "LAZY_QUERY_EXECUTION");
list.add(BUILTIN_ALIAS_OVERRIDE, "BUILTIN_ALIAS_OVERRIDE");
list.add(COLUMN_NAME_RULES, "COLUMN_NAME_RULES");
list.add(AUTHENTICATOR, "AUTHENTICATOR");
TYPES = list;
}
......
......@@ -74,9 +74,9 @@ public class ConnectionInfo implements Cloneable {
readProperties(info);
readSettingsFromURL();
setUserName(removeProperty("USER", ""));
convertPasswords();
name = url.substring(Constants.START_URL.length());
parseName();
convertPasswords();
String recoverTest = removeProperty("RECOVER_TEST", null);
if (recoverTest != null) {
FilePathRec.register();
......@@ -95,7 +95,7 @@ public class ConnectionInfo implements Cloneable {
"IFEXISTS", "INIT", "PASSWORD", "RECOVER", "RECOVER_TEST",
"USER", "AUTO_SERVER", "AUTO_SERVER_PORT", "NO_UPGRADE",
"AUTO_RECONNECT", "OPEN_NEW", "PAGE_SIZE", "PASSWORD_HASH", "JMX",
"SCOPE_GENERATED_KEYS" };
"SCOPE_GENERATED_KEYS", "AUTHREALM", "_PASSWORD" };
HashSet<String> set = new HashSet<>(128);
set.addAll(SetTypes.getTypes());
for (String key : connectionTime) {
......@@ -274,8 +274,15 @@ public class ConnectionInfo implements Cloneable {
}
}
private void preservePasswordForAuthentication(Object password) {
if ((isRemote() ==false || isSSL()) && prop.containsKey("AUTHREALM") && password!=null) {
prop.put("_PASSWORD",password);
}
}
private char[] removePassword() {
Object p = prop.remove("PASSWORD");
preservePasswordForAuthentication(p);
if (p == null) {
return new char[0];
} else if (p instanceof char[]) {
......@@ -657,4 +664,8 @@ public class ConnectionInfo implements Cloneable {
return url;
}
public void cleanAuthenticationInfo() {
removeProperty("AUTHREALM", false);
removeProperty("_PASSWORD", false);
}
}
......@@ -20,6 +20,7 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
import org.h2.api.Authenticator;
import org.h2.api.DatabaseEventListener;
import org.h2.api.ErrorCode;
import org.h2.api.JavaObjectSerializer;
......@@ -43,6 +44,7 @@ import org.h2.schema.Schema;
import org.h2.schema.SchemaObject;
import org.h2.schema.Sequence;
import org.h2.schema.TriggerObject;
import org.h2.security.auth.AuthenticatorBuilder;
import org.h2.store.DataHandler;
import org.h2.store.FileLock;
import org.h2.store.FileLockMethod;
......@@ -207,6 +209,9 @@ public class Database implements DataHandler {
private QueryStatisticsData queryStatisticsData;
private RowFactory rowFactory = RowFactory.DEFAULT;
private Authenticator authenticator;
private boolean authenticatorInitialized;
public Database(ConnectionInfo ci, String cipher) {
META_LOCK_DEBUGGING.set(null);
META_LOCK_DEBUGGING_DB.set(null);
......@@ -2947,4 +2952,42 @@ public class Database implements DataHandler {
return engine;
}
private void initAuthenticator() {
if (authenticatorInitialized) {
return;
}
try {
String authenticatorString=null;
Setting authenticatorSetting =findSetting("AUTHENTICATOR");
if (authenticatorSetting!=null) {
authenticatorString = authenticatorSetting.getStringValue();
}
Authenticator authenticator=AuthenticatorBuilder.buildAuthenticator(authenticatorString);
setAuthenticator(authenticator);
} catch (Exception e) {
throw DbException.convert(e);
}
}
/**
* get authenticator for database users
* @return authenticator set for database
*/
public Authenticator getAuthenticator() {
initAuthenticator();
return authenticator == null ? InternalAuthenticator.INSTANCE : 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;
authenticatorInitialized = true;
}
}
......@@ -14,6 +14,8 @@ import org.h2.command.Parser;
import org.h2.command.dml.SetTypes;
import org.h2.message.DbException;
import org.h2.message.Trace;
import org.h2.security.auth.AuthenticationException;
import org.h2.security.auth.AuthenticationInfo;
import org.h2.store.FileLock;
import org.h2.store.FileLockMethod;
import org.h2.util.MathUtils;
......@@ -53,6 +55,7 @@ public class Engine implements SessionFactory {
boolean openNew = ci.getProperty("OPEN_NEW", false);
boolean opened = false;
User user = null;
AuthenticationInfo authenticationInfo=new AuthenticationInfo(ci);
synchronized (DATABASES) {
if (openNew || ci.isUnnamedInMemory()) {
database = null;
......@@ -90,11 +93,12 @@ public class Engine implements SessionFactory {
}
if (user == null) {
if (database.validateFilePasswordHash(cipher, ci.getFilePasswordHash())) {
user = database.findUser(ci.getUserName());
if (user != null) {
if (!user.validateUserPasswordHash(ci.getUserPasswordHash())) {
user = null;
}
try {
user = database.getAuthenticator().authenticate(authenticationInfo, database);
} catch (AuthenticationException authenticationError) {
database.getTrace(Trace.DATABASE).error(authenticationError,
"an error occurred during authentication; user: \"" +
ci.getUserName() + "\"",ci.getUserName());
}
}
if (opened && (user == null || !user.isAdmin())) {
......@@ -103,6 +107,7 @@ public class Engine implements SessionFactory {
database.setEventListener(null);
}
}
ci.cleanAuthenticationInfo();
if (user == null) {
DbException er = DbException.get(ErrorCode.WRONG_USER_OR_PASSWORD);
database.getTrace(Trace.DATABASE).error(er, "wrong user or password; 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.engine;
import org.h2.api.Authenticator;
import org.h2.security.auth.AuthConfigException;
import org.h2.security.auth.AuthenticationException;
import org.h2.security.auth.AuthenticationInfo;
/**
* Default authentication implementation. It validate user and password internally on database
*/
public class InternalAuthenticator implements Authenticator {
public static final InternalAuthenticator INSTANCE = new InternalAuthenticator();
@Override
public User authenticate(AuthenticationInfo authenticationInfo, Database database) throws AuthenticationException {
User user = database.findUser(authenticationInfo.getUserName());
if (user != null) {
if (!user.validateUserPasswordHash(authenticationInfo.getConnectionInfo().getUserPasswordHash())) {
user = null;
}
}
return user;
}
@Override
public void init(Database database) throws AuthConfigException {
}
}
......@@ -5,7 +5,10 @@
*/
package org.h2.engine;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map.Entry;
import org.h2.table.Table;
......@@ -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()==false) {
rolesToRemove.add(currentEntry.getKey());
}
}
for (Role currentRoleToRemove : rolesToRemove) {
revokeRole(currentRoleToRemove);
}
}
/**
* Get the 'grant schema' right of this object.
*
......
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 == false);
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;
/**
* 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 {
ConnectionInfo connectionInfo;
String password;
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("_PASSWORD", 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.api.Authenticator;
public class AuthenticatorBuilder {
private static String getAuthenticatorClassNameFrom(String authenticatorString) {
if (authenticatorString==null) {
return null;
}
switch (authenticatorString) {
case "":
case "0":
case "no":
case "off":
case "disable":
case "false":
return null;
case "1":
case "yes":
case "on":
case "enable":
case "true":
case "default":
return "org.h2.security.auth.DefaultAuthenticator";
default:
return authenticatorString;
}
}
public static Authenticator buildAuthenticator(String authenticatorStringValue) throws Exception {
String authenticatorClassName=getAuthenticatorClassNameFrom(authenticatorStringValue);
return authenticatorClassName==null ? null : (Authenticator) Class.forName(authenticatorClassName).newInstance();
}
}
/*
* 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;
/**
* wrapper for configuration properties
*/
public class ConfigProperties {
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;
}
switch (result) {
case "true":
case "yes":
case "1":
return true;
default:
return false;
}
}
}
/*
* 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.Authenticator;
import org.h2.api.CredentialsValidator;
import org.h2.api.UserToRolesMapper;
import org.h2.engine.Database;
import org.h2.engine.InternalAuthenticator;
import org.h2.engine.Right;
import org.h2.engine.Role;
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 implementation of authenticator. Credentials (typically user id and
* password) are validated by CredentialsValidators (one per realm). Rights on
* the database can be managed trough UserToRolesMapper.
*
*/
public class DefaultAuthenticator implements Authenticator {
public static final String DEFAULT_REALMNAME = "H2";
Map<String, CredentialsValidator> realms = new HashMap<>();
List<UserToRolesMapper> userToRolesMappers = new ArrayList<>();
boolean allowUserRegistration;
boolean persistUsers;
boolean createMissingRoles;
boolean skipDefaultInitialization;
boolean initialized;
/**
* 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
* 1. Check h2auth.configurationFile system property.
* 2. Check h2auth.xml in the classpath
* 3. Use the default configuration hard coded
* initialization
* @param database
* @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 = System.getProperty("h2auth.configurationFile", null);
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) {
h2AuthenticatorConfigurationUrl = Thread.currentThread().getContextClassLoader()
.getResource("h2auth.xml");
if (h2AuthenticatorConfigurationUrl!=null) {
if (trace.isDebugEnabled()) {
trace.debug("DefaultAuthenticator.config: configuration read from classpath {0}", h2AuthenticatorConfigurationUrl);
}
}
}
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() == false) {
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 {
//Allows internal users authentication
if (authenticationInfo.getRealm()==null) {
return InternalAuthenticator.INSTANCE.authenticate(authenticationInfo, database);
}
String userName = authenticationInfo.getFullyQualifiedName();
User user = database.findUser(userName);
if (user == null && isAllowUserRegistration() == false) {
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) == false) {
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
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)
String name;
@XmlAttribute
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)
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")
String className;
@XmlElement(name = "property")
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
*
* by default role name is @{realm}
*/
public class AssignRealmNameRole implements UserToRolesMapper{
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
*
* configuration parameters:
* appName = application name inside the JAAS configuration (by default h2)
*
*/
public class JaasCredentialsValidator implements CredentialsValidator {
public static final String DEFAULT_APPNAME="h2";
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
*
* configuration parameters:
* bindDnPattern = bind dn pattern with %u istead of username (example: uid=%u,ou=users,dc=example,dc=com)
* host = ldap host
* port (optional) = ldap port (by default 389 for unsecure, 636 for secure)
* secure (optional) = use ssl (default true)
*/
public class LdapCredentialsValidator implements CredentialsValidator {
String bindDnPattern;
String host;
int port;
boolean secure;
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
*
* parameters:
* roles = role list separated by comma
*/
public class StaticRolesMapper implements UserToRolesMapper {
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 {
Pattern userNamePattern;
String password;
byte[] salt;
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()==false) {
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);
}
}
}
/*
* 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;
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();
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();
}
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();
}
}
}
\ No newline at end of file
/*
* 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.Properties;
import javax.security.auth.login.Configuration;
import org.h2.api.Authenticator;
import org.h2.engine.ConnectionInfo;
import org.h2.engine.Database;
import org.h2.engine.Engine;
import org.h2.engine.Role;
import org.h2.engine.User;
import org.h2.security.auth.AuthConfigException;
import org.h2.security.auth.AuthenticationException;
import org.h2.security.auth.AuthenticationInfo;
import org.h2.security.auth.DefaultAuthenticator;
import org.h2.security.auth.impl.JaasCredentialsValidator;
import org.h2.test.TestBase;
public class TestAuthenticationDefaults extends TestAuthentication {
public static void main(String... a) throws Exception {
TestBase.createCaller().init().test();
}
@Override
String getRealmName() {
return DefaultAuthenticator.DEFAULT_REALMNAME;
}
@Override
String getJaasConfigName() {
return JaasCredentialsValidator.DEFAULT_APPNAME;
}
@Override
void configureAuthentication(Database database) {
database.setAuthenticator(new DefaultAuthenticator());
}
@Override
public void test() throws Exception {
Configuration oldConfiguration = Configuration.getConfiguration();
try {
configureJaas();
Properties properties = new Properties();
ConnectionInfo connectionInfo = new ConnectionInfo(getDatabaseURL(), properties);
session = Engine.getInstance().createSession(connectionInfo);
database = session.getDatabase();
configureAuthentication(database);
try {
testInvalidPassword();
testExternalUserWihoutRealm();
testExternalUser();
testAssignRealNameRole();
} finally {
session.close();
}
} finally {
Configuration.setConfiguration(oldConfiguration);
}
}
}
\ No newline at end of file
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论