提交 11b94d86 authored 作者: mysinmyc's avatar mysinmyc

fix and improvements

上级 10123530
......@@ -1836,7 +1836,7 @@ 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.
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>
......@@ -1857,7 +1857,7 @@ h2 {
};
</pre>
<p>
Is it possible to specify custom authentication settings by putting <code>h2auth.xml</code> file in the classpath or by using
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>
......@@ -1896,11 +1896,11 @@ JVM argument <code>-Dh2auth.configurationFile={urlOfH2Auth.xml}</code>. Here an
</pre>
<p>
Custom credentials validators must implement the interface
<code>org.h2.api.CredentialsValidator</code>
<code>org.h2.security.auth.CredentialsValidator</code>
</p>
<p>
Custom criteria for role assignments must implement the interface
<code>org.h2.api.UserToRoleMapper</code>
<code>org.h2.security.auth.UserToRoleMapper</code>
</p>
<!-- [close] { --></div></td></tr></table><!-- } --><!-- analytics --></body></html>
......
......@@ -11,7 +11,6 @@ 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 {
......@@ -19,7 +18,7 @@ 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
* @throws Exception any exception occurred (invalid credentials or internal issue) prevent user login
*/
boolean validateCredentials(AuthenticationInfo authenticationInfo) throws Exception;
......
......@@ -15,7 +15,6 @@ 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 {
......
......@@ -22,7 +22,7 @@ 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.security.auth.AuthenticatorFactory;
import org.h2.table.Table;
import org.h2.tools.CompressTool;
import org.h2.util.JdbcUtils;
......@@ -544,14 +544,17 @@ public class Set extends Prepared {
}
case SetTypes.AUTHENTICATOR: {
session.getUser().checkAdmin();
String authenticatorString=expression.getValue(session).getString();
try {
database.setAuthenticator(AuthenticatorBuilder.buildAuthenticator(authenticatorString));
addOrUpdateSetting(name,"'"+authenticatorString+"'",0);
if (expression.getBooleanValue(session)) {
database.setAuthenticator(AuthenticatorFactory.createAuthenticator());
} else {
database.setAuthenticator(null);
}
addOrUpdateSetting(name,stringValue,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);
database.getTrace(Trace.DATABASE).error(e, "SET AUTHENTICATOR: failed to set authenticator {0} during database start",stringValue);
} else {
throw DbException.convert(e);
}
......
......@@ -275,7 +275,7 @@ public class ConnectionInfo implements Cloneable {
}
private void preservePasswordForAuthentication(Object password) {
if ((isRemote() ==false || isSSL()) && prop.containsKey("AUTHREALM") && password!=null) {
if ((!isRemote() || isSSL()) && prop.containsKey("AUTHREALM") && password!=null) {
prop.put("_PASSWORD",password);
}
}
......
......@@ -20,7 +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;
......@@ -44,7 +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.AuthenticatorBuilder;
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;
......@@ -2956,7 +2957,7 @@ public class Database implements DataHandler {
* @return authenticator set for database
*/
public Authenticator getAuthenticator() {
return authenticator == null ? InternalAuthenticator.INSTANCE : authenticator;
return authenticator;
}
/**
......
......@@ -16,6 +16,7 @@ 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;
......@@ -92,13 +93,31 @@ public class Engine implements SessionFactory {
}
if (user == null) {
if (database.validateFilePasswordHash(cipher, ci.getFilePasswordHash())) {
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() + "\"",ci.getUserName());
if (ci.getProperty("AUTHREALM")== null) {
user = database.findUser(ci.getUserName());
if (user != null) {
if (!user.validateUserPasswordHash(ci.getUserPasswordHash())) {
user = null;
}
}
} else {
try {
Authenticator authenticator = database.getAuthenticator();
if (authenticator==null) {
DbException er = DbException.get(ErrorCode.WRONG_USER_OR_PASSWORD);
database.getTrace(Trace.DATABASE).error(er,"no authenticator for database users");
}else {
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() + "\"");
}finally {
ci.cleanAuthenticationInfo();
}
}
}
if (opened && (user == null || !user.isAdmin())) {
......@@ -107,7 +126,6 @@ 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 {
}
}
......@@ -163,7 +163,7 @@ public abstract class RightOwner extends DbObjectBase {
}
List<Role> rolesToRemove= new ArrayList<>();
for (Entry<Role,Right> currentEntry : grantedRoles.entrySet()) {
if ( currentEntry.getValue().isTemporary() || currentEntry.getValue().isValid()==false) {
if ( currentEntry.getValue().isTemporary() || !currentEntry.getValue().isValid()) {
rolesToRemove.add(currentEntry.getKey());
}
}
......
......@@ -562,6 +562,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() {
......
......@@ -16,7 +16,7 @@ public class UserBuilder {
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);
user.setTemporary(!persistent);
return user;
}
......
......@@ -3,13 +3,10 @@
* and the EPL 1.0 (http://h2database.com/html/license.html).
* Initial Developer: Alessandro Ventura
*/
package org.h2.api;
package org.h2.security.auth;
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
......
/*
* 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;
/**
* Authenticator factory
*/
public class AuthenticatorFactory {
public static Authenticator createAuthenticator() {
return new DefaultAuthenticator();
}
}
......@@ -10,6 +10,8 @@ import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import org.h2.util.Utils;
/**
* wrapper for configuration properties
*/
......@@ -73,14 +75,7 @@ public class ConfigProperties {
if (result == null) {
return defaultValue;
}
switch (result) {
case "true":
case "yes":
case "1":
return true;
default:
return false;
}
return Utils.parseBoolean(name, defaultValue, true);
}
}
......@@ -16,13 +16,12 @@ 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.SysProperties;
import org.h2.engine.User;
import org.h2.engine.UserBuilder;
import org.h2.message.Trace;
......@@ -31,10 +30,23 @@ 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.
*
* Default authenticator implementation.
* <p>
* When client connectionInfo contains property AUTHREALM={realName} credentials
* (typically user id and password) are validated by
* by {@link org.h2.api.CredentialsValidator} configured for that realm.
* </p>
* <p>
* When client connectionInfo doesn't contains AUTHREALM property credentials
* are validated internally on the database
* </p>
* <p>
* Rights assignment can be managed trough {@link org.h2.api.UserToRolesMapper}
* </p>
* <p>
* Default configuration has a realm H2 that validate credentials trough JAAS api (appName=h2).
* To customize configuration set h2.authConfigFile system property to refer a valid h2auth.xml config file
* </p>
*/
public class DefaultAuthenticator implements Authenticator {
......@@ -65,7 +77,7 @@ public class DefaultAuthenticator implements Authenticator {
* option is useful when the authenticator is configured at code level
*
* @param skipDefaultInitialization
* = if true default initialization is skipped
* if true default initialization is skipped
*/
public DefaultAuthenticator(boolean skipDefaultInitialization) {
this.skipDefaultInitialization = skipDefaultInitialization;
......@@ -114,10 +126,8 @@ public class DefaultAuthenticator implements Authenticator {
/**
* Add an authentication realm. Realms are case insensitive
*
* @param name
* = realm name
* @param credentialsValidator
* = credentials validator for realm
* @param name realm name
* @param credentialsValidator credentials validator for realm
*/
public void addRealm(String name, CredentialsValidator credentialsValidator) {
realms.put(StringUtils.toUpperEnglish(name), credentialsValidator);
......@@ -145,11 +155,11 @@ public class DefaultAuthenticator implements Authenticator {
*
* 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
* <ol>
* <li>Check h2.authConfigFile system property.</li>
* <li>Use the default configuration hard coded</li>
* </ol>
* @param database where authenticator is initialized
* @throws AuthConfigException
*/
public void init(Database database) throws AuthConfigException {
......@@ -166,22 +176,13 @@ public class DefaultAuthenticator implements Authenticator {
Trace trace=database.getTrace(Trace.DATABASE);
URL h2AuthenticatorConfigurationUrl = null;
try {
String configFile = System.getProperty("h2auth.configurationFile", null);
String configFile = SysProperties.AUTH_CONFIG_FILE;
if (configFile != null) {
if (trace.isDebugEnabled()) {
trace.debug("DefaultAuthenticator.config: configuration read from system property h2auth.configurationfile={0}", configFile);
}
h2AuthenticatorConfigurationUrl = new URL(configFile);
}
if (h2AuthenticatorConfigurationUrl == null) {
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");
......@@ -214,8 +215,7 @@ public class DefaultAuthenticator implements Authenticator {
/**
* Configure the authenticator from a configuration file
*
* @param configUrl
* = URL of configuration file
* @param configUrl URL of configuration file
* @throws Exception
*/
public void configureFromUrl(URL configUrl) throws Exception {
......@@ -267,7 +267,7 @@ public class DefaultAuthenticator implements Authenticator {
Set<String> roles = new HashSet<>();
for (UserToRolesMapper currentUserToRolesMapper : userToRolesMappers) {
Collection<String> currentRoles = currentUserToRolesMapper.mapUserToRoles(authenticationInfo);
if (currentRoles != null && currentRoles.isEmpty() == false) {
if (currentRoles != null && !currentRoles.isEmpty()) {
roles.addAll(currentRoles);
}
}
......@@ -300,13 +300,9 @@ public class DefaultAuthenticator implements Authenticator {
@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) {
if (user == null && !isAllowUserRegistration()) {
throw new AuthenticationException("User " + userName + " not found in db");
}
CredentialsValidator validator = realms.get(authenticationInfo.getRealm());
......@@ -314,7 +310,7 @@ public class DefaultAuthenticator implements Authenticator {
throw new AuthenticationException("realm " + authenticationInfo.getRealm() + " not configured");
}
try {
if (validator.validateCredentials(authenticationInfo) == false) {
if (!validator.validateCredentials(authenticationInfo)) {
return null;
}
} catch (Exception e) {
......
......@@ -11,7 +11,12 @@ import org.h2.security.auth.ConfigProperties;
/**
* Assign to user a role based on realm name
*
* by default role name is @{realm}
* * <p>
* Configuration parameters:
* </p>
* <ul>
* <li> roleNameFormat, optional by default is @{realm}</li>
* </ul>
*/
public class AssignRealmNameRole implements UserToRolesMapper{
......
......@@ -21,8 +21,12 @@ 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)
* <p>
* Configuration parameters:
* </p>
* <ul>
* <li>appName inside the JAAS configuration (by default h2)</li>
* </ul>
*
*/
public class JaasCredentialsValidator implements CredentialsValidator {
......
......@@ -17,12 +17,15 @@ 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)
* <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 {
......
......@@ -16,9 +16,13 @@ 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>
*
* parameters:
* roles = role list separated by comma
*/
public class StaticRolesMapper implements UserToRolesMapper {
......
......@@ -43,7 +43,7 @@ public class StaticUserCredentialsValidator implements CredentialsValidator {
public boolean validateCredentials(AuthenticationInfo authenticationInfo) throws AuthenticationException {
if (userNamePattern!=null) {
if (userNamePattern.matcher(authenticationInfo.getUserName()).matches()==false) {
if (!userNamePattern.matcher(authenticationInfo.getUserName()).matches()) {
return false;
}
}
......
......@@ -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;
......@@ -974,6 +975,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);
......
......@@ -14,6 +14,9 @@ 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;
......
......@@ -104,6 +104,7 @@ public class TestAuthentication extends TestBase {
try {
configureJaas();
Properties properties = new Properties();
properties.setProperty("USER", "dba");
ConnectionInfo connectionInfo = new ConnectionInfo(getDatabaseURL(), properties);
session = Engine.getInstance().createSession(connectionInfo);
database = session.getDatabase();
......@@ -126,6 +127,7 @@ public class TestAuthentication extends TestBase {
testStaticRole();
testStaticUserCredentials();
testUserRegistration();
testSet();
}
protected void testInvalidPassword() throws Exception {
......@@ -235,4 +237,20 @@ public class TestAuthentication extends TestBase {
rightConnection.close();
}
}
protected void testSet() throws Exception{
Connection rightConnection = DriverManager.getConnection(
getDatabaseURL()+";AUTHENTICATOR=FALSE","DBA","");
try {
try {
testExternalUser();
throw new Exception("External user shouldnt be allowed");
}catch (Exception e) {
}
} finally {
configureAuthentication(database);
rightConnection.close();
}
testExternalUser();
}
}
\ No newline at end of file
/*
* 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 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论