mirror of
https://github.com/dbeaver/cloudbeaver.git
synced 2026-09-24 16:04:36 +08:00
CB-2127 security manager asynchronous authorization
This commit is contained in:
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* DBeaver - Universal Database Manager
|
||||
* Copyright (C) 2010-2022 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.cloudbeaver.auth;
|
||||
|
||||
public interface CBAuthConstants {
|
||||
String CB_AUTH_ID_COOKIE_NAME = "cb-auth-id";
|
||||
|
||||
String CB_AUTH_ID_REQUEST_PARAM = "authId";
|
||||
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* DBeaver - Universal Database Manager
|
||||
* Copyright (C) 2010-2022 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.cloudbeaver.auth;
|
||||
|
||||
import org.jkiss.code.Nullable;
|
||||
import org.jkiss.dbeaver.model.auth.SMCredentials;
|
||||
import org.jkiss.dbeaver.model.auth.SMCredentialsProvider;
|
||||
|
||||
public class NoAuthCredentialsProvider implements SMCredentialsProvider {
|
||||
@Nullable
|
||||
@Override
|
||||
public SMCredentials getActiveUserCredentials() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
+1
@@ -38,4 +38,5 @@ public interface WebAppConfiguration {
|
||||
|
||||
boolean isResourceManagerEnabled();
|
||||
|
||||
boolean isFeaturesEnabled(String[] requiredFeatures);
|
||||
}
|
||||
|
||||
@@ -45,4 +45,5 @@ public interface WebApplication extends DBPApplication {
|
||||
|
||||
RMController getResourceController(@NotNull SMCredentialsProvider credentialsProvider);
|
||||
|
||||
String getServerURL();
|
||||
}
|
||||
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* DBeaver - Universal Database Manager
|
||||
* Copyright (C) 2010-2022 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.cloudbeaver.model.app;
|
||||
|
||||
import io.cloudbeaver.auth.provider.AuthProviderConfig;
|
||||
|
||||
/**
|
||||
* Application configuration
|
||||
*/
|
||||
public interface WebAuthConfiguration {
|
||||
boolean isAuthProviderEnabled(String authProviderId);
|
||||
|
||||
AuthProviderConfig getAuthProviderConfigurations(String configId);
|
||||
}
|
||||
+20
-2
@@ -17,6 +17,7 @@
|
||||
package io.cloudbeaver.model.session;
|
||||
|
||||
import io.cloudbeaver.DBWUserIdentity;
|
||||
import io.cloudbeaver.model.WebAsyncTaskInfo;
|
||||
import io.cloudbeaver.model.user.WebAuthProviderConfiguration;
|
||||
import io.cloudbeaver.model.user.WebUser;
|
||||
import io.cloudbeaver.model.user.WebUserOriginInfo;
|
||||
@@ -46,6 +47,8 @@ public class WebAuthInfo implements SMSessionPrincipal {
|
||||
private final OffsetDateTime loginTime;
|
||||
private final DBWUserIdentity userIdentity;
|
||||
private String message;
|
||||
private String redirectLink;
|
||||
private final WebAsyncTaskInfo taskInfo;
|
||||
|
||||
private transient Map<String, Object> userCredentials;
|
||||
|
||||
@@ -55,14 +58,29 @@ public class WebAuthInfo implements SMSessionPrincipal {
|
||||
@NotNull AuthProviderDescriptor authProvider,
|
||||
@NotNull DBWUserIdentity userIdentity,
|
||||
@NotNull SMSession authSession,
|
||||
@NotNull OffsetDateTime loginTime)
|
||||
{
|
||||
@NotNull OffsetDateTime loginTime) {
|
||||
this.session = session;
|
||||
this.user = user;
|
||||
this.authProvider = authProvider;
|
||||
this.userIdentity = userIdentity;
|
||||
this.authSession = authSession;
|
||||
this.loginTime = loginTime;
|
||||
this.taskInfo = null;
|
||||
}
|
||||
|
||||
public WebAuthInfo(@NotNull WebSession session,
|
||||
@NotNull WebAsyncTaskInfo taskInfo,
|
||||
@NotNull String redirectLink
|
||||
) {
|
||||
this.session = session;
|
||||
this.taskInfo = taskInfo;
|
||||
this.redirectLink = redirectLink;
|
||||
|
||||
this.user = null;
|
||||
this.authProvider = null;
|
||||
this.userIdentity = null;
|
||||
this.authSession = null;
|
||||
this.loginTime = null;
|
||||
}
|
||||
|
||||
@Property
|
||||
|
||||
+227
@@ -0,0 +1,227 @@
|
||||
/*
|
||||
* DBeaver - Universal Database Manager
|
||||
* Copyright (C) 2010-2022 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.cloudbeaver.model.session;
|
||||
|
||||
import io.cloudbeaver.DBWConstants;
|
||||
import io.cloudbeaver.DBWUserIdentity;
|
||||
import io.cloudbeaver.DBWebException;
|
||||
import io.cloudbeaver.auth.SMAuthProviderExternal;
|
||||
import io.cloudbeaver.model.app.WebAuthConfiguration;
|
||||
import io.cloudbeaver.model.user.WebUser;
|
||||
import io.cloudbeaver.utils.WebAppUtils;
|
||||
import org.jkiss.code.NotNull;
|
||||
import org.jkiss.dbeaver.DBException;
|
||||
import org.jkiss.dbeaver.Log;
|
||||
import org.jkiss.dbeaver.model.auth.SMAuthInfo;
|
||||
import org.jkiss.dbeaver.model.auth.SMAuthProvider;
|
||||
import org.jkiss.dbeaver.model.auth.SMAuthStatus;
|
||||
import org.jkiss.dbeaver.model.auth.SMSession;
|
||||
import org.jkiss.dbeaver.model.exec.DBCException;
|
||||
import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor;
|
||||
import org.jkiss.dbeaver.model.security.SMController;
|
||||
import org.jkiss.dbeaver.registry.auth.AuthProviderDescriptor;
|
||||
import org.jkiss.dbeaver.registry.auth.AuthProviderRegistry;
|
||||
import org.jkiss.utils.CommonUtils;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
public class WebSessionAuthJob extends WebAsyncTaskProcessor<SMAuthInfo> {
|
||||
private static final Log log = Log.getLog(WebSessionAuthJob.class);
|
||||
private static final int MAX_ATTEMPT = 10;
|
||||
@NotNull
|
||||
private final WebSession webSession;
|
||||
@NotNull
|
||||
private SMAuthInfo smAuthInfo;
|
||||
@NotNull
|
||||
private final SMController smController;
|
||||
private boolean linkWithActiveUser;
|
||||
private int attemptCounter = 0;
|
||||
|
||||
public WebSessionAuthJob(@NotNull WebSession webSession, @NotNull SMAuthInfo smAuthInfo, boolean linkWithActiveUser) {
|
||||
this.webSession = webSession;
|
||||
this.smController = webSession.getSecurityController();
|
||||
this.smAuthInfo = smAuthInfo;
|
||||
this.linkWithActiveUser = linkWithActiveUser;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run(DBRProgressMonitor monitor) throws InvocationTargetException, InterruptedException {
|
||||
boolean resetUserStateOnError = webSession.getUser() == null;
|
||||
try {
|
||||
while (attemptCounter < MAX_ATTEMPT && smAuthInfo.getAuthStatus() == SMAuthStatus.IN_PROGRESS) {
|
||||
attemptCounter++;
|
||||
smAuthInfo = smController.getAuthStatus(smAuthInfo.getAuthAttemptId());
|
||||
if (smAuthInfo.getAuthStatus() == SMAuthStatus.IN_PROGRESS) {
|
||||
Thread.sleep(1000);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var authStatus = smAuthInfo.getAuthStatus();
|
||||
|
||||
switch (authStatus) {
|
||||
case SUCCESS:
|
||||
finishWebSessionAuthorization(smAuthInfo);
|
||||
break;
|
||||
case ERROR:
|
||||
throw new DBException("Authentication failed: " + smAuthInfo.getError());
|
||||
case IN_PROGRESS:
|
||||
throw new DBException("Authorization didn't complete within the expected period");
|
||||
default:
|
||||
throw new DBException("Unexpected authorization status: " + authStatus);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
if (resetUserStateOnError) {
|
||||
webSession.resetUserState();
|
||||
}
|
||||
throw new InvocationTargetException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private void finishWebSessionAuthorization(SMAuthInfo authInfo) throws DBException {
|
||||
boolean configMode = WebAppUtils.getWebApplication().isConfigurationMode();
|
||||
boolean resetUserStateOnError = webSession.getUser() == null;
|
||||
|
||||
try {
|
||||
webSession.updateSMAuthInfo(authInfo);
|
||||
WebUser curUser = webSession.getUser();
|
||||
if (curUser == null) {
|
||||
//should never happen in theory since we should get the error much earlier
|
||||
throw new DBWebException("Missing user after authorization in security controller");
|
||||
}
|
||||
String userId = curUser.getUserId();
|
||||
|
||||
var securityController = webSession.getSecurityController();
|
||||
Map<String, Object> providerConfig = Collections.emptyMap();
|
||||
var newAuthInfos = new ArrayList<WebAuthInfo>();
|
||||
for (Map.Entry<String, Object> entry : authInfo.getAuthData().entrySet()) {
|
||||
String providerId = entry.getKey();
|
||||
Map<String, Object> userCredentials = (Map<String, Object>) entry.getValue();
|
||||
|
||||
var authProviderDescriptor = getAuthProvider(providerId);
|
||||
SMAuthProvider<?> authProviderInstance = authProviderDescriptor.getInstance();
|
||||
SMAuthProviderExternal<?> authProviderExternal = authProviderInstance instanceof SMAuthProviderExternal<?> ?
|
||||
(SMAuthProviderExternal<?>) authProviderInstance : null;
|
||||
|
||||
boolean providerEnabled = isProviderEnabled(providerId);
|
||||
if (configMode || webSession.hasPermission(DBWConstants.PERMISSION_ADMIN)) {
|
||||
// 1. Admin can authorize in any providers
|
||||
// 2. When it authorizes in non-local provider for the first time we force linkUser flag
|
||||
if (!providerEnabled && webSession.getUser() != null) {
|
||||
linkWithActiveUser = true;
|
||||
}
|
||||
} else if (!providerEnabled || !webSession.hasPermission(DBWConstants.PERMISSION_ADMIN)) {
|
||||
throw new DBWebException("Authentication provider '" + providerId + "' is disabled");
|
||||
}
|
||||
|
||||
SMSession authSession;
|
||||
if (configMode) {
|
||||
if (webSession.getUser() != null) {
|
||||
// Already logged in - remove auth token
|
||||
webSession.removeAuthInfo(providerId);
|
||||
webSession.resetAuthToken();
|
||||
}
|
||||
} else {
|
||||
if (authProviderExternal != null) {
|
||||
// We may need to associate new credentials with active user
|
||||
if (linkWithActiveUser) {
|
||||
securityController.setUserCredentials(userId, authProviderDescriptor.getId(), userCredentials);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!configMode && !webSession.isAuthorizedInSecurityManager()) {
|
||||
throw new DBCException("No authorization in the security manager");
|
||||
}
|
||||
|
||||
DBWUserIdentity userIdentity = null;
|
||||
|
||||
if (authProviderExternal != null) {
|
||||
try {
|
||||
userIdentity =
|
||||
authProviderExternal.getUserIdentity(
|
||||
webSession.getProgressMonitor(),
|
||||
providerConfig,
|
||||
userCredentials);
|
||||
} catch (DBException e) {
|
||||
log.debug("Error reading auth display name from provider " + providerId, e);
|
||||
}
|
||||
}
|
||||
if (userIdentity == null) {
|
||||
userIdentity = new DBWUserIdentity(userId, userId);
|
||||
}
|
||||
if (CommonUtils.isEmpty(curUser.getDisplayName())) {
|
||||
curUser.setDisplayName(userIdentity.getDisplayName());
|
||||
}
|
||||
|
||||
authSession = authProviderInstance.openSession(
|
||||
webSession.getProgressMonitor(),
|
||||
webSession,
|
||||
providerConfig,
|
||||
userCredentials);
|
||||
|
||||
if (!configMode && securityController.getUserPermissions(userId).isEmpty()) {
|
||||
throw new DBWebException("Access denied (no permissions)");
|
||||
}
|
||||
if (!configMode && !securityController.getUserById(userId).isEnabled()) {
|
||||
throw new DBWebException("User account is locked");
|
||||
}
|
||||
|
||||
WebAuthInfo webAuthInfo = new WebAuthInfo(
|
||||
webSession,
|
||||
curUser,
|
||||
authProviderDescriptor,
|
||||
userIdentity,
|
||||
authSession,
|
||||
OffsetDateTime.now());
|
||||
webAuthInfo.setMessage("Authenticated with " + authProviderDescriptor.getLabel() + " provider");
|
||||
if (configMode) {
|
||||
webAuthInfo.setUserCredentials(userCredentials);
|
||||
}
|
||||
|
||||
webSession.addAuthInfo(webAuthInfo);
|
||||
newAuthInfos.add(webAuthInfo);
|
||||
}
|
||||
this.extendedResults = newAuthInfos;
|
||||
} catch (DBException e) {
|
||||
if (resetUserStateOnError) {
|
||||
webSession.resetUserState();
|
||||
}
|
||||
throw new DBWebException("User authentication failed", e);
|
||||
}
|
||||
}
|
||||
|
||||
private AuthProviderDescriptor getAuthProvider(String providerId) throws DBWebException {
|
||||
AuthProviderDescriptor authProvider = AuthProviderRegistry.getInstance().getAuthProvider(providerId);
|
||||
if (authProvider == null) {
|
||||
throw new DBWebException("Invalid auth provider '" + providerId + "'");
|
||||
}
|
||||
return authProvider;
|
||||
}
|
||||
|
||||
private boolean isProviderEnabled(@NotNull String providerId) {
|
||||
WebAuthConfiguration appConfiguration = (WebAuthConfiguration) WebAppUtils.getWebApplication().getAppConfiguration();
|
||||
return appConfiguration.isAuthProviderEnabled(providerId);
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -59,7 +59,7 @@ public class WebUserContext implements SMCredentialsProvider {
|
||||
* refresh context state based on new token from security manager
|
||||
*
|
||||
* @param smAuthInfo - auth info from security manager
|
||||
* @throws DBException - if user already authorized and new token
|
||||
* @throws DBException - if user already authorized and new token come from another user
|
||||
*/
|
||||
public void refresh(SMAuthInfo smAuthInfo) throws DBException {
|
||||
var isNonAnonymousUserAuthorized = isAuthorizedInSecurityManager() && getUser() != null;
|
||||
@@ -67,7 +67,7 @@ public class WebUserContext implements SMCredentialsProvider {
|
||||
if (isNonAnonymousUserAuthorized && !Objects.equals(getUserId(), tokenInfo.getUserId())) {
|
||||
throw new DBCException("Another user is already logged in");
|
||||
}
|
||||
this.smCredentials = new SMCredentials(smAuthInfo.getAuthToken(), tokenInfo.getUserId());
|
||||
this.smCredentials = new SMCredentials(smAuthInfo.getSmAuthToken(), tokenInfo.getUserId());
|
||||
this.userPermissions = tokenInfo.getPermissions();
|
||||
this.securityController = application.getSecurityController(this);
|
||||
this.adminSecurityController = application.getAdminSecurityController(this);
|
||||
|
||||
@@ -22,6 +22,7 @@ import io.cloudbeaver.DBWFeatureSet;
|
||||
import io.cloudbeaver.auth.provider.AuthProviderConfig;
|
||||
import io.cloudbeaver.auth.provider.local.LocalAuthProvider;
|
||||
import io.cloudbeaver.model.app.BaseWebAppConfiguration;
|
||||
import io.cloudbeaver.model.app.WebAuthConfiguration;
|
||||
import io.cloudbeaver.registry.WebFeatureRegistry;
|
||||
import org.jkiss.code.NotNull;
|
||||
import org.jkiss.code.Nullable;
|
||||
@@ -39,7 +40,7 @@ import java.util.Map;
|
||||
/**
|
||||
* Application configuration
|
||||
*/
|
||||
public class CBAppConfig extends BaseWebAppConfiguration {
|
||||
public class CBAppConfig extends BaseWebAppConfiguration implements WebAuthConfiguration {
|
||||
public static final DataSourceNavigatorSettings DEFAULT_VIEW_SETTINGS = DataSourceNavigatorSettings.PRESET_FULL.getSettings();
|
||||
|
||||
private boolean supportsCustomConnections;
|
||||
@@ -186,7 +187,22 @@ public class CBAppConfig extends BaseWebAppConfiguration {
|
||||
}
|
||||
|
||||
public boolean isAuthProviderEnabled(String id) {
|
||||
return ArrayUtils.contains(getEnabledAuthProviders(), id);
|
||||
var authProviderDescriptor = AuthProviderRegistry.getInstance().getAuthProvider(id);
|
||||
if (authProviderDescriptor == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ArrayUtils.contains(getEnabledAuthProviders(), id)) {
|
||||
return false;
|
||||
}
|
||||
if (!ArrayUtils.isEmpty(authProviderDescriptor.getRequiredFeatures())) {
|
||||
for (String rf : authProviderDescriptor.getRequiredFeatures()) {
|
||||
if (!isFeatureEnabled(rf)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public String getDefaultAuthProvider() {
|
||||
|
||||
@@ -63,26 +63,30 @@ type AuthProviderInfo {
|
||||
}
|
||||
|
||||
type UserAuthToken {
|
||||
# Auth provider used for autgorization
|
||||
# Auth provider used for authorization
|
||||
authProvider: ID!
|
||||
|
||||
# Auth provider configuration ID
|
||||
authConfiguration: ID
|
||||
|
||||
# Authorization time
|
||||
loginTime: DateTime!
|
||||
loginTime: DateTime
|
||||
|
||||
# User identity (aka user name) specific to auth provider
|
||||
userId: String!
|
||||
userId: String
|
||||
|
||||
# User display name specific to auth provider
|
||||
displayName: String!
|
||||
displayName: String
|
||||
|
||||
# Optional login message
|
||||
message: String
|
||||
|
||||
# Auth origin
|
||||
origin: ObjectOrigin!
|
||||
origin: ObjectOrigin
|
||||
|
||||
redirectLink: String
|
||||
|
||||
taskInfo: AsyncTaskInfo
|
||||
}
|
||||
|
||||
type UserInfo {
|
||||
|
||||
+1
@@ -37,6 +37,7 @@ public interface DBWServiceAuth extends DBWService {
|
||||
WebAuthInfo authLogin(
|
||||
@NotNull WebSession webSession,
|
||||
@NotNull String providerId,
|
||||
@Nullable String providerConfigurationId,
|
||||
@NotNull Map<String, Object> credentials,
|
||||
boolean linkWithActiveUser) throws DBWebException;
|
||||
|
||||
|
||||
+2
-2
@@ -81,8 +81,8 @@ public class RPSessionHandler implements DBWSessionHandler {
|
||||
webSession.getProgressMonitor(), sessionParameters, credentials);
|
||||
try {
|
||||
SMAuthInfo smAuthInfo = securityController.authenticate(
|
||||
webSession.getSessionId(), sessionParameters,
|
||||
WebSession.CB_SESSION_TYPE, authProvider.getId(), userCredentials);
|
||||
webSession.getSessionId(), sessionParameters,
|
||||
WebSession.CB_SESSION_TYPE, authProvider.getId(), null, userCredentials);
|
||||
webSession.updateSMAuthInfo(smAuthInfo);
|
||||
} catch (SMException e) {
|
||||
log.debug("Error during user authentication", e);
|
||||
|
||||
+1
@@ -39,6 +39,7 @@ public class WebServiceBindingAuth extends WebServiceBindingBase<DBWServiceAuth>
|
||||
.dataFetcher("authLogin", env -> getService(env).authLogin(
|
||||
getWebSession(env, false),
|
||||
env.getArgument("provider"),
|
||||
env.getArgument("configuration"),
|
||||
env.getArgument("credentials"),
|
||||
CommonUtils.toBoolean(env.getArgument("linkUser"))))
|
||||
.dataFetcher("authLogout", env -> {
|
||||
|
||||
+26
-199
@@ -16,39 +16,29 @@
|
||||
*/
|
||||
package io.cloudbeaver.service.auth.impl;
|
||||
|
||||
import io.cloudbeaver.DBWConstants;
|
||||
import io.cloudbeaver.DBWUserIdentity;
|
||||
import io.cloudbeaver.DBWebException;
|
||||
import io.cloudbeaver.auth.SMAuthProviderExternal;
|
||||
import io.cloudbeaver.auth.provider.local.LocalAuthProvider;
|
||||
import io.cloudbeaver.model.WebAsyncTaskInfo;
|
||||
import io.cloudbeaver.model.WebPropertyInfo;
|
||||
import io.cloudbeaver.model.session.WebAuthInfo;
|
||||
import io.cloudbeaver.model.session.WebSession;
|
||||
import io.cloudbeaver.model.session.WebSessionAuthJob;
|
||||
import io.cloudbeaver.model.user.WebAuthProviderInfo;
|
||||
import io.cloudbeaver.model.user.WebUser;
|
||||
import io.cloudbeaver.registry.WebUserProfileRegistry;
|
||||
import io.cloudbeaver.server.CBAppConfig;
|
||||
import io.cloudbeaver.server.CBApplication;
|
||||
import io.cloudbeaver.service.auth.DBWServiceAuth;
|
||||
import io.cloudbeaver.service.auth.WebUserInfo;
|
||||
import org.jkiss.code.NotNull;
|
||||
import org.jkiss.code.Nullable;
|
||||
import org.jkiss.dbeaver.DBException;
|
||||
import org.jkiss.dbeaver.Log;
|
||||
import org.jkiss.dbeaver.model.auth.SMAuthInfo;
|
||||
import org.jkiss.dbeaver.model.auth.SMAuthProvider;
|
||||
import org.jkiss.dbeaver.model.auth.SMSession;
|
||||
import org.jkiss.dbeaver.model.exec.DBCException;
|
||||
import org.jkiss.dbeaver.model.auth.SMAuthStatus;
|
||||
import org.jkiss.dbeaver.model.security.SMController;
|
||||
import org.jkiss.dbeaver.model.security.exception.SMException;
|
||||
import org.jkiss.dbeaver.model.security.user.SMUser;
|
||||
import org.jkiss.dbeaver.registry.auth.AuthProviderDescriptor;
|
||||
import org.jkiss.dbeaver.registry.auth.AuthProviderRegistry;
|
||||
import org.jkiss.utils.ArrayUtils;
|
||||
import org.jkiss.utils.CommonUtils;
|
||||
|
||||
import java.time.OffsetDateTime;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
@@ -63,202 +53,39 @@ public class WebServiceAuthImpl implements DBWServiceAuth {
|
||||
public WebAuthInfo authLogin(
|
||||
@NotNull WebSession webSession,
|
||||
@NotNull String providerId,
|
||||
@Nullable String providerConfigurationId,
|
||||
@NotNull Map<String, Object> authParameters,
|
||||
boolean linkWithActiveUser) throws DBWebException {
|
||||
SMController securityController = webSession.getSecurityController();
|
||||
|
||||
boolean linkWithActiveUser
|
||||
) throws DBWebException {
|
||||
if (CommonUtils.isEmpty(providerId)) {
|
||||
throw new DBWebException("Missing auth provider parameter");
|
||||
}
|
||||
AuthProviderDescriptor authProvider = AuthProviderRegistry.getInstance().getAuthProvider(providerId);
|
||||
if (authProvider == null) {
|
||||
throw new DBWebException("Invalid auth provider '" + providerId + "'");
|
||||
}
|
||||
|
||||
boolean configMode = CBApplication.getInstance().isConfigurationMode();
|
||||
|
||||
// Check enabled auth providers
|
||||
boolean providerEnabled = isProviderEnabled(providerId, authProvider);
|
||||
boolean resetUserStateOnError = webSession.getUser() == null;
|
||||
|
||||
SMController securityController = webSession.getSecurityController();
|
||||
try {
|
||||
Map<String, Object> providerConfig = Collections.emptyMap();
|
||||
SMAuthProvider<?> authProviderInstance = authProvider.getInstance();
|
||||
SMAuthProviderExternal<?> authProviderExternal = authProviderInstance instanceof SMAuthProviderExternal<?> ?
|
||||
(SMAuthProviderExternal<?>) authProviderInstance : null;
|
||||
Map<String, Object> userCredentials;
|
||||
var smAuthInfo = securityController.authenticate(
|
||||
webSession.getSessionId(),
|
||||
webSession.getSessionParameters(),
|
||||
WebSession.CB_SESSION_TYPE,
|
||||
providerId,
|
||||
providerConfigurationId,
|
||||
authParameters
|
||||
);
|
||||
|
||||
if (authProviderExternal != null) {
|
||||
userCredentials = authProviderExternal.authExternalUser(webSession.getProgressMonitor(), providerConfig, authParameters);
|
||||
if (smAuthInfo.getAuthStatus() == SMAuthStatus.IN_PROGRESS) {
|
||||
//run async auth process
|
||||
WebAsyncTaskInfo taskInfo = webSession.createAndRunAsyncTask("Authentication", new WebSessionAuthJob(webSession, smAuthInfo, linkWithActiveUser));
|
||||
return new WebAuthInfo(webSession, taskInfo, smAuthInfo.getRedirectUrl());
|
||||
} else {
|
||||
// User credentials are the same as auth parameters
|
||||
userCredentials = authParameters;
|
||||
}
|
||||
|
||||
if (configMode || webSession.hasPermission(DBWConstants.PERMISSION_ADMIN)) {
|
||||
// 1. Admin can authorize in any providers
|
||||
// 2. When it authorizes in non-local provider for the first time we force linkUser flag
|
||||
if (!providerEnabled && webSession.getUser() != null) {
|
||||
linkWithActiveUser = true;
|
||||
}
|
||||
} else if (!providerEnabled) {
|
||||
if (!isAdminAuthTry(webSession, authProvider, userCredentials)) {
|
||||
throw new DBWebException("Authentication provider '" + providerId + "' is disabled");
|
||||
}
|
||||
}
|
||||
|
||||
WebUser user = null;
|
||||
String userId;
|
||||
SMSession authSession;
|
||||
if (configMode) {
|
||||
if (webSession.getUser() != null) {
|
||||
// Already logged in - remove auth token
|
||||
webSession.removeAuthInfo(providerId);
|
||||
webSession.resetAuthToken();
|
||||
}
|
||||
if (authProviderExternal != null) {
|
||||
userId = authProviderExternal.validateLocalAuth(
|
||||
webSession.getProgressMonitor(),
|
||||
securityController,
|
||||
providerConfig,
|
||||
userCredentials,
|
||||
webSession.getUserId());
|
||||
} else {
|
||||
userId = CONFIG_TEMP_ADMIN_USER_ID;
|
||||
}
|
||||
} else {
|
||||
WebUser curUser = webSession.getUser();
|
||||
if (curUser == null) {
|
||||
try {
|
||||
SMAuthInfo smAuthInfo = securityController.authenticate(webSession.getSessionId(), webSession.getSessionParameters(), WebSession.CB_SESSION_TYPE, authProvider.getId(), userCredentials);
|
||||
userId = smAuthInfo.getAuthPermissions().getUserId();
|
||||
if (userId == null) {
|
||||
throw new SMException("Anonymous authentication restricted");
|
||||
}
|
||||
webSession.updateSMAuthInfo(smAuthInfo);
|
||||
curUser = webSession.getUser();
|
||||
securityController = webSession.getSecurityController();
|
||||
} catch (SMException e) {
|
||||
log.debug("Error during user authentication", e);
|
||||
throw e;
|
||||
}
|
||||
} else { // user already logged in
|
||||
userId = curUser.getUserId();
|
||||
if (authProviderExternal != null) {
|
||||
// We may need to associate new credentials with active user
|
||||
if (linkWithActiveUser) {
|
||||
securityController.setUserCredentials(userId, authProvider.getId(), userCredentials);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (linkWithActiveUser && curUser != null && !curUser.getUserId().equals(userId)) {
|
||||
log.debug("Attempt to authorize user '" + userId + "' while user '" + curUser.getUserId() + "' already authorized");
|
||||
throw new DBCException("You cannot authorize with different users credentials");
|
||||
}
|
||||
|
||||
user = curUser;
|
||||
}
|
||||
if (user == null) {
|
||||
user = new WebUser(new SMUser(userId));
|
||||
}
|
||||
if (!configMode && !webSession.isAuthorizedInSecurityManager()) {
|
||||
throw new DBCException("No authorization in the security manager");
|
||||
}
|
||||
|
||||
DBWUserIdentity userIdentity = null;
|
||||
|
||||
if (authProviderExternal != null) {
|
||||
try {
|
||||
userIdentity =
|
||||
authProviderExternal.getUserIdentity(
|
||||
webSession.getProgressMonitor(),
|
||||
providerConfig,
|
||||
userCredentials);
|
||||
} catch (DBException e) {
|
||||
log.debug("Error reading auth display name from provider " + providerId, e);
|
||||
}
|
||||
}
|
||||
if (userIdentity == null) {
|
||||
userIdentity = new DBWUserIdentity(userId, userId);
|
||||
}
|
||||
if (CommonUtils.isEmpty(user.getDisplayName())) {
|
||||
user.setDisplayName(userIdentity.getDisplayName());
|
||||
}
|
||||
|
||||
authSession = authProviderInstance.openSession(
|
||||
webSession.getProgressMonitor(),
|
||||
webSession,
|
||||
providerConfig,
|
||||
userCredentials);
|
||||
|
||||
if (!configMode && securityController.getUserPermissions(userId).isEmpty()) {
|
||||
throw new DBWebException("Access denied (no permissions)");
|
||||
}
|
||||
if (!configMode && !securityController.getUserById(userId).isEnabled()) {
|
||||
throw new DBWebException("User account is locked");
|
||||
}
|
||||
|
||||
WebAuthInfo authInfo = new WebAuthInfo(
|
||||
webSession,
|
||||
user,
|
||||
authProvider,
|
||||
userIdentity,
|
||||
authSession,
|
||||
OffsetDateTime.now());
|
||||
authInfo.setMessage("Authenticated with " + authProvider.getLabel() + " provider");
|
||||
if (configMode) {
|
||||
authInfo.setUserCredentials(userCredentials);
|
||||
}
|
||||
|
||||
webSession.addAuthInfo(authInfo);
|
||||
|
||||
return authInfo;
|
||||
} catch (DBException e) {
|
||||
if (resetUserStateOnError) {
|
||||
webSession.resetUserState();
|
||||
//run it sync
|
||||
var job = new WebSessionAuthJob(webSession, smAuthInfo, linkWithActiveUser);
|
||||
job.run(webSession.getProgressMonitor());
|
||||
//TODO return list
|
||||
return ((List<WebAuthInfo>) job.getExtendedResults()).stream().findFirst().orElseThrow();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new DBWebException("User authentication failed", e);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isProviderEnabled(@NotNull String providerId, AuthProviderDescriptor authProvider) {
|
||||
boolean providerEnabled = true;
|
||||
CBAppConfig appConfiguration = CBApplication.getInstance().getAppConfiguration();
|
||||
String[] enabledAuthProviders = appConfiguration.getEnabledAuthProviders();
|
||||
if (enabledAuthProviders != null && !ArrayUtils.contains(enabledAuthProviders, providerId)) {
|
||||
providerEnabled = false;
|
||||
} else {
|
||||
if (!ArrayUtils.isEmpty(authProvider.getRequiredFeatures())) {
|
||||
for (String rf : authProvider.getRequiredFeatures()) {
|
||||
if (!appConfiguration.isFeatureEnabled(rf)) {
|
||||
providerEnabled = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return providerEnabled;
|
||||
}
|
||||
|
||||
private boolean isAdminAuthTry(@NotNull WebSession session, @NotNull AuthProviderDescriptor authProvider, @NotNull Map<String, Object> userCredentials) {
|
||||
SMController securityController = session.getSecurityController();
|
||||
boolean isAdmin = false;
|
||||
|
||||
try {
|
||||
SMAuthInfo authInfo = securityController.authenticate(
|
||||
session.getSessionId(),
|
||||
session.getSessionParameters(),
|
||||
WebSession.CB_SESSION_TYPE,
|
||||
authProvider.getId(),
|
||||
userCredentials);
|
||||
|
||||
isAdmin = authInfo.getAuthPermissions().getPermissions().contains(DBWConstants.PERMISSION_ADMIN);
|
||||
} catch (DBException e) {
|
||||
log.error(e);
|
||||
}
|
||||
|
||||
return isAdmin;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -227,5 +227,34 @@ CREATE TABLE CB_AUTH_TOKEN
|
||||
FOREIGN KEY (USER_ID) REFERENCES CB_USER (USER_ID) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE TABLE CB_AUTH_ATTEMPT
|
||||
(
|
||||
AUTH_ID VARCHAR(128) NOT NULL,
|
||||
AUTH_STATUS VARCHAR(32) NOT NULL,
|
||||
AUTH_ERROR TEXT NOT NULL,
|
||||
APP_SESSION_ID VARCHAR(64) NOT NULL,
|
||||
SESSION_ID VARCHAR(64),
|
||||
SESSION_TYPE VARCHAR(64),
|
||||
APP_SESSION_STATE TEXT NOT NULL,
|
||||
|
||||
CREATE_TIME TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
PRIMARY KEY (AUTH_ID),
|
||||
FOREIGN KEY (SESSION_ID) REFERENCES CB_SESSION (SESSION_ID) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE TABLE CB_AUTH_ATTEMPT_INFO
|
||||
(
|
||||
AUTH_ID VARCHAR(128) NOT NULL,
|
||||
AUTH_PROVIDER_ID VARCHAR(128) NOT NULL,
|
||||
AUTH_PROVIDER_CONFIGURATION_ID VARCHAR(128) NULL,
|
||||
AUTH_STATE TEXT NOT NULL,
|
||||
|
||||
CREATE_TIME TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
PRIMARY KEY (AUTH_ID, AUTH_PROVIDER_ID),
|
||||
FOREIGN KEY (AUTH_ID) REFERENCES CB_AUTH_ATTEMPT (AUTH_ID) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE INDEX CB_SESSION_LOG_INDEX ON CB_SESSION_LOG(SESSION_ID,LOG_TIME);
|
||||
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
CREATE TABLE CB_AUTH_ATTEMPT
|
||||
(
|
||||
AUTH_ID VARCHAR(128) NOT NULL,
|
||||
AUTH_STATUS VARCHAR(32) NOT NULL,
|
||||
AUTH_ERROR TEXT NOT NULL,
|
||||
APP_SESSION_ID VARCHAR(64) NOT NULL,
|
||||
SESSION_ID VARCHAR(64),
|
||||
SESSION_TYPE VARCHAR(64),
|
||||
APP_SESSION_STATE TEXT NOT NULL,
|
||||
|
||||
CREATE_TIME TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
PRIMARY KEY (AUTH_ID),
|
||||
FOREIGN KEY (SESSION_ID) REFERENCES CB_SESSION (SESSION_ID) ON DELETE CASCADE
|
||||
|
||||
);
|
||||
|
||||
CREATE TABLE CB_AUTH_ATTEMPT_INFO
|
||||
(
|
||||
AUTH_ID VARCHAR(128) NOT NULL,
|
||||
AUTH_PROVIDER_ID VARCHAR(128) NOT NULL,
|
||||
AUTH_PROVIDER_CONFIGURATION_ID VARCHAR(128) NULL,
|
||||
AUTH_STATE TEXT NOT NULL,
|
||||
|
||||
CREATE_TIME TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
PRIMARY KEY (AUTH_ID, AUTH_PROVIDER_ID),
|
||||
FOREIGN KEY (AUTH_ID) REFERENCES CB_AUTH_ATTEMPT (AUTH_ID) ON DELETE CASCADE
|
||||
);
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* DBeaver - Universal Database Manager
|
||||
* Copyright (C) 2010-2022 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.cloudbeaver.service.security.internal;
|
||||
|
||||
import org.jkiss.dbeaver.model.security.SMSessionType;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public class AuthAttemptSessionInfo {
|
||||
private final String appSessionId;
|
||||
|
||||
private final SMSessionType sessionType;
|
||||
private final Map<String, Object> sessionParams;
|
||||
|
||||
public AuthAttemptSessionInfo(String appSessionId, SMSessionType sessionType, Map<String, Object> sessionParams) {
|
||||
this.appSessionId = appSessionId;
|
||||
this.sessionType = sessionType;
|
||||
this.sessionParams = sessionParams;
|
||||
}
|
||||
|
||||
public String getAppSessionId() {
|
||||
return appSessionId;
|
||||
}
|
||||
|
||||
public SMSessionType getSessionType() {
|
||||
return sessionType;
|
||||
}
|
||||
|
||||
public Map<String, Object> getSessionParams() {
|
||||
return sessionParams;
|
||||
}
|
||||
}
|
||||
+251
-12
@@ -16,8 +16,12 @@
|
||||
*/
|
||||
package io.cloudbeaver.service.security.internal;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import io.cloudbeaver.DBWConstants;
|
||||
import io.cloudbeaver.auth.SMAuthProviderExternal;
|
||||
import io.cloudbeaver.auth.SMWAuthProviderFederated;
|
||||
import io.cloudbeaver.model.app.WebApplication;
|
||||
import io.cloudbeaver.service.security.internal.db.CBDatabase;
|
||||
import io.cloudbeaver.utils.WebAppUtils;
|
||||
@@ -43,6 +47,7 @@ import org.jkiss.utils.ArrayUtils;
|
||||
import org.jkiss.utils.CommonUtils;
|
||||
import org.jkiss.utils.SecurityUtils;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
import java.sql.*;
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDateTime;
|
||||
@@ -63,6 +68,9 @@ public class CBEmbeddedSecurityController implements SMAdminController {
|
||||
|
||||
private static final String SUBJECT_USER = "U";
|
||||
private static final String SUBJECT_ROLE = "R";
|
||||
private static final Type MAP_STRING_OBJECT_TYPE = new TypeToken<Map<String, Object>>() {
|
||||
}.getType();
|
||||
private static final Gson gson = new GsonBuilder().create();
|
||||
|
||||
|
||||
private final CBDatabase database;
|
||||
@@ -831,7 +839,7 @@ public class CBEmbeddedSecurityController implements SMAdminController {
|
||||
var token = generateAuthToken(smSessionId, null, dbCon);
|
||||
var permissions = getAnonymousUserPermissions();
|
||||
txn.commit();
|
||||
return new SMAuthInfo(token, new SMAuthPermissions(null, smSessionId, permissions));
|
||||
return SMAuthInfo.success(UUID.randomUUID().toString(), token, new SMAuthPermissions(null, smSessionId, permissions), Map.of());
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
throw new DBException(e.getMessage(), e);
|
||||
@@ -844,27 +852,258 @@ public class CBEmbeddedSecurityController implements SMAdminController {
|
||||
}
|
||||
|
||||
@Override
|
||||
public SMAuthInfo authenticate(@NotNull String appSessionId, @NotNull Map<String, Object> sessionParameters, @NotNull SMSessionType sessionType, @NotNull String authProviderId, @NotNull Map<String, Object> userCredentials) throws DBException {
|
||||
public SMAuthInfo authenticate(
|
||||
@NotNull String appSessionId,
|
||||
@NotNull Map<String, Object> sessionParameters,
|
||||
@NotNull SMSessionType sessionType,
|
||||
@NotNull String authProviderId,
|
||||
@Nullable String authProviderConfigurationId,
|
||||
@NotNull Map<String, Object> userCredentials
|
||||
) throws DBException {
|
||||
var authProgressMonitor = new VoidProgressMonitor();
|
||||
try (Connection dbCon = database.openConnection()) {
|
||||
try (JDBCTransaction txn = new JDBCTransaction(dbCon)) {
|
||||
var userId = findOrCreateExternalUserByCredentials(authProviderId,
|
||||
sessionParameters,
|
||||
userCredentials,
|
||||
new VoidProgressMonitor());
|
||||
if (userId == null) {
|
||||
throw new SMException("Invalid user credentials");
|
||||
Map<String, Object> userIdentifyingCredentials = userCredentials;
|
||||
AuthProviderDescriptor authProviderDescriptor = getAuthProvider(authProviderId);
|
||||
var authProviderInstance = authProviderDescriptor.getInstance();
|
||||
if (SMAuthProviderExternal.class.isAssignableFrom(authProviderInstance.getClass())) {
|
||||
var authProviderExternal = (SMAuthProviderExternal<?>) authProviderInstance;
|
||||
userIdentifyingCredentials = authProviderExternal.authExternalUser(authProgressMonitor, Map.of(), userCredentials);
|
||||
}
|
||||
|
||||
Map<String, Object> authData = Map.of(authProviderId, userIdentifyingCredentials);
|
||||
var authAttemptId = createNewAuthAttempt(
|
||||
SMAuthStatus.IN_PROGRESS,
|
||||
authProviderId,
|
||||
authProviderConfigurationId,
|
||||
authData,
|
||||
appSessionId,
|
||||
sessionType,
|
||||
sessionParameters
|
||||
);
|
||||
|
||||
if (SMWAuthProviderFederated.class.isAssignableFrom(authProviderInstance.getClass())) {
|
||||
//async auth
|
||||
var authProviderFederated = (SMWAuthProviderFederated) authProviderInstance;
|
||||
var redirectUrl = authProviderFederated.getRedirectLink(authProviderConfigurationId, Map.of());
|
||||
return SMAuthInfo.inProgress(authAttemptId, redirectUrl, authData);
|
||||
}
|
||||
var smSessionId = createSessionIfNotExist(appSessionId, userId, sessionParameters, sessionType, dbCon);
|
||||
var token = generateAuthToken(smSessionId, userId, dbCon);
|
||||
var permissions = getUserPermissions(userId);
|
||||
txn.commit();
|
||||
return new SMAuthInfo(token, new SMAuthPermissions(userId, smSessionId, permissions));
|
||||
return finishAuthentication(authAttemptId);
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
throw new DBException(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private String createNewAuthAttempt(
|
||||
SMAuthStatus status,
|
||||
String authProviderId,
|
||||
String authProviderConfigurationId,
|
||||
Map<String, Object> authData,
|
||||
String appSessionId,
|
||||
SMSessionType sessionType,
|
||||
Map<String, Object> sessionParameters
|
||||
) throws DBException {
|
||||
String authAttemptId = UUID.randomUUID().toString();
|
||||
try (Connection dbCon = database.openConnection()) {
|
||||
try (JDBCTransaction txn = new JDBCTransaction(dbCon)) {
|
||||
try (PreparedStatement dbStat = dbCon.prepareStatement(
|
||||
"INSERT INTO CB_AUTH_ATTEMPT(AUTH_ID,AUTH_STATUS,APP_SESSION_ID,SESSION_TYPE,APP_SESSION_STATE) " +
|
||||
"VALUES(?,?,?,?,?)")) {
|
||||
dbStat.setString(1, authAttemptId);
|
||||
dbStat.setString(2, status.toString());
|
||||
dbStat.setString(3, appSessionId);
|
||||
dbStat.setString(4, sessionType.getSessionType());
|
||||
dbStat.setString(5, gson.toJson(sessionParameters));
|
||||
dbStat.execute();
|
||||
}
|
||||
|
||||
try (PreparedStatement dbStat = dbCon.prepareStatement(
|
||||
"INSERT INTO CB_AUTH_ATTEMPT_INFO(AUTH_ID,AUTH_PROVIDER_ID,AUTH_PROVIDER_CONFIGURATION_ID,AUTH_STATE) " +
|
||||
"VALUES(?,?,?,?,?)")) {
|
||||
dbStat.setString(1, authAttemptId);
|
||||
dbStat.setString(2, authProviderId);
|
||||
dbStat.setString(3, authProviderConfigurationId);
|
||||
dbStat.setString(4, gson.toJson(authData));
|
||||
dbStat.execute();
|
||||
}
|
||||
txn.commit();
|
||||
}
|
||||
return authAttemptId;
|
||||
} catch (SQLException e) {
|
||||
throw new DBException(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateAuthStatus(@NotNull String authId,
|
||||
@NotNull SMAuthStatus authStatus,
|
||||
@NotNull Map<String, Object> authInfo,
|
||||
@Nullable String error) throws DBException {
|
||||
try (Connection dbCon = database.openConnection()) {
|
||||
try (PreparedStatement dbStat = dbCon.prepareStatement(
|
||||
"UPDATE CB_AUTH_INFO SET AUTH_STATUS=?,AUTH_STATE=? WHERE AUTH_ID=?")) {
|
||||
dbStat.setString(1, authStatus.toString());
|
||||
dbStat.setString(2, gson.toJson(authId));
|
||||
dbStat.setString(3, authId);
|
||||
if (dbStat.executeUpdate() <= 0) {
|
||||
throw new DBCException("Auth attempt '" + authId + "' doesn't exist");
|
||||
}
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
throw new DBCException("Error reading session state", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public SMAuthInfo getAuthStatus(@NotNull String authId) throws DBException {
|
||||
try (Connection dbCon = database.openConnection()) {
|
||||
SMAuthStatus smAuthStatus;
|
||||
String authError;
|
||||
String smSessionId;
|
||||
try (PreparedStatement dbStat = dbCon.prepareStatement(
|
||||
"SELECT AUTH_STATUS,AUTH_ERROR,SESSION_ID FROM CB_AUTH_ATTEMPT WHERE AUTH_ID=?"
|
||||
)) {
|
||||
try (ResultSet dbResult = dbStat.executeQuery()) {
|
||||
if (!dbResult.next()) {
|
||||
throw new SMException("Auth attempt not found");
|
||||
}
|
||||
smAuthStatus = SMAuthStatus.valueOf(dbResult.getString(1));
|
||||
authError = dbResult.getString(2);
|
||||
smSessionId = dbResult.getString(3);
|
||||
}
|
||||
}
|
||||
Map<String, Object> authData = new LinkedHashMap<>();
|
||||
String redirectUrl = null;
|
||||
try (PreparedStatement dbStat = dbCon.prepareStatement(
|
||||
"SELECT AUTH_PROVIDER_ID,AUTH_PROVIDER_CONFIGURATION_ID,AUTH_STATE FROM CB_AUTH_ATTEMPT_INFO "
|
||||
+ "WHERE AUTH_ID=? ORDER BY CREATE_TIME"
|
||||
)) {
|
||||
try (ResultSet dbResult = dbStat.executeQuery()) {
|
||||
while (dbResult.next()) {
|
||||
String authProviderId = dbResult.getString(1);
|
||||
String authProviderConfiguration = dbResult.getString(2);
|
||||
Map<String, Object> authProviderData = gson.fromJson(dbResult.getString(3), MAP_STRING_OBJECT_TYPE);
|
||||
if (authProviderConfiguration != null) {
|
||||
var authProviderInstance = getAuthProvider(authProviderId).getInstance();
|
||||
if (SMWAuthProviderFederated.class.isAssignableFrom(authProviderInstance.getClass())) {
|
||||
redirectUrl = ((SMWAuthProviderFederated) authProviderInstance).getRedirectLink(authProviderConfiguration, Map.of());
|
||||
}
|
||||
}
|
||||
authData.put(authProviderId, authProviderData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (smAuthStatus != SMAuthStatus.SUCCESS) {
|
||||
switch (smAuthStatus) {
|
||||
case IN_PROGRESS:
|
||||
return SMAuthInfo.inProgress(authId, redirectUrl, authData);
|
||||
case ERROR:
|
||||
return SMAuthInfo.error(authId, authError);
|
||||
default:
|
||||
throw new SMException("Unknown auth status:" + smAuthStatus);
|
||||
}
|
||||
}
|
||||
|
||||
String smToken = findTokenBySmSession(smSessionId);
|
||||
return SMAuthInfo.success(authId, smToken, getTokenPermissions(smToken), authData);
|
||||
|
||||
} catch (SQLException e) {
|
||||
throw new DBException("Error while read auth info", e);
|
||||
}
|
||||
}
|
||||
|
||||
private String findTokenBySmSession(String smSessionId) throws DBException {
|
||||
try (Connection dbCon = database.openConnection();
|
||||
PreparedStatement dbStat = dbCon.prepareStatement("SELECT TOKEN_ID FROM CB_AUTH_TOKEN WHERE SESSION_ID=?");
|
||||
) {
|
||||
dbStat.setString(1, smSessionId);
|
||||
try (var dbResult = dbStat.executeQuery()) {
|
||||
if (!dbResult.next()) {
|
||||
throw new SMException("Token not found");
|
||||
}
|
||||
return dbResult.getString(1);
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
throw new DBCException("Error reading token info in database", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public SMAuthInfo finishAuthentication(@NotNull String authId) throws DBException {
|
||||
SMAuthInfo authInfo = getAuthStatus(authId);
|
||||
if (authInfo.getAuthStatus() != SMAuthStatus.IN_PROGRESS) {
|
||||
throw new SMException("Authorization has already been completed with status: " + authInfo.getAuthStatus());
|
||||
}
|
||||
Set<String> authProviderIds = authInfo.getAuthData().keySet();
|
||||
if (authProviderIds.isEmpty()) {
|
||||
throw new SMException("Authorization providers are not defined");
|
||||
}
|
||||
String userId = null;
|
||||
var finishAuthMonitor = new VoidProgressMonitor();
|
||||
AuthAttemptSessionInfo authAttemptSessionInfo = readAuthAttemptSessionInfo(authId);
|
||||
for (String authProviderId : authProviderIds) {
|
||||
var userCredentials = (Map<String, Object>) authInfo.getAuthData().get(authProviderId);
|
||||
var userIdFromCreds = findOrCreateExternalUserByCredentials(
|
||||
authProviderId,
|
||||
authAttemptSessionInfo.getSessionParams(),
|
||||
userCredentials,
|
||||
finishAuthMonitor
|
||||
);
|
||||
if (userId == null) {
|
||||
userId = userIdFromCreds;
|
||||
} else if (!userId.equals(userIdFromCreds)) {
|
||||
throw new SMException("Authorization attempt contains different users");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (userId == null) {
|
||||
return SMAuthInfo.error(authId, "Invalid user credentials");
|
||||
}
|
||||
try (Connection dbCon = database.openConnection()) {
|
||||
try (JDBCTransaction txn = new JDBCTransaction(dbCon)) {
|
||||
var smSessionId = createSessionIfNotExist(
|
||||
authAttemptSessionInfo.getAppSessionId(),
|
||||
userId,
|
||||
authAttemptSessionInfo.getSessionParams(),
|
||||
authAttemptSessionInfo.getSessionType(),
|
||||
dbCon
|
||||
);
|
||||
var token = generateAuthToken(smSessionId, userId, dbCon);
|
||||
var permissions = getUserPermissions(userId);
|
||||
return SMAuthInfo.success(authId, token, new SMAuthPermissions(userId, smSessionId, permissions), authInfo.getAuthData());
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
throw new SMException("Error during token generation", e);
|
||||
}
|
||||
}
|
||||
|
||||
private AuthAttemptSessionInfo readAuthAttemptSessionInfo(@NotNull String authId) throws DBException {
|
||||
try (Connection dbCon = database.openConnection()) {
|
||||
try (PreparedStatement dbStat = dbCon.prepareStatement(
|
||||
"SELECT APP_SESSION_ID,SESSION_TYPE,APP_SESSION_STATE FROM CB_AUTH_ATTEMPT WHERE AUTH_ID=?"
|
||||
)) {
|
||||
dbStat.setString(1, authId);
|
||||
try (ResultSet dbResult = dbStat.executeQuery()) {
|
||||
if (!dbResult.next()) {
|
||||
throw new SMException("Auth attempt not found");
|
||||
}
|
||||
String appSessionId = dbResult.getString(1);
|
||||
SMSessionType sessionType = new SMSessionType(dbResult.getString(2));
|
||||
Map<String, Object> sessionParams = gson.fromJson(
|
||||
dbResult.getString(3), MAP_STRING_OBJECT_TYPE
|
||||
);
|
||||
return new AuthAttemptSessionInfo(appSessionId, sessionType, sessionParams);
|
||||
}
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
throw new DBException("Error while read auth info", e);
|
||||
}
|
||||
}
|
||||
|
||||
private String findOrCreateExternalUserByCredentials(@NotNull String authProviderId,
|
||||
@NotNull Map<String, Object> sessionParameters,
|
||||
@NotNull Map<String, Object> userCredentials,
|
||||
|
||||
Reference in New Issue
Block a user