提交 b940440b authored 作者: andrei's avatar andrei

Merge remote-tracking branch 'h2database/master' into txcommit-atomic

......@@ -45,11 +45,7 @@ SELECT * FROM (SELECT ID, COUNT(*) FROM TEST
"
"Commands (DML)","INSERT","
INSERT INTO tableName
{ [ ( columnName [,...] ) ]
{ VALUES { ( { DEFAULT | expression } [,...] ) } [,...]
| [ DIRECT ] [ SORTED ] select } } |
{ SET { columnName = { DEFAULT | expression } } [,...] }
INSERT INTO tableName insertColumnsAndSource
","
Inserts a new row / new rows into a table.
......@@ -61,9 +57,7 @@ INSERT INTO TEST VALUES(1, 'Hello')
"
"Commands (DML)","UPDATE","
UPDATE tableName [ [ AS ] newTableAlias ] SET
{ { columnName = { DEFAULT | expression } } [,...] } |
{ ( columnName [,...] ) = ( select ) }
UPDATE tableName [ [ AS ] newTableAlias ] SET setClauseList
[ WHERE expression ] [ ORDER BY order [,...] ] [ LIMIT expression ]
","
Updates data in a table.
......@@ -74,7 +68,7 @@ UPDATE PERSON P SET NAME=(SELECT A.NAME FROM ADDRESS A WHERE A.ID=P.ID);
"
"Commands (DML)","DELETE","
DELETE [ TOP term ] FROM tableName [ WHERE expression ] [ LIMIT term ]
DELETE [ TOP term ] FROM tableName deleteSearchCondition
","
Deletes rows form a table.
If TOP or LIMIT is specified, at most the specified number of rows are deleted (no limit if null or smaller than zero).
......@@ -129,16 +123,19 @@ MERGE INTO TEST KEY(ID) VALUES(2, 'World')
MERGE INTO targetTableName [ [AS] targetAlias]
USING { ( select ) | sourceTableName }[ [AS] sourceAlias ]
ON ( expression )
[ WHEN MATCHED THEN [ update ] [ delete] ]
[ WHEN NOT MATCHED THEN insert ]
[ WHEN MATCHED THEN
[ UPDATE SET setClauseList ] [ DELETE deleteSearchCondition ] ]
[ WHEN NOT MATCHED THEN INSERT insertColumnsAndSource ]
","
Updates or deletes existing rows, and insert rows that don't exist. The ON clause
specifies the matching column expression and must be specified. If more than one row
is updated per input row, an exception is thrown.
If the source data contains duplicate rows (specifically those columns used in the
row matching ON clause), then an exception is thrown to prevent two updates applying
to the same target row. The embedded update, delete or insert statements can not re-specify
the target table name.
to the same target row.
WHEN MATCHED THEN or WHEN NOT MATCHED THEN clauses or both of them in any order should be specified.
If WHEN MATCHED THEN is specified it should contain UPDATE or DELETE clauses of both of them.
If statement doesn't need a source table a DUAL table can be substituted.
","
MERGE INTO TARGET_TABLE AS T USING SOURCE_TABLE AS S
ON (T.ID = S.ID)
......@@ -154,6 +151,9 @@ MERGE INTO TARGET_TABLE AS T USING (SELECT * FROM SOURCE_TABLE) AS S
DELETE WHERE T.COL2='FINAL'
WHEN NOT MATCHED THEN
INSERT (ID,COL1,COL2) VALUES(S.ID,S.COL1,S.COL2)
MERGE INTO TARGET_TABLE USING DUAL ON (ID = 1)
WHEN NOT MATCHED THEN INSERT (ID, NAME) VALUES (1, 'Test')
WHEN MATCHED THEN UPDATE SET NAME = 'Test'
"
"Commands (DML)","RUNSCRIPT","
......@@ -2252,6 +2252,14 @@ SELECT CAST(0 AS DOUBLE)
SELECT -1.4e-10
"
"Other Grammar","Delete search condition","
[ WHERE expression ] [ LIMIT term ]
","
Search condition for DELETE statement.
","
WHERE ID = 2
"
"Other Grammar","Digit","
0-9
","
......@@ -2313,6 +2321,17 @@ the column in the same way.
NAME
"
"Other Grammar","Insert columns and source","
{ [ ( columnName [,...] ) ]
{ VALUES { ( { DEFAULT | expression } [,...] ) } [,...]
| [ DIRECT ] [ SORTED ] select } } |
{ SET { columnName = { DEFAULT | expression } } [,...] }
","
Names of columns and their values for INSERT statement.
","
(ID, NAME) VALUES (1, 'Test')
"
"Other Grammar","Int","
[ + | - ] number
","
......@@ -2438,6 +2457,15 @@ An expression in a SELECT statement.
ID AS VALUE
"
"Other Grammar","Set clause list","
{ { columnName = { DEFAULT | expression } } [,...] } |
{ ( columnName [,...] ) = ( select ) }
","
List of SET clauses.
","
NAME = 'Test', VALUE = 2
"
"Other Grammar","String","
'anythingExceptSingleQuote'
","
......
......@@ -83,6 +83,8 @@ Features
Compacting a Database</a><br />
<a href="#cache_settings">
Cache Settings</a><br />
<a href="#external_authentication">
External Authentication (Experimental)</a><br />
<h2 id="feature_list">Feature List</h2>
<h3>Main Features</h3>
......@@ -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
is listed.
</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>
/*
* 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 {
*/
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() {
// 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;
}
......@@ -48,8 +48,8 @@ public abstract class Command implements CommandInterface {
private boolean canReuse;
Command(Parser parser, String sql) {
this.session = parser.getSession();
Command(Session session, String sql) {
this.session = session;
this.sql = sql;
trace = session.getDatabase().getTrace(Trace.COMMAND);
}
......
......@@ -9,6 +9,7 @@ import java.util.ArrayList;
import org.h2.api.DatabaseEventListener;
import org.h2.command.dml.Explain;
import org.h2.command.dml.Query;
import org.h2.engine.Session;
import org.h2.expression.Parameter;
import org.h2.expression.ParameterInterface;
import org.h2.result.ResultInterface;
......@@ -26,8 +27,8 @@ public class CommandContainer extends Command {
private boolean readOnlyKnown;
private boolean readOnly;
CommandContainer(Parser parser, String sql, Prepared prepared) {
super(parser, sql);
CommandContainer(Session session, String sql, Prepared prepared) {
super(session, sql);
prepared.setCommand(this);
this.prepared = prepared;
}
......
......@@ -6,6 +6,8 @@
package org.h2.command;
import java.util.ArrayList;
import org.h2.engine.Session;
import org.h2.expression.ParameterInterface;
import org.h2.result.ResultInterface;
......@@ -17,8 +19,8 @@ class CommandList extends Command {
private final Command command;
private final String remaining;
CommandList(Parser parser, String sql, Command c, String remaining) {
super(parser, sql);
CommandList(Session session, String sql, Command c, String remaining) {
super(session, sql);
this.command = c;
this.remaining = remaining;
}
......
......@@ -390,31 +390,21 @@ public class MergeUsing extends Prepared {
query.prepare();
}
int embeddedStatementsCount = 0;
// Prepare each of the sub-commands ready to aid in the MERGE
// collaboration
if (updateCommand != null) {
updateCommand.setSourceTableFilter(sourceTableFilter);
updateCommand.setCondition(appendOnCondition(updateCommand));
updateCommand.prepare();
embeddedStatementsCount++;
}
if (deleteCommand != null) {
deleteCommand.setSourceTableFilter(sourceTableFilter);
deleteCommand.setCondition(appendOnCondition(deleteCommand));
deleteCommand.prepare();
embeddedStatementsCount++;
}
if (insertCommand != null) {
insertCommand.setSourceTableFilter(sourceTableFilter);
insertCommand.prepare();
embeddedStatementsCount++;
}
if (embeddedStatementsCount == 0) {
throw DbException.get(ErrorCode.SYNTAX_ERROR_1,
"At least UPDATE, DELETE or INSERT embedded statement must be supplied.");
}
// setup the targetMatchQuery - for detecting if the target row exists
......
......@@ -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.AuthenticatorFactory;
import org.h2.table.Table;
import org.h2.tools.CompressTool;
import org.h2.util.JdbcUtils;
......@@ -540,6 +542,25 @@ public class Set extends Prepared {
session.getColumnNamerConfiguration().configure(expression.getColumnName());
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:
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", "AUTHZPWD" };
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() || isSSL()) && prop.containsKey("AUTHREALM") && password!=null) {
prop.put("AUTHZPWD",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("AUTHZPWD", 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.DatabaseEventListener;
import org.h2.api.ErrorCode;
import org.h2.api.JavaObjectSerializer;
......@@ -43,6 +44,8 @@ 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.Authenticator;
import org.h2.security.auth.AuthenticatorFactory;
import org.h2.store.DataHandler;
import org.h2.store.FileLock;
import org.h2.store.FileLockMethod;
......@@ -225,6 +228,8 @@ public class Database implements DataHandler {
private QueryStatisticsData queryStatisticsData;
private RowFactory rowFactory = RowFactory.DEFAULT;
private Authenticator authenticator;
public Database(ConnectionInfo ci, String cipher) {
if (ASSERT) {
META_LOCK_DEBUGGING.set(null);
......@@ -2973,4 +2978,23 @@ public class Database implements DataHandler {
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;
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.security.auth.Authenticator;
import org.h2.store.FileLock;
import org.h2.store.FileLockMethod;
import org.h2.util.MathUtils;
......@@ -90,12 +93,28 @@ public class Engine implements SessionFactory {
}
if (user == null) {
if (database.validateFilePasswordHash(cipher, ci.getFilePasswordHash())) {
if (ci.getProperty("AUTHREALM")== null) {
user = database.findUser(ci.getUserName());
if (user != null) {
if (!user.validateUserPasswordHash(ci.getUserPasswordHash())) {
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())) {
// reset - because the user is not an admin, and has no
......@@ -110,6 +129,8 @@ public class Engine implements SessionFactory {
database.removeSession(null);
throw er;
}
//Prevent to set _PASSWORD
ci.cleanAuthenticationInfo();
checkClustering(ci, database);
Session session = database.createSession(user);
if (session == null) {
......
......@@ -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()) {
rolesToRemove.add(currentEntry.getKey());
}
}
for (Role currentRoleToRemove : rolesToRemove) {
revokeRole(currentRoleToRemove);
}
}
/**
* Get the 'grant schema' right of this object.
*
......
......@@ -555,6 +555,16 @@ public class SysProperties {
public static final String CUSTOM_DATA_TYPES_HANDLER =
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 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 @@
90141=Serializer cannot be changed because there is a data table: {0}
90142=Step size must not be zero
90143=Row {1} not found in primary index {0}
90144=Authenticator not enabled on database {0}
HY000=General error: {0}
HY004=Unknown data type: {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.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;
import org.h2.engine.Constants;
import org.h2.store.fs.FilePathRec;
import org.h2.store.fs.FileUtils;
import org.h2.test.auth.TestAuthentication;
import org.h2.test.bench.TestPerformance;
import org.h2.test.db.TestAlter;
import org.h2.test.db.TestAlterSchemaRename;
......@@ -972,6 +973,7 @@ kill -9 `jps -l | grep "org.h2.test." | cut -d " " -f 1`
addTest(new TestSampleApps());
addTest(new TestStringCache());
addTest(new TestValueMemory());
addTest(new TestAuthentication());
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;
}
}
......@@ -161,7 +161,7 @@ public class TestMergeUsing extends TestBase implements Trigger {
GATHER_ORDERED_RESULTS_SQL,
"SELECT X AS ID, 'Marcy'||X AS NAME FROM SYSTEM_RANGE(1,3) WHERE X<0",
0,
"At least UPDATE, DELETE or INSERT embedded statement must be supplied.");
"expected \"WHEN\"");
// Two updates to same row - update and delete together - emptying the
// parent table
testMergeUsing(
......
......@@ -149,3 +149,29 @@ SELECT * FROM TEST ORDER BY C1, C2;
DROP TABLE TEST;
> ok
CREATE TABLE TEST (ID INT, VALUE INT);
> ok
MERGE INTO TEST USING DUAL ON (ID = 1)
WHEN MATCHED THEN UPDATE SET VALUE = 1
WHEN;
> exception SYNTAX_ERROR_2
MERGE INTO TEST USING DUAL ON (ID = 1)
WHEN MATCHED THEN UPDATE SET VALUE = 1
WHEN NOT MATCHED THEN;
> exception SYNTAX_ERROR_2
MERGE INTO TEST USING DUAL ON (ID = 1)
WHEN NOT MATCHED THEN INSERT (ID, VALUE) VALUES (1, 1)
WHEN;
> exception SYNTAX_ERROR_2
MERGE INTO TEST USING DUAL ON (ID = 1)
WHEN NOT MATCHED THEN INSERT (ID, VALUE) VALUES (1, 1)
WHEN MATCHED THEN;
> exception SYNTAX_ERROR_2
DROP TABLE TEST;
> ok
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论