From 20cf2bc68840253ae916aeeedd33c2cf54fc990e Mon Sep 17 00:00:00 2001 From: serge-rider Date: Mon, 4 May 2020 17:06:01 +0300 Subject: [PATCH] AWS auth provider. User auth + user registration --- .../sample-databases/cloudbeaver-sample.json | 7 ---- .../db/cb-schema-create.sql | 37 +++++++++++++++--- .../bundles/io.cloudbeaver.server/plugin.xml | 10 +++-- .../cloudbeaver/DBWAuthProviderExternal.java | 6 +++ .../io/cloudbeaver/DBWSecurityController.java | 5 ++- .../registry/WebAuthProviderDescriptor.java | 39 +++++++++++++------ .../io/cloudbeaver/server/CBAppConfig.java | 4 ++ .../io/cloudbeaver/server/CBApplication.java | 4 ++ .../server/CBSecurityController.java | 34 ++++++++++------ .../schema/service.auth.graphqls | 3 +- .../service/auth/impl/WebServiceAuthImpl.java | 28 +++++++++---- 11 files changed, 130 insertions(+), 47 deletions(-) delete mode 100644 samples/sample-databases/cloudbeaver-sample.json diff --git a/samples/sample-databases/cloudbeaver-sample.json b/samples/sample-databases/cloudbeaver-sample.json deleted file mode 100644 index d642dd54f8..0000000000 --- a/samples/sample-databases/cloudbeaver-sample.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - // Server configuration - "logView": { - "logRefreshInterval": 3, - "logBatchSize": 1000 - } -} \ No newline at end of file diff --git a/server/bundles/io.cloudbeaver.server/db/cb-schema-create.sql b/server/bundles/io.cloudbeaver.server/db/cb-schema-create.sql index fac0204dc8..5db0ee1f37 100644 --- a/server/bundles/io.cloudbeaver.server/db/cb-schema-create.sql +++ b/server/bundles/io.cloudbeaver.server/db/cb-schema-create.sql @@ -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); + diff --git a/server/bundles/io.cloudbeaver.server/plugin.xml b/server/bundles/io.cloudbeaver.server/plugin.xml index b6b64d80a9..f9651164c4 100644 --- a/server/bundles/io.cloudbeaver.server/plugin.xml +++ b/server/bundles/io.cloudbeaver.server/plugin.xml @@ -50,10 +50,12 @@ - - - - + + + + + + diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/DBWAuthProviderExternal.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/DBWAuthProviderExternal.java index 9c862e2f47..bb9536de28 100644 --- a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/DBWAuthProviderExternal.java +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/DBWAuthProviderExternal.java @@ -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 extends DBWAuthProvider { + /** + * Returns new identifying credentials which can be used to find/create user in database + */ Map readExternalCredentials( Map providerConfig, // Auth provider configuration (e.g. 3rd party auth server address) Map authParameters // Passed auth parameters (e.g. user name or password) ) throws DBException; + WebUser registerNewUser(DBWSecurityController securityController, Map providerConfig, Map credentials) throws DBException; + } diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/DBWSecurityController.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/DBWSecurityController.java index 8240e93406..9e28237001 100644 --- a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/DBWSecurityController.java +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/DBWSecurityController.java @@ -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 authParameters) throws DBCException; /** diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/registry/WebAuthProviderDescriptor.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/registry/WebAuthProviderDescriptor.java index 53b57af74f..415bd76809 100644 --- a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/registry/WebAuthProviderDescriptor.java +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/registry/WebAuthProviderDescriptor.java @@ -39,19 +39,32 @@ public class WebAuthProviderDescriptor extends AbstractDescriptor { private ObjectType implType; private DBWAuthProvider instance; - private final Map properties = new LinkedHashMap<>(); + private final Map configurationParameters = new LinkedHashMap<>(); + private final Map 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 getProperties() { - return new ArrayList<>(properties.values()); + public List getConfigurationParameters() { + return new ArrayList<>(configurationParameters.values()); } - public WebAuthProviderPropertyDescriptor getProperty(String id) { - return properties.get(id); + public List getCredentialParameters() { + return new ArrayList<>(credentialParameters.values()); + } + + public WebAuthProviderPropertyDescriptor getCredentialParameter(String id) { + return credentialParameters.get(id); } @NotNull diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/CBAppConfig.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/CBAppConfig.java index 85a54230a8..471f39d1ca 100644 --- a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/CBAppConfig.java +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/CBAppConfig.java @@ -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; + } } diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/CBApplication.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/CBApplication.java index 4fb5dfdb64..9f168742d4 100644 --- a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/CBApplication.java +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/CBApplication.java @@ -105,6 +105,10 @@ public class CBApplication extends BaseApplicationImpl { return maxSessionIdleTime; } + public CBDatabaseConfig getDatabaseConfiguration() { + return databaseConfiguration; + } + public CBAppConfig getAppConfiguration() { return appConfiguration; } diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/CBSecurityController.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/CBSecurityController.java index 61f1195db8..56d7d45088 100644 --- a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/CBSecurityController.java +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/CBSecurityController.java @@ -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 authParameters) throws DBCException { Map 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"); } diff --git a/server/bundles/io.cloudbeaver.service.auth/schema/service.auth.graphqls b/server/bundles/io.cloudbeaver.service.auth/schema/service.auth.graphqls index ad1edba81e..b74d4185f9 100644 --- a/server/bundles/io.cloudbeaver.service.auth/schema/service.auth.graphqls +++ b/server/bundles/io.cloudbeaver.service.auth/schema/service.auth.graphqls @@ -25,7 +25,8 @@ type AuthProviderInfo { isDefault: Boolean - properties: [AuthCredentialInfo]! + configurationParameters: [ObjectPropertyInfo]! + credentialParameters: [AuthCredentialInfo]! } type UserAuthInfo { diff --git a/server/bundles/io.cloudbeaver.service.auth/src/io/cloudbeaver/service/auth/impl/WebServiceAuthImpl.java b/server/bundles/io.cloudbeaver.service.auth/src/io/cloudbeaver/service/auth/impl/WebServiceAuthImpl.java index 2f9cdee10e..b38730e8d9 100644 --- a/server/bundles/io.cloudbeaver.service.auth/src/io/cloudbeaver/service/auth/impl/WebServiceAuthImpl.java +++ b/server/bundles/io.cloudbeaver.service.auth/src/io/cloudbeaver/service/auth/impl/WebServiceAuthImpl.java @@ -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 providerConfig = Collections.emptyMap(); DBWAuthProvider authProviderInstance = authProvider.getInstance(); - if (authProviderInstance instanceof DBWAuthProviderExternal) { - Map 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 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;