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-4561 te UI dynamic connection credentials based on the users team #2381

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
558ab61
CB-4598 npe fix
alexander-skoblikov Feb 5, 2024
caaf439
Merge remote-tracking branch 'origin/devel' into CB-4561-te-ui-dynami…
Wroud Feb 7, 2024
403faa3
Merge remote-tracking branch 'origin/devel' into CB-4561-te-ui-dynami…
Wroud Feb 8, 2024
fa4c72c
CB-4561 refactor: connection info resource
Wroud Feb 8, 2024
3e5d200
CB-4561 refactor: connection info resource
Wroud Feb 8, 2024
d321a87
Merge remote-tracking branch 'origin/devel' into CB-4561-te-ui-dynami…
Wroud Feb 12, 2024
cd73b17
Merge branch 'devel' into CB-4561-te-ui-dynamic-connection-credential…
Wroud Feb 13, 2024
df923ce
CB-4561 load credentials from secret in initConnection
alexander-skoblikov Feb 13, 2024
711eb95
Merge remote-tracking branch 'origin/devel' into CB-4561-te-ui-dynami…
alexander-skoblikov Feb 13, 2024
f73835a
CB-4561 merge devel
alexander-skoblikov Feb 13, 2024
0523219
CB-4561 allow initial team secrets test
alexander-skoblikov Feb 13, 2024
63128da
CB-4561 remove ability to use any secret in test connection
alexander-skoblikov Feb 13, 2024
a828e7b
CB-4561 feat: shared credentials management
Wroud Feb 13, 2024
6bad13d
CB-4561 feat: connection secret authentication
Wroud Feb 13, 2024
5899fef
CB-4561 little fix
alexander-skoblikov Feb 13, 2024
0828d2a
Merge remote-tracking branch 'origin/devel' into CB-4561-te-ui-dynami…
alexander-skoblikov Feb 14, 2024
bb10659
CB-4561 feat: shared credentials
Wroud Feb 14, 2024
fbdff19
CB-4561 chore: remove save credentials checkbox for shared credentials
Wroud Feb 15, 2024
83a344f
CB-4561 fix: send saved state for shared connection credentials
Wroud Feb 15, 2024
cd745c3
CB-4561 feat: change shared secret authentication description
Wroud Feb 15, 2024
efaad73
CB-4561 fix: pr review comments
Wroud Feb 19, 2024
4cd3bcc
CB-4561 fix: disable connection test for shared connections in distri…
Wroud Feb 19, 2024
e2fa99c
CB-4561 fix: types
Wroud Feb 20, 2024
47ba5c7
Merge branch 'devel' into CB-4561-te-ui-dynamic-connection-credential…
mr-anton-t Feb 20, 2024
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 @@ -448,5 +448,12 @@ public int getKeepAliveInterval() {
return dataSourceContainer.getConnectionConfiguration().getKeepAliveInterval();
}

@Property
public List<WebSecretInfo> getSharedSecrets() throws DBException {
return dataSourceContainer.listSharedCredentials()
.stream()
.map(WebSecretInfo::new)
.collect(Collectors.toList());
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* 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.model;

import org.jkiss.dbeaver.model.meta.Property;
import org.jkiss.dbeaver.model.secret.DBSSecretValue;

public class WebSecretInfo {
private final DBSSecretValue secretValue;

public WebSecretInfo(DBSSecretValue secretValue) {
this.secretValue = secretValue;
}

@Property
public String getDisplayName() {
return secretValue.getDisplayName();
}

@Property
public String getSecretId() {
return secretValue.getSubjectId();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1008,6 +1008,17 @@ public WebProjectImpl getProjectById(@Nullable String projectId) {
return getWorkspace().getProjectById(projectId);
}

public WebProjectImpl getAccessibleProjectById(@Nullable String projectId) throws DBWebException {
WebProjectImpl project = null;
if (projectId != null) {
project = getWorkspace().getProjectById(projectId);
}
if (project == null) {
throw new DBWebException("Project not found: " + projectId);
}
return project;
}

public List<WebProjectImpl> getAccessibleProjects() {
return getWorkspace().getProjects();
}
Expand Down
12 changes: 10 additions & 2 deletions server/bundles/io.cloudbeaver.server/schema/service.core.graphqls
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,11 @@ type NetworkHandlerConfig {
secureProperties: Object!
}

type SecretInfo {
displayName: String!
secretId: String!
}

# Connection instance
type ConnectionInfo {
id: ID!
Expand Down Expand Up @@ -337,6 +342,8 @@ type ConnectionInfo {
saveCredentials: Boolean!
# Shared credentials - the same for all users, stored in secure storage.
sharedCredentials: Boolean!

sharedSecrets: [SecretInfo!]! @since(version: "23.3.5")
# Determines that credentials were saved for current user.
# This field read is slow, it should be read only when it really needed
credentialsSaved: Boolean!
Expand Down Expand Up @@ -488,6 +495,7 @@ input ConnectionConfig {
saveCredentials: Boolean
sharedCredentials: Boolean
authModelId: ID
selectedSecretId: ID @since(version: "23.3.5")
credentials: Object

# Map of provider properties (name/value)
Expand Down Expand Up @@ -584,14 +592,14 @@ extend type Mutation {
copyConnectionFromNode( nodePath: String!, config: ConnectionConfig, projectId: ID ): ConnectionInfo!

# Test connection configuration. Returns remote server version
testConnection( config: ConnectionConfig!, projectId: ID ): ConnectionInfo!
testConnection( config: ConnectionConfig!, projectId: ID): ConnectionInfo!

# Test connection configuration. Returns remote server version
testNetworkHandler( config: NetworkHandlerConfigInput! ): NetworkEndpointInfo!

# Initiate existing connection
initConnection( id: ID!, projectId: ID, credentials: Object, networkCredentials: [NetworkHandlerConfigInput!],
saveCredentials:Boolean, sharedCredentials: Boolean ): ConnectionInfo!
saveCredentials:Boolean, sharedCredentials: Boolean, selectedSecretId:String ): ConnectionInfo!

# Disconnect from database
closeConnection( id: ID!, projectId: ID ): ConnectionInfo!
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
*/
package io.cloudbeaver.model;

import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.model.connection.DBPDriverConfigurationType;
import org.jkiss.dbeaver.model.data.json.JSONUtils;
import org.jkiss.dbeaver.model.meta.Property;
Expand Down Expand Up @@ -59,6 +60,7 @@ public class WebConnectionConfig {
private Map<String, Object> providerProperties;
private List<WebNetworkHandlerConfigInput> networkHandlersConfig;
private DBPDriverConfigurationType configurationType;
private String selectedSecretId;

public WebConnectionConfig() {
}
Expand Down Expand Up @@ -91,6 +93,7 @@ public WebConnectionConfig(Map<String, Object> params) {
properties = JSONUtils.getObjectOrNull(params, "properties");
userName = JSONUtils.getString(params, "userName");
userPassword = JSONUtils.getString(params, "userPassword");
selectedSecretId = JSONUtils.getString(params, "selectedSecretId");

authModelId = JSONUtils.getString(params, "authModelId");
credentials = JSONUtils.getObjectOrNull(params, "credentials");
Expand Down Expand Up @@ -231,4 +234,9 @@ public Map<String, Object> getProviderProperties() {
public Integer getKeepAliveInterval() {
return keepAliveInterval;
}

@Nullable
public String getSelectedSecretId() {
return selectedSecretId;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,8 @@ WebConnectionInfo initConnection(
@NotNull Map<String, Object> authProperties,
@Nullable List<WebNetworkHandlerConfigInput> networkCredentials,
@Nullable Boolean saveCredentials,
@Nullable Boolean sharedCredentials
@Nullable Boolean sharedCredentials,
@Nullable String selectedCredentials
) throws DBWebException;

@WebProjectAction(requireProjectPermissions = {RMConstants.PERMISSION_PROJECT_DATASOURCES_EDIT})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,8 @@ public void bindWiring(DBWBindingContext model) throws DBWebException {
env.getArgument("credentials"),
nhc,
env.getArgument("saveCredentials"),
env.getArgument("sharedCredentials")
env.getArgument("sharedCredentials"),
env.getArgument("selectedSecretId")
);
}
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
import org.jkiss.dbeaver.model.rm.RMProjectType;
import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor;
import org.jkiss.dbeaver.model.secret.DBSSecretController;
import org.jkiss.dbeaver.model.secret.DBSSecretValue;
import org.jkiss.dbeaver.model.websocket.WSConstants;
import org.jkiss.dbeaver.model.websocket.event.datasource.WSDataSourceProperty;
import org.jkiss.dbeaver.registry.DataSourceDescriptor;
Expand Down Expand Up @@ -318,15 +319,32 @@ public WebConnectionInfo initConnection(
@NotNull Map<String, Object> authProperties,
@Nullable List<WebNetworkHandlerConfigInput> networkCredentials,
@Nullable Boolean saveCredentials,
@Nullable Boolean sharedCredentials
@Nullable Boolean sharedCredentials,
@Nullable String selectedSecretId
) throws DBWebException {
WebConnectionInfo connectionInfo = webSession.getWebConnectionInfo(projectId, connectionId);
connectionInfo.setSavedCredentials(authProperties, networkCredentials);

DBPDataSourceContainer dataSourceContainer = connectionInfo.getDataSourceContainer();
var dataSourceContainer = (DataSourceDescriptor) connectionInfo.getDataSourceContainer();
if (dataSourceContainer.isConnected()) {
throw new DBWebException("Datasource '" + dataSourceContainer.getName() + "' is already connected");
}
if (dataSourceContainer.isSharedCredentials() && selectedSecretId != null) {
List<DBSSecretValue> allSecrets;
try {
allSecrets = dataSourceContainer.listSharedCredentials();
} catch (DBException e) {
throw new DBWebException("Error loading connection secret", e);
}
DBSSecretValue selectedSecret =
allSecrets.stream()
.filter(secret -> selectedSecretId.equals(secret.getSubjectId()))
.findFirst().orElse(null);
if (selectedSecret == null) {
throw new DBWebException("Secret not found:" + selectedSecretId);
}
dataSourceContainer.setSelectedSharedCredentials(selectedSecret);
}

boolean oldSavePassword = dataSourceContainer.isSavePassword();
try {
Expand Down Expand Up @@ -642,12 +660,12 @@ public WebConnectionInfo testConnection(

connectionConfig.setSaveCredentials(true); // It is used in createConnectionFromConfig

DBPDataSourceContainer dataSource = WebDataSourceUtils.getLocalOrGlobalDataSource(
DataSourceDescriptor dataSource = (DataSourceDescriptor) WebDataSourceUtils.getLocalOrGlobalDataSource(
CBApplication.getInstance(), webSession, projectId, connectionId);

WebProjectImpl project = getProjectById(webSession, projectId);
DBPDataSourceRegistry sessionRegistry = project.getDataSourceRegistry();
DBPDataSourceContainer testDataSource;
DataSourceDescriptor testDataSource;
if (dataSource != null) {
try {
// Check that creds are saved to trigger secrets resolve
Expand All @@ -656,12 +674,27 @@ public WebConnectionInfo testConnection(
throw new DBWebException("Can't determine whether datasource credentials are saved", e);
}

testDataSource = dataSource.createCopy(dataSource.getRegistry());
testDataSource = (DataSourceDescriptor) dataSource.createCopy(dataSource.getRegistry());
WebServiceUtils.setConnectionConfiguration(
testDataSource.getDriver(),
testDataSource.getConnectionConfiguration(),
connectionConfig
);
if (connectionConfig.getSelectedSecretId() != null) {
try {
DBSSecretValue secretValue = dataSource.listSharedCredentials()
.stream()
.filter(secret -> connectionConfig.getSelectedSecretId().equals(secret.getSubjectId()))
.findFirst()
.orElse(null);

if (secretValue != null) {
testDataSource.setSelectedSharedCredentials(secretValue);
}
} catch (DBException e) {
throw new DBWebException("Failed to load secret value: " + connectionConfig.getSelectedSecretId());
}
}
WebServiceUtils.saveAuthProperties(
testDataSource,
testDataSource.getConnectionConfiguration(),
Expand All @@ -671,7 +704,8 @@ public WebConnectionInfo testConnection(
true
);
} else {
testDataSource = WebServiceUtils.createConnectionFromConfig(connectionConfig, sessionRegistry);
testDataSource = (DataSourceDescriptor) WebServiceUtils.createConnectionFromConfig(connectionConfig,
sessionRegistry);
}
webSession.provideAuthParameters(webSession.getProgressMonitor(), testDataSource, testDataSource.getConnectionConfiguration());
testDataSource.setSavePassword(true); // We need for test to avoid password callback
Expand Down
4 changes: 2 additions & 2 deletions webapp/packages/core-authentication/src/AppAuthService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ export class AppAuthService extends Bootstrap {

get loaders(): ILoadableState[] {
return [
getCachedDataResourceLoaderState(this.userInfoResource, undefined),
getCachedDataResourceLoaderState(this.serverConfigResource, undefined),
getCachedDataResourceLoaderState(this.userInfoResource, () => undefined),
getCachedDataResourceLoaderState(this.serverConfigResource, () => undefined),
];
}

Expand Down
2 changes: 1 addition & 1 deletion webapp/packages/core-blocks/public/icons/success_sm.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import { Button } from '../Button';
import { Container } from '../Containers/Container';
import { Fill } from '../Fill';
import { Form } from '../FormControls/Form';
import { InputField } from '../FormControls/InputField';
import { InputField } from '../FormControls/InputField/InputField';
import { useTranslate } from '../localization/useTranslate';
import { s } from '../s';
import { useFocus } from '../useFocus';
Expand Down
16 changes: 14 additions & 2 deletions webapp/packages/core-blocks/src/Containers/Group.m.css
Original file line number Diff line number Diff line change
@@ -1,6 +1,18 @@
.group {
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2024 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/

.secondary {
composes: theme-background-secondary theme-text-on-secondary from global;
}
.surface {
composes: theme-background-surface theme-text-on-surface from global;

}
.group {
align-content: baseline;
box-sizing: border-box;
padding: 24px;
Expand Down
5 changes: 4 additions & 1 deletion webapp/packages/core-blocks/src/Containers/Group.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,14 @@ import elementsSizeStyles from './shared/ElementsSize.m.css';

interface Props extends IContainerProps {
form?: boolean;
secondary?: boolean;
center?: boolean;
box?: boolean;
boxNoOverflow?: boolean;
}

export const Group = forwardRef<HTMLDivElement, Props & React.HTMLAttributes<HTMLDivElement>>(function Group(
{ form, center, box, boxNoOverflow, className, ...rest },
{ form, center, box, secondary, boxNoOverflow, className, ...rest },
ref,
) {
const styles = useS(style, containerStyles, elementsSizeStyles);
Expand All @@ -40,6 +41,8 @@ export const Group = forwardRef<HTMLDivElement, Props & React.HTMLAttributes<HTM
...containerProps,
group: true,
container: true,
secondary,
surface: !secondary,
form,
center,
boxNoOverflow,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
.field.fill,
.container.fill,
.container > .fill {
flex-grow: 1;
max-width: none;
}

Expand Down
9 changes: 7 additions & 2 deletions webapp/packages/core-blocks/src/ExceptionMessage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import { useErrorDetails } from './useErrorDetails';
import { useS } from './useS';

interface Props {
exception?: Error;
exception?: Error | null;
icon?: boolean;
inline?: boolean;
className?: string;
Expand All @@ -39,6 +39,10 @@ export const ExceptionMessage = observer<Props>(function ExceptionMessage({ exce
};
}

if (!exception) {
return null;
}

return (
<div className={s(styles, { error: true, icon, inline }, className)}>
<div className={s(styles, { errorIcon: true })} title={error.message}>
Expand All @@ -51,7 +55,8 @@ export const ExceptionMessage = observer<Props>(function ExceptionMessage({ exce
<span>{translate('core_blocks_exception_message_error_title')}</span>
</h2>
<div className={s(styles, { errorMessage: true })}>
{translate('core_blocks_exception_message_error_message')} {onRetry && translate('ui_please_retry')}
{(error.hasDetails && error.message) || translate('core_blocks_exception_message_error_message')}{' '}
{onRetry && translate('ui_please_retry')}
</div>
<div className={s(styles, { errorActions: true })}>
<Button type="button" mod={['outlined']} disabled={error.isOpen} onClick={error.open}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
& .fieldLabel {
cursor: pointer;
user-select: none;
padding-left: 10px;
line-height: 16px;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import type { ILayoutSizeProps } from '../../Containers/ILayoutSizeProps';
import { s } from '../../s';
import { useS } from '../../useS';
import { Field } from '../Field';
import { FieldDescription } from '../FieldDescription';
import { FieldLabel } from '../FieldLabel';
import { isControlPresented } from '../isControlPresented';
import { Checkbox, CheckboxBaseProps, CheckboxType, ICheckboxControlledProps, ICheckboxObjectProps } from './Checkbox';
Expand Down
Loading
Loading