From f669f96d493dfe467734ee0c6b32da64b8f90607 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ivar=20Conradi=20=C3=98sthus?=
Date: Tue, 20 Apr 2021 19:13:31 +0200
Subject: [PATCH] wip: frontend should understand rbac permissions (#269)
* chore: update changelog
* 4.0.0-alpha.4
* wip: frontend should understand rbac permissions
* move all feature components to hasAccess
* fix: remove all change permissions
* fix all the tests
* fix all the tests x2
* fix snapshot for node 12
* fine tune perms a bit
* refactor: rewrite to ts
* refactor: use admin constant
* fix: import
Co-authored-by: Fredrik Oseberg
---
frontend/CHANGELOG.md | 6 +
frontend/package.json | 2 +-
frontend/src/accessStoreFake.js | 12 +
.../AccessProvider/AccessProvider.tsx | 38 ++
.../AccessProvider/permissions.ts} | 10 +-
.../component/addons/AddonList/AddonList.jsx | 11 +-
.../AvailableAddons/AvailableAddons.jsx | 8 +-
.../ConfiguredAddons/ConfiguredAddons.jsx | 12 +-
frontend/src/component/addons/index.jsx | 2 -
.../application-edit-component-test.js | 28 +-
.../application/application-edit-component.js | 15 +-
.../application/application-edit-container.js | 2 -
.../application/application-view.jsx | 8 +-
.../src/component/archive/view-container.js | 2 -
.../context/ContextList/ContextList.jsx | 13 +-
.../component/context/ContextList/index.jsx | 2 -
.../src/component/error/error-component.jsx | 9 +-
.../src/component/error/error-container.jsx | 11 +-
.../FeatureToggleList/FeatureToggleList.jsx | 72 ++--
.../FeatureToggleListItem.jsx | 12 +-
.../feature-list-item-component-test.jsx.snap | 11 +-
.../list-component-test.jsx.snap | 36 +-
.../feature-list-item-component-test.jsx | 5 +-
.../__tests__/list-component-test.jsx | 14 +-
.../feature/FeatureToggleList/index.jsx | 10 +-
.../feature/FeatureView/FeatureView.jsx | 41 +-
.../component/feature/FeatureView/index.jsx | 2 -
.../feature/create/add-feature-component.jsx | 1 +
.../feature/feature-type-select-component.jsx | 4 +-
.../update-variant-component-test.jsx.snap | 30 ++
.../update-variant-component-test.jsx | 7 +-
.../variant/update-variant-component.jsx | 9 +-
.../variant/update-variant-container.jsx | 1 -
.../variant/variant-view-component.jsx | 7 +-
.../view-component-test.jsx.snap | 2 +
.../view/__tests__/view-component-test.jsx | 7 +-
.../view/update-description-component.jsx | 9 +-
.../__snapshots__/routes-test.jsx.snap | 16 +-
frontend/src/component/menu/routes.js | 34 +-
.../project/ProjectList/ProjectList.jsx | 15 +-
.../component/project/ProjectList/index.jsx | 2 -
.../StrategiesList/StrategiesList.jsx | 12 +-
.../strategies/StrategiesList/index.jsx | 2 -
.../list-component-test.jsx.snap | 230 +++++-----
.../strategy-details-component-test.jsx.snap | 401 +++++++-----------
.../__tests__/list-component-test.jsx | 26 +-
.../strategy-details-component-test.jsx | 28 +-
.../strategies/strategy-details-component.jsx | 11 +-
.../strategies/strategy-details-container.js | 2 -
.../tag-types/TagTypeList/TagTypeList.jsx | 14 +-
.../tag-type-create-component-test.js | 2 -
.../__tests__/tag-type-list-component-test.js | 52 ++-
frontend/src/component/tag-types/index.jsx | 2 -
.../src/component/tags/TagList/TagList.jsx | 17 +-
frontend/src/component/tags/index.jsx | 2 -
frontend/src/contexts/AccessContext.js | 5 +
frontend/src/index.tsx | 23 +-
.../page/admin/api/api-key-list-container.js | 3 -
frontend/src/page/admin/api/api-key-list.jsx | 41 +-
.../page/admin/auth/google-auth-container.js | 2 -
frontend/src/page/admin/auth/google-auth.jsx | 10 +-
frontend/src/page/admin/auth/index.js | 2 -
.../page/admin/auth/saml-auth-container.js | 2 -
frontend/src/page/admin/auth/saml-auth.jsx | 10 +-
.../page/admin/users/UsersList/UsersList.jsx | 13 +-
.../src/page/admin/users/UsersList/index.js | 2 -
frontend/src/store/user/index.js | 5 +-
67 files changed, 742 insertions(+), 715 deletions(-)
create mode 100644 frontend/src/accessStoreFake.js
create mode 100644 frontend/src/component/AccessProvider/AccessProvider.tsx
rename frontend/src/{permissions.js => component/AccessProvider/permissions.ts} (83%)
create mode 100644 frontend/src/contexts/AccessContext.js
diff --git a/frontend/CHANGELOG.md b/frontend/CHANGELOG.md
index a764450170..22e5b5bece 100644
--- a/frontend/CHANGELOG.md
+++ b/frontend/CHANGELOG.md
@@ -8,6 +8,12 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
The latest version of this document is always available in
[releases][releases-url].
+# 4.0.0-alpha.4
+- fix: overall bugs
+- feat: user flow
+- fix: small description for toggles
+- fix: make admin pages fork for OSS and enterprise
+
# 4.0.0-alpha.3
- fix: logout redirect logic
- fix: redirect from login page if authorized
diff --git a/frontend/package.json b/frontend/package.json
index fec0e13bd3..b51d51b065 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -1,7 +1,7 @@
{
"name": "unleash-frontend",
"description": "unleash your features",
- "version": "4.0.0-alpha.3",
+ "version": "4.0.0-alpha.4",
"keywords": [
"unleash",
"feature toggle",
diff --git a/frontend/src/accessStoreFake.js b/frontend/src/accessStoreFake.js
new file mode 100644
index 0000000000..c0069ca1b0
--- /dev/null
+++ b/frontend/src/accessStoreFake.js
@@ -0,0 +1,12 @@
+import { Map as $MAp } from 'immutable';
+
+export const createFakeStore = (permissions) => {
+ return {
+ getState: () => ({
+ user:
+ new $MAp({
+ permissions
+ })
+ }),
+ }
+}
\ No newline at end of file
diff --git a/frontend/src/component/AccessProvider/AccessProvider.tsx b/frontend/src/component/AccessProvider/AccessProvider.tsx
new file mode 100644
index 0000000000..45c62d41e7
--- /dev/null
+++ b/frontend/src/component/AccessProvider/AccessProvider.tsx
@@ -0,0 +1,38 @@
+import { FC } from "react";
+
+import AccessContext from '../../contexts/AccessContext'
+import { ADMIN } from "./permissions";
+
+// TODO: Type up redux store
+interface IAccessProvider {
+ store: any;
+}
+
+interface IPermission {
+ permission: string;
+ project: string | null;
+}
+
+const AccessProvider: FC = ({store, children}) => {
+ const hasAccess = (permission: string, project: string) => {
+ const permissions = store.getState().user.get('permissions') || [];
+
+ const result = permissions.some((p: IPermission) => {
+ if(p.permission === ADMIN) {
+ return true
+ }
+ if(p.permission === permission && p.project === project) {
+ return true;
+ }
+ return false;
+ });
+
+ return result;
+ };
+
+ const context = { hasAccess };
+
+ return {children}
+}
+
+export default AccessProvider;
\ No newline at end of file
diff --git a/frontend/src/permissions.js b/frontend/src/component/AccessProvider/permissions.ts
similarity index 83%
rename from frontend/src/permissions.js
rename to frontend/src/component/AccessProvider/permissions.ts
index ac6edb8b0a..b9bd814315 100644
--- a/frontend/src/permissions.js
+++ b/frontend/src/component/AccessProvider/permissions.ts
@@ -20,10 +20,6 @@ export const DELETE_TAG = 'DELETE_TAG';
export const CREATE_ADDON = 'CREATE_ADDON';
export const UPDATE_ADDON = 'UPDATE_ADDON';
export const DELETE_ADDON = 'DELETE_ADDON';
-
-export function hasPermission(user, permission) {
- return (
- user &&
- (!user.permissions || user.permissions.indexOf(ADMIN) !== -1 || user.permissions.indexOf(permission) !== -1)
- );
-}
+export const UPDATE_API_TOKEN = 'UPDATE_API_TOKEN';
+export const CREATE_API_TOKEN = 'CREATE_API_TOKEN';
+export const DELETE_API_TOKEN = 'DELETE_API_TOKEN';
diff --git a/frontend/src/component/addons/AddonList/AddonList.jsx b/frontend/src/component/addons/AddonList/AddonList.jsx
index 5237567add..87e3ad9683 100644
--- a/frontend/src/component/addons/AddonList/AddonList.jsx
+++ b/frontend/src/component/addons/AddonList/AddonList.jsx
@@ -1,9 +1,10 @@
-import React, { useEffect } from 'react';
+import React, { useContext, useEffect } from 'react';
import PropTypes from 'prop-types';
import ConfiguredAddons from './ConfiguredAddons';
import AvailableAddons from './AvailableAddons';
import { Avatar, Icon } from '@material-ui/core';
import ConditionallyRender from '../../common/ConditionallyRender/ConditionallyRender';
+import AccessContext from '../../../contexts/AccessContext';
const style = {
width: '40px',
@@ -29,7 +30,8 @@ const getIcon = name => {
}
};
-const AddonList = ({ addons, providers, fetchAddons, removeAddon, toggleAddon, history, hasPermission }) => {
+const AddonList = ({ addons, providers, fetchAddons, removeAddon, toggleAddon, history }) => {
+ const { hasAccess } = useContext(AccessContext);
useEffect(() => {
if (addons.length === 0) {
fetchAddons();
@@ -45,7 +47,7 @@ const AddonList = ({ addons, providers, fetchAddons, removeAddon, toggleAddon, h
@@ -53,7 +55,7 @@ const AddonList = ({ addons, providers, fetchAddons, removeAddon, toggleAddon, h
/>
-
+
>
);
};
@@ -65,7 +67,6 @@ AddonList.propTypes = {
removeAddon: PropTypes.func.isRequired,
toggleAddon: PropTypes.func.isRequired,
history: PropTypes.object.isRequired,
- hasPermission: PropTypes.func.isRequired,
};
export default AddonList;
diff --git a/frontend/src/component/addons/AddonList/AvailableAddons/AvailableAddons.jsx b/frontend/src/component/addons/AddonList/AvailableAddons/AvailableAddons.jsx
index ad75d2d9a6..10606b0438 100644
--- a/frontend/src/component/addons/AddonList/AvailableAddons/AvailableAddons.jsx
+++ b/frontend/src/component/addons/AddonList/AvailableAddons/AvailableAddons.jsx
@@ -2,17 +2,17 @@ import React from 'react';
import PageContent from '../../../common/PageContent/PageContent';
import { Button, List, ListItem, ListItemAvatar, ListItemSecondaryAction, ListItemText } from '@material-ui/core';
import ConditionallyRender from '../../../common/ConditionallyRender/ConditionallyRender';
-import { CREATE_ADDON } from '../../../../permissions';
+import { CREATE_ADDON } from '../../../AccessProvider/permissions';
import PropTypes from 'prop-types';
-const AvailableAddons = ({ providers, getIcon, hasPermission, history }) => {
+const AvailableAddons = ({ providers, getIcon, hasAccess, history }) => {
const renderProvider = provider => (
{getIcon(provider.name)}
{
AvailableAddons.propTypes = {
providers: PropTypes.array.isRequired,
getIcon: PropTypes.func.isRequired,
- hasPermission: PropTypes.func.isRequired,
+ hasAccess: PropTypes.func.isRequired,
history: PropTypes.object.isRequired,
};
diff --git a/frontend/src/component/addons/AddonList/ConfiguredAddons/ConfiguredAddons.jsx b/frontend/src/component/addons/AddonList/ConfiguredAddons/ConfiguredAddons.jsx
index a6d56ee09b..f132576cfd 100644
--- a/frontend/src/component/addons/AddonList/ConfiguredAddons/ConfiguredAddons.jsx
+++ b/frontend/src/component/addons/AddonList/ConfiguredAddons/ConfiguredAddons.jsx
@@ -9,12 +9,12 @@ import {
ListItemText,
} from '@material-ui/core';
import ConditionallyRender from '../../../common/ConditionallyRender/ConditionallyRender';
-import { DELETE_ADDON, UPDATE_ADDON } from '../../../../permissions';
+import { DELETE_ADDON, UPDATE_ADDON } from '../../../AccessProvider/permissions';
import { Link } from 'react-router-dom';
import PageContent from '../../../common/PageContent/PageContent';
import PropTypes from 'prop-types';
-const ConfiguredAddons = ({ addons, hasPermission, removeAddon, getIcon, toggleAddon }) => {
+const ConfiguredAddons = ({ addons, hasAccess, removeAddon, getIcon, toggleAddon }) => {
const onRemoveAddon = addon => () => removeAddon(addon);
const renderAddon = addon => (
@@ -23,7 +23,7 @@ const ConfiguredAddons = ({ addons, hasPermission, removeAddon, getIcon, toggleA
primary={
{addon.provider}
@@ -38,7 +38,7 @@ const ConfiguredAddons = ({ addons, hasPermission, removeAddon, getIcon, toggleA
/>
delete
@@ -68,7 +68,7 @@ const ConfiguredAddons = ({ addons, hasPermission, removeAddon, getIcon, toggleA
};
ConfiguredAddons.propTypes = {
addons: PropTypes.array.isRequired,
- hasPermission: PropTypes.func.isRequired,
+ hasAccess: PropTypes.func.isRequired,
removeAddon: PropTypes.func.isRequired,
toggleAddon: PropTypes.func.isRequired,
getIcon: PropTypes.func.isRequired,
diff --git a/frontend/src/component/addons/index.jsx b/frontend/src/component/addons/index.jsx
index 1a5c6d65b8..c04dbe4966 100644
--- a/frontend/src/component/addons/index.jsx
+++ b/frontend/src/component/addons/index.jsx
@@ -1,7 +1,6 @@
import { connect } from 'react-redux';
import AddonsListComponent from './AddonList';
import { fetchAddons, removeAddon, updateAddon } from '../../store/addons/actions';
-import { hasPermission } from '../../permissions';
const mapStateToProps = state => {
const list = state.addons.toJS();
@@ -9,7 +8,6 @@ const mapStateToProps = state => {
return {
addons: list.addons,
providers: list.providers,
- hasPermission: hasPermission.bind(null, state.user.get('profile')),
};
};
diff --git a/frontend/src/component/application/__tests__/application-edit-component-test.js b/frontend/src/component/application/__tests__/application-edit-component-test.js
index c8935c57b7..65499899ed 100644
--- a/frontend/src/component/application/__tests__/application-edit-component-test.js
+++ b/frontend/src/component/application/__tests__/application-edit-component-test.js
@@ -4,19 +4,23 @@ import { ThemeProvider } from '@material-ui/core';
import ClientApplications from '../application-edit-component';
import renderer from 'react-test-renderer';
import { MemoryRouter } from 'react-router-dom';
-import { CREATE_FEATURE, CREATE_STRATEGY, UPDATE_APPLICATION } from '../../../permissions';
+import { ADMIN, CREATE_FEATURE, CREATE_STRATEGY, UPDATE_APPLICATION } from '../../AccessProvider/permissions';
import theme from '../../../themes/main-theme';
+import { createFakeStore } from '../../../accessStoreFake';
+import AccessProvider from '../../AccessProvider/AccessProvider';
+
test('renders correctly if no application', () => {
const tree = renderer
.create(
- Promise.resolve({})}
- storeApplicationMetaData={jest.fn()}
- deleteApplication={jest.fn()}
- hasPermission={() => true}
- history={{}}
- />
+
+ Promise.resolve({})}
+ storeApplicationMetaData={jest.fn()}
+ deleteApplication={jest.fn()}
+ history={{}}
+ />
+
)
.toJSON();
@@ -28,6 +32,7 @@ test('renders correctly without permission', () => {
.create(
+
Promise.resolve({})}
storeApplicationMetaData={jest.fn()}
@@ -71,8 +76,8 @@ test('renders correctly without permission', () => {
description: 'app description',
}}
location={{ locale: 'en-GB' }}
- hasPermission={() => false}
/>
+
)
@@ -86,6 +91,7 @@ test('renders correctly with permissions', () => {
.create(
+
Promise.resolve({})}
storeApplicationMetaData={jest.fn()}
@@ -129,10 +135,8 @@ test('renders correctly with permissions', () => {
description: 'app description',
}}
location={{ locale: 'en-GB' }}
- hasPermission={permission =>
- [CREATE_FEATURE, CREATE_STRATEGY, UPDATE_APPLICATION].indexOf(permission) !== -1
- }
/>
+
)
diff --git a/frontend/src/component/application/application-edit-component.js b/frontend/src/component/application/application-edit-component.js
index bedd4bc1bf..bbfb73bf4d 100644
--- a/frontend/src/component/application/application-edit-component.js
+++ b/frontend/src/component/application/application-edit-component.js
@@ -5,15 +5,18 @@ import PropTypes from 'prop-types';
import { Avatar, Link, Icon, IconButton, Button, LinearProgress, Typography } from '@material-ui/core';
import ConditionallyRender from '../common/ConditionallyRender/ConditionallyRender';
import { formatFullDateTimeWithLocale, formatDateWithLocale } from '../common/util';
-import { UPDATE_APPLICATION } from '../../permissions';
+import { UPDATE_APPLICATION } from '../AccessProvider/permissions';
import ApplicationView from './application-view';
import ApplicationUpdate from './application-update';
import TabNav from '../common/TabNav/TabNav';
import Dialogue from '../common/Dialogue';
import PageContent from '../common/PageContent';
import HeaderTitle from '../common/HeaderTitle';
+import AccessContext from '../../contexts/AccessContext';
class ClientApplications extends PureComponent {
+ static contextType = AccessContext;
+
static propTypes = {
fetchApplication: PropTypes.func.isRequired,
appName: PropTypes.string,
@@ -21,7 +24,6 @@ class ClientApplications extends PureComponent {
location: PropTypes.object,
storeApplicationMetaData: PropTypes.func.isRequired,
deleteApplication: PropTypes.func.isRequired,
- hasPermission: PropTypes.func.isRequired,
history: PropTypes.object.isRequired,
};
@@ -60,7 +62,8 @@ class ClientApplications extends PureComponent {
} else if (!this.props.application) {
return Application ({this.props.appName}) not found
;
}
- const { application, storeApplicationMetaData, hasPermission } = this.props;
+ const { hasAccess } = this.context;
+ const { application, storeApplicationMetaData } = this.props;
const { appName, instances, strategies, seenToggles, url, description, icon = 'apps', createdAt } = application;
const toggleModal = () => {
@@ -84,7 +87,7 @@ class ClientApplications extends PureComponent {
strategies={strategies}
instances={instances}
seenToggles={seenToggles}
- hasPermission={hasPermission}
+ hasAccess={hasAccess}
formatFullDateTime={this.formatFullDateTime}
/>
),
@@ -126,7 +129,7 @@ class ClientApplications extends PureComponent {
/>
Delete
@@ -145,7 +148,7 @@ class ClientApplications extends PureComponent {
{renderModal()}
diff --git a/frontend/src/component/application/application-edit-container.js b/frontend/src/component/application/application-edit-container.js
index de708d4ac6..4d17ce5d88 100644
--- a/frontend/src/component/application/application-edit-container.js
+++ b/frontend/src/component/application/application-edit-container.js
@@ -1,7 +1,6 @@
import { connect } from 'react-redux';
import ApplicationEdit from './application-edit-component';
import { fetchApplication, storeApplicationMetaData, deleteApplication } from './../../store/application/actions';
-import { hasPermission } from '../../permissions';
const mapStateToProps = (state, props) => {
let application = state.applications.getIn(['apps', props.appName]);
@@ -12,7 +11,6 @@ const mapStateToProps = (state, props) => {
return {
application,
location,
- hasPermission: hasPermission.bind(null, state.user.get('profile')),
};
};
diff --git a/frontend/src/component/application/application-view.jsx b/frontend/src/component/application/application-view.jsx
index 5ac900932c..c32994497c 100644
--- a/frontend/src/component/application/application-view.jsx
+++ b/frontend/src/component/application/application-view.jsx
@@ -3,14 +3,14 @@ import { Link } from 'react-router-dom';
import PropTypes from 'prop-types';
import { Grid, List, ListItem, ListItemText, ListItemAvatar, Switch, Icon, Typography } from '@material-ui/core';
import { shorten } from '../common';
-import { CREATE_FEATURE, CREATE_STRATEGY } from '../../permissions';
+import { CREATE_FEATURE, CREATE_STRATEGY } from '../AccessProvider/permissions';
import ConditionallyRender from '../common/ConditionallyRender/ConditionallyRender';
-function ApplicationView({ seenToggles, hasPermission, strategies, instances, formatFullDateTime }) {
+function ApplicationView({ seenToggles, hasAccess, strategies, instances, formatFullDateTime }) {
const notFoundListItem = ({ createUrl, name, permission }) => (
@@ -149,7 +149,7 @@ ApplicationView.propTypes = {
instances: PropTypes.array.isRequired,
seenToggles: PropTypes.array.isRequired,
strategies: PropTypes.array.isRequired,
- hasPermission: PropTypes.func.isRequired,
+ hasAccess: PropTypes.func.isRequired,
formatFullDateTime: PropTypes.func.isRequired,
};
diff --git a/frontend/src/component/archive/view-container.js b/frontend/src/component/archive/view-container.js
index 40d584cd5a..d572f7e919 100644
--- a/frontend/src/component/archive/view-container.js
+++ b/frontend/src/component/archive/view-container.js
@@ -1,7 +1,6 @@
import { connect } from 'react-redux';
import { fetchArchive, revive } from './../../store/archive/actions';
import ViewToggleComponent from '../feature/FeatureView/FeatureView';
-import { hasPermission } from '../../permissions';
import { fetchTags } from '../../store/feature-tags/actions';
export default connect(
@@ -14,7 +13,6 @@ export default connect(
tagTypes: state.tagTypes.toJS(),
featureTags: state.featureTags.toJS(),
activeTab: props.activeTab,
- hasPermission: hasPermission.bind(null, state.user.get('profile')),
}),
{
fetchArchive,
diff --git a/frontend/src/component/context/ContextList/ContextList.jsx b/frontend/src/component/context/ContextList/ContextList.jsx
index 7c5279ab25..7d03957dd3 100644
--- a/frontend/src/component/context/ContextList/ContextList.jsx
+++ b/frontend/src/component/context/ContextList/ContextList.jsx
@@ -2,14 +2,16 @@ import PropTypes from 'prop-types';
import PageContent from '../../common/PageContent/PageContent';
import HeaderTitle from '../../common/HeaderTitle';
import ConditionallyRender from '../../common/ConditionallyRender/ConditionallyRender';
-import { CREATE_CONTEXT_FIELD, DELETE_CONTEXT_FIELD } from '../../../permissions';
+import { CREATE_CONTEXT_FIELD, DELETE_CONTEXT_FIELD } from '../../AccessProvider/permissions';
import { Icon, IconButton, List, ListItem, ListItemIcon, ListItemText, Tooltip } from '@material-ui/core';
-import React, { useState } from 'react';
+import React, { useContext, useState } from 'react';
import { Link } from 'react-router-dom';
import { useStyles } from './styles';
import ConfirmDialogue from '../../common/Dialogue';
+import AccessContext from '../../../contexts/AccessContext';
-const ContextList = ({ removeContextField, hasPermission, history, contextFields }) => {
+const ContextList = ({ removeContextField, history, contextFields }) => {
+ const { hasAccess } = useContext(AccessContext);
const [showDelDialogue, setShowDelDialogue] = useState(false);
const [name, setName] = useState();
@@ -29,7 +31,7 @@ const ContextList = ({ removeContextField, hasPermission, history, contextFields
secondary={field.description}
/>
(
history.push('/context/create')}>
@@ -88,7 +90,6 @@ ContextList.propTypes = {
contextFields: PropTypes.array.isRequired,
removeContextField: PropTypes.func.isRequired,
history: PropTypes.object.isRequired,
- hasPermission: PropTypes.func.isRequired,
};
export default ContextList;
diff --git a/frontend/src/component/context/ContextList/index.jsx b/frontend/src/component/context/ContextList/index.jsx
index 56fb6afda7..34d0e1c684 100644
--- a/frontend/src/component/context/ContextList/index.jsx
+++ b/frontend/src/component/context/ContextList/index.jsx
@@ -1,14 +1,12 @@
import { connect } from 'react-redux';
import ContextList from './ContextList';
import { fetchContext, removeContextField } from '../../../store/context/actions';
-import { hasPermission } from '../../../permissions';
const mapStateToProps = state => {
const list = state.context.toJS();
return {
contextFields: list,
- hasPermission: hasPermission.bind(null, state.user.get('profile')),
};
};
diff --git a/frontend/src/component/error/error-component.jsx b/frontend/src/component/error/error-component.jsx
index 01e2b182da..b1595b0d63 100644
--- a/frontend/src/component/error/error-component.jsx
+++ b/frontend/src/component/error/error-component.jsx
@@ -3,24 +3,23 @@ import PropTypes from 'prop-types';
import { Snackbar, Icon, IconButton } from '@material-ui/core';
-const ErrorComponent = ({ errors, ...props }) => {
+const ErrorComponent = ({ errors, muteError }) => {
const showError = errors.length > 0;
const error = showError ? errors[0] : undefined;
- const muteError = () => props.muteError(error);
return (
-
+
close
}
open={showError}
- onClose={muteError}
+ onClose={() => muteError(error)}
autoHideDuration={10000}
message={
-
+
question_answer
{error}
diff --git a/frontend/src/component/error/error-container.jsx b/frontend/src/component/error/error-container.jsx
index 8cda379397..6c8fca1ab8 100644
--- a/frontend/src/component/error/error-container.jsx
+++ b/frontend/src/component/error/error-container.jsx
@@ -6,11 +6,14 @@ const mapDispatchToProps = {
muteError,
};
-const mapStateToProps = state => ({
- errors: state.error
+const mapStateToProps = state => {
+ return {
+ errors: state.error
.get('list')
.toArray()
- .reverse(),
-});
+ .reverse()
+ }
+
+};
export default connect(mapStateToProps, mapDispatchToProps)(ErrorComponent);
diff --git a/frontend/src/component/feature/FeatureToggleList/FeatureToggleList.jsx b/frontend/src/component/feature/FeatureToggleList/FeatureToggleList.jsx
index d3e7d7fabf..444ed6810f 100644
--- a/frontend/src/component/feature/FeatureToggleList/FeatureToggleList.jsx
+++ b/frontend/src/component/feature/FeatureToggleList/FeatureToggleList.jsx
@@ -1,4 +1,4 @@
-import { useLayoutEffect } from 'react';
+import { useContext, useLayoutEffect } from 'react';
import PropTypes from 'prop-types';
import classnames from 'classnames';
import { Link } from 'react-router-dom';
@@ -21,21 +21,24 @@ import HeaderTitle from '../../common/HeaderTitle';
import loadingFeatures from './loadingFeatures';
-import { CREATE_FEATURE } from '../../../permissions';
+import { CREATE_FEATURE } from '../../AccessProvider/permissions';
+
+import AccessContext from '../../../contexts/AccessContext';
import { useStyles } from './styles';
const FeatureToggleList = ({
fetcher,
features,
- hasPermission,
settings,
revive,
+ currentProjectId,
updateSetting,
featureMetrics,
toggleFeature,
loading,
}) => {
+ const { hasAccess } = useContext(AccessContext);
const styles = useStyles();
const smallScreen = useMediaQuery('(max-width:700px)');
@@ -66,7 +69,7 @@ const FeatureToggleList = ({
feature={feature}
toggleFeature={toggleFeature}
revive={revive}
- hasPermission={hasPermission}
+ hasAccess={hasAccess}
className={'skeleton'}
/>
));
@@ -86,7 +89,7 @@ const FeatureToggleList = ({
feature={feature}
toggleFeature={toggleFeature}
revive={revive}
- hasPermission={hasPermission}
+ hasAccess={hasAccess}
/>
))}
elseShow={
@@ -132,39 +135,38 @@ const FeatureToggleList = ({
}
/>
+
-
- add
-
-
- }
- elseShow={
-
- }
- />
+
+
+ add
+
+
+ }
+ elseShow={
+
}
/>
+
}
/>
@@ -185,8 +187,8 @@ FeatureToggleList.propTypes = {
toggleFeature: PropTypes.func,
settings: PropTypes.object,
history: PropTypes.object.isRequired,
- hasPermission: PropTypes.func.isRequired,
loading: PropTypes.bool,
+ currentProjectId: PropTypes.string.isRequired,
};
export default FeatureToggleList;
diff --git a/frontend/src/component/feature/FeatureToggleList/FeatureToggleListItem/FeatureToggleListItem.jsx b/frontend/src/component/feature/FeatureToggleList/FeatureToggleListItem/FeatureToggleListItem.jsx
index 28f8ac086a..d100591dc3 100644
--- a/frontend/src/component/feature/FeatureToggleList/FeatureToggleListItem/FeatureToggleListItem.jsx
+++ b/frontend/src/component/feature/FeatureToggleList/FeatureToggleListItem/FeatureToggleListItem.jsx
@@ -10,7 +10,7 @@ import Status from '../../status-component';
import FeatureToggleListItemChip from './FeatureToggleListItemChip';
import ConditionallyRender from '../../../common/ConditionallyRender/ConditionallyRender';
-import { UPDATE_FEATURE } from '../../../../permissions';
+import { UPDATE_FEATURE } from '../../../AccessProvider/permissions';
import { calc, styles as commonStyles } from '../../../common';
import { useStyles } from './styles';
@@ -22,12 +22,12 @@ const FeatureToggleListItem = ({
metricsLastHour = { yes: 0, no: 0, isFallback: true },
metricsLastMinute = { yes: 0, no: 0, isFallback: true },
revive,
- hasPermission,
+ hasAccess,
...rest
}) => {
const styles = useStyles();
- const { name, description, enabled, type, stale, createdAt } = feature;
+ const { name, description, enabled, type, stale, createdAt, project } = feature;
const { showLastHour = false } = settings;
const isStale = showLastHour
? metricsLastHour.isFallback
@@ -64,7 +64,7 @@ const FeatureToggleListItem = ({
revive(feature.name)}>
undo
@@ -134,7 +134,7 @@ FeatureToggleListItem.propTypes = {
metricsLastHour: PropTypes.object,
metricsLastMinute: PropTypes.object,
revive: PropTypes.func,
- hasPermission: PropTypes.func.isRequired,
+ hasAccess: PropTypes.func.isRequired,
};
export default memo(FeatureToggleListItem);
diff --git a/frontend/src/component/feature/FeatureToggleList/__tests__/__snapshots__/feature-list-item-component-test.jsx.snap b/frontend/src/component/feature/FeatureToggleList/__tests__/__snapshots__/feature-list-item-component-test.jsx.snap
index 42e0c72d6a..519e35cdf4 100644
--- a/frontend/src/component/feature/FeatureToggleList/__tests__/__snapshots__/feature-list-item-component-test.jsx.snap
+++ b/frontend/src/component/feature/FeatureToggleList/__tests__/__snapshots__/feature-list-item-component-test.jsx.snap
@@ -120,8 +120,8 @@ exports[`renders correctly with one feature without permission 1`] = `
className="MuiSwitch-root"
>
@@ -150,6 +150,9 @@ exports[`renders correctly with one feature without permission 1`] = `
className="MuiSwitch-thumb"
/>
+
+
+
+ Create feature toggle
+
+
@@ -366,7 +392,7 @@ exports[`renders correctly with one feature without permissions 1`] = `
"reviveName": "Another",
}
}
- hasPermission={[Function]}
+ hasAccess={[Function]}
settings={
Object {
"sort": "name",
diff --git a/frontend/src/component/feature/FeatureToggleList/__tests__/feature-list-item-component-test.jsx b/frontend/src/component/feature/FeatureToggleList/__tests__/feature-list-item-component-test.jsx
index d2f43ddc98..4a0e2345ef 100644
--- a/frontend/src/component/feature/FeatureToggleList/__tests__/feature-list-item-component-test.jsx
+++ b/frontend/src/component/feature/FeatureToggleList/__tests__/feature-list-item-component-test.jsx
@@ -4,7 +4,6 @@ import { ThemeProvider } from '@material-ui/core';
import FeatureToggleListItem from '../FeatureToggleListItem';
import renderer from 'react-test-renderer';
-import { UPDATE_FEATURE } from '../../../../permissions';
import theme from '../../../../themes/main-theme';
@@ -38,7 +37,7 @@ test('renders correctly with one feature', () => {
metricsLastMinute={featureMetrics.lastMinute[feature.name]}
feature={feature}
toggleFeature={jest.fn()}
- hasPermission={permission => permission === UPDATE_FEATURE}
+ hasAccess={() => true}
/>
@@ -75,7 +74,7 @@ test('renders correctly with one feature without permission', () => {
metricsLastMinute={featureMetrics.lastMinute[feature.name]}
feature={feature}
toggleFeature={jest.fn()}
- hasPermission={() => false}
+ hasAccess={() => true}
/>
diff --git a/frontend/src/component/feature/FeatureToggleList/__tests__/list-component-test.jsx b/frontend/src/component/feature/FeatureToggleList/__tests__/list-component-test.jsx
index 1c3e6fa801..eda6282fe6 100644
--- a/frontend/src/component/feature/FeatureToggleList/__tests__/list-component-test.jsx
+++ b/frontend/src/component/feature/FeatureToggleList/__tests__/list-component-test.jsx
@@ -4,8 +4,12 @@ import { ThemeProvider } from '@material-ui/core';
import FeatureToggleList from '../FeatureToggleList';
import renderer from 'react-test-renderer';
-import { CREATE_FEATURE } from '../../../../permissions';
import theme from '../../../../themes/main-theme';
+import { createFakeStore } from '../../../../accessStoreFake';
+import { ADMIN, CREATE_FEATURE } from '../../../AccessProvider/permissions';
+import AccessProvider from '../../../AccessProvider/AccessProvider';
+
+
jest.mock('../FeatureToggleListItem', () => ({
__esModule: true,
@@ -25,6 +29,7 @@ test('renders correctly with one feature', () => {
const tree = renderer.create(
+
{
features={features}
toggleFeature={jest.fn()}
fetcher={jest.fn()}
- hasPermission={permission => permission === CREATE_FEATURE}
+ currentProjectId='default'
/>
+
);
@@ -53,6 +59,7 @@ test('renders correctly with one feature without permissions', () => {
const tree = renderer.create(
+
{
features={features}
toggleFeature={jest.fn()}
fetcher={jest.fn()}
- hasPermission={() => false}
+ currentProjectId='default'
/>
+
);
diff --git a/frontend/src/component/feature/FeatureToggleList/index.jsx b/frontend/src/component/feature/FeatureToggleList/index.jsx
index 0039ad6989..6013c6957f 100644
--- a/frontend/src/component/feature/FeatureToggleList/index.jsx
+++ b/frontend/src/component/feature/FeatureToggleList/index.jsx
@@ -3,8 +3,6 @@ import { toggleFeature, fetchFeatureToggles } from '../../../store/feature-toggl
import { updateSettingForGroup } from '../../../store/settings/actions';
import FeatureToggleList from './FeatureToggleList';
-import { hasPermission } from '../../../permissions';
-
function checkConstraints(strategy, regex) {
if (!strategy.constraints) {
return;
@@ -12,6 +10,12 @@ function checkConstraints(strategy, regex) {
return strategy.constraints.some(c => c.values.some(v => regex.test(v)));
}
+function resolveCurrentProjectId(settings) {
+ if(!settings.currentProjectId || settings.currentProjectId === '*') {
+ return 'default';
+ } return settings.currentProjectId;
+}
+
export const mapStateToPropsConfigurable = isFeature => state => {
const featureMetrics = state.featureMetrics.toJS();
const settings = state.settings.toJS().feature || {};
@@ -96,9 +100,9 @@ export const mapStateToPropsConfigurable = isFeature => state => {
return {
features,
+ currentProjectId: resolveCurrentProjectId(settings),
featureMetrics,
settings,
- hasPermission: hasPermission.bind(null, state.user.get('profile')),
loading: state.apiCalls.fetchTogglesState.loading,
};
};
diff --git a/frontend/src/component/feature/FeatureView/FeatureView.jsx b/frontend/src/component/feature/FeatureView/FeatureView.jsx
index 18d0096b1a..b3c83e8312 100644
--- a/frontend/src/component/feature/FeatureView/FeatureView.jsx
+++ b/frontend/src/component/feature/FeatureView/FeatureView.jsx
@@ -1,4 +1,4 @@
-import React, { useEffect, useLayoutEffect, useState } from 'react';
+import React, { useContext, useEffect, useLayoutEffect, useState } from 'react';
import classnames from 'classnames';
import PropTypes from 'prop-types';
import { Link } from 'react-router-dom';
@@ -18,10 +18,9 @@ import FeatureTypeSelect from '../feature-type-select-container';
import ProjectSelect from '../project-select-container';
import UpdateDescriptionComponent from '../view/update-description-component';
import {
- CREATE_FEATURE,
DELETE_FEATURE,
UPDATE_FEATURE,
-} from '../../../permissions';
+} from '../../AccessProvider/permissions';
import StatusComponent from '../status-component';
import FeatureTagComponent from '../feature-tag-component';
import StatusUpdateComponent from '../view/status-update-component';
@@ -35,6 +34,7 @@ import styles from './FeatureView.module.scss';
import ConfirmDialogue from '../../common/Dialogue';
import { useCommonStyles } from '../../../common.styles';
+import AccessContext from '../../../contexts/AccessContext';
const FeatureView = ({
activeTab,
@@ -49,7 +49,6 @@ const FeatureView = ({
editFeatureToggle,
featureToggle,
history,
- hasPermission,
untagFeature,
featureTags,
fetchTags,
@@ -58,6 +57,8 @@ const FeatureView = ({
const isFeatureView = !!fetchFeatureToggles;
const [delDialog, setDelDialog] = useState(false);
const commonStyles = useCommonStyles();
+ const { hasAccess } = useContext(AccessContext);
+ const { project } = featureToggle || { };
useEffect(() => {
scrollToTop();
@@ -76,26 +77,17 @@ const FeatureView = ({
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
+ const editable = isFeatureView && hasAccess(UPDATE_FEATURE, project);
+
const getTabComponent = key => {
switch (key) {
case 'activation':
- if (isFeatureView && hasPermission(UPDATE_FEATURE)) {
- return (
-
- );
- }
- return (
-
- );
case 'metrics':
return ;
case 'variants':
@@ -104,7 +96,7 @@ const FeatureView = ({
featureToggle={featureToggle}
features={features}
history={history}
- hasPermission={hasPermission}
+ editable={editable}
/>
);
case 'log':
@@ -152,7 +144,7 @@ const FeatureView = ({
Could not find the toggle{' '}
+
+
+
+ Add variant
+
+
+
+
`;
diff --git a/frontend/src/component/feature/variant/__tests__/update-variant-component-test.jsx b/frontend/src/component/feature/variant/__tests__/update-variant-component-test.jsx
index f2219efd55..78977b9d48 100644
--- a/frontend/src/component/feature/variant/__tests__/update-variant-component-test.jsx
+++ b/frontend/src/component/feature/variant/__tests__/update-variant-component-test.jsx
@@ -3,7 +3,6 @@ import { ThemeProvider } from '@material-ui/core';
import UpdateVariant from './../update-variant-component';
import renderer from 'react-test-renderer';
-import { UPDATE_FEATURE } from '../../../../permissions';
import { weightTypes } from '../enums';
import theme from '../../../../themes/main-theme';
@@ -24,7 +23,7 @@ test('renders correctly with without variants', () => {
updateVariant={jest.fn()}
stickinessOptions={['default']}
updateStickiness={jest.fn()}
- hasPermission={permission => permission === UPDATE_FEATURE}
+ editable
/>
@@ -45,7 +44,7 @@ test('renders correctly with without variants and no permissions', () => {
updateVariant={jest.fn()}
stickinessOptions={['default']}
updateStickiness={jest.fn()}
- hasPermission={() => false}
+ editable
/>
@@ -105,7 +104,7 @@ test('renders correctly with with variants', () => {
updateVariant={jest.fn()}
stickinessOptions={['default']}
updateStickiness={jest.fn()}
- hasPermission={permission => permission === UPDATE_FEATURE}
+ editable
/>
diff --git a/frontend/src/component/feature/variant/update-variant-component.jsx b/frontend/src/component/feature/variant/update-variant-component.jsx
index 1fb2c79d13..982a82fe64 100644
--- a/frontend/src/component/feature/variant/update-variant-component.jsx
+++ b/frontend/src/component/feature/variant/update-variant-component.jsx
@@ -4,7 +4,6 @@ import classnames from 'classnames';
import VariantViewComponent from './variant-view-component';
import styles from './variant.module.scss';
-import { UPDATE_FEATURE } from '../../../permissions';
import {
Table,
TableHead,
@@ -46,7 +45,7 @@ class UpdateVariantComponent extends Component {
openEditVariant = (e, index, variant) => {
e.preventDefault();
- if (this.props.hasPermission(UPDATE_FEATURE)) {
+ if (this.props.editable) {
this.setState({
showDialog: true,
editVariant: variant,
@@ -73,7 +72,7 @@ class UpdateVariantComponent extends Component {
variant={variant}
editVariant={e => this.openEditVariant(e, index, variant)}
removeVariant={e => this.onRemoveVariant(e, index)}
- hasPermission={this.props.hasPermission}
+ editable={this.props.editable}
/>
);
@@ -162,7 +161,7 @@ class UpdateVariantComponent extends Component {
({
variants: ownProps.featureToggle.variants || [],
stickinessOptions: ['default', ...state.context.filter(c => c.stickiness).map(c => c.name)],
- hasPermission: ownProps.hasPermission,
});
const mapDispatchToProps = (dispatch, ownProps) => ({
diff --git a/frontend/src/component/feature/variant/variant-view-component.jsx b/frontend/src/component/feature/variant/variant-view-component.jsx
index 5dd4ba4d29..87cf128c98 100644
--- a/frontend/src/component/feature/variant/variant-view-component.jsx
+++ b/frontend/src/component/feature/variant/variant-view-component.jsx
@@ -1,13 +1,12 @@
import React from 'react';
import PropTypes from 'prop-types';
import { IconButton, Chip, Icon, TableCell, TableRow } from '@material-ui/core';
-import { UPDATE_FEATURE } from '../../../permissions';
import { weightTypes } from './enums';
import ConditionallyRender from '../../common/ConditionallyRender/ConditionallyRender';
import styles from './variant.module.scss';
-function VariantViewComponent({ variant, editVariant, removeVariant, hasPermission }) {
+function VariantViewComponent({ variant, editVariant, removeVariant, editable }) {
const { FIX } = weightTypes;
return (
@@ -29,7 +28,7 @@ function VariantViewComponent({ variant, editVariant, removeVariant, hasPermissi
{variant.weight / 10.0} %
{variant.weightType === FIX ? 'Fix' : 'Variable'}
@@ -52,7 +51,7 @@ VariantViewComponent.propTypes = {
variant: PropTypes.object,
removeVariant: PropTypes.func,
editVariant: PropTypes.func,
- hasPermission: PropTypes.func.isRequired,
+ editable: PropTypes.bool.isRequired,
};
export default VariantViewComponent;
diff --git a/frontend/src/component/feature/view/__tests__/__snapshots__/view-component-test.jsx.snap b/frontend/src/component/feature/view/__tests__/__snapshots__/view-component-test.jsx.snap
index f3ffff0bcd..5e287660dc 100644
--- a/frontend/src/component/feature/view/__tests__/__snapshots__/view-component-test.jsx.snap
+++ b/frontend/src/component/feature/view/__tests__/__snapshots__/view-component-test.jsx.snap
@@ -82,6 +82,7 @@ exports[`renders correctly with one feature 1`] = `
className="selectContainer"
>
({
__esModule: true,
@@ -61,6 +63,7 @@ test('renders correctly with one feature', () => {
+
{
fetchFeatureToggles={jest.fn()}
history={{}}
featureTags={[]}
- hasPermission={permission => [DELETE_FEATURE, UPDATE_FEATURE].indexOf(permission) !== -1}
fetchTags={jest.fn()}
untagFeature={jest.fn()}
/>
+
diff --git a/frontend/src/component/feature/view/update-description-component.jsx b/frontend/src/component/feature/view/update-description-component.jsx
index 0709a417b8..ecf51a0cea 100644
--- a/frontend/src/component/feature/view/update-description-component.jsx
+++ b/frontend/src/component/feature/view/update-description-component.jsx
@@ -5,8 +5,6 @@ import { Typography, IconButton, FormControl, TextField, Button } from '@materia
import CreateIcon from '@material-ui/icons/Create';
import ConditionallyRender from '../../common/ConditionallyRender/ConditionallyRender';
-import { UPDATE_FEATURE } from '../../../permissions';
-
import styles from './update-description-component.module.scss';
export default class UpdateDescriptionComponent extends React.Component {
@@ -19,7 +17,7 @@ export default class UpdateDescriptionComponent extends React.Component {
isFeatureView: PropTypes.bool.isRequired,
update: PropTypes.func,
featureToggle: PropTypes.object,
- hasPermission: PropTypes.func.isRequired,
+ editable: PropTypes.bool,
};
onEditMode = (description, evt) => {
@@ -43,14 +41,13 @@ export default class UpdateDescriptionComponent extends React.Component {
this.setState({ editMode: false, description: undefined });
};
- renderRead({ description, isFeatureView, hasPermission }) {
- const showButton = isFeatureView && hasPermission(UPDATE_FEATURE);
+ renderRead({ description, editable }) {
return (
{description || 'No feature toggle description'}
{
+const ProjectList = ({ projects, fetchProjects, removeProject, history }) => {
+ const { hasAccess } = useContext(AccessContext);
const [showDelDialogue, setShowDelDialogue] = useState(false);
const [project, setProject] = useState(undefined);
const styles = useStyles();
@@ -19,7 +21,7 @@ const ProjectList = ({ projects, fetchProjects, removeProject, history, hasPermi
const addProjectButton = () => (
history.push('/projects/create')}>
@@ -68,10 +70,10 @@ const ProjectList = ({ projects, fetchProjects, removeProject, history, hasPermi
-
+
));
@@ -106,7 +108,6 @@ ProjectList.propTypes = {
fetchProjects: PropTypes.func.isRequired,
removeProject: PropTypes.func.isRequired,
history: PropTypes.object.isRequired,
- hasPermission: PropTypes.func.isRequired,
};
export default ProjectList;
diff --git a/frontend/src/component/project/ProjectList/index.jsx b/frontend/src/component/project/ProjectList/index.jsx
index f562a7764b..73dacbfef7 100644
--- a/frontend/src/component/project/ProjectList/index.jsx
+++ b/frontend/src/component/project/ProjectList/index.jsx
@@ -1,6 +1,5 @@
import { connect } from 'react-redux';
import { fetchProjects, removeProject } from '../../../store/project/actions';
-import { hasPermission } from '../../../permissions';
import ProjectList from './ProjectList';
const mapStateToProps = state => {
@@ -8,7 +7,6 @@ const mapStateToProps = state => {
return {
projects,
- hasPermission: hasPermission.bind(null, state.user.get('profile')),
};
};
diff --git a/frontend/src/component/strategies/StrategiesList/StrategiesList.jsx b/frontend/src/component/strategies/StrategiesList/StrategiesList.jsx
index 656ea42699..5175f40002 100644
--- a/frontend/src/component/strategies/StrategiesList/StrategiesList.jsx
+++ b/frontend/src/component/strategies/StrategiesList/StrategiesList.jsx
@@ -1,17 +1,18 @@
-import React, { useEffect } from 'react';
+import React, { useContext, useEffect } from 'react';
import PropTypes from 'prop-types';
import classnames from 'classnames';
import { Link, useHistory } from 'react-router-dom';
import useMediaQuery from '@material-ui/core/useMediaQuery';
import { List, ListItem, ListItemAvatar, IconButton, Icon, ListItemText, Button, Tooltip } from '@material-ui/core';
-import { CREATE_STRATEGY, DELETE_STRATEGY } from '../../../permissions';
+import { CREATE_STRATEGY, DELETE_STRATEGY } from '../../AccessProvider/permissions';
import ConditionallyRender from '../../common/ConditionallyRender/ConditionallyRender';
import PageContent from '../../common/PageContent/PageContent';
import HeaderTitle from '../../common/HeaderTitle';
import { useStyles } from './styles';
+import AccessContext from '../../../contexts/AccessContext';
const StrategiesList = ({
strategies,
@@ -19,11 +20,11 @@ const StrategiesList = ({
removeStrategy,
deprecateStrategy,
reactivateStrategy,
- hasPermission,
}) => {
const history = useHistory();
const styles = useStyles();
const smallScreen = useMediaQuery('(max-width:700px)');
+ const { hasAccess } = useContext(AccessContext);
useEffect(() => {
fetchStrategies();
@@ -32,7 +33,7 @@ const StrategiesList = ({
const headerButton = () => (
-
+
));
@@ -157,7 +158,6 @@ StrategiesList.propTypes = {
deprecateStrategy: PropTypes.func.isRequired,
reactivateStrategy: PropTypes.func.isRequired,
history: PropTypes.object.isRequired,
- hasPermission: PropTypes.func.isRequired,
name: PropTypes.string,
deprecated: PropTypes.bool,
};
diff --git a/frontend/src/component/strategies/StrategiesList/index.jsx b/frontend/src/component/strategies/StrategiesList/index.jsx
index 624ce5e914..1d1cb12518 100644
--- a/frontend/src/component/strategies/StrategiesList/index.jsx
+++ b/frontend/src/component/strategies/StrategiesList/index.jsx
@@ -6,14 +6,12 @@ import {
deprecateStrategy,
reactivateStrategy,
} from '../../../store/strategy/actions';
-import { hasPermission } from '../../../permissions';
const mapStateToProps = state => {
const list = state.strategies.get('list').toArray();
return {
strategies: list,
- hasPermission: hasPermission.bind(null, state.user.get('profile')),
};
};
diff --git a/frontend/src/component/strategies/__tests__/__snapshots__/list-component-test.jsx.snap b/frontend/src/component/strategies/__tests__/__snapshots__/list-component-test.jsx.snap
index eaac711451..e9eb84dc82 100644
--- a/frontend/src/component/strategies/__tests__/__snapshots__/list-component-test.jsx.snap
+++ b/frontend/src/component/strategies/__tests__/__snapshots__/list-component-test.jsx.snap
@@ -1,6 +1,121 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`renders correctly with one strategy 1`] = `
+
+`;
+
+exports[`renders correctly with one strategy without permissions 1`] = `
@@ -182,118 +297,3 @@ exports[`renders correctly with one strategy 1`] = `
`;
-
-exports[`renders correctly with one strategy without permissions 1`] = `
-
-`;
diff --git a/frontend/src/component/strategies/__tests__/__snapshots__/strategy-details-component-test.jsx.snap b/frontend/src/component/strategies/__tests__/__snapshots__/strategy-details-component-test.jsx.snap
index b3a1c79fbb..93ad6d319c 100644
--- a/frontend/src/component/strategies/__tests__/__snapshots__/strategy-details-component-test.jsx.snap
+++ b/frontend/src/component/strategies/__tests__/__snapshots__/strategy-details-component-test.jsx.snap
@@ -35,294 +35,187 @@ exports[`renders correctly with one strategy 1`] = `
>
another's description
-
+
-
+ Parameters
+
+
+
-
-
- Edit
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Parameters
-
-
-
- -
-
-
- add
-
-
-
-
-
- customParam
-
-
- (
- list
- )
-
-
-
-
- customList
-
-
-
-
-
-
-
- Applications using this strategy
-
-
-
+
-
-
-
- apps
-
+
+ customParam
+
+
+ (
+ list
+ )
+
-
-
+
-
-
- appA
-
-
-
- app description
-
-
-
-
-
-
+
+
+
+
+
+
+ Applications using this strategy
+
+
+
-
- Toggles using this strategy
-
-
-
- -
- pause
+ apps
-
+
+
+ Toggles using this strategy
+
+
+
-
+ toggleA
+
+
+
+ toggle description
+
+
+
+
-
-
+
diff --git a/frontend/src/component/strategies/__tests__/list-component-test.jsx b/frontend/src/component/strategies/__tests__/list-component-test.jsx
index bd6366b3b6..41f8e35010 100644
--- a/frontend/src/component/strategies/__tests__/list-component-test.jsx
+++ b/frontend/src/component/strategies/__tests__/list-component-test.jsx
@@ -4,8 +4,10 @@ import { ThemeProvider } from '@material-ui/core';
import StrategiesListComponent from '../StrategiesList/StrategiesList';
import renderer from 'react-test-renderer';
-import { CREATE_STRATEGY, DELETE_STRATEGY } from '../../../permissions';
import theme from '../../../themes/main-theme';
+import AccessProvider from '../../AccessProvider/AccessProvider';
+import { createFakeStore } from '../../../accessStoreFake';
+import { ADMIN } from '../../AccessProvider/permissions';
test('renders correctly with one strategy', () => {
const strategy = {
@@ -15,15 +17,16 @@ test('renders correctly with one strategy', () => {
const tree = renderer.create(
- [CREATE_STRATEGY, DELETE_STRATEGY].indexOf(permission) !== -1}
- />
+
+
+
);
@@ -39,6 +42,7 @@ test('renders correctly with one strategy without permissions', () => {
const tree = renderer.create(
+
{
deprecateStrategy={jest.fn()}
reactivateStrategy={jest.fn()}
history={{}}
- hasPermission={() => false}
/>
+
);
diff --git a/frontend/src/component/strategies/__tests__/strategy-details-component-test.jsx b/frontend/src/component/strategies/__tests__/strategy-details-component-test.jsx
index 99cf0a5412..5d22a32a82 100644
--- a/frontend/src/component/strategies/__tests__/strategy-details-component-test.jsx
+++ b/frontend/src/component/strategies/__tests__/strategy-details-component-test.jsx
@@ -2,9 +2,10 @@ import React from 'react';
import { ThemeProvider } from '@material-ui/core';
import StrategyDetails from '../strategy-details-component';
import renderer from 'react-test-renderer';
-import { UPDATE_STRATEGY } from '../../../permissions';
import { MemoryRouter } from 'react-router-dom';
import theme from '../../../themes/main-theme';
+import { createFakeStore } from '../../../accessStoreFake';
+import AccessProvider from '../../AccessProvider/AccessProvider';
test('renders correctly with one strategy', () => {
const strategy = {
@@ -34,20 +35,21 @@ test('renders correctly with one strategy', () => {
];
const tree = renderer.create(
+
- [UPDATE_STRATEGY].indexOf(permission) !== -1}
- />
+
+
);
diff --git a/frontend/src/component/strategies/strategy-details-component.jsx b/frontend/src/component/strategies/strategy-details-component.jsx
index 42046c79cd..0bc2aad017 100644
--- a/frontend/src/component/strategies/strategy-details-component.jsx
+++ b/frontend/src/component/strategies/strategy-details-component.jsx
@@ -3,12 +3,15 @@ import PropTypes from 'prop-types';
import { Grid, Typography } from '@material-ui/core';
import ShowStrategy from './show-strategy-component';
import EditStrategy from './form-container';
-import { UPDATE_STRATEGY } from '../../permissions';
+import { UPDATE_STRATEGY } from '../AccessProvider/permissions';
import ConditionallyRender from '../common/ConditionallyRender/ConditionallyRender';
import TabNav from '../common/TabNav/TabNav';
import PageContent from '../common/PageContent/PageContent';
+import AccessContext from '../../contexts/AccessContext';
export default class StrategyDetails extends Component {
+ static contextType = AccessContext;
+
static propTypes = {
strategyName: PropTypes.string.isRequired,
toggles: PropTypes.array,
@@ -19,7 +22,6 @@ export default class StrategyDetails extends Component {
fetchApplications: PropTypes.func.isRequired,
fetchFeatureToggles: PropTypes.func.isRequired,
history: PropTypes.object.isRequired,
- hasPermission: PropTypes.func.isRequired,
};
componentDidMount() {
@@ -52,13 +54,16 @@ export default class StrategyDetails extends Component {
component: ,
},
];
+
+ const { hasAccess } = this.context;
+
return (
{strategy.description}
diff --git a/frontend/src/component/strategies/strategy-details-container.js b/frontend/src/component/strategies/strategy-details-container.js
index 85a8626591..00a6b4611d 100644
--- a/frontend/src/component/strategies/strategy-details-container.js
+++ b/frontend/src/component/strategies/strategy-details-container.js
@@ -3,7 +3,6 @@ import ShowStrategy from './strategy-details-component';
import { fetchStrategies } from './../../store/strategy/actions';
import { fetchAll } from './../../store/application/actions';
import { fetchFeatureToggles } from './../../store/feature-toggle/actions';
-import { hasPermission } from '../../permissions';
const mapStateToProps = (state, props) => {
let strategy = state.strategies.get('list').find(n => n.name === props.strategyName);
@@ -22,7 +21,6 @@ const mapStateToProps = (state, props) => {
applications: applications && applications.toJS(),
toggles: toggles && toggles.toJS(),
activeTab: props.activeTab,
- hasPermission: hasPermission.bind(null, state.user.get('profile')),
};
};
diff --git a/frontend/src/component/tag-types/TagTypeList/TagTypeList.jsx b/frontend/src/component/tag-types/TagTypeList/TagTypeList.jsx
index 950794244e..8e294653dd 100644
--- a/frontend/src/component/tag-types/TagTypeList/TagTypeList.jsx
+++ b/frontend/src/component/tag-types/TagTypeList/TagTypeList.jsx
@@ -1,4 +1,4 @@
-import React, { useEffect, useState } from 'react';
+import React, { useContext, useEffect, useState } from 'react';
import PropTypes from 'prop-types';
import { Link, useHistory } from 'react-router-dom';
@@ -16,16 +16,19 @@ import {
import HeaderTitle from '../../common/HeaderTitle';
import PageContent from '../../common/PageContent/PageContent';
import ConditionallyRender from '../../common/ConditionallyRender/ConditionallyRender';
-import { CREATE_TAG_TYPE, DELETE_TAG_TYPE } from '../../../permissions';
+import { CREATE_TAG_TYPE, DELETE_TAG_TYPE } from '../../AccessProvider/permissions';
import Dialogue from '../../common/Dialogue/Dialogue';
import useMediaQuery from '@material-ui/core/useMediaQuery';
import styles from '../TagType.module.scss';
+import AccessContext from '../../../contexts/AccessContext';
-const TagTypeList = ({ tagTypes, fetchTagTypes, removeTagType, hasPermission }) => {
+const TagTypeList = ({ tagTypes, fetchTagTypes, removeTagType }) => {
+ const { hasAccess } = useContext(AccessContext);
const [deletion, setDeletion] = useState({ open: false });
const history = useHistory();
const smallScreen = useMediaQuery('(max-width:700px)');
+
useEffect(() => {
fetchTagTypes();
@@ -37,7 +40,7 @@ const TagTypeList = ({ tagTypes, fetchTagTypes, removeTagType, hasPermission })
title="Tag Types"
actions={
label
-
+
);
};
@@ -127,7 +130,6 @@ TagTypeList.propTypes = {
tagTypes: PropTypes.array.isRequired,
fetchTagTypes: PropTypes.func.isRequired,
removeTagType: PropTypes.func.isRequired,
- hasPermission: PropTypes.func.isRequired,
};
export default TagTypeList;
diff --git a/frontend/src/component/tag-types/__tests__/tag-type-create-component-test.js b/frontend/src/component/tag-types/__tests__/tag-type-create-component-test.js
index 8a314b7249..187de7478d 100644
--- a/frontend/src/component/tag-types/__tests__/tag-type-create-component-test.js
+++ b/frontend/src/component/tag-types/__tests__/tag-type-create-component-test.js
@@ -15,7 +15,6 @@ test('renders correctly for creating', () => {
title="Add tag type"
createTagType={jest.fn()}
validateName={() => Promise.resolve(true)}
- hasPermission={() => true}
tagType={{ name: '', description: '', icon: '' }}
editMode={false}
submit={jest.fn()}
@@ -35,7 +34,6 @@ test('it supports editMode', () => {
title="Add tag type"
createTagType={jest.fn()}
validateName={() => Promise.resolve(true)}
- hasPermission={() => true}
tagType={{ name: '', description: '', icon: '' }}
editMode
submit={jest.fn()}
diff --git a/frontend/src/component/tag-types/__tests__/tag-type-list-component-test.js b/frontend/src/component/tag-types/__tests__/tag-type-list-component-test.js
index 0de6050d93..c9be42d784 100644
--- a/frontend/src/component/tag-types/__tests__/tag-type-list-component-test.js
+++ b/frontend/src/component/tag-types/__tests__/tag-type-list-component-test.js
@@ -5,18 +5,25 @@ import renderer from 'react-test-renderer';
import { MemoryRouter } from 'react-router-dom';
import { ThemeProvider } from '@material-ui/styles';
import theme from '../../../themes/main-theme';
+import { createFakeStore } from '../../../accessStoreFake';
+import AccessProvider from '../../AccessProvider/AccessProvider';
+
+import { ADMIN, CREATE_TAG_TYPE, UPDATE_TAG_TYPE, DELETE_TAG_TYPE } from '../../AccessProvider/permissions';
+
+
test('renders an empty list correctly', () => {
const tree = renderer.create(
- true}
- />
+
+
+
);
@@ -27,19 +34,24 @@ test('renders a list with elements correctly', () => {
const tree = renderer.create(
- true}
- />
+
+
+
);
diff --git a/frontend/src/component/tag-types/index.jsx b/frontend/src/component/tag-types/index.jsx
index 8701bf5f71..5cd1cf92d5 100644
--- a/frontend/src/component/tag-types/index.jsx
+++ b/frontend/src/component/tag-types/index.jsx
@@ -1,13 +1,11 @@
import { connect } from 'react-redux';
import TagTypesListComponent from './TagTypeList';
import { fetchTagTypes, removeTagType } from '../../store/tag-type/actions';
-import { hasPermission } from '../../permissions';
const mapStateToProps = state => {
const list = state.tagTypes.toJS();
return {
tagTypes: list,
- hasPermission: hasPermission.bind(null, state.user.get('profile')),
};
};
diff --git a/frontend/src/component/tags/TagList/TagList.jsx b/frontend/src/component/tags/TagList/TagList.jsx
index 7e1db5e51b..1ba550d9bf 100644
--- a/frontend/src/component/tags/TagList/TagList.jsx
+++ b/frontend/src/component/tags/TagList/TagList.jsx
@@ -1,20 +1,22 @@
-import React, { useEffect } from 'react';
+import React, { useContext, useEffect } from 'react';
import PropTypes from 'prop-types';
import useMediaQuery from '@material-ui/core/useMediaQuery';
import { useHistory } from 'react-router-dom';
import { Button, Icon, IconButton, List, ListItem, ListItemIcon, ListItemText, Tooltip } from '@material-ui/core';
-import { CREATE_TAG, DELETE_TAG } from '../../../permissions';
+import { CREATE_TAG, DELETE_TAG } from '../../AccessProvider/permissions';
import ConditionallyRender from '../../common/ConditionallyRender/ConditionallyRender';
import HeaderTitle from '../../common/HeaderTitle';
import PageContent from '../../common/PageContent/PageContent';
import { useStyles } from './TagList.styles';
+import AccessContext from '../../../contexts/AccessContext';
-const TagList = ({ tags, fetchTags, removeTag, hasPermission }) => {
+const TagList = ({ tags, fetchTags, removeTag }) => {
const history = useHistory();
const smallScreen = useMediaQuery('(max-width:700px)');
const styles = useStyles();
+ const { hasAccess } = useContext(AccessContext);
useEffect(() => {
fetchTags();
@@ -33,7 +35,7 @@ const TagList = ({ tags, fetchTags, removeTag, hasPermission }) => {
}
/>
@@ -52,9 +54,9 @@ const TagList = ({ tags, fetchTags, removeTag, hasPermission }) => {
tagValue: PropTypes.string,
};
- const AddButton = ({ hasPermission }) => (
+ const AddButton = ({ hasAccess }) => (
{
/>
);
return (
- } />}>
+ } />}>
0}
@@ -100,7 +102,6 @@ TagList.propTypes = {
tags: PropTypes.array.isRequired,
fetchTags: PropTypes.func.isRequired,
removeTag: PropTypes.func.isRequired,
- hasPermission: PropTypes.func.isRequired,
};
export default TagList;
diff --git a/frontend/src/component/tags/index.jsx b/frontend/src/component/tags/index.jsx
index c6b529c88d..35483b01fa 100644
--- a/frontend/src/component/tags/index.jsx
+++ b/frontend/src/component/tags/index.jsx
@@ -1,13 +1,11 @@
import { connect } from 'react-redux';
import TagsListComponent from './TagList';
import { fetchTags, removeTag } from '../../store/tag/actions';
-import { hasPermission } from '../../permissions';
const mapStateToProps = state => {
const list = state.tags.toJS();
return {
tags: list,
- hasPermission: hasPermission.bind(null, state.user.get('profile')),
};
};
diff --git a/frontend/src/contexts/AccessContext.js b/frontend/src/contexts/AccessContext.js
new file mode 100644
index 0000000000..18ddd1fe72
--- /dev/null
+++ b/frontend/src/contexts/AccessContext.js
@@ -0,0 +1,5 @@
+import React from 'react';
+
+const AccessContext = React.createContext()
+
+export default AccessContext;
diff --git a/frontend/src/index.tsx b/frontend/src/index.tsx
index 9441593e9f..809024718d 100644
--- a/frontend/src/index.tsx
+++ b/frontend/src/index.tsx
@@ -16,6 +16,7 @@ import MetricsPoller from './metrics-poller';
import App from './component/App';
import ScrollToTop from './component/scroll-to-top';
import { writeWarning } from './security-logger';
+import AccessProvider from './component/AccessProvider/AccessProvider';
let composeEnhancers;
@@ -38,16 +39,18 @@ metricsPoller.start();
ReactDOM.render(
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
,
document.getElementById('app')
);
diff --git a/frontend/src/page/admin/api/api-key-list-container.js b/frontend/src/page/admin/api/api-key-list-container.js
index ec1553ab8a..1a872a55ad 100644
--- a/frontend/src/page/admin/api/api-key-list-container.js
+++ b/frontend/src/page/admin/api/api-key-list-container.js
@@ -2,14 +2,11 @@ import { connect } from 'react-redux';
import Component from './api-key-list';
import { fetchApiKeys, removeKey, addKey } from './../../../store/e-api-admin/actions';
-import { hasPermission } from '../../../permissions';
-
export default connect(
state => ({
location: state.settings.toJS().location || {},
unleashUrl: state.uiConfig.toJS().unleashUrl,
keys: state.apiAdmin.toJS(),
- hasPermission: permission => hasPermission(state.user.get('profile'), permission),
}),
{ fetchApiKeys, removeKey, addKey }
)(Component);
diff --git a/frontend/src/page/admin/api/api-key-list.jsx b/frontend/src/page/admin/api/api-key-list.jsx
index 94e7e00c0a..6b786e61a5 100644
--- a/frontend/src/page/admin/api/api-key-list.jsx
+++ b/frontend/src/page/admin/api/api-key-list.jsx
@@ -1,5 +1,5 @@
/* eslint-disable no-alert */
-import React, { useEffect, useState } from 'react';
+import React, { useContext, useEffect, useState } from 'react';
import PropTypes from 'prop-types';
import { Icon, Table, TableHead, TableBody, TableRow, TableCell, IconButton } from '@material-ui/core';
import { Alert } from '@material-ui/lab';
@@ -8,8 +8,11 @@ import CreateApiKey from './api-key-create';
import Secret from './secret';
import ConditionallyRender from '../../../component/common/ConditionallyRender/ConditionallyRender';
import Dialogue from '../../../component/common/Dialogue/Dialogue';
+import AccessContext from '../../../contexts/AccessContext';
+import { DELETE_API_TOKEN, CREATE_API_TOKEN } from '../../../component/AccessProvider/permissions';
-function ApiKeyList({ location, fetchApiKeys, removeKey, addKey, keys, hasPermission, unleashUrl }) {
+function ApiKeyList({ location, fetchApiKeys, removeKey, addKey, keys, unleashUrl }) {
+ const { hasAccess } = useContext(AccessContext);
const [showDelete, setShowDelete] = useState(false);
const [delKey, setDelKey] = useState(undefined);
const deleteKey = async () => {
@@ -55,7 +58,7 @@ function ApiKeyList({ location, fetchApiKeys, removeKey, addKey, keys, hasPermis
{keys.map(item => (
- {formatFullDateTimeWithLocale(item.created, location.locale)}
+ {formatFullDateTimeWithLocale(item.createdAt, location.locale)}
{item.username}
{item.type}
@@ -63,7 +66,7 @@ function ApiKeyList({ location, fetchApiKeys, removeKey, addKey, keys, hasPermis
- {
- setShowDelete(false);
- setDelKey(undefined);
- }}
- title="Really delete API key?"
- >
- Are you sure you want to delete?
-
- }
- />
- } />
+ {
+ setShowDelete(false);
+ setDelKey(undefined);
+ }}
+ title="Really delete API key?"
+ >
+ Are you sure you want to delete?
+
+ } />
);
}
@@ -109,7 +107,6 @@ ApiKeyList.propTypes = {
addKey: PropTypes.func.isRequired,
keys: PropTypes.array.isRequired,
unleashUrl: PropTypes.string,
- hasPermission: PropTypes.func.isRequired,
};
export default ApiKeyList;
diff --git a/frontend/src/page/admin/auth/google-auth-container.js b/frontend/src/page/admin/auth/google-auth-container.js
index e5cc107d77..d1a25ed616 100644
--- a/frontend/src/page/admin/auth/google-auth-container.js
+++ b/frontend/src/page/admin/auth/google-auth-container.js
@@ -1,12 +1,10 @@
import { connect } from 'react-redux';
import GoogleAuth from './google-auth';
import { getGoogleConfig, updateGoogleConfig } from './../../../store/e-admin-auth/actions';
-import { hasPermission } from '../../../permissions';
const mapStateToProps = state => ({
config: state.authAdmin.get('google'),
unleashUrl: state.uiConfig.toJS().unleashUrl,
- hasPermission: permission => hasPermission(state.user.get('profile'), permission),
});
const Container = connect(mapStateToProps, { getGoogleConfig, updateGoogleConfig })(GoogleAuth);
diff --git a/frontend/src/page/admin/auth/google-auth.jsx b/frontend/src/page/admin/auth/google-auth.jsx
index 8d5c3770ee..4d84dd5572 100644
--- a/frontend/src/page/admin/auth/google-auth.jsx
+++ b/frontend/src/page/admin/auth/google-auth.jsx
@@ -1,8 +1,10 @@
-import React, { useState, useEffect } from 'react';
+import React, { useState, useEffect, useContext } from 'react';
import PropTypes from 'prop-types';
import { Button, Grid, Switch, TextField } from '@material-ui/core';
import { Alert } from '@material-ui/lab';
import PageContent from '../../../component/common/PageContent/PageContent';
+import AccessContext from '../../../contexts/AccessContext';
+import { ADMIN } from '../../../component/AccessProvider/permissions';
const initialState = {
enabled: false,
@@ -10,9 +12,10 @@ const initialState = {
unleashHostname: location.hostname,
};
-function GoogleAuth({ config, getGoogleConfig, updateGoogleConfig, hasPermission, unleashUrl }) {
+function GoogleAuth({ config, getGoogleConfig, updateGoogleConfig, unleashUrl }) {
const [data, setData] = useState(initialState);
const [info, setInfo] = useState();
+ const { hasAccess } = useContext(AccessContext);
useEffect(() => {
getGoogleConfig();
@@ -25,7 +28,7 @@ function GoogleAuth({ config, getGoogleConfig, updateGoogleConfig, hasPermission
}
}, [config]);
- if (!hasPermission('ADMIN')) {
+ if (!hasAccess(ADMIN)) {
return You need admin privileges to access this section.;
}
@@ -193,7 +196,6 @@ GoogleAuth.propTypes = {
unleashUrl: PropTypes.string,
getGoogleConfig: PropTypes.func.isRequired,
updateGoogleConfig: PropTypes.func.isRequired,
- hasPermission: PropTypes.func.isRequired,
};
export default GoogleAuth;
diff --git a/frontend/src/page/admin/auth/index.js b/frontend/src/page/admin/auth/index.js
index 05a89bf256..07d89be806 100644
--- a/frontend/src/page/admin/auth/index.js
+++ b/frontend/src/page/admin/auth/index.js
@@ -1,10 +1,8 @@
import { connect } from 'react-redux';
import component from './authentication';
-import { hasPermission } from '../../../permissions';
const mapStateToProps = state => ({
authenticationType: state.uiConfig.toJS().authenticationType,
- hasPermission: permission => hasPermission(state.user.get('profile'), permission),
});
const Container = connect(mapStateToProps, { })(component);
diff --git a/frontend/src/page/admin/auth/saml-auth-container.js b/frontend/src/page/admin/auth/saml-auth-container.js
index f91ffab053..29e410e84d 100644
--- a/frontend/src/page/admin/auth/saml-auth-container.js
+++ b/frontend/src/page/admin/auth/saml-auth-container.js
@@ -1,12 +1,10 @@
import { connect } from 'react-redux';
import SamlAuth from './saml-auth';
import { getSamlConfig, updateSamlConfig } from './../../../store/e-admin-auth/actions';
-import { hasPermission } from '../../../permissions';
const mapStateToProps = state => ({
config: state.authAdmin.get('saml'),
unleashUrl: state.uiConfig.toJS().unleashUrl,
- hasPermission: permission => hasPermission(state.user.get('profile'), permission),
});
const Container = connect(mapStateToProps, { getSamlConfig, updateSamlConfig })(SamlAuth);
diff --git a/frontend/src/page/admin/auth/saml-auth.jsx b/frontend/src/page/admin/auth/saml-auth.jsx
index 8e819bbcce..7c635f4f91 100644
--- a/frontend/src/page/admin/auth/saml-auth.jsx
+++ b/frontend/src/page/admin/auth/saml-auth.jsx
@@ -1,8 +1,10 @@
-import React, { useState, useEffect } from 'react';
+import React, { useState, useEffect, useContext } from 'react';
import PropTypes from 'prop-types';
import { Button, Grid, Switch, TextField } from '@material-ui/core';
import { Alert } from '@material-ui/lab';
import PageContent from '../../../component/common/PageContent/PageContent';
+import AccessContext from '../../../contexts/AccessContext';
+import { ADMIN } from '../../../component/AccessProvider/permissions';
const initialState = {
enabled: false,
@@ -10,9 +12,10 @@ const initialState = {
unleashHostname: location.hostname,
};
-function SamlAuth({ config, getSamlConfig, updateSamlConfig, hasPermission, unleashUrl }) {
+function SamlAuth({ config, getSamlConfig, updateSamlConfig, unleashUrl }) {
const [data, setData] = useState(initialState);
const [info, setInfo] = useState();
+ const { hasAccess } = useContext(AccessContext);
useEffect(() => {
getSamlConfig();
@@ -26,7 +29,7 @@ function SamlAuth({ config, getSamlConfig, updateSamlConfig, hasPermission, unle
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [config]);
- if (!hasPermission('ADMIN')) {
+ if (!hasAccess(ADMIN)) {
return You need to be a root admin to access this section.;
}
@@ -188,7 +191,6 @@ SamlAuth.propTypes = {
unleash: PropTypes.string,
getSamlConfig: PropTypes.func.isRequired,
updateSamlConfig: PropTypes.func.isRequired,
- hasPermission: PropTypes.func.isRequired,
};
export default SamlAuth;
diff --git a/frontend/src/page/admin/users/UsersList/UsersList.jsx b/frontend/src/page/admin/users/UsersList/UsersList.jsx
index fe42e72f93..4c8fa1a605 100644
--- a/frontend/src/page/admin/users/UsersList/UsersList.jsx
+++ b/frontend/src/page/admin/users/UsersList/UsersList.jsx
@@ -1,5 +1,5 @@
/* eslint-disable no-alert */
-import React, { useEffect, useState } from 'react';
+import React, { useContext, useEffect, useState } from 'react';
import PropTypes from 'prop-types';
import { Button, Icon, IconButton, Table, TableBody, TableCell, TableHead, TableRow, Avatar } from '@material-ui/core';
import { formatDateWithLocale } from '../../../../component/common/util';
@@ -8,6 +8,8 @@ import ChangePassword from '../change-password-component';
import UpdateUser from '../update-user-component';
import DelUser from '../del-user-component';
import ConditionallyRender from '../../../../component/common/ConditionallyRender/ConditionallyRender';
+import AccessContext from '../../../../contexts/AccessContext';
+import { ADMIN } from '../../../../component/AccessProvider/permissions';
function UsersList({
roles,
@@ -18,9 +20,9 @@ function UsersList({
changePassword,
users,
location,
- hasPermission,
validatePassword,
}) {
+ const { hasAccess } = useContext(AccessContext);
const [showDialog, setDialog] = useState(false);
const [pwDialog, setPwDialog] = useState({ open: false });
const [delDialog, setDelDialog] = useState(false);
@@ -83,7 +85,7 @@ function UsersList({
Name
Username
Role
- {hasPermission('ADMIN') ? 'Action' : ''}
+ {hasAccess('ADMIN') ? 'Action' : ''}
@@ -95,7 +97,7 @@ function UsersList({
{item.username || item.email}
{renderRole(item.rootRole)}
@@ -117,7 +119,7 @@ function UsersList({
Add new user
@@ -168,7 +170,6 @@ UsersList.propTypes = {
fetchUsers: PropTypes.func.isRequired,
removeUser: PropTypes.func.isRequired,
addUser: PropTypes.func.isRequired,
- hasPermission: PropTypes.func.isRequired,
validatePassword: PropTypes.func.isRequired,
updateUser: PropTypes.func.isRequired,
changePassword: PropTypes.func.isRequired,
diff --git a/frontend/src/page/admin/users/UsersList/index.js b/frontend/src/page/admin/users/UsersList/index.js
index 18516e6f1d..4b8853c049 100644
--- a/frontend/src/page/admin/users/UsersList/index.js
+++ b/frontend/src/page/admin/users/UsersList/index.js
@@ -8,13 +8,11 @@ import {
updateUser,
validatePassword,
} from '../../../../store/e-user-admin/actions';
-import { hasPermission } from '../../../../permissions';
const mapStateToProps = state => ({
users: state.userAdmin.toJS(),
roles: state.roles.get('root').toJS() || [],
location: state.settings.toJS().location || {},
- hasPermission: permission => hasPermission(state.user.get('profile'), permission),
});
const Container = connect(mapStateToProps, {
diff --git a/frontend/src/store/user/index.js b/frontend/src/store/user/index.js
index b1f3ceb7e5..6ccbcc9a6d 100644
--- a/frontend/src/store/user/index.js
+++ b/frontend/src/store/user/index.js
@@ -2,11 +2,12 @@ import { Map as $Map } from 'immutable';
import { USER_CHANGE_CURRENT, USER_LOGOUT } from './actions';
import { AUTH_REQUIRED } from '../util';
-const userStore = (state = new $Map(), action) => {
+const userStore = (state = new $Map({permissions: []}), action) => {
switch (action.type) {
case USER_CHANGE_CURRENT:
state = state
- .set('profile', action.value)
+ .set('profile', action.value.user)
+ .set('permissions', action.value.permissions || [])
.set('showDialog', false)
.set('authDetails', undefined);
return state;