AWS auth provider. User auth + user registration

This commit is contained in:
serge-rider
2020-05-04 17:06:01 +03:00
parent 1bdf382e6a
commit 20cf2bc688
11 changed files with 130 additions and 47 deletions
@@ -1,7 +0,0 @@
{
// Server configuration
"logView": {
"logRefreshInterval": 3,
"logBatchSize": 1000
}
}
@@ -9,7 +9,7 @@ CREATE TABLE CB_SERVER(
);
CREATE TABLE CB_USER(
USER_ID VARCHAR(32) NOT NULL,
USER_ID VARCHAR(64) NOT NULL,
IS_ACTIVE CHAR(1) NOT NULL,
CREATE_TIME TIMESTAMP NOT NULL,
@@ -17,6 +17,16 @@ CREATE TABLE CB_USER(
PRIMARY KEY(USER_ID)
);
-- Additional user properties (profile)
CREATE TABLE CB_USER_META(
USER_ID VARCHAR(64) NOT NULL,
META_ID VARCHAR(32) NOT NULL,
META_VALUE VARCHAR(1024),
PRIMARY KEY(USER_ID,META_ID),
FOREIGN KEY(USER_ID) REFERENCES CB_USER(USER_ID) ON DELETE CASCADE
);
CREATE TABLE CB_ROLE(
ROLE_ID VARCHAR(32) NOT NULL,
ROLE_NAME VARCHAR(100) NOT NULL,
@@ -39,7 +49,7 @@ CREATE TABLE CB_ROLE_PERMISSIONS(
);
CREATE TABLE CB_USER_ROLE(
USER_ID VARCHAR(32) NOT NULL,
USER_ID VARCHAR(64) NOT NULL,
ROLE_ID VARCHAR(32) NOT NULL,
GRANT_TIME TIMESTAMP NOT NULL,
@@ -50,8 +60,24 @@ CREATE TABLE CB_USER_ROLE(
FOREIGN KEY(ROLE_ID) REFERENCES CB_ROLE(ROLE_ID) ON DELETE CASCADE
);
CREATE TABLE CB_AUTH_PROVIDER(
PROVIDER_ID VARCHAR(32) NOT NULL,
IS_ENABLED CHAR(1) NOT NULL,
PRIMARY KEY(PROVIDER_ID)
);
CREATE TABLE CB_AUTH_CONFIGURATION(
PROVIDER_ID VARCHAR(32) NOT NULL,
PARAM_ID VARCHAR(32) NOT NULL,
PARAM_VALUE VARCHAR(1024),
PRIMARY KEY(PROVIDER_ID,PARAM_ID),
FOREIGN KEY(PROVIDER_ID) REFERENCES CB_AUTH_PROVIDER(PROVIDER_ID) ON DELETE CASCADE
);
CREATE TABLE CB_USER_CREDENTIALS(
USER_ID VARCHAR(32) NOT NULL,
USER_ID VARCHAR(64) NOT NULL,
PROVIDER_ID VARCHAR(32) NOT NULL,
CRED_ID VARCHAR(32) NOT NULL,
CRED_VALUE VARCHAR(1024) NOT NULL,
@@ -63,7 +89,7 @@ CREATE TABLE CB_USER_CREDENTIALS(
CREATE INDEX CB_USER_CREDENTIALS_SEARCH_IDX ON CB_USER_CREDENTIALS(PROVIDER_ID,CRED_ID);
CREATE TABLE CB_USER_STATE(
USER_ID VARCHAR(32) NOT NULL,
USER_ID VARCHAR(64) NOT NULL,
USER_CONFIGURATION TEXT NULL,
@@ -75,7 +101,7 @@ CREATE TABLE CB_USER_STATE(
CREATE TABLE CB_SESSION(
SESSION_ID VARCHAR(64) NOT NULL,
USER_ID VARCHAR(32) NULL,
USER_ID VARCHAR(64) NULL,
CREATE_TIME TIMESTAMP NOT NULL,
LAST_ACCESS_TIME TIMESTAMP NOT NULL,
@@ -105,3 +131,4 @@ CREATE TABLE CB_SESSION_LOG(
);
CREATE INDEX CB_SESSION_LOG_INDEX ON CB_SESSION_LOG(SESSION_ID,LOG_TIME);
@@ -50,10 +50,12 @@
</service>
<authProvider id="local" label="Local" description="Local name/password based authentication" class="io.cloudbeaver.auth.provider.local.LocalAuthProvider">
<propertyGroup label="General">
<property id="user" label="User name" type="string" description="User name" admin="true" user="true" identifying="true"/>
<property id="password" label="User password" type="string" description="User password" encryption="hash" admin="true" user="true"/>
</propertyGroup>
<credentials>
<propertyGroup label="General">
<property id="user" label="User name" type="string" description="User name" admin="true" user="true" identifying="true"/>
<property id="password" label="User password" type="string" description="User password" encryption="hash" admin="true" user="true"/>
</propertyGroup>
</credentials>
</authProvider>
</extension>
@@ -16,6 +16,7 @@
*/
package io.cloudbeaver;
import io.cloudbeaver.model.user.WebUser;
import org.jkiss.dbeaver.DBException;
import java.util.Map;
@@ -25,9 +26,14 @@ import java.util.Map;
*/
public interface DBWAuthProviderExternal<AUTH_SESSION> extends DBWAuthProvider<AUTH_SESSION> {
/**
* Returns new identifying credentials which can be used to find/create user in database
*/
Map<String, Object> readExternalCredentials(
Map<String, Object> providerConfig, // Auth provider configuration (e.g. 3rd party auth server address)
Map<String, Object> authParameters // Passed auth parameters (e.g. user name or password)
) throws DBException;
WebUser registerNewUser(DBWSecurityController securityController, Map<String, Object> providerConfig, Map<String, Object> credentials) throws DBException;
}
@@ -22,6 +22,7 @@ import io.cloudbeaver.model.user.WebUser;
import io.cloudbeaver.registry.WebAuthProviderDescriptor;
import io.cloudbeaver.server.CBPlatform;
import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.model.exec.DBCException;
import java.util.Map;
@@ -41,6 +42,8 @@ public interface DBWSecurityController {
void setUserRoles(String userId, String[] roleIds, String grantorId) throws DBCException;
WebUser getUserById(String userId) throws DBCException;
///////////////////////////////////////////
// Credentials
@@ -53,7 +56,7 @@ public interface DBWSecurityController {
* Find user with matching credentials.
* It doesn't check credentials like passwords, just searches user id by identifying credentials.
*/
@NotNull
@Nullable
String getUserByCredentials(WebAuthProviderDescriptor authProvider, Map<String, Object> authParameters) throws DBCException;
/**
@@ -39,19 +39,32 @@ public class WebAuthProviderDescriptor extends AbstractDescriptor {
private ObjectType implType;
private DBWAuthProvider instance;
private final Map<String, WebAuthProviderPropertyDescriptor> properties = new LinkedHashMap<>();
private final Map<String, PropertyDescriptor> configurationParameters = new LinkedHashMap<>();
private final Map<String, WebAuthProviderPropertyDescriptor> credentialParameters = new LinkedHashMap<>();
public WebAuthProviderDescriptor(IConfigurationElement cfg) {
super(cfg);
this.cfg = cfg;
this.implType = new ObjectType(cfg, "class");
for (IConfigurationElement propGroup : ArrayUtils.safeArray(cfg.getChildren(PropertyDescriptor.TAG_PROPERTY_GROUP))) {
String category = propGroup.getAttribute(PropertyDescriptor.ATTR_LABEL);
IConfigurationElement[] propElements = propGroup.getChildren(PropertyDescriptor.TAG_PROPERTY);
for (IConfigurationElement prop : propElements) {
WebAuthProviderPropertyDescriptor propertyDescriptor = new WebAuthProviderPropertyDescriptor(category, prop);
properties.put(CommonUtils.toString(propertyDescriptor.getId()), propertyDescriptor);
for (IConfigurationElement cfgElement : cfg.getChildren("configuration")) {
for (IConfigurationElement propGroup : ArrayUtils.safeArray(cfgElement.getChildren(PropertyDescriptor.TAG_PROPERTY_GROUP))) {
String category = propGroup.getAttribute(PropertyDescriptor.ATTR_LABEL);
IConfigurationElement[] propElements = propGroup.getChildren(PropertyDescriptor.TAG_PROPERTY);
for (IConfigurationElement prop : propElements) {
PropertyDescriptor propertyDescriptor = new PropertyDescriptor(category, prop);
configurationParameters.put(CommonUtils.toString(propertyDescriptor.getId()), propertyDescriptor);
}
}
}
for (IConfigurationElement credElement : cfg.getChildren("credentials")) {
for (IConfigurationElement propGroup : ArrayUtils.safeArray(credElement.getChildren(PropertyDescriptor.TAG_PROPERTY_GROUP))) {
String category = propGroup.getAttribute(PropertyDescriptor.ATTR_LABEL);
IConfigurationElement[] propElements = propGroup.getChildren(PropertyDescriptor.TAG_PROPERTY);
for (IConfigurationElement prop : propElements) {
WebAuthProviderPropertyDescriptor propertyDescriptor = new WebAuthProviderPropertyDescriptor(category, prop);
credentialParameters.put(CommonUtils.toString(propertyDescriptor.getId()), propertyDescriptor);
}
}
}
}
@@ -73,12 +86,16 @@ public class WebAuthProviderDescriptor extends AbstractDescriptor {
return cfg.getAttribute("icon");
}
public List<WebAuthProviderPropertyDescriptor> getProperties() {
return new ArrayList<>(properties.values());
public List<PropertyDescriptor> getConfigurationParameters() {
return new ArrayList<>(configurationParameters.values());
}
public WebAuthProviderPropertyDescriptor getProperty(String id) {
return properties.get(id);
public List<WebAuthProviderPropertyDescriptor> getCredentialParameters() {
return new ArrayList<>(credentialParameters.values());
}
public WebAuthProviderPropertyDescriptor getCredentialParameter(String id) {
return credentialParameters.get(id);
}
@NotNull
@@ -23,6 +23,7 @@ public class CBAppConfig {
private boolean anonymousAccessEnabled = true;
private boolean authenticationEnabled = true;
private String anonymousUserRole = CBConstants.DEFAUL_APP_ANONYMOUS_ROLE_NAME;
private String defaultUserRole = CBConstants.DEFAUL_APP_ANONYMOUS_ROLE_NAME;
public boolean isAuthenticationEnabled() {
return authenticationEnabled;
@@ -36,4 +37,7 @@ public class CBAppConfig {
return anonymousUserRole;
}
public String getDefaultUserRole() {
return defaultUserRole;
}
}
@@ -105,6 +105,10 @@ public class CBApplication extends BaseApplicationImpl {
return maxSessionIdleTime;
}
public CBDatabaseConfig getDatabaseConfiguration() {
return databaseConfiguration;
}
public CBAppConfig getAppConfiguration() {
return appConfiguration;
}
@@ -17,14 +17,13 @@
package io.cloudbeaver.server;
import io.cloudbeaver.DBWSecurityController;
import io.cloudbeaver.DBWebException;
import io.cloudbeaver.model.session.WebSession;
import io.cloudbeaver.model.user.WebRole;
import io.cloudbeaver.model.user.WebUser;
import io.cloudbeaver.registry.WebAuthProviderDescriptor;
import io.cloudbeaver.registry.WebAuthProviderPropertyDescriptor;
import io.cloudbeaver.registry.WebAuthProviderPropertyEncryption;
import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.Log;
import org.jkiss.dbeaver.model.exec.DBCException;
import org.jkiss.dbeaver.model.impl.jdbc.JDBCUtils;
@@ -97,6 +96,23 @@ class CBSecurityController implements DBWSecurityController {
}
}
@Override
public WebUser getUserById(String userId) throws DBCException {
try (Connection dbCon = database.openConnection()) {
try (PreparedStatement dbStat = dbCon.prepareStatement("SELECT * FROM CB_USER WHERE USER_ID=?")) {
dbStat.setString(1, userId);
try (ResultSet dbResult = dbStat.executeQuery()) {
if (dbResult.next()) {
return new WebUser(dbResult.getString(1));
}
return null;
}
}
} catch (SQLException e) {
throw new DBCException("Error while searching credentials", e);
}
}
///////////////////////////////////////////
// Credentials
@@ -106,7 +122,7 @@ class CBSecurityController implements DBWSecurityController {
try {
transformedCredentials = credentials.entrySet().stream().map(cred -> {
String propertyName = cred.getKey();
WebAuthProviderPropertyDescriptor property = authProvider.getProperty(propertyName);
WebAuthProviderPropertyDescriptor property = authProvider.getCredentialParameter(propertyName);
if (property == null) {
throw new IllegalArgumentException("Invalid auth provider '" + authProvider.getId() + "' property '" + propertyName + "'");
}
@@ -115,7 +131,7 @@ class CBSecurityController implements DBWSecurityController {
return new String[] {propertyName, encodedValue };
}).collect(Collectors.toList());
} catch (Exception e) {
throw new DBCException("Error passing properties to provider", e);
throw new DBCException(e.getMessage(), e);
}
try (Connection dbCon = database.openConnection()) {
JDBCUtils.executeStatement(dbCon, "DELETE FROM CB_USER_CREDENTIALS WHERE USER_ID=? AND PROVIDER_ID=?", userId, authProvider.getId());
@@ -135,11 +151,11 @@ class CBSecurityController implements DBWSecurityController {
}
}
@NotNull
@Nullable
@Override
public String getUserByCredentials(WebAuthProviderDescriptor authProvider, Map<String, Object> authParameters) throws DBCException {
Map<String, Object> identCredentials = new LinkedHashMap<>();
for (WebAuthProviderPropertyDescriptor prop : authProvider.getProperties()) {
for (WebAuthProviderPropertyDescriptor prop : authProvider.getCredentialParameters()) {
if (prop.isIdentifying()) {
String propId = CommonUtils.toString(prop.getId());
Object paramValue = authParameters.get(propId);
@@ -191,11 +207,7 @@ class CBSecurityController implements DBWSecurityController {
}
}
if (userId == null) {
// User doesn't exist. We can create new user automatically if auth provider supports this
throw new DBCException("Invalid user credentials");
}
if (!isActive) {
if (userId != null && !isActive) {
throw new DBCException("User account is locked");
}
@@ -25,7 +25,8 @@ type AuthProviderInfo {
isDefault: Boolean
properties: [AuthCredentialInfo]!
configurationParameters: [ObjectPropertyInfo]!
credentialParameters: [AuthCredentialInfo]!
}
type UserAuthInfo {
@@ -28,6 +28,7 @@ import io.cloudbeaver.server.CBPlatform;
import io.cloudbeaver.service.auth.DBWServiceAuth;
import io.cloudbeaver.service.auth.WebAuthInfo;
import org.jkiss.dbeaver.DBException;
import org.jkiss.dbeaver.model.exec.DBCException;
import org.jkiss.utils.CommonUtils;
import java.time.OffsetDateTime;
@@ -53,15 +54,26 @@ public class WebServiceAuthImpl implements DBWServiceAuth {
Map<String, Object> providerConfig = Collections.emptyMap();
DBWAuthProvider<?> authProviderInstance = authProvider.getInstance();
if (authProviderInstance instanceof DBWAuthProviderExternal<?>) {
Map<String, Object> externalCredentials = ((DBWAuthProviderExternal<?>) authProviderInstance).readExternalCredentials(providerConfig,
authParameters);
if (externalCredentials != null) {
authParameters.putAll(externalCredentials);
DBWAuthProviderExternal<?> authProviderExternal = authProviderInstance instanceof DBWAuthProviderExternal<?> ?
(DBWAuthProviderExternal<?>) authProviderInstance : null;
if (authProviderExternal != null) {
authParameters = authProviderExternal.readExternalCredentials(providerConfig, authParameters);
}
WebUser user = null;
String userId = serverController.getUserByCredentials(authProvider, authParameters);
if (userId == null) {
// User doesn't exist. We can create new user automatically if auth provider supports this
if (authProviderExternal != null) {
user = authProviderExternal.registerNewUser(serverController, providerConfig, authParameters);
userId = user.getUserId();
}
if (userId == null) {
throw new DBCException("Invalid user credentials");
}
}
String userId = serverController.getUserByCredentials(authProvider, authParameters);
Map<String, Object> userCredentials = serverController.getUserCredentials(userId, authProvider);
Object authToken = authProviderInstance.openSession(
@@ -75,7 +87,9 @@ public class WebServiceAuthImpl implements DBWServiceAuth {
authInfo.setAuthToken(authToken);
authInfo.setMessage("Logged using " + authProvider.getLabel() + " provider");
WebUser user = new WebUser(userId);
if (user == null) {
user = new WebUser(userId);
}
webSession.setUser(user);
return authInfo;