diff --git a/package-lock.json b/package-lock.json index 8e267f59..7ca7bf6b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -33,6 +33,7 @@ "react-promise-tracker": "^2.1.1", "react-redux": "^7.2.4", "react-router-dom": "^5.2.0", + "react-select": "^5.8.0", "redux": "^4.1.0", "redux-devtools-extension": "^2.13.9", "redux-thunk": "^2.3.0", diff --git a/package.json b/package.json index 9424b22b..993165b5 100644 --- a/package.json +++ b/package.json @@ -31,6 +31,7 @@ "react-promise-tracker": "^2.1.1", "react-redux": "^7.2.4", "react-router-dom": "^5.2.0", + "react-select": "^5.8.0", "redux": "^4.1.0", "redux-devtools-extension": "^2.13.9", "redux-thunk": "^2.3.0", diff --git a/src/RoleSelector.jsx b/src/RoleSelector.jsx new file mode 100644 index 00000000..f15ba69a --- /dev/null +++ b/src/RoleSelector.jsx @@ -0,0 +1,61 @@ +import React, { useState, useEffect } from "react"; +import Select from "react-select"; +import PropTypes from "prop-types"; +import { ROLE } from "./constants/DefaultConstants.js"; +import Row from "react-bootstrap/Row"; +import { Col, FormGroup, FormLabel } from "react-bootstrap"; + +const roleOptions = Object.keys(ROLE).map((key) => ({ + value: ROLE[key], + label: ROLE[key], +})); + +const RoleSelector = ({ selected = [], handler, readOnly = true, label = "Roles" }) => { + const formatSelected = (selected) => { + return selected.map((value) => ({ + value: value, + label: value, + })); + }; + + const [selectedRoles, setSelectedRoles] = useState(formatSelected(selected)); + + useEffect(() => { + setSelectedRoles(formatSelected(selected)); + }, [selected]); + + const handleChange = (selectedOptions) => { + setSelectedRoles(selectedOptions); + const selectedValues = selectedOptions.map((option) => option.value); + handler(selectedValues); + }; + + return ( + + + {label} + + + + + + ); +}; + +RoleSelector.propTypes = { + selected: PropTypes.array, + handler: PropTypes.func.isRequired, + readOnly: PropTypes.bool, + label: PropTypes.string, +}; + +export default RoleSelector; diff --git a/src/components/user/User.jsx b/src/components/user/User.jsx index a7f4bda3..a281a205 100644 --- a/src/components/user/User.jsx +++ b/src/components/user/User.jsx @@ -4,7 +4,7 @@ import withI18n from "../../i18n/withI18n"; import { injectIntl } from "react-intl"; import HorizontalInput from "../HorizontalInput"; import UserValidator from "../../validation/UserValidator"; -import { ACTION_STATUS, ROLE } from "../../constants/DefaultConstants"; +import { ACTION_STATUS, ROLE, ROLE_TYPE } from "../../constants/DefaultConstants"; import { getRole, processInstitutions } from "../../utils/Utils"; import * as Vocabulary from "../../constants/Vocabulary"; import { LoaderCard, LoaderSmall } from "../Loader"; @@ -12,7 +12,8 @@ import HelpIcon from "../HelpIcon"; import PropTypes from "prop-types"; import { FaRandom } from "react-icons/fa"; import { isUsingOidcAuth } from "../../utils/OidcUtils"; -import { isAdmin } from "../../utils/SecurityUtils"; +import { getRoles, isAdmin } from "../../utils/SecurityUtils"; +import RoleSelector from "../../RoleSelector.jsx"; class User extends React.Component { static propTypes = { @@ -38,6 +39,7 @@ class User extends React.Component { this.i18n = this.props.i18n; this.formatMessage = this.props.formatMessage; this.state = { savedWithEmail: false }; + this._onRoleSelected = this._onRoleSelected.bind(this); } _onChange = (e) => { @@ -160,7 +162,7 @@ class User extends React.Component { _impersonateButton() { const { user, currentUser, handlers, impersonation } = this.props; - if (!user.isNew && isAdmin(currentUser) && getRole(user) !== ROLE.ADMIN) { + if (!user.isNew && isAdmin(currentUser) && !isAdmin(user)) { return ( { + return ROLE_TYPE[role]; + }); + this.props.handlers.onChange({ types: types }); + } + _onSaveAndSendEmail() { this.props.handlers.onSave(); this.setState({ savedWithEmail: true }); @@ -302,20 +311,6 @@ class User extends React.Component { )} - - - {this._generateRolesOptions()} - - {user.isNew && ( @@ -332,6 +327,14 @@ class User extends React.Component { )} + + + {this._impersonateButton()} {isUsingOidcAuth() ? this._redirectToKeycloakButton() : this._passwordChangeButton()} diff --git a/src/constants/DefaultConstants.js b/src/constants/DefaultConstants.js index e80232db..acddf125 100644 --- a/src/constants/DefaultConstants.js +++ b/src/constants/DefaultConstants.js @@ -111,6 +111,7 @@ export const TYPE_ROLE = { [Vocabulary.EDIT_USERS_TYPE]: ROLE.EDIT_USERS, [Vocabulary.IMPORT_CODELISTS_TYPE]: ROLE.IMPORT_CODELISTS, }; +export const ROLE_TYPE = Object.fromEntries(Object.entries(TYPE_ROLE).map(([key, value]) => [value, key])); // Default number of table elements per page. export const DEFAULT_PAGE_SIZE = 10; diff --git a/src/i18n/cs.js b/src/i18n/cs.js index 8a71fbfd..d7a07e4e 100644 --- a/src/i18n/cs.js +++ b/src/i18n/cs.js @@ -98,6 +98,7 @@ export default { "user.password-confirm": "Potvrzení hesla", "user.passwords-not-matching-tooltip": "Hesla se neshodují", "user.role": "Role", + "user.roles": "Roles", "user.save-success": "Uživatel úspěšně uložen", "user.save-success-with-email": "Uživatel úspěšně uložen a informován emailem.", "user.save-error": "Uživatele se nepodařilo uložit. {error}", diff --git a/src/i18n/en.js b/src/i18n/en.js index 24b83dd5..ae03237e 100644 --- a/src/i18n/en.js +++ b/src/i18n/en.js @@ -98,6 +98,7 @@ export default { "user.password-confirm": "Confirm password", "user.passwords-not-matching-tooltip": "Passwords don't match", "user.role": "Role", + "user.roles": "Roles", "user.save-success": "User saved successfully", "user.save-success-with-email": "User saved successfully and informed by email.", "user.save-error": "Unable to save user. {error}", diff --git a/src/utils/SecurityUtils.js b/src/utils/SecurityUtils.js index 83267969..43f7c259 100644 --- a/src/utils/SecurityUtils.js +++ b/src/utils/SecurityUtils.js @@ -16,8 +16,11 @@ export function clearToken() { sessionStorage.removeItem(getOidcIdentityStorageKey()); } -export function isAdmin(currentUser) { - return currentUser.roles ? currentUser.roles.includes(ROLE.ADMIN) : false; +export function isAdmin(user) { + if (user.roles) { + return user.roles.includes(ROLE.ADMIN); + } + return user.types ? getRoles(user).includes(ROLE.ADMIN) : false; } export function hasRole(currentUser, role) { @@ -30,7 +33,7 @@ export function isImpersonator(currentUser) { } export function getRoles(user) { - if (!user) { + if (!user || !user.types) { return undefined; } let roles = [];