Skip to content

Commit

Permalink
Merge branch 'main' into safari-copy-link-fix
Browse files Browse the repository at this point in the history
  • Loading branch information
leanneeliatra authored Nov 22, 2023
2 parents 4a89daf + d5a6626 commit 9672422
Show file tree
Hide file tree
Showing 11 changed files with 463 additions and 26 deletions.
4 changes: 2 additions & 2 deletions .github/actions/download-plugin/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ runs:
run: |
cat > setup.sh <<'EOF'
chmod +x ./opensearch-${{ inputs.opensearch-version}}-SNAPSHOT/plugins/${{ inputs.plugin-name }}/tools/install_demo_configuration.sh
/bin/bash -c "yes | ./opensearch-${{ inputs.opensearch-version}}-SNAPSHOT/plugins/${{ inputs.plugin-name }}/tools/install_demo_configuration.sh"
/bin/bash -c "yes | ./opensearch-${{ inputs.opensearch-version}}-SNAPSHOT/plugins/${{ inputs.plugin-name }}/tools/install_demo_configuration.sh -t"
echo "plugins.security.unsupported.restapi.allow_securityconfig_modification: true" >> ./opensearch-${{ inputs.opensearch-version }}-SNAPSHOT/config/opensearch.yml
echo "cluster.routing.allocation.disk.threshold_enabled: false" >> ./opensearch-${{ inputs.opensearch-version }}-SNAPSHOT/config/opensearch.yml
EOF
Expand All @@ -40,7 +40,7 @@ runs:
if: ${{ runner.os == 'Windows' }}
run: |
New-Item .\setup.bat -type file
Set-Content .\setup.bat -Value "powershell.exe -noexit -command `".\opensearch-${{ inputs.opensearch-version}}-SNAPSHOT\plugins\${{ inputs.plugin-name }}\tools\install_demo_configuration.bat -y -i -c`""
Set-Content .\setup.bat -Value "powershell.exe -noexit -command `".\opensearch-${{ inputs.opensearch-version}}-SNAPSHOT\plugins\${{ inputs.plugin-name }}\tools\install_demo_configuration.bat -y -i -c -t`""
Add-Content -Path .\setup.bat -Value "echo plugins.security.unsupported.restapi.allow_securityconfig_modification: true >> .\opensearch-${{ inputs.opensearch-version}}-SNAPSHOT\config\opensearch.yml"
Add-Content -Path .\setup.bat -Value "echo cluster.routing.allocation.disk.threshold_enabled: false >> .\opensearch-${{ inputs.opensearch-version}}-SNAPSHOT\config\opensearch.yml"
Get-Content .\setup.bat
Expand Down
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
5 changes: 5 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 Expand Up @@ -180,6 +182,9 @@ export const CLUSTER_PERMISSIONS: string[] = [
'cluster:admin/script/put',
'cluster:admin/script_context/get',
'cluster:admin/script_language/get',
'cluster:admin/search/pipeline/delete',
'cluster:admin/search/pipeline/get',
'cluster:admin/search/pipeline/put',
'cluster:admin/settings/update',
'cluster:admin/snapshot/create',
'cluster:admin/snapshot/clone',
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 9672422

Please sign in to comment.