Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
18 changes: 18 additions & 0 deletions docs/docs/configuration/networking-settings.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,24 @@ To enable this entry, add the following line to the `.env` file:
SUPERSET_FEATURE_EMBEDDED_SUPERSET=true
```

### Hiding the Logout Button in Embedded Contexts

When Superset is embedded in an application that manages authentication via SSO (OAuth2, SAML, or JWT), the logout button should be hidden since session management is handled by the parent application.
Comment thread
richardfogaca marked this conversation as resolved.

To hide the logout button in embedded contexts, add to `superset_config.py`:

```python
FEATURE_FLAGS = {
"DISABLE_EMBEDDED_SUPERSET_LOGOUT": True,
}
Comment thread
richardfogaca marked this conversation as resolved.
```

This flag only hides the logout button when Superset detects it is running inside an iframe. Users accessing Superset directly (not embedded) will still see the logout button regardless of this setting.

:::note
When embedding with SSO, also set `SESSION_COOKIE_SAMESITE = 'None'` and `SESSION_COOKIE_SECURE = True`. See [Security documentation](/docs/security/securing_superset) for details.
:::

## CSRF settings

Similarly, [flask-wtf](https://flask-wtf.readthedocs.io/en/0.15.x/config/) is used to manage
Expand Down
8 changes: 8 additions & 0 deletions docs/static/feature-flags.json
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,14 @@
"description": "Data panel closed by default in chart builder",
"category": "runtime_config"
},
{
"name": "DISABLE_EMBEDDED_SUPERSET_LOGOUT",
"default": false,
"lifecycle": "stable",
"description": "Hide the logout button in embedded contexts (e.g., when using SSO in iframes)",
"docs": "https://superset.apache.org/docs/configuration/networking-settings#hiding-the-logout-button-in-embedded-contexts",
"category": "runtime_config"
},
{
"name": "DRILL_BY",
"default": true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ export enum FeatureFlag {
DatapanelClosedByDefault = 'DATAPANEL_CLOSED_BY_DEFAULT',
DatasetFolders = 'DATASET_FOLDERS',
DateRangeTimeshiftsEnabled = 'DATE_RANGE_TIMESHIFTS_ENABLED',
DisableEmbeddedSupersetLogout = 'DISABLE_EMBEDDED_SUPERSET_LOGOUT',
/** @deprecated */
DrillToDetail = 'DRILL_TO_DETAIL',
DrillBy = 'DRILL_BY',
Expand Down
83 changes: 83 additions & 0 deletions superset-frontend/src/features/home/RightMenu.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,26 @@ import {
userEvent,
waitFor,
} from 'spec/helpers/testing-library';
import { isFeatureEnabled, FeatureFlag } from '@superset-ui/core';
import { isEmbedded } from 'src/dashboard/util/isEmbedded';
import RightMenu from './RightMenu';
import { GlobalMenuDataOptions, RightMenuProps } from './types';

jest.mock('@superset-ui/core', () => ({
...jest.requireActual('@superset-ui/core'),
isFeatureEnabled: jest.fn(),
}));

const mockIsFeatureEnabled = isFeatureEnabled as jest.MockedFunction<
typeof isFeatureEnabled
>;

jest.mock('src/dashboard/util/isEmbedded', () => ({
isEmbedded: jest.fn(() => false),
}));

const mockIsEmbedded = isEmbedded as jest.MockedFunction<typeof isEmbedded>;

jest.mock('react-redux', () => ({
...jest.requireActual('react-redux'),
useSelector: jest.fn(),
Expand Down Expand Up @@ -160,6 +177,8 @@ const getDatabaseWithNameFilterMockUrl =
'glob:*api/v1/database/?q=(filters:!((col:database_name,opr:neq,value:examples)))';

beforeEach(async () => {
mockIsFeatureEnabled.mockReturnValue(false);
Comment thread
richardfogaca marked this conversation as resolved.
mockIsEmbedded.mockReturnValue(false);
useSelectorMock.mockReset();
fetchMock.get(
getDatabaseWithFileFiterMockUrl,
Expand Down Expand Up @@ -393,3 +412,67 @@ test('Logs out and clears local storage item redux', async () => {
expect(sessionStorage.getItem('login_attempted')).toBeNull();
});
});

test('shows logout button when not embedded', async () => {
mockIsEmbedded.mockReturnValue(false);
mockIsFeatureEnabled.mockReturnValue(false);
resetUseSelectorMock();
render(<RightMenu {...createProps()} />, {
useRedux: true,
useQueryParams: true,
useRouter: true,
useTheme: true,
});

userEvent.hover(await screen.findByText(/Settings/i));
expect(await screen.findByText('Logout')).toBeInTheDocument();
});

test('shows logout button when embedded but flag is disabled', async () => {
mockIsEmbedded.mockReturnValue(true);
mockIsFeatureEnabled.mockReturnValue(false);
resetUseSelectorMock();
render(<RightMenu {...createProps()} />, {
useRedux: true,
useQueryParams: true,
useRouter: true,
useTheme: true,
});

userEvent.hover(await screen.findByText(/Settings/i));
expect(await screen.findByText('Logout')).toBeInTheDocument();
});

test('shows logout button when not embedded even if flag is enabled', async () => {
mockIsEmbedded.mockReturnValue(false);
mockIsFeatureEnabled.mockImplementation(
(flag: FeatureFlag) => flag === FeatureFlag.DisableEmbeddedSupersetLogout,
);
resetUseSelectorMock();
render(<RightMenu {...createProps()} />, {
useRedux: true,
useQueryParams: true,
useRouter: true,
useTheme: true,
});

userEvent.hover(await screen.findByText(/Settings/i));
expect(await screen.findByText('Logout')).toBeInTheDocument();
});

test('hides logout button when embedded and flag is enabled', async () => {
mockIsEmbedded.mockReturnValue(true);
mockIsFeatureEnabled.mockImplementation(
(flag: FeatureFlag) => flag === FeatureFlag.DisableEmbeddedSupersetLogout,
);
resetUseSelectorMock();
render(<RightMenu {...createProps()} />, {
useRedux: true,
useQueryParams: true,
useRouter: true,
useTheme: true,
});

userEvent.hover(await screen.findByText(/Settings/i));
expect(screen.queryByText('Logout')).not.toBeInTheDocument();
Comment thread
richardfogaca marked this conversation as resolved.
});
33 changes: 23 additions & 10 deletions superset-frontend/src/features/home/RightMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,12 @@ import { Link } from 'react-router-dom';
import { useQueryParams, BooleanParam } from 'use-query-params';
import { isEmpty } from 'lodash';
import { t } from '@apache-superset/core';
import { SupersetClient, getExtensionsRegistry } from '@superset-ui/core';
import {
SupersetClient,
getExtensionsRegistry,
isFeatureEnabled,
FeatureFlag,
} from '@superset-ui/core';
import { styled, css, SupersetTheme, useTheme } from '@apache-superset/core/ui';
import {
Tag,
Expand All @@ -35,6 +40,7 @@ import {
} from '@superset-ui/core/components';
import type { ItemType, MenuItem } from '@superset-ui/core/components/Menu';
import { ensureAppRoot, makeUrl } from 'src/utils/pathUtils';
import { isEmbedded } from 'src/dashboard/util/isEmbedded';
import { findPermission } from 'src/utils/findPermission';
import { isUserAdmin } from 'src/dashboard/util/permissionUtils';
import {
Expand Down Expand Up @@ -489,15 +495,22 @@ const RightMenu = ({
),
});
}
userItems.push({
key: 'logout',
label: (
<Typography.Link href={navbarRight.user_logout_url}>
{t('Logout')}
</Typography.Link>
),
onClick: handleLogout,
});
const showLogout =
!isEmbedded() ||
!isFeatureEnabled(FeatureFlag.DisableEmbeddedSupersetLogout);
if (showLogout) {
userItems.push({
key: 'logout',
label: (
<Typography.Link
href={ensureAppRoot(navbarRight.user_logout_url)}
>
{t('Logout')}
</Typography.Link>
),
onClick: handleLogout,
});
}

items.push({
type: 'group',
Expand Down
5 changes: 5 additions & 0 deletions superset/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -714,6 +714,11 @@ class D3TimeFormat(TypedDict, total=False):
# @lifecycle: stable
# @category: runtime_config
"DATAPANEL_CLOSED_BY_DEFAULT": False,
# Hide the logout button in embedded contexts (e.g., when using SSO in iframes)
# @lifecycle: stable
# @category: runtime_config
# @docs: https://superset.apache.org/docs/configuration/networking-settings#hiding-the-logout-button-in-embedded-contexts
"DISABLE_EMBEDDED_SUPERSET_LOGOUT": False,
# Enable drill-by functionality in charts
# @lifecycle: stable
# @category: runtime_config
Expand Down
Loading