Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/devel' into CB-4257-ability-to-a…
Browse files Browse the repository at this point in the history
…ssume-aws-session-using-any-open-id-provider
  • Loading branch information
alexander-skoblikov committed Jan 22, 2024
2 parents afcbc19 + 3274988 commit c67319f
Show file tree
Hide file tree
Showing 17 changed files with 120 additions and 46 deletions.
18 changes: 8 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,21 +22,19 @@ You can see live demo of CloudBeaver here: https://demo.cloudbeaver.io

## Changelog


### 23.3.2. 2024-01-22
- Added password policy for the local authorization. Password parameters can be set in the configuration file;
- The 'Keep alive' option has been added to the connection settings to keep the connection active even in case of inactivity;
- Added ability to display full text for a string data type in value panel;
- The DuckDB driver has been added;
- Different bug fixes and enhancements have been made.

### 23.3.2. 2024-01-08
- Added the ability to view decoded binary-type data in the Value panel;
- Enhanced security for unauthorized access;
- Different bug fixes and enhancements have been made.

### 23.3.1. 2023-12-25
- Performance:
- Upgraded to Jetty 11, delivering improved performance, enhanced features, and better alignment with the latest Java specifications.
- Resource management:
- Read-only scripts now have a padlock icon.
- UX improvement:
- Added validation for mandatory fields in all forms for creating and editing entities.
- Driver management:
- Apache Derby driver has been removed because of the vulnerability issues.
- Many small bug fixes, enhancements, and improvements have been made


### Old CloudBeaver releases
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,8 @@ List<SMUserProvisioning> listExternalUsers(
@NotNull SMAuthProviderCustomConfiguration customConfiguration,
@NotNull SMProvisioningFilter filter
) throws DBException;

default boolean isAuthRoleProvided(SMAuthProviderCustomConfiguration configuration) {
return false;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
import io.cloudbeaver.auth.CBAuthConstants;
import org.jkiss.dbeaver.DBException;

import java.util.List;

public interface WebAuthApplication extends WebApplication {
WebAuthConfiguration getAuthConfiguration();

Expand All @@ -30,4 +32,6 @@ default long getMaxSessionIdleTime() {
}

void flushConfiguration() throws DBException;

String getDefaultAuthRole();
}
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,12 @@ public boolean isPrivate() {
public boolean isRequired() {
return descriptor.isRequired();
}
public boolean isAuthRoleProvided(SMAuthProviderCustomConfiguration configuration) {
if (descriptor.getInstance() instanceof SMProvisioner provisioner) {
return provisioner.isAuthRoleProvided(configuration);
}
return false;
}

public boolean isSupportProvisioning() {
return descriptor.getInstance() instanceof SMProvisioner;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ type AuthProviderConfiguration {
id: ID!
displayName: String!
disabled: Boolean!
authRoleProvided: Boolean

iconURL: String
description: String
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,10 +133,6 @@ public void createUser(
try (Connection dbCon = database.openConnection()) {
try (JDBCTransaction txn = new JDBCTransaction(dbCon)) {
createUser(dbCon, userId, metaParameters, enabled, defaultAuthRole);
String defaultTeamName = application.getAppConfiguration().getDefaultUserTeam();
if (!CommonUtils.isEmpty(defaultTeamName)) {
setUserTeams(dbCon, userId, new String[]{defaultTeamName}, userId);
}
txn.commit();
}
} catch (SQLException e) {
Expand Down Expand Up @@ -167,31 +163,32 @@ public void createUser(
dbStat.execute();
}
saveSubjectMetas(dbCon, userId, metaParameters);

String defaultTeamName = application.getAppConfiguration().getDefaultUserTeam();
if (!CommonUtils.isEmpty(defaultTeamName)) {
setUserTeams(dbCon, userId, new String[]{defaultTeamName}, userId);
}
}

@Override
public void importUsers(@NotNull SMUserImportList userImportList) throws DBException {
for (SMUserProvisioning user : userImportList.getUsers()) {
if (isSubjectExists(user.getUserId())) {
log.info("Skip already exist user: " + user.getUserId());
setUserAuthRole(user.getUserId(), userImportList.getAuthRole());
continue;
}
createUser(user.getUserId(), user.getMetaParameters(), true, userImportList.getAuthRole());
}
try (var dbCon = database.openConnection()) {
importUsers(dbCon, userImportList);
} catch (SQLException e) {
log.error("Failed attempt import user: " + e.getMessage());
}
}

protected void importUsers(@NotNull Connection connection, @NotNull SMUserImportList userImportList)
throws DBException, SQLException {
for (SMUserProvisioning user : userImportList.getUsers()) {
String authRole = user.getAuthRole() == null ? userImportList.getAuthRole() : user.getAuthRole();
if (isSubjectExists(user.getUserId())) {
log.info("User already exist : " + user.getUserId());
setUserAuthRole(connection, user.getUserId(), userImportList.getAuthRole());
setUserAuthRole(connection, user.getUserId(), authRole);
enableUser(connection, user.getUserId(), true);
continue;
}
createUser(connection, user.getUserId(), user.getMetaParameters(), true, userImportList.getAuthRole());
createUser(connection, user.getUserId(), user.getMetaParameters(), true, authRole);
}
}

Expand Down
6 changes: 3 additions & 3 deletions webapp/packages/core-blocks/src/Icon.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,20 @@ import { Icon } from './Icon';
test('icons.svg#name', () => {
(globalThis as any)._ROOT_URI_ = undefined;

render(<Icon name="test" />);
render(<Icon data-testid="Icon" name="test" />);
expect(screen.getByTestId('Icon').querySelector('use')).toHaveAttribute('href', '/icons/icons.svg#test');
});

test('/image.jpg', () => {
(globalThis as any)._ROOT_URI_ = undefined;

render(<Icon name="/image.jpg" />);
render(<Icon data-testid="Icon" name="/image.jpg" />);
expect(screen.getByTestId('Icon').querySelector('use')).toHaveAttribute('href', '/image.jpg');
});

test('{_ROOT_URI_}/icons.svg#name', () => {
(globalThis as any)._ROOT_URI_ = '/path/';

render(<Icon name="test" />);
render(<Icon data-testid="Icon" name="test" />);
expect(screen.getByTestId('Icon').querySelector('use')).toHaveAttribute('href', '/path/icons/icons.svg#test');
});
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
.treeNodeIcon {
position: relative;
box-sizing: border-box;
pointer-events: none;
flex-shrink: 0;
width: 16px;
height: 16px;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import { useS } from '../../useS';
import style from './TreeNodeNested.m.css';

interface Props extends React.PropsWithChildren {
expanded?: boolean;
root?: boolean;
className?: string;
}
Expand Down
2 changes: 1 addition & 1 deletion webapp/packages/core-cli/configs/jest.babel.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@
*/
module.exports = {
presets: ['@babel/preset-env', ['@babel/preset-react', { runtime: 'automatic' }]],
plugins: ['../dist/babel-plugins/TestingAttributes.js', require('@reshadow/babel')],
plugins: [require('@reshadow/babel')],
};
5 changes: 4 additions & 1 deletion webapp/packages/core-cli/tests/test.environment.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,8 @@ module.exports = class CustomTestEnvironment extends Environment {
this.global.TextDecoder = TextDecoder;
this.global.Response = Response;
this.global.Request = Request;

// different machine has its own timezones and some tests can fail because of it
process.env.TZ = 'UTC';
}
};
};
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
fragment AuthProviderConfigurationInfo on AuthProviderConfiguration {
id
displayName
authRoleProvided
iconURL
description
signInLink
Expand Down
2 changes: 1 addition & 1 deletion webapp/packages/core-ui/src/DragAndDrop/useDNDData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ export function useDNDData(context: IDataContextProvider, options: IOptions = {}
}
}

state.isDragging = monitor.isDragging();
state.isDragging = dragging;
},
}));

Expand Down
19 changes: 8 additions & 11 deletions webapp/packages/core-utils/src/TempMap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export class TempMap<TKey, TValue> implements Map<TKey, TValue> {
return 'TempMap';
}

private readonly deleted: TKey[];
private readonly deleted: Map<TKey, boolean>;
private readonly temp: Map<TKey, TValue>;
private flushTask: NodeJS.Timeout | null;
private readonly keysTemp: ICachedValueObject<TKey[]>;
Expand All @@ -33,7 +33,7 @@ export class TempMap<TKey, TValue> implements Map<TKey, TValue> {
constructor(private readonly target: Map<TKey, TValue>, private readonly onSync?: () => void) {
this.temp = new Map();
this.flushTask = null;
this.deleted = [];
this.deleted = new Map();
this.keysTemp = cacheValue();
this.entriesTemp = cacheValue();
this.valuesTemp = cacheValue();
Expand All @@ -45,7 +45,7 @@ export class TempMap<TKey, TValue> implements Map<TKey, TValue> {
}

isDeleted(key: TKey): boolean {
return this.deleted.includes(key);
return this.deleted.get(key) || false;
}

/**
Expand All @@ -56,7 +56,7 @@ export class TempMap<TKey, TValue> implements Map<TKey, TValue> {
clearTimeout(this.flushTask);
this.flushTask = null;
}
this.deleted.splice(0, this.deleted.length);
this.deleted.clear();
this.temp.clear();
this.keysTemp.invalidate();
this.valuesTemp.invalidate();
Expand All @@ -65,7 +65,7 @@ export class TempMap<TKey, TValue> implements Map<TKey, TValue> {

delete(key: TKey): boolean {
this.temp.delete(key);
this.deleted.push(key);
this.deleted.set(key, true);
this.scheduleFlush();
return this.has(key);
}
Expand Down Expand Up @@ -105,10 +105,7 @@ export class TempMap<TKey, TValue> implements Map<TKey, TValue> {
set(key: TKey, value: TValue): this {
this.temp.set(key, value);

const indexOfDeleted = this.deleted.indexOf(key);
if (indexOfDeleted !== -1) {
this.deleted.splice(indexOfDeleted, 1);
}
this.deleted.delete(key);

this.scheduleFlush();
return this;
Expand Down Expand Up @@ -139,10 +136,10 @@ export class TempMap<TKey, TValue> implements Map<TKey, TValue> {

this.flushTask = setTimeout(
action(() => {
for (const deleted of this.deleted) {
for (const [deleted] of this.deleted) {
this.target.delete(deleted);
}
this.deleted.splice(0, this.deleted.length);
this.deleted.clear();

for (const [key, value] of this.temp) {
this.target.set(key, value);
Expand Down
47 changes: 47 additions & 0 deletions webapp/packages/core-utils/src/cacheValue.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { cacheValue } from './cacheValue';

describe('cacheValue', () => {
it('should return cached value', () => {
const cache = cacheValue();
const value = cache.value(() => 1);
expect(value).toBe(1);
expect(cache.invalid).toBe(false);
});

it('should invalidate cache', () => {
const cache = cacheValue();
cache.value(() => 1);
cache.invalidate();
expect(cache.invalid).toBe(true);
});

it('should calculate new value if invalidated', () => {
const fn = jest.fn(() => 1);
const cache = cacheValue();
cache.value(fn);
cache.invalidate();
expect(cache.invalid).toBe(true);
const value = cache.value(fn);
expect(value).toBe(1);
expect(cache.invalid).toBe(false);
expect(fn).toHaveBeenCalledTimes(2);
});

it('should not calculate new value if not invalidated', () => {
const fn = jest.fn(() => 1);
const cache = cacheValue();
cache.value(fn);
const value = cache.value(fn);
expect(value).toBe(1);
expect(cache.invalid).toBe(false);
expect(fn).toHaveBeenCalledTimes(1);
});

it('should cache value until it is invalidated', () => {
const cache = cacheValue();
expect(cache.value(() => 1)).toBe(1);
expect(cache.value(() => 2)).toBe(1);
cache.invalidate();
expect(cache.value(() => 3)).toBe(3);
});
});
18 changes: 18 additions & 0 deletions webapp/packages/core-utils/src/timestampToDate.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { timestampToDate } from './timestampToDate';

describe('timestampToDate', () => {
it('should convert timestamp to date', () => {
const date = timestampToDate(1591862400000);
expect(date).toBe('6/11/2020, 8:00:00 AM');
});

it('should convert negative timestamp to date', () => {
const date = timestampToDate(-1591862400000);
expect(date).toBe('7/23/1919, 4:00:00 PM');
});

it('should convert zero timestamp to date', () => {
const date = timestampToDate(0);
expect(date).toBe('1/1/1970, 12:00:00 AM');
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,13 @@ import React, { useContext, useEffect, useRef } from 'react';

import { getComputed, useExecutor, useObjectRef } from '@cloudbeaver/core-blocks';
import { useService } from '@cloudbeaver/core-di';
import { SyncExecutor } from '@cloudbeaver/core-executor';
import { EObjectFeature, type NavNode, NavNodeInfoResource } from '@cloudbeaver/core-navigation-tree';
import { resourceKeyList } from '@cloudbeaver/core-resource';
import type { IDNDData } from '@cloudbeaver/core-ui';

import { useChildren } from '../../../NodesManager/useChildren';
import { useNode } from '../../../NodesManager/useNode';
import { ElementsTreeContext } from '../ElementsTreeContext';
import type { IElementsTreeAction } from '../IElementsTreeAction';
import type { NavTreeControlComponent } from '../NavigationNodeComponent';
import type { IElementsTree } from '../useElementsTree';

Expand Down Expand Up @@ -94,7 +92,7 @@ export function useNavigationNode(node: NavNode, path: string[]): INavigationNod
}, []);

useExecutor({
executor: contextRef.context?.tree.actions || new SyncExecutor<IElementsTreeAction>(),
executor: contextRef.context?.tree.actions,
handlers: [
function refreshRoot({ type, nodeId }) {
if (type === 'show' && nodeId === node.id) {
Expand Down

0 comments on commit c67319f

Please sign in to comment.