Skip to content

Commit

Permalink
Adds a new tab for Service Accounts (#1502)
Browse files Browse the repository at this point in the history
---------

Signed-off-by: Craig Perkins <[email protected]>
Signed-off-by: Sam <[email protected]>
Signed-off-by: Ryan Liang <[email protected]>
Signed-off-by: Ryan Liang <[email protected]>
Signed-off-by: Craig Perkins <[email protected]>
Signed-off-by: Darshit Chanpura <[email protected]>
Signed-off-by: Chang Liu <[email protected]>
Signed-off-by: leanneeliatra <[email protected]>
Signed-off-by: nursaadat <[email protected]>
Signed-off-by: Saadat Nursultan <[email protected]>
Signed-off-by: nurbqq <[email protected]>
Signed-off-by: nurbqq <[email protected]>
Signed-off-by: vamsi-amazon <[email protected]>
Signed-off-by: Sirazh Gabdullin <[email protected]>
Signed-off-by: Jochen Kressin <[email protected]>
Signed-off-by: Abhi Kalra <[email protected]>
Signed-off-by: opensearch-ci-bot <[email protected]>
Signed-off-by: Leanne Lacey-Byrne <[email protected]>
Signed-off-by: zhichao-aws <[email protected]>
Signed-off-by: Derek Ho <[email protected]>
Signed-off-by: leanneeliatra <[email protected]>
Signed-off-by: Hailong Cui <[email protected]>
Signed-off-by: Peter Nied <[email protected]>
Signed-off-by: Peter Nied <[email protected]>
Signed-off-by: Stephen Crawford <[email protected]>
Signed-off-by: Darshit Chanpura <[email protected]>
Co-authored-by: Craig Perkins <[email protected]>
Co-authored-by: Ryan Liang <[email protected]>
Co-authored-by: Darshit Chanpura <[email protected]>
Co-authored-by: leanneeliatra <[email protected]>
Co-authored-by: Chang Liu <[email protected]>
Co-authored-by: mattieserver <[email protected]>
Co-authored-by: Saadat Nursultan <[email protected]>
Co-authored-by: nursaadat <[email protected]>
Co-authored-by: Saadat Nursultan <[email protected]>
Co-authored-by: Nurbakhyt Sembayev <[email protected]>
Co-authored-by: Stephen Crawford <[email protected]>
Co-authored-by: Vamsi Manohar <[email protected]>
Co-authored-by: Sirazh Gabdullin <[email protected]>
Co-authored-by: Jochen Kressin <[email protected]>
Co-authored-by: Abhi Kalra <[email protected]>
Co-authored-by: Abhi Kalra <[email protected]>
Co-authored-by: opensearch-trigger-bot[bot] <98922864+opensearch-trigger-bot[bot]@users.noreply.github.com>
Co-authored-by: opensearch-ci-bot <[email protected]>
Co-authored-by: zhichao-aws <[email protected]>
Co-authored-by: Darshit Chanpura <[email protected]>
Co-authored-by: Derek Ho <[email protected]>
Co-authored-by: Hailong Cui <[email protected]>
Co-authored-by: Peter Nied <[email protected]>
Co-authored-by: Derek Ho <[email protected]>
  • Loading branch information
1 parent 31c5a72 commit 94961bd
Show file tree
Hide file tree
Showing 10 changed files with 458 additions and 24 deletions.
12 changes: 2 additions & 10 deletions DEVELOPER_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,17 +92,9 @@ Next, go to the base directory (`cd ../..`) and run `yarn osd bootstrap` to inst
From the base directory, run `yarn start`. This should start dashboard UI successfully. `Cmd+click` the url in the console output (It should look something like `http://0:5601/omf`). Once the page loads, you should be able to log in with user `admin` and password `admin`.
## Testing
## Integration Tests
The security-dashboards-plugin project uses Jest, Cypress and Selenium and makes use of the [OpenSearch Dashboards Functional Test]( https://github.com/opensearch-project/opensearch-dashboards-functional-test) project.
Make sure you have the OpenSearch and OpenSearch Dashboards running with the Security Plugin and that you can log in to it using a web browser.
Clone [OpenSearch Dashboards Functional Test]( https://github.com/opensearch-project/opensearch-dashboards-functional-test) in your local machine and follow the instructions in its DEVELOPER_GUIDE.md
### Integration Tests
To run selenium based integration tests, download and export the firefox web-driver to your PATH. Also, run `node scripts/build_opensearch_dashboards_platform_plugins.js` or `yarn start` before running the tests. This is essential to generate the bundles.
To run selenium based integration tests, download and export the firefox web-driver to your PATH. Also, run `node scripts/build_opensearch_dashboards_platform_plugins.js` or `yarn start` before running the tests. This is essential to generate the bundles.
The integration tests take advantage of [npm "pre" scripts](https://docs.npmjs.com/cli/v9/using-npm/scripts) to run a node based SAML IdP for integration tests related to SAML authentication. This will run a background process that listens on port 7000.
Expand Down
13 changes: 13 additions & 0 deletions public/apps/configuration/app-router.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import { RoleEditMappedUser } from './panels/role-mapping/role-edit-mapped-user'
import { RoleView } from './panels/role-view/role-view';
import { TenantList } from './panels/tenant-list/tenant-list';
import { UserList } from './panels/user-list';
import { ServiceAccountList } from './panels/service-account-list';
import { Action, ResourceType, RouteItem, SubAction } from './types';
import { buildHashUrl, buildUrl } from './utils/url-builder';
import { CrossPageToast } from './cross-page-toast';
Expand All @@ -54,6 +55,10 @@ const ROUTE_MAP: { [key: string]: RouteItem } = {
name: 'Internal users',
href: buildUrl(ResourceType.users),
},
[ResourceType.serviceAccounts]: {
name: 'Service Accounts',
href: buildUrl(ResourceType.serviceAccounts),
},
[ResourceType.permissions]: {
name: 'Permissions',
href: buildUrl(ResourceType.permissions),
Expand Down Expand Up @@ -85,6 +90,7 @@ const ROUTE_LIST = [
ROUTE_MAP[ResourceType.auth],
ROUTE_MAP[ResourceType.roles],
ROUTE_MAP[ResourceType.users],
ROUTE_MAP[ResourceType.serviceAccounts],
ROUTE_MAP[ResourceType.permissions],
ROUTE_MAP[ResourceType.tenants],
ROUTE_MAP[ResourceType.auditLogging],
Expand Down Expand Up @@ -209,6 +215,13 @@ export function AppRouter(props: AppDependencies) {
return <UserList {...props} />;
}}
/>
<Route
path={ROUTE_MAP.serviceAccounts.href}
render={() => {
setGlobalBreadcrumbs(ResourceType.serviceAccounts);
return <ServiceAccountList {...props} />;
}}
/>
<Route
path={buildUrl(ResourceType.auditLogging) + SUB_URL_FOR_GENERAL_SETTINGS_EDIT}
render={() => {
Expand Down
2 changes: 2 additions & 0 deletions public/apps/configuration/constants.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ export const API_ENDPOINT_MULTITENANCY = API_PREFIX + '/multitenancy/tenant';
export const API_ENDPOINT_TENANCY_CONFIGS = API_ENDPOINT + '/tenancy/config';
export const API_ENDPOINT_SECURITYCONFIG = API_ENDPOINT + '/securityconfig';
export const API_ENDPOINT_INTERNALUSERS = API_ENDPOINT + '/internalusers';
export const API_ENDPOINT_INTERNALACCOUNTS = API_ENDPOINT + '/internalaccounts';
export const API_ENDPOINT_SERVICEACCOUNTS = API_ENDPOINT + '/serviceaccounts';
export const API_ENDPOINT_AUDITLOGGING = API_ENDPOINT + '/audit';
export const API_ENDPOINT_AUDITLOGGING_UPDATE = API_ENDPOINT_AUDITLOGGING + '/config';
export const API_ENDPOINT_PERMISSIONS_INFO = API_PREFIX + '/restapiinfo';
Expand Down
220 changes: 220 additions & 0 deletions public/apps/configuration/panels/service-account-list.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
/*
* 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 {
EuiBadge,
EuiButtonEmpty,
EuiFlexGroup,
EuiFlexItem,
EuiInMemoryTable,
EuiLink,
EuiPageBody,
EuiPageContent,
EuiPageContentHeader,
EuiPageContentHeaderSection,
EuiPageHeader,
EuiText,
EuiTitle,
Query,
} from '@elastic/eui';
import { Dictionary, difference, isEmpty, map } from 'lodash';
import React, { useState } from 'react';
import { getAuthInfo } from '../../../utils/auth-info-utils';
import { AppDependencies } from '../../types';
import { API_ENDPOINT_SERVICEACCOUNTS, DocLinks } from '../constants';
import { Action, ResourceType } from '../types';
import { EMPTY_FIELD_VALUE } from '../ui-constants';
import { useContextMenuState } from '../utils/context-menu';
import { ExternalLink, tableItemsUIProps, truncatedListView } from '../utils/display-utils';
import { getUserList, InternalUsersListing } from '../utils/internal-user-list-utils';
import { showTableStatusMessage } from '../utils/loading-spinner-utils';
import { buildHashUrl } from '../utils/url-builder';

export function dictView(items: Dictionary<string>) {
if (isEmpty(items)) {
return EMPTY_FIELD_VALUE;
}
return (
<EuiFlexGroup direction="column" style={{ margin: '1px' }}>
{map(items, (v, k) => (
<EuiText key={k} className={tableItemsUIProps.cssClassName}>
{k}: {`"${v}"`}
</EuiText>
))}
</EuiFlexGroup>
);
}

export function getColumns(currentUsername: string) {
return [
{
field: 'username',
name: 'Username',
render: (username: string) => (
<>
<a href={buildHashUrl(ResourceType.users, Action.edit, username)}>{username}</a>
{username === currentUsername && (
<>
&nbsp;
<EuiBadge>Current</EuiBadge>
</>
)}
</>
),
sortable: true,
},
{
field: 'backend_roles',
name: 'Backend roles',
render: truncatedListView(tableItemsUIProps),
},
{
field: 'attributes',
name: 'Attributes',
render: dictView,
truncateText: true,
},
];
}

export function ServiceAccountList(props: AppDependencies) {
const [userData, setUserData] = React.useState<InternalUsersListing[]>([]);
const [errorFlag, setErrorFlag] = React.useState(false);
const [selection, setSelection] = React.useState<InternalUsersListing[]>([]);
const [currentUsername, setCurrentUsername] = useState('');
const [loading, setLoading] = useState(false);
const [query, setQuery] = useState<Query | null>(null);

React.useEffect(() => {
const fetchData = async () => {
try {
setLoading(true);
const userDataPromise = getUserList(props.coreStart.http, ResourceType.serviceAccounts);
setCurrentUsername((await getAuthInfo(props.coreStart.http)).user_name);
setUserData(await userDataPromise);
} catch (e) {
console.log(e);
setErrorFlag(true);
} finally {
setLoading(false);
}
};

fetchData();
}, [props.coreStart.http]);

const actionsMenuItems = [
<EuiButtonEmpty
data-test-subj="edit"
key="edit"
onClick={() => {
window.location.href = buildHashUrl(ResourceType.users, Action.edit, selection[0].username);
}}
disabled={selection.length !== 1}
>
Edit
</EuiButtonEmpty>,
<EuiButtonEmpty
data-test-subj="duplicate"
key="duplicate"
onClick={() => {
window.location.href = buildHashUrl(
ResourceType.users,
Action.duplicate,
selection[0].username
);
}}
disabled={selection.length !== 1}
>
Duplicate
</EuiButtonEmpty>,
<EuiButtonEmpty
key="export"
disabled={selection.length !== 1}
href={
selection.length === 1
? `${props.coreStart.http.basePath.serverBasePath}${API_ENDPOINT_SERVICEACCOUNTS}/${selection[0].username}`
: ''
}
target="_blank"
>
Export JSON
</EuiButtonEmpty>,
];

const [actionsMenu, closeActionsMenu] = useContextMenuState('Actions', {}, actionsMenuItems);

return (
<>
<EuiPageHeader>
<EuiTitle size="l">
<h1>Service accounts</h1>
</EuiTitle>
</EuiPageHeader>
<EuiPageContent>
<EuiPageContentHeader>
<EuiPageContentHeaderSection>
<EuiTitle size="s">
<h3>
Service accounts
<span className="panel-header-count">
{' '}
({Query.execute(query || '', userData).length})
</span>
</h3>
</EuiTitle>
<EuiText size="s" color="subdued">
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
<EuiLink href={buildHashUrl(ResourceType.roles)}>Roles</EuiLink>
“Manage mapping”
<ExternalLink href={DocLinks.BackendConfigurationAuthenticationDoc} />
</EuiText>
</EuiPageContentHeaderSection>
<EuiPageContentHeaderSection>
<EuiFlexGroup>
<EuiFlexItem>{actionsMenu}</EuiFlexItem>
</EuiFlexGroup>
</EuiPageContentHeaderSection>
</EuiPageContentHeader>
<EuiPageBody>
<EuiInMemoryTable
tableLayout={'auto'}
loading={userData === [] && !errorFlag}
columns={getColumns(currentUsername)}
// @ts-ignore
items={userData}
itemId={'username'}
pagination
search={{
box: { placeholder: 'Search service accounts' },
onChange: (arg) => {
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)}
/>
</EuiPageBody>
</EuiPageContent>
</>
);
}
Loading

0 comments on commit 94961bd

Please sign in to comment.