diff --git a/public/apps/access-error-component.tsx b/public/apps/access-error-component.tsx new file mode 100644 index 000000000..74f41af61 --- /dev/null +++ b/public/apps/access-error-component.tsx @@ -0,0 +1,32 @@ +/* + * Copyright OpenSearch Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the "license" file accompanying this file. This file 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. + */ + +import { EuiPageContent } from '@elastic/eui'; +import React from 'react'; + +interface AccessErrorComponentProps { + dataSourceLabel?: string; + message?: string; +} + +export const AccessErrorComponent: React.FC = (props) => { + const { dataSourceLabel, message = 'You do not have permissions to view this data' } = props; + return ( + + {message} + {dataSourceLabel ? ` for ${props.dataSourceLabel}.` : '.'} + + ); +}; diff --git a/public/apps/configuration/panels/audit-logging/audit-logging.tsx b/public/apps/configuration/panels/audit-logging/audit-logging.tsx index 7b3bd5429..c89acdd12 100644 --- a/public/apps/configuration/panels/audit-logging/audit-logging.tsx +++ b/public/apps/configuration/panels/audit-logging/audit-logging.tsx @@ -45,6 +45,7 @@ import { ViewSettingGroup } from './view-setting-group'; import { DocLinks } from '../../constants'; import { DataSourceContext } from '../../app-router'; import { SecurityPluginTopNavMenu } from '../../top-nav-menu'; +import { AccessErrorComponent } from '../../../access-error-component'; interface AuditLoggingProps extends AppDependencies { fromType: string; @@ -137,6 +138,7 @@ export function renderComplianceSettings(config: AuditLoggingSettings) { export function AuditLogging(props: AuditLoggingProps) { const [configuration, setConfiguration] = React.useState({}); const { dataSource, setDataSource } = useContext(DataSourceContext)!; + const [errorFlag, setErrorFlag] = React.useState(false); const onSwitchChange = async () => { try { @@ -156,9 +158,11 @@ export function AuditLogging(props: AuditLoggingProps) { try { const auditLogging = await getAuditLogging(props.coreStart.http, dataSource.id); setConfiguration(auditLogging); + setErrorFlag(false); } catch (e) { // TODO: switch to better error handling. console.log(e); + setErrorFlag(true); } }; @@ -237,7 +241,11 @@ export function AuditLogging(props: AuditLoggingProps) { setDataSource={setDataSource} selectedDataSource={dataSource} /> - {content} + {errorFlag ? ( + + ) : ( + content + )} ); } diff --git a/public/apps/configuration/panels/auth-view/auth-view.tsx b/public/apps/configuration/panels/auth-view/auth-view.tsx index f425a357c..d32efcedf 100644 --- a/public/apps/configuration/panels/auth-view/auth-view.tsx +++ b/public/apps/configuration/panels/auth-view/auth-view.tsx @@ -25,12 +25,14 @@ import { getSecurityConfig } from '../../utils/auth-view-utils'; import { InstructionView } from './instruction-view'; import { DataSourceContext } from '../../app-router'; import { SecurityPluginTopNavMenu } from '../../top-nav-menu'; +import { AccessErrorComponent } from '../../../access-error-component'; export function AuthView(props: AppDependencies) { const [authentication, setAuthentication] = React.useState([]); const [authorization, setAuthorization] = React.useState([]); const [loading, setLoading] = useState(false); const { dataSource, setDataSource } = useContext(DataSourceContext)!; + const [errorFlag, setErrorFlag] = React.useState(false); React.useEffect(() => { const fetchData = async () => { @@ -40,8 +42,10 @@ export function AuthView(props: AppDependencies) { setAuthentication(config.authc); setAuthorization(config.authz); + setErrorFlag(false); } catch (e) { console.log(e); + setErrorFlag(true); } finally { setLoading(false); } @@ -59,7 +63,11 @@ export function AuthView(props: AppDependencies) { setDataSource={setDataSource} selectedDataSource={dataSource} /> - + {errorFlag ? ( + + ) : ( + + )} ); } @@ -72,22 +80,35 @@ export function AuthView(props: AppDependencies) { setDataSource={setDataSource} selectedDataSource={dataSource} /> - - -

Authentication and authorization

-
- {props.config.ui.backend_configurable && ( - - )} -
- {/* @ts-ignore */} - - - {/* @ts-ignore */} - + {errorFlag ? ( + <> + + +

Authentication and authorization

+
+
+ + + ) : ( + <> + + +

Authentication and authorization

+
+ {props.config.ui.backend_configurable && ( + + )} +
+ /* @ts-ignore */ + + + /* @ts-ignore */ + + + )} ); } diff --git a/public/apps/configuration/panels/permission-list/permission-list.tsx b/public/apps/configuration/panels/permission-list/permission-list.tsx index d7237c7b5..41cf84a22 100644 --- a/public/apps/configuration/panels/permission-list/permission-list.tsx +++ b/public/apps/configuration/panels/permission-list/permission-list.tsx @@ -63,6 +63,7 @@ import { generateResourceName } from '../../utils/resource-utils'; import { DocLinks } from '../../constants'; import { SecurityPluginTopNavMenu } from '../../top-nav-menu'; import { DataSourceContext } from '../../app-router'; +import { AccessErrorComponent } from '../../../access-error-component'; export function renderBooleanToCheckMark(value: boolean): React.ReactNode { return value ? : ''; @@ -361,57 +362,66 @@ export function PermissionList(props: AppDependencies) {

Permissions

- - - - -

- Permissions - - {' '} - ({Query.execute(query || '', permissionList).length}) - -

-
- - Permissions are individual actions, such as cluster:admin/snapshot/restore, which lets - you restore snapshots. Action groups are reusable collections of permissions, such as - MANAGE_SNAPSHOTS, which lets you view, take, delete, and restore snapshots. You can - often meet your security needs using the default action groups, but you might find it - convenient to create your own. - -
- - - {actionsMenu} - {createActionGroupMenu} - - -
- - { - setQuery(arg.query); - return true; - }, - }} - selection={{ onSelectionChange: setSelection }} - sorting={{ sort: { field: 'type', direction: 'asc' } }} - error={errorFlag ? 'Load data failed, please check console log for more detail.' : ''} - isExpandable={true} - itemIdToExpandedRowMap={itemIdToExpandedRowMap} - message={showTableStatusMessage(loading, permissionList)} - /> - -
+ {errorFlag ? ( + + ) : ( + + + + +

+ Permissions + + {' '} + ({Query.execute(query || '', permissionList).length}) + +

+
+ + Permissions are individual actions, such as cluster:admin/snapshot/restore, which + lets you restore snapshots. Action groups are reusable collections of permissions, + such as MANAGE_SNAPSHOTS, which lets you view, take, delete, and restore snapshots. + You can often meet your security needs using the default action groups, but you + might find it convenient to create your own.{' '} + + +
+ + + {actionsMenu} + {createActionGroupMenu} + + +
+ + { + setQuery(arg.query); + return true; + }, + }} + selection={{ onSelectionChange: setSelection }} + sorting={{ sort: { field: 'type', direction: 'asc' } }} + error={errorFlag ? 'Load data failed, please check console log for more detail.' : ''} + isExpandable={true} + itemIdToExpandedRowMap={itemIdToExpandedRowMap} + message={showTableStatusMessage(loading, permissionList)} + /> + +
+ )} {editModal} {deleteConfirmModal} diff --git a/public/apps/configuration/panels/role-list.tsx b/public/apps/configuration/panels/role-list.tsx index 72b8a5837..2d7b46e5b 100644 --- a/public/apps/configuration/panels/role-list.tsx +++ b/public/apps/configuration/panels/role-list.tsx @@ -56,6 +56,7 @@ import { useContextMenuState } from '../utils/context-menu'; import { DocLinks } from '../constants'; import { DataSourceContext } from '../app-router'; import { SecurityPluginTopNavMenu } from '../top-nav-menu'; +import { AccessErrorComponent } from '../../access-error-component'; const columns: Array> = [ { @@ -265,58 +266,62 @@ export function RoleList(props: AppDependencies) {

Roles

- - - - -

- Roles - - {' '} - ({Query.execute(query || '', roleData).length}) - -

-
- - Roles are the core way of controlling access to your cluster. Roles contain any - combination of cluster-wide permission, index-specific permissions, document- and - field-level security, and tenants. Then you map users to these roles so that users - gain those permissions. - -
- - - {actionsMenu} - - - Create role - - - - -
- - - - {deleteConfirmModal} -
+ {errorFlag ? ( + + ) : ( + + + + +

+ Roles + + {' '} + ({Query.execute(query || '', roleData).length}) + +

+
+ + Roles are the core way of controlling access to your cluster. Roles contain any + combination of cluster-wide permission, index-specific permissions, document- and + field-level security, and tenants. Then you map users to these roles so that users + gain those permissions. + +
+ + + {actionsMenu} + + + Create role + + + + +
+ + + + {deleteConfirmModal} +
+ )} ); } diff --git a/public/apps/configuration/panels/service-account-list.tsx b/public/apps/configuration/panels/service-account-list.tsx index 4ed2d7a5f..f1e34defc 100644 --- a/public/apps/configuration/panels/service-account-list.tsx +++ b/public/apps/configuration/panels/service-account-list.tsx @@ -44,6 +44,7 @@ import { showTableStatusMessage } from '../utils/loading-spinner-utils'; import { buildHashUrl } from '../utils/url-builder'; import { LocalCluster } from '../app-router'; import { SecurityPluginTopNavMenu } from '../top-nav-menu'; +import { AccessErrorComponent } from '../../access-error-component'; export function dictView(items: Dictionary) { if (isEmpty(items)) { @@ -176,58 +177,62 @@ export function ServiceAccountList(props: AppDependencies) {

Service accounts

- - - - -

- Service accounts - - {' '} - ({Query.execute(query || '', userData).length}) - -

-
- - Here you have a list of special accounts that represent services like extensions, - plugins or other third party applications. You can map an account to a role from - Roles - “Manage mapping” - - -
- - - {actionsMenu} - - -
- - { - setQuery(arg.query); - return true; - }, - }} - // @ts-ignore - selection={{ onSelectionChange: setSelection }} - sorting - error={ - errorFlag ? 'Load data failed, please check the console log for more details.' : '' - } - message={showTableStatusMessage(loading, userData)} - /> - -
+ {errorFlag ? ( + + ) : ( + + + + +

+ Service accounts + + {' '} + ({Query.execute(query || '', userData).length}) + +

+
+ + Here you have a list of special accounts that represent services like extensions, + plugins or other third party applications. You can map an account to a role from + Roles + “Manage mapping” + + +
+ + + {actionsMenu} + + +
+ + { + setQuery(arg.query); + return true; + }, + }} + // @ts-ignore + selection={{ onSelectionChange: setSelection }} + sorting + error={ + errorFlag ? 'Load data failed, please check the console log for more details.' : '' + } + message={showTableStatusMessage(loading, userData)} + /> + +
+ )} ); } diff --git a/public/apps/configuration/panels/user-list.tsx b/public/apps/configuration/panels/user-list.tsx index 100cef0e0..cf3f6c832 100644 --- a/public/apps/configuration/panels/user-list.tsx +++ b/public/apps/configuration/panels/user-list.tsx @@ -50,6 +50,7 @@ import { showTableStatusMessage } from '../utils/loading-spinner-utils'; import { buildHashUrl } from '../utils/url-builder'; import { DataSourceContext } from '../app-router'; import { SecurityPluginTopNavMenu } from '../top-nav-menu'; +import { AccessErrorComponent } from '../../access-error-component'; export function dictView(items: Dictionary) { if (isEmpty(items)) { @@ -213,67 +214,71 @@ export function UserList(props: AppDependencies) {

Internal users

- - - - -

- Internal users - - {' '} - ({Query.execute(query || '', userData).length}) - -

-
- - The Security plugin includes an internal user database. Use this database in place of, - or in addition to, an external authentication system such as LDAP server or Active - Directory. You can map an user account to a role from{' '} - Roles - . First, click into the detail page of the role. Then, under “Mapped users”, click - “Manage mapping” - -
- - - {actionsMenu} - - - Create user account - - - - -
- - { - setQuery(arg.query); - return true; - }, - }} - // @ts-ignore - selection={{ onSelectionChange: setSelection }} - sorting - error={errorFlag ? 'Load data failed, please check console log for more detail.' : ''} - message={showTableStatusMessage(loading, userData)} - /> - - {deleteConfirmModal} -
+ {errorFlag ? ( + + ) : ( + + + + +

+ Internal users + + {' '} + ({Query.execute(query || '', userData).length}) + +

+
+ + The Security plugin includes an internal user database. Use this database in place + of, or in addition to, an external authentication system such as LDAP server or + Active Directory. You can map an user account to a role from{' '} + Roles + . First, click into the detail page of the role. Then, under “Mapped users”, click + “Manage mapping” + +
+ + + {actionsMenu} + + + Create user account + + + + +
+ + { + setQuery(arg.query); + return true; + }, + }} + // @ts-ignore + selection={{ onSelectionChange: setSelection }} + sorting + error={errorFlag ? 'Load data failed, please check console log for more detail.' : ''} + message={showTableStatusMessage(loading, userData)} + /> + + {deleteConfirmModal} +
+ )} ); } diff --git a/public/plugin.ts b/public/plugin.ts index ca9798a94..bc799768f 100644 --- a/public/plugin.ts +++ b/public/plugin.ts @@ -87,6 +87,7 @@ export class SecurityPlugin deps: SecurityPluginSetupDependencies ): Promise { const apiPermission = await hasApiPermission(core); + const mdsEnabled = true; const config = this.initializerContext.config.get(); @@ -96,7 +97,7 @@ export class SecurityPlugin (config.readonly_mode?.roles || DEFAULT_READONLY_ROLES).includes(role) ); - if (apiPermission) { + if (mdsEnabled || apiPermission) { core.application.register({ id: PLUGIN_NAME, title: 'Security', diff --git a/public/utils/datasource-utils.ts b/public/utils/datasource-utils.ts index 84b7cc135..638c515eb 100644 --- a/public/utils/datasource-utils.ts +++ b/public/utils/datasource-utils.ts @@ -27,7 +27,12 @@ export function getClusterInfo(dataSourceEnabled: boolean, cluster: DataSourceOp export function getDataSourceFromUrl(): DataSourceOption { const urlParams = new URLSearchParams(window.location.search); const dataSourceParam = (urlParams && urlParams.get(DATASOURCEURLKEY)) || '{}'; - return JSON.parse(dataSourceParam); + // following block is needed if the dataSource param is set to non-JSON value, say 'undefined' + try { + return JSON.parse(dataSourceParam); + } catch (e) { + return JSON.parse('{}'); // Return an empty object or some default value if parsing fails + } } export function setDataSourceInUrl(dataSource: DataSourceOption) { diff --git a/public/utils/test/datasource-utils.test.ts b/public/utils/test/datasource-utils.test.ts index 040166ec8..5f945155f 100644 --- a/public/utils/test/datasource-utils.test.ts +++ b/public/utils/test/datasource-utils.test.ts @@ -69,4 +69,13 @@ describe('Tests datasource utils', () => { 'http://localhost:5601/app/security-dashboards-plugin?dataSource=%7B%22id%22%3A%22%22%2C%22label%22%3A%22Local+cluster%22%7D#/auth' ); }); + + it('Tests getting the datasource from the url with undefined dataSource', () => { + const mockSearchUndefinedDataSource = '?dataSource=undefined'; + Object.defineProperty(window, 'location', { + value: { search: mockSearchUndefinedDataSource }, + writable: true, + }); + expect(getDataSourceFromUrl()).toEqual({}); + }); });