Compare commits

...
3 Commits
Author SHA1 Message Date
3154204ad7 dbeaver/pro#3393 Add "UDBT" feature (#3000)
Co-authored-by: Serge Rider <serge@jkiss.org>
Co-authored-by: kseniaguzeeva <112612526+kseniaguzeeva@users.noreply.github.com>
2024-10-16 10:33:48 +02:00
6122926c1e CB-5710 return anonymous user info (#2972)
* CB-5710 return anonymous user info

* CB-5710 support anonymous user

* CB-5710 anonymous access disabled active user fix

* CB-5710 simplify logic

* CB-5710 fix error messages

* CB-5710 review fixes

* CB-5710 add computed

* CB-5710 change getters to methods

* CB-5710 fixes after review

---------

Co-authored-by: naumov <iamemptyhuh@gmail.com>
Co-authored-by: alex <48489896+devnaumov@users.noreply.github.com>
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
Co-authored-by: Serge Rider <serge@jkiss.org>
2024-10-16 07:02:18 +02:00
697b76a05b CB-5811 applies new design styles for selects, textareas with disabled or readonly prop (#2999)
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
2024-10-16 06:47:41 +02:00
24 changed files with 76 additions and 43 deletions
@@ -84,14 +84,18 @@ public abstract class BaseWebAppConfiguration implements WebAppConfiguration {
return resourceManagerEnabled;
}
@Override
public boolean isFeatureEnabled(String id) {
return ArrayUtils.contains(getEnabledFeatures(), id);
}
@Override
public boolean isFeaturesEnabled(String[] features) {
return ArrayUtils.containsAll(getEnabledFeatures(), features);
}
@NotNull
@Override
public String[] getEnabledFeatures() {
if (enabledFeatures == null) {
// No config - enable all features (+backward compatibility)
@@ -44,6 +44,11 @@ public interface WebAppConfiguration {
boolean isFeatureEnabled(String id);
@NotNull
default String[] getEnabledFeatures() {
return new String[0];
}
default boolean isSupportsCustomConnections() {
return true;
}
@@ -142,7 +142,7 @@ public class WebServiceNavigator implements DBWServiceNavigator {
return result.subList(offset, result.size());
}
} catch (DBException e) {
throw new DBWebException(e);
throw new DBWebException(e.getMessage(), e);
}
}
@@ -137,6 +137,9 @@ type UserInfo {
configurationParameters: Object!
# User teams
teams: [UserTeamInfo!]!
@since(version: "24.2.3")
isAnonymous: Boolean!
}
type UserTeamInfo {
@@ -69,6 +69,9 @@ public class WebUserInfo {
@Property
public List<String> getLinkedAuthProviders() throws DBWebException {
if (isAnonymous()) {
return List.of();
}
if (linkedProviders == null) {
try {
linkedProviders = session.getSecurityController().getCurrentUserLinkedProviders();
@@ -104,4 +107,9 @@ public class WebUserInfo {
return List.of();
}
}
@Property
public boolean isAnonymous() {
return session.getUser() == null;
}
}
@@ -21,6 +21,7 @@ import io.cloudbeaver.WebServiceUtils;
import io.cloudbeaver.auth.SMSignOutLinkProvider;
import io.cloudbeaver.auth.provider.local.LocalAuthProvider;
import io.cloudbeaver.model.WebPropertyInfo;
import io.cloudbeaver.model.app.WebAppConfiguration;
import io.cloudbeaver.model.session.WebAuthInfo;
import io.cloudbeaver.model.session.WebSession;
import io.cloudbeaver.model.session.WebSessionAuthProcessor;
@@ -189,7 +190,12 @@ public class WebServiceAuthImpl implements DBWServiceAuth {
@Override
public WebUserInfo activeUser(@NotNull WebSession webSession) throws DBWebException {
if (webSession.getUser() == null) {
return null;
WebAppConfiguration appConfiguration = webSession.getApplication().getAppConfiguration();
if (!appConfiguration.isAnonymousAccessEnabled()) {
return null;
}
SMUser anonymous = new SMUser("anonymous", true, null);
return new WebUserInfo(webSession, new WebUser(anonymous));
}
try {
// Read user from security controller. It will also read meta parameters
@@ -16,9 +16,7 @@ import { UserInfoResource } from './UserInfoResource.js';
@injectable()
export class AppAuthService extends Bootstrap {
get authenticated(): boolean {
const user = this.userInfoResource.data;
return this.serverConfigResource.anonymousAccessEnabled || this.serverConfigResource.configurationMode || user !== null;
return this.serverConfigResource.configurationMode || this.userInfoResource.hasAccess();
}
get loaders(): ILoadableState[] {
@@ -56,9 +54,9 @@ export class AppAuthService extends Bootstrap {
throw new Error("Can't configure Authentication");
}
const user = await this.userInfoResource.load();
await this.userInfoResource.load();
return !this.serverConfigResource.configurationMode && !this.serverConfigResource.anonymousAccessEnabled && user === null;
return !this.serverConfigResource.configurationMode && !this.userInfoResource.hasAccess();
}
async authUser(): Promise<boolean> {
@@ -25,10 +25,6 @@ export class AuthInfoService {
return this.userInfoResource.data;
}
get isAnonymous(): boolean {
return !this.userInfoResource.data;
}
constructor(
private readonly userInfoResource: UserInfoResource,
private readonly authProvidersResource: AuthProvidersResource,
@@ -76,6 +76,18 @@ export class UserInfoResource extends CachedDataResource<UserInfo | null, void,
});
}
isAnonymous(): this is { data: UserInfo } {
return this.data?.isAnonymous === true;
}
isAuthenticated(): this is { data: UserInfo } {
return !!this.data && !this.isAnonymous();
}
hasAccess(): this is { data: UserInfo } {
return this.isAnonymous() || this.isAuthenticated();
}
isLinked(provideId: string): boolean {
return this.data?.linkedAuthProviders.includes(provideId) || false;
}
@@ -7,7 +7,7 @@
*/
import { computed, makeObservable } from 'mobx';
import { ANONYMOUS_USER_ID, UserDataService, UserInfoResource } from '@cloudbeaver/core-authentication';
import { UserDataService, UserInfoResource } from '@cloudbeaver/core-authentication';
import { Dependency, injectable } from '@cloudbeaver/core-di';
import { Executor, ExecutorInterrupter, type IExecutor, type ISyncExecutor, SyncExecutor } from '@cloudbeaver/core-executor';
import { CachedMapAllKey, resourceKeyList, ResourceKeyUtils } from '@cloudbeaver/core-resource';
@@ -35,8 +35,6 @@ export class ProjectsService extends Dependency {
if (this.userInfoResource.data) {
project = this.projectInfoResource.getUserProject(this.userInfoResource.data.userId);
} else {
project = this.projectInfoResource.get(ANONYMOUS_USER_ID);
}
return project;
@@ -1,6 +1,7 @@
query getActiveUser($includeConfigurationParameters: Boolean!) {
user: activeUser {
userId
isAnonymous
displayName
authRole
linkedAuthProviders
@@ -65,7 +65,7 @@ export class UserSettingsService extends SettingsSource {
}
async save() {
if (this.userInfoResource.data) {
if (this.userInfoResource.isAuthenticated()) {
await this.userInfoResource.updatePreferences(Object.fromEntries(this.changes));
} else {
this.update(() => {
@@ -94,7 +94,7 @@ export class UserSettingsService extends SettingsSource {
private refreshConfig() {
this.update(() => {
if (!this.userInfoResource.data) {
if (!this.userInfoResource.isAuthenticated()) {
this.clear();
this.lastConfig = null;
return;
@@ -121,7 +121,7 @@ export class UserSettingsService extends SettingsSource {
}
private getSource() {
if (this.userInfoResource.data) {
if (this.userInfoResource.isAuthenticated()) {
return this.settings;
}
@@ -83,11 +83,13 @@
box-shadow: 0 0 0 50px $input-background-readonly inset;
}
}
&:not([data-select='true'])[disabled] {
&[disabled] {
@include mdc-theme-prop(color, input-color-readonly, false);
@include mdc-theme-prop(border-color, input-border-readonly, false);
@include mdc-theme-prop(background-color, input-background-readonly, false);
pointer-events: all;
opacity: 1;
&:-internal-autofill-selected,
&:-internal-autofill-previewed {
box-shadow: 0 0 0 50px $input-background-readonly inset;
@@ -67,7 +67,7 @@ export class AuthenticationService extends Bootstrap {
this.onLogin = new Executor();
this.onLogout.before(this.navigationService.navigationTask);
this.onLogin.before(this.navigationService.navigationTask, undefined, () => authInfoService.isAnonymous);
this.onLogin.before(this.navigationService.navigationTask, undefined, () => userInfoResource.isAnonymous());
this.authPromise = null;
this.configureAuthProvider = null;
@@ -5,7 +5,7 @@
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { AuthInfoService } from '@cloudbeaver/core-authentication';
import { UserInfoResource } from '@cloudbeaver/core-authentication';
import { Bootstrap, injectable } from '@cloudbeaver/core-di';
import { ServerConfigResource } from '@cloudbeaver/core-root';
import { MenuBaseItem, MenuService } from '@cloudbeaver/core-view';
@@ -18,7 +18,7 @@ export class PluginBootstrap extends Bootstrap {
constructor(
private readonly serverConfigResource: ServerConfigResource,
private readonly authenticationService: AuthenticationService,
private readonly authInfoService: AuthInfoService,
private readonly userInfoResource: UserInfoResource,
private readonly menuService: MenuService,
) {
super();
@@ -28,7 +28,7 @@ export class PluginBootstrap extends Bootstrap {
this.menuService.addCreator({
menus: [TOP_NAV_BAR_SETTINGS_MENU],
getItems: (context, items) => {
if (this.serverConfigResource.enabledAuthProviders.length > 0 && !this.authInfoService.userInfo) {
if (this.serverConfigResource.enabledAuthProviders.length > 0 && this.userInfoResource.isAnonymous()) {
return [
...items,
new MenuBaseItem(
@@ -42,7 +42,7 @@ export class PluginBootstrap extends Bootstrap {
];
}
if (this.authInfoService.userInfo) {
if (this.userInfoResource.isAuthenticated()) {
return [
...items,
new MenuBaseItem(
@@ -5,7 +5,7 @@
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { AuthInfoService, AuthProviderService } from '@cloudbeaver/core-authentication';
import { AuthProviderService, UserInfoResource } from '@cloudbeaver/core-authentication';
import { importLazyComponent } from '@cloudbeaver/core-blocks';
import {
type Connection,
@@ -28,7 +28,7 @@ export class ConnectionAuthService extends Dependency {
private readonly connectionInfoResource: ConnectionInfoResource,
private readonly commonDialogService: CommonDialogService,
private readonly authProviderService: AuthProviderService,
private readonly authInfoService: AuthInfoService,
private readonly userInfoResource: UserInfoResource,
private readonly connectionsManagerService: ConnectionsManagerService,
private readonly authenticationService: AuthenticationService,
) {
@@ -41,7 +41,7 @@ export class ConnectionAuthService extends Dependency {
connections: connectionInfoResource.values.filter(connection => connection.connected).map(createConnectionParam),
state,
}),
state => state === 'before' && authInfoService.isAnonymous,
state => state === 'before' && userInfoResource.isAnonymous(),
);
this.authenticationService.onLogout.before(
connectionsManagerService.onDisconnect,
@@ -126,7 +126,7 @@ export class ConnectionFoldersBootstrap extends Bootstrap {
isActionApplicable: (context, action) => {
const tree = context.get(DATA_CONTEXT_ELEMENTS_TREE)!;
if (action !== ACTION_NEW_FOLDER || !this.userInfoResource.data || tree.baseRoot !== ROOT_NODE_PATH) {
if (action !== ACTION_NEW_FOLDER || !this.userInfoResource.isAuthenticated() || tree.baseRoot !== ROOT_NODE_PATH) {
return false;
}
@@ -60,8 +60,9 @@ export class PublicConnectionFormService {
executorHandlerFilter(
() => !!this.formState && this.optionsPanelService.isOpen(formGetter),
async (event, context) => {
if (event === 'before' && this.userInfoResource.data === null) {
if (event === 'before' && this.userInfoResource.isAnonymous()) {
const confirmed = await this.showUnsavedChangesDialog();
if (!confirmed) {
ExecutorInterrupter.interrupt(context);
}
@@ -89,7 +89,7 @@ export class ResourceFoldersBootstrap extends Bootstrap {
isActionApplicable: context => {
const tree = context.get(DATA_CONTEXT_ELEMENTS_TREE);
if (!tree?.baseRoot.startsWith(RESOURCES_NODE_PATH) || !this.userInfoResource.data) {
if (!tree?.baseRoot.startsWith(RESOURCES_NODE_PATH) || !this.userInfoResource.isAuthenticated()) {
return false;
}
@@ -7,7 +7,7 @@
*/
import { computed, makeObservable } from 'mobx';
import { AuthInfoService } from '@cloudbeaver/core-authentication';
import { UserInfoResource } from '@cloudbeaver/core-authentication';
import { injectable } from '@cloudbeaver/core-di';
import type { ProjectInfo } from '@cloudbeaver/core-projects';
import { ServerConfigResource } from '@cloudbeaver/core-root';
@@ -15,11 +15,11 @@ import { ServerConfigResource } from '@cloudbeaver/core-root';
@injectable()
export class ResourceManagerService {
get enabled() {
return !!this.serverConfigResource.data?.resourceManagerEnabled && !!this.authInfoService.userInfo;
return !!this.serverConfigResource.data?.resourceManagerEnabled && this.userInfoResource.isAuthenticated();
}
constructor(
private readonly authInfoService: AuthInfoService,
private readonly userInfoResource: UserInfoResource,
private readonly serverConfigResource: ServerConfigResource,
) {
makeObservable(this, {
@@ -38,7 +38,7 @@ export class SessionExpireWarningDialogBootstrap extends Bootstrap {
}
private handleSessionResourceDataUpdate(isValid?: boolean, remainingTime?: number) {
if (!this.serverConfigResource.anonymousAccessEnabled && !this.userInfoResource.data && !this.serverConfigResource.configurationMode) {
if (!this.serverConfigResource.configurationMode && !this.userInfoResource.hasAccess()) {
return;
}
@@ -7,7 +7,7 @@
*/
import { observer } from 'mobx-react-lite';
import { AuthInfoService, DATA_CONTEXT_USER } from '@cloudbeaver/core-authentication';
import { DATA_CONTEXT_USER, UserInfoResource } from '@cloudbeaver/core-authentication';
import { Icon, Loader, s, useS } from '@cloudbeaver/core-blocks';
import { useDataContextLink } from '@cloudbeaver/core-data-context';
import { useService } from '@cloudbeaver/core-di';
@@ -20,21 +20,20 @@ import style from './UserMenu.module.css';
export const UserMenu = observer(function UserMenu() {
const styles = useS(style);
const authInfoService = useService(AuthInfoService);
const userInfoResource = useService(UserInfoResource);
const menu = useMenu({ menu: MENU_USER_PROFILE });
const userInfo = authInfoService.userInfo;
useDataContextLink(menu.context, (context, id) => {
context.set(DATA_CONTEXT_USER, userInfo, id);
context.set(DATA_CONTEXT_USER, userInfoResource.data, id);
});
if (!userInfo) {
if (!userInfoResource.isAuthenticated()) {
return null;
}
return (
<Loader suspense inline>
<UserInfo info={userInfo} />
<UserInfo info={userInfoResource.data} />
<ContextMenu className={s(styles, { contextMenu: true })} menu={menu} modal>
<Icon className={s(styles, { icon: true })} name="angle" viewBox="0 0 15 8" />
</ContextMenu>
@@ -5,7 +5,7 @@
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { AuthInfoService } from '@cloudbeaver/core-authentication';
import { UserInfoResource } from '@cloudbeaver/core-authentication';
import { importLazyComponent } from '@cloudbeaver/core-blocks';
import { Bootstrap, injectable } from '@cloudbeaver/core-di';
@@ -17,7 +17,7 @@ const UserProfileFormPanel = importLazyComponent(() => import('./UserProfileForm
export class UserProfileFormBootstrap extends Bootstrap {
constructor(
private readonly userProfileTabsService: UserProfileTabsService,
private readonly authInfoService: AuthInfoService,
private readonly userInfoResource: UserInfoResource,
) {
super();
}
@@ -27,7 +27,7 @@ export class UserProfileFormBootstrap extends Bootstrap {
key: 'account',
name: 'plugin_user_profile_account_title',
order: 1,
isHidden: () => this.authInfoService.isAnonymous,
isHidden: () => this.userInfoResource.isAnonymous(),
panel: () => UserProfileFormPanel,
});
}
@@ -82,7 +82,7 @@ export class UserProfileOptionsPanelService {
return;
}
if (this.userInfoResource.data === null) {
if (!this.userInfoResource.hasAccess()) {
this.close(true);
}
}