diff --git a/api/src/routes/HttpRoutes.js b/api/src/routes/HttpRoutes.js index a64bf64bce..d390390072 100644 --- a/api/src/routes/HttpRoutes.js +++ b/api/src/routes/HttpRoutes.js @@ -3,6 +3,11 @@ import express from 'express'; import restify from 'express-restify-mongoose'; import git from 'git-rev'; import Promise from 'bluebird'; +import { omit, findIndex } from 'lodash'; +import getAuthFromRequest from 'lib/helpers/getAuthFromRequest'; +import getScopesFromRequest from 'lib/services/auth/authInfoSelectors/getScopesFromAuthInfo'; +import { SITE_ADMIN } from 'lib/constants/scopes'; +import getUserIdFromAuthInfo from 'lib/services/auth/authInfoSelectors/getUserIdFromAuthInfo'; import { jsonSuccess, serverError } from 'api/utils/responses'; import passport from 'api/auth/passport'; import { @@ -199,7 +204,22 @@ restify.serve(router, Export); restify.serve(router, Download); restify.serve(router, Query); restify.serve(router, ImportCsv); -restify.serve(router, User); +restify.serve(router, User, { + preUpdate: (req, res, next) => { + const authInfo = getAuthFromRequest(req); + const scopes = getScopesFromRequest(authInfo); + + if ( + findIndex(scopes, item => item === SITE_ADMIN) < 0 && + (req.body._id !== getUserIdFromAuthInfo(authInfo).toString()) + ) { + // Don't allow changing of passwords + req.body = omit(req.body, 'password'); + } + + next(); + } +}); restify.serve(router, Client); restify.serve(router, Visualisation); restify.serve(router, Dashboard); diff --git a/ui/src/containers/UserForm/index.js b/ui/src/containers/UserForm/index.js index 5018be91cd..39442ffff1 100644 --- a/ui/src/containers/UserForm/index.js +++ b/ui/src/containers/UserForm/index.js @@ -9,6 +9,12 @@ import ValidationList from 'ui/components/ValidationList'; import Checkbox from 'ui/components/Material/Checkbox'; import uuid from 'uuid'; import { validatePasswordUtil } from 'lib/utils/validators/User'; +import { connect } from 'react-redux'; +import { + hasScopeSelector, + loggedInUserId as loggedInUserIdSelector +} from 'ui/redux/modules/auth'; +import { SITE_ADMIN } from 'lib/constants/scopes'; import styles from './userform.css'; const changeModelAttr = (updateModel, model, attr) => value => @@ -170,6 +176,8 @@ const render = ({ setPassword, passwordConfirmation, setPasswordConfirmation, + isSiteAdmin, + loggedInUserId }) => { const ownerOrganisationSettings = model.get('ownerOrganisationSettings', new Map()).toJS(); @@ -179,7 +187,12 @@ const render = ({ password, passwordConfirmation, ownerOrganisationSettings ).concat(password === '' ? serverErrors : new List()); const hasPasswordErrors = !passwordErrors.isEmpty(); - const canChangePassword = (changePasswordChecked || hasPasswordErrors); + const canChangePassword = + (changePasswordChecked || hasPasswordErrors); + const isAuthorisedToChangePassword = ( + isSiteAdmin || + model.get('_id') === loggedInUserId + ); const passwordInputsVisible = (!model.get('verified') || canChangePassword); const passwordGroupClasses = classNames({ 'form-group': true, @@ -195,7 +208,7 @@ const render = ({ {renderVerified(model, styles)} {renderName(model, onChangeAttr)} {renderEmail(model, onChangeAttr)} - {renderPasswordChanges(model, onPasswordCheckboxChange(updateModel, model, setChangePasswordChecked), canChangePassword)} + {isAuthorisedToChangePassword && renderPasswordChanges(model, onPasswordCheckboxChange(updateModel, model, setChangePasswordChecked), canChangePassword)} {passwordInputsVisible && (
@@ -229,5 +242,9 @@ export default compose( schema: 'user', id: model.get('_id') })), + connect(state => ({ + isSiteAdmin: hasScopeSelector(SITE_ADMIN)(state), + loggedInUserId: loggedInUserIdSelector(state) + })), withModel )(render);