Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CB-4039 logout with context #2296

Merged
merged 16 commits into from
Jan 17, 2024
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.DBException;
import org.jkiss.dbeaver.model.security.SMAuthProviderCustomConfiguration;

import java.util.Map;

Expand All @@ -28,15 +29,21 @@
*/
public interface SMAuthProviderFederated {

/**
* Returns new identifying credentials which can be used to find/create user in database
*/
@NotNull
String getSignInLink(String id, @NotNull Map<String, Object> providerConfig) throws DBException;


/**

Check warning on line 35 in server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/auth/SMAuthProviderFederated.java

View check run for this annotation

Jenkins-CI-integration / CheckStyle Java Report

server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/auth/SMAuthProviderFederated.java#L35

Summary javadoc is missing.
* @return a common link for logout, not related with the user context
*/
@NotNull
String getSignOutLink(String id, @NotNull Map<String, Object> providerConfig) throws DBException;
String getCommonSignOutLink(String id, @NotNull Map<String, Object> providerConfig) throws DBException;

default String getUserSignOutLink(

Check warning on line 41 in server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/auth/SMAuthProviderFederated.java

View check run for this annotation

Jenkins-CI-integration / CheckStyle Java Report

server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/auth/SMAuthProviderFederated.java#L41

Missing a Javadoc comment.
@NotNull SMAuthProviderCustomConfiguration providerConfig,
@NotNull Map<String, Object> userCredentials
) throws DBException {
return getCommonSignOutLink(providerConfig.getId(), providerConfig.getParameters());
}

@Nullable
String getMetadataLink(String id, @NotNull Map<String, Object> providerConfig) throws DBException;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,8 +131,6 @@ void closeAuth() {
authProviderInstance.closeSession(session, authSession);
} catch (Exception e) {
log.error(e);
} finally {
authSession = null;
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -614,7 +614,7 @@
super.close();
}

private void clearAuthTokens() throws DBException {
private List<WebAuthInfo> clearAuthTokens() throws DBException {
ArrayList<WebAuthInfo> tokensCopy;
synchronized (authTokens) {
tokensCopy = new ArrayList<>(this.authTokens);
Expand All @@ -623,6 +623,7 @@
removeAuthInfo(ai);
}
resetAuthToken();
return tokensCopy;
}

public DBRProgressMonitor getProgressMonitor() {
Expand Down Expand Up @@ -873,18 +874,23 @@
}
}

public void removeAuthInfo(String providerId) throws DBException {
public List<WebAuthInfo> removeAuthInfo(String providerId) throws DBException {

Check warning on line 877 in server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/session/WebSession.java

View check run for this annotation

Jenkins-CI-integration / CheckStyle Java Report

server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/session/WebSession.java#L877

Missing a Javadoc comment.
List<WebAuthInfo> oldInfo;
if (providerId == null) {
clearAuthTokens();
oldInfo = clearAuthTokens();
} else {
WebAuthInfo authInfo = getAuthInfo(providerId);
if (authInfo != null) {
removeAuthInfo(authInfo);
oldInfo = List.of(authInfo);
} else {
oldInfo = List.of();
}
}
if (authTokens.isEmpty()) {
resetUserState();
}
return oldInfo;
}

public List<DBACredentialsProvider> getContextCredentialsProviders() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ private String buildRedirectUrl(String baseUrl) {
public String getSignOutLink() throws DBException {
SMAuthProvider<?> instance = providerDescriptor.getInstance();
return instance instanceof SMAuthProviderFederated
? ((SMAuthProviderFederated) instance).getSignOutLink(getId(), config.getParameters())
? ((SMAuthProviderFederated) instance).getCommonSignOutLink(getId(), config.getParameters())
: null;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,11 @@ type AuthInfo {
userTokens: [UserAuthToken!]
}


type LogoutInfo @since(version: "23.3.3") {
redirectLinks: [String!]!
}

type UserAuthToken {
# Auth provider used for authorization
authProvider: ID!
Expand Down Expand Up @@ -139,8 +144,13 @@ extend type Query {
authUpdateStatus(authId: ID!, linkUser: Boolean): AuthInfo!

# Logouts user. If provider not specified then all authorizations are revoked from session.
@deprecated
authLogout(provider: ID, configuration: ID): Boolean

# Same as #authLogout, but returns additional information
@since(version: "23.3.3")
authLogoutExtended(provider: ID, configuration: ID): LogoutInfo!
Comment on lines +150 to +152
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's not clear what Extended suffix means especially if we will remove authLogout

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alexander decided authLogout function to be for a while for back backward compatibility. so this is why we have new function with Extended suffix

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

problem is that Extended means nothing this is not good suffix for api
authLogoutInfo sound better


# Active user information. null is no user was authorized within session
activeUser: UserInfo

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,11 @@ WebAuthStatus authLogin(
WebAuthStatus authUpdateStatus(@NotNull WebSession webSession, @NotNull String authId, boolean linkWithActiveUser) throws DBWebException;

@WebAction(authRequired = false)
void authLogout(@NotNull WebSession webSession, @Nullable String providerId, @Nullable String configurationId) throws DBWebException;
WebLogoutInfo authLogout(
@NotNull WebSession webSession,
@Nullable String providerId,
@Nullable String configurationId
) throws DBWebException;

@WebAction(authRequired = false)
WebUserInfo activeUser(@NotNull WebSession webSession) throws DBWebException;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2024 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.auth;

import org.jkiss.code.NotNull;

import java.util.List;

public record WebLogoutInfo(@NotNull List<String> redirectLinks) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,15 @@ public void bindWiring(DBWBindingContext model) throws DBWebException {
env.getArgument("configuration"),
env.getArgument("credentials"),
CommonUtils.toBoolean(env.getArgument("linkUser"))))
.dataFetcher("authLogoutExtended", env -> getService(env).authLogout(
getWebSession(env, false),
env.getArgument("provider"),
env.getArgument("configuration")
))
.dataFetcher("authLogout", env -> {
getService(env).authLogout(
getWebSession(env, false),
getService(env).authLogout(getWebSession(env, false),
env.getArgument("provider"),
env.getArgument("configuration")
);
env.getArgument("configuration"));
return true;
})
.dataFetcher("authUpdateStatus", env -> getService(env).authUpdateStatus(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import io.cloudbeaver.DBWebException;
import io.cloudbeaver.WebServiceUtils;
import io.cloudbeaver.auth.SMAuthProviderFederated;
import io.cloudbeaver.auth.provider.local.LocalAuthProvider;
import io.cloudbeaver.model.WebPropertyInfo;
import io.cloudbeaver.model.session.WebAuthInfo;
Expand All @@ -31,6 +32,7 @@
import io.cloudbeaver.server.CBApplication;
import io.cloudbeaver.service.auth.DBWServiceAuth;
import io.cloudbeaver.service.auth.WebAuthStatus;
import io.cloudbeaver.service.auth.WebLogoutInfo;
import io.cloudbeaver.service.auth.WebUserInfo;
import io.cloudbeaver.service.security.SMUtils;
import org.jkiss.code.NotNull;
Expand All @@ -39,6 +41,7 @@
import org.jkiss.dbeaver.Log;
import org.jkiss.dbeaver.model.auth.SMAuthInfo;
import org.jkiss.dbeaver.model.auth.SMAuthStatus;
import org.jkiss.dbeaver.model.auth.SMSessionExternal;
import org.jkiss.dbeaver.model.preferences.DBPPropertyDescriptor;
import org.jkiss.dbeaver.model.security.SMController;
import org.jkiss.dbeaver.model.security.SMSubjectType;
Expand Down Expand Up @@ -131,7 +134,7 @@ public WebAuthStatus authUpdateStatus(@NotNull WebSession webSession, @NotNull S
}

@Override
public void authLogout(
public WebLogoutInfo authLogout(
@NotNull WebSession webSession,
@Nullable String providerId,
@Nullable String configurationId
Expand All @@ -140,7 +143,26 @@ public void authLogout(
throw new DBWebException("Not logged in");
}
try {
webSession.removeAuthInfo(providerId);
List<WebAuthInfo> removedInfos = webSession.removeAuthInfo(providerId);
List<String> logoutUrls = new ArrayList<>();
var cbApp = CBApplication.getInstance();
for (WebAuthInfo removedInfo : removedInfos) {
if (removedInfo.getAuthProviderDescriptor()
.getInstance() instanceof SMAuthProviderFederated federatedProvider
&& removedInfo.getAuthSession() instanceof SMSessionExternal externalSession
) {
var providerConfig =
cbApp.getAuthConfiguration().getAuthProviderConfiguration(removedInfo.getAuthConfiguration());
if (providerConfig == null) {
log.warn(removedInfo.getAuthConfiguration() + " provider configuration wasn't found");
continue;
}
String logoutUrl = federatedProvider.getUserSignOutLink(providerConfig,
externalSession.getAuthParameters());
logoutUrls.add(logoutUrl);
}
}
return new WebLogoutInfo(logoutUrls);
} catch (DBException e) {
throw new DBWebException("User logout failed", e);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1348,7 +1348,8 @@ public SMAuthInfo authenticate(
var authProviderFederated = (SMAuthProviderFederated) authProviderInstance;
String signInLink = buildRedirectLink(authProviderFederated.getSignInLink(authProviderConfigurationId, Map.of()),
authAttemptId);
String signOutLink = authProviderFederated.getSignOutLink(authProviderConfigurationId, Map.of());
String signOutLink = authProviderFederated.getCommonSignOutLink(authProviderConfigurationId,
Map.of());
Map<SMAuthConfigurationReference, Object> authData = Map.of(new SMAuthConfigurationReference(authProviderId,
authProviderConfigurationId), filteredUserCreds);
return SMAuthInfo.inProgress(authAttemptId, signInLink, signOutLink, authData, isMainSession);
Expand Down Expand Up @@ -1621,9 +1622,12 @@ private SMAuthInfo getAuthStatus(@NotNull String authId, boolean readExpiredData
signInLink = buildRedirectLink(((SMAuthProviderFederated) authProviderInstance).getRedirectLink(
authProviderConfiguration,
Map.of()), authId);
signOutLink = buildRedirectLink(((SMAuthProviderFederated) authProviderInstance).getSignOutLink(
authProviderConfiguration,
Map.of()), authId);
var userCustomSignOutLink =
((SMAuthProviderFederated) authProviderInstance).getUserSignOutLink(
application.getAuthConfiguration()
.getAuthProviderConfiguration(authProviderConfiguration),
authProviderData);
signOutLink = userCustomSignOutLink;
}

}
Expand Down
19 changes: 18 additions & 1 deletion webapp/packages/core-authentication/src/UserInfoResource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ import { injectable } from '@cloudbeaver/core-di';
import { AutoRunningTask, ISyncExecutor, ITask, SyncExecutor, whileTask } from '@cloudbeaver/core-executor';
import { CachedDataResource, type ResourceKeySimple, ResourceKeyUtils } from '@cloudbeaver/core-resource';
import { SessionDataResource, SessionResource } from '@cloudbeaver/core-root';
import { WindowsService } from '@cloudbeaver/core-routing';
import { AuthInfo, AuthStatus, GetActiveUserQueryVariables, GraphQLService, UserInfo } from '@cloudbeaver/core-sdk';
import { getUniqueId } from '@cloudbeaver/core-utils';

import { AUTH_PROVIDER_LOCAL_ID } from './AUTH_PROVIDER_LOCAL_ID';
import { AuthProviderService } from './AuthProviderService';
Expand Down Expand Up @@ -46,6 +48,7 @@ export class UserInfoResource extends CachedDataResource<UserInfo | null, void,
private readonly authProviderService: AuthProviderService,
sessionResource: SessionResource,
private readonly sessionDataResource: SessionDataResource,
private readonly windowsService: WindowsService,
) {
super(() => null, undefined, ['customIncludeOriginDetails', 'includeConfigurationParameters']);

Expand Down Expand Up @@ -152,11 +155,25 @@ export class UserInfoResource extends CachedDataResource<UserInfo | null, void,
}

async logout(provider?: string, configuration?: string): Promise<void> {
await this.graphQLService.sdk.authLogout({
const { authLogoutExtended } = await this.graphQLService.sdk.authLogout({
provider,
configuration,
});

// TODO handle all redirect links once we know what to do with multiple popups issue
const redirectLinks = authLogoutExtended?.redirectLinks || [];

if (redirectLinks.length) {
const oktaLink = authLogoutExtended?.redirectLinks[0];
const id = `okta-logout-id-${getUniqueId()}`;

this.windowsService.open(id, {
url: oktaLink,
width: 400,
height: 400,
});
}
Wroud marked this conversation as resolved.
Show resolved Hide resolved

this.resetIncludes();
this.setData(await this.loader());
this.sessionDataResource.markOutdated();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
query authLogout(
$provider: ID
$configuration: ID
) {
authLogout(
provider: $provider
configuration: $configuration
)
}
query authLogout($provider: ID, $configuration: ID) {
authLogoutExtended(provider: $provider, configuration: $configuration) {
redirectLinks
}
}
30 changes: 30 additions & 0 deletions webapp/packages/core-utils/src/getUniqueId.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { getUniqueId } from './getUniqueId';

describe('getUniqueId', () => {
const uniqueIdsCount = 100;

it('should return unique ids', () => {
const ids = new Set();
for (let i = 0; i < uniqueIdsCount; i++) {
ids.add(getUniqueId());
}
expect(ids.size).toBe(uniqueIdsCount);
});

it('should return unique ids in parallel', async () => {
const ids = new Set();
const promises = [];
for (let i = 0; i < uniqueIdsCount; i++) {
promises.push(
new Promise(resolve => {
setTimeout(() => {
ids.add(getUniqueId());
resolve(true);
}, Math.random() * 100);
}),
);
}
await Promise.all(promises);
expect(ids.size).toBe(uniqueIdsCount);
});
});
10 changes: 10 additions & 0 deletions webapp/packages/core-utils/src/getUniqueId.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
function getNextIdGenerator() {
let id = 0;
return () => id++;
}

const getNextId = getNextIdGenerator();

export function getUniqueId() {
return getNextId();
}
Wroud marked this conversation as resolved.
Show resolved Hide resolved
1 change: 1 addition & 0 deletions webapp/packages/core-utils/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export * from './isPropertiesEqual';
export * from './isSafari';
export * from './isSameDay';
export * from './isValuesEqual';
export * from './getUniqueId';
export * from './md5';
export * from './MetadataMap';
export * from './OrderedMap';
Expand Down
Loading