Skip to content

Commit

Permalink
CB-4561 te UI dynamic connection credentials based on the users team (#…
Browse files Browse the repository at this point in the history
…2381)

* CB-4598 npe fix

* CB-4561 refactor: connection info resource

* CB-4561 refactor: connection info resource

* CB-4561 load credentials from secret in initConnection

* CB-4561 merge devel

* CB-4561 allow initial team secrets test

* CB-4561 remove ability to use any secret in test connection

* CB-4561 feat: shared credentials management

* CB-4561 feat: connection secret authentication

* CB-4561 little fix

* CB-4561 feat: shared credentials

* CB-4561 chore: remove save credentials checkbox for shared credentials

* CB-4561 fix: send saved state for shared connection credentials

* CB-4561 feat: change shared secret authentication description

* CB-4561 fix: pr review comments

* CB-4561 fix: disable connection test for shared connections in distributed env

* CB-4561 fix: types

---------

Co-authored-by: Aleksandr Skoblikov <[email protected]>
Co-authored-by: mr-anton-t <[email protected]>
  • Loading branch information
3 people authored Feb 20, 2024
1 parent 662d991 commit 2b90eb0
Show file tree
Hide file tree
Showing 110 changed files with 1,164 additions and 497 deletions.
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

0 comments on commit 2b90eb0

Please sign in to comment.