mirror of
https://github.com/LaCasemate/fab-manager.git
synced 2025-02-19 13:54:25 +01:00
(ui) Option to prevent users from changing their group
This commit is contained in:
parent
1d2b814d6f
commit
e45872956c
@ -3,9 +3,13 @@
|
||||
## next deploy
|
||||
|
||||
- Option to disable the 'machines' module
|
||||
- Option to prevent users from changing their group
|
||||
- Ability to define social networks for the FabLab "about page"
|
||||
- Improved security when changing passwords
|
||||
- Support for OpenID Connect in Sign-Sign-On authentication providers
|
||||
- ICS file attached to the reservation notification email
|
||||
- Refactored the user profile edition form
|
||||
- Improved the profile completion page
|
||||
- No longer needed to recompile the assets when switching the authentication provider
|
||||
- Updated the documentation about the minimum docker version
|
||||
- Updated nodejs version to 16.13.2 for dev environment, to reflect production version
|
||||
|
@ -10,6 +10,9 @@ import { useTranslation } from 'react-i18next';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { FormSelect } from '../form/form-select';
|
||||
import MemberAPI from '../../api/member';
|
||||
import SettingAPI from '../../api/setting';
|
||||
import { SettingName } from '../../models/setting';
|
||||
import UserLib from '../../lib/user';
|
||||
|
||||
declare const Application: IApplication;
|
||||
|
||||
@ -18,6 +21,7 @@ interface ChangeGroupProps {
|
||||
onSuccess: (message: string, user: User) => void,
|
||||
onError: (message: string) => void,
|
||||
allowChange?: boolean,
|
||||
className?: string,
|
||||
}
|
||||
|
||||
/**
|
||||
@ -26,18 +30,22 @@ interface ChangeGroupProps {
|
||||
*/
|
||||
type selectOption = { value: number, label: string };
|
||||
|
||||
export const ChangeGroup: React.FC<ChangeGroupProps> = ({ user, onSuccess, onError, allowChange }) => {
|
||||
export const ChangeGroup: React.FC<ChangeGroupProps> = ({ user, onSuccess, onError, allowChange, className }) => {
|
||||
const { t } = useTranslation('shared');
|
||||
|
||||
const [groups, setGroups] = useState<Array<Group>>([]);
|
||||
const [changeRequested, setChangeRequested] = useState<boolean>(false);
|
||||
const [operator, setOperator] = useState<User>(null);
|
||||
const [allowedUserChangeGoup, setAllowedUserChangeGoup] = useState<boolean>(false);
|
||||
|
||||
const { handleSubmit, control } = useForm();
|
||||
|
||||
useEffect(() => {
|
||||
GroupAPI.index({ disabled: false, admins: false }).then(setGroups).catch(onError);
|
||||
MemberAPI.current().then(setOperator).catch(onError);
|
||||
SettingAPI.get(SettingName.UserChangeGroup).then((setting) => {
|
||||
setAllowedUserChangeGoup(setting.value === 'true');
|
||||
}).catch(onError);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
@ -55,7 +63,8 @@ export const ChangeGroup: React.FC<ChangeGroupProps> = ({ user, onSuccess, onErr
|
||||
* Check if the group changing is currently allowed.
|
||||
*/
|
||||
const canChangeGroup = (): boolean => {
|
||||
return allowChange;
|
||||
const userLib = new UserLib(operator);
|
||||
return allowChange && (allowedUserChangeGoup || userLib.isPrivileged(user));
|
||||
};
|
||||
|
||||
/**
|
||||
@ -81,7 +90,7 @@ export const ChangeGroup: React.FC<ChangeGroupProps> = ({ user, onSuccess, onErr
|
||||
if (!user) return null;
|
||||
|
||||
return (
|
||||
<div className="change-group">
|
||||
<div className={`change-group ${className || ''}`}>
|
||||
<h3>{t('app.shared.change_group.title', { OPERATOR: operator?.id === user.id ? 'self' : 'admin' })}</h3>
|
||||
{!changeRequested && <div className="display">
|
||||
<div className="current-group">
|
||||
@ -114,4 +123,4 @@ const ChangeGroupWrapper: React.FC<ChangeGroupProps> = (props) => {
|
||||
);
|
||||
};
|
||||
|
||||
Application.Components.component('changeGroup', react2angular(ChangeGroupWrapper, ['user', 'onSuccess', 'onError', 'allowChange']));
|
||||
Application.Components.component('changeGroup', react2angular(ChangeGroupWrapper, ['user', 'onSuccess', 'onError', 'allowChange', 'className']));
|
||||
|
@ -140,20 +140,24 @@ Application.Controllers.controller('EditProfileController', ['$scope', '$rootSco
|
||||
};
|
||||
|
||||
/**
|
||||
* Change the group of the current user to the one set in $scope.userGroup
|
||||
* Callback triggered when the user has successfully changed his group
|
||||
*/
|
||||
$scope.selectGroup = () =>
|
||||
Member.update({ id: $scope.user.id }, { user: { group_id: $scope.userGroup } }, function (user) {
|
||||
$scope.user = user;
|
||||
$rootScope.currentUser = user;
|
||||
Auth._currentUser.group_id = user.group_id;
|
||||
$scope.group.change = false;
|
||||
return growl.success(_t('app.logged.dashboard.settings.your_group_has_been_successfully_changed'));
|
||||
}
|
||||
, function (err) {
|
||||
growl.error(_t('app.logged.dashboard.settings.an_unexpected_error_prevented_your_group_from_being_changed'));
|
||||
return console.error(err);
|
||||
});
|
||||
$scope.onGroupUpdateSuccess = function (message, user) {
|
||||
growl.success(message);
|
||||
setTimeout(() => {
|
||||
$scope.user = _.cloneDeep(user);
|
||||
$scope.$apply();
|
||||
}, 50);
|
||||
$rootScope.currentUser.group_id = user.group_id;
|
||||
Auth._currentUser.group_id = user.group_id;
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if it is allowed the change the group of the current user
|
||||
*/
|
||||
$scope.isAllowedChangingGroup = function () {
|
||||
return !$scope.user.subscribed_plan?.name && $scope.user.role !== 'admin';
|
||||
};
|
||||
|
||||
/**
|
||||
* Callback to diplay the datepicker as a dropdown when clicking on the input field
|
||||
|
@ -16,16 +16,6 @@ Application.Controllers.controller('PlansIndexController', ['$scope', '$rootScop
|
||||
function ($scope, $rootScope, $state, $uibModal, Auth, AuthService, dialogs, growl, groupsPromise, Subscription, Member, subscriptionExplicationsPromise, _t, Wallet, helpers, settingsPromise, Price) {
|
||||
/* PUBLIC SCOPE */
|
||||
|
||||
// list of groups
|
||||
$scope.groups = groupsPromise.filter(function (g) { return (g.slug !== 'admins') & !g.disabled; });
|
||||
|
||||
// default : do not show the group changing form
|
||||
// group ID of the current/selected user
|
||||
$scope.group = {
|
||||
change: false,
|
||||
id: null
|
||||
};
|
||||
|
||||
// user to deal with
|
||||
$scope.ctrl = {
|
||||
member: null,
|
||||
@ -59,10 +49,8 @@ Application.Controllers.controller('PlansIndexController', ['$scope', '$rootScop
|
||||
$scope.updateMember = function () {
|
||||
$scope.selectedPlan = null;
|
||||
$scope.paid.plan = null;
|
||||
$scope.group.change = false;
|
||||
Member.get({ id: $scope.ctrl.member.id }, function (member) {
|
||||
$scope.ctrl.member = member;
|
||||
$scope.group.id = $scope.ctrl.member.group_id;
|
||||
});
|
||||
};
|
||||
|
||||
@ -104,56 +92,6 @@ Application.Controllers.controller('PlansIndexController', ['$scope', '$rootScop
|
||||
growl.error(message);
|
||||
};
|
||||
|
||||
/**
|
||||
* Return the group object, identified by the ID set in $scope.group.id
|
||||
*/
|
||||
$scope.getUserGroup = function () {
|
||||
for (const group of Array.from($scope.groups)) {
|
||||
if (group.id === $scope.group.id) {
|
||||
return group;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Change the group of the current/selected user to the one set in $scope.group.id
|
||||
*/
|
||||
$scope.selectGroup = function () {
|
||||
Member.update({ id: $scope.ctrl.member.id }, { user: { group_id: $scope.group.id } }, function (user) {
|
||||
$scope.ctrl.member = user;
|
||||
$scope.group.change = false;
|
||||
$scope.selectedPlan = null;
|
||||
if (AuthService.isAuthorized('member') ||
|
||||
(AuthService.isAuthorized('manager') && $scope.currentUser.id !== $scope.ctrl.member.id)) {
|
||||
$rootScope.currentUser = user;
|
||||
Auth._currentUser.group_id = user.group_id;
|
||||
growl.success(_t('app.public.plans.your_group_was_successfully_changed'));
|
||||
} else {
|
||||
growl.success(_t('app.public.plans.the_user_s_group_was_successfully_changed'));
|
||||
}
|
||||
}
|
||||
, function (err) {
|
||||
if (AuthService.isAuthorized('member') ||
|
||||
(AuthService.isAuthorized('manager') && $scope.currentUser.id !== $scope.ctrl.member.id)) {
|
||||
growl.error(_t('app.public.plans.an_error_prevented_your_group_from_being_changed'));
|
||||
} else {
|
||||
growl.error(_t('app.public.plans.an_error_prevented_to_change_the_user_s_group'));
|
||||
}
|
||||
console.error(err);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Return an enumerable meaninful string for the gender of the provider user
|
||||
* @param user {Object} Database user record
|
||||
* @return {string} 'male' or 'female'
|
||||
*/
|
||||
$scope.getGender = function (user) {
|
||||
if (user && user.statistic_profile_attributes) {
|
||||
if (user.statistic_profile_attributes.gender === 'true') { return 'male'; } else { return 'female'; }
|
||||
} else { return 'other'; }
|
||||
};
|
||||
|
||||
/**
|
||||
* Test if the provided date is in the future
|
||||
* @param dateTime {Date}
|
||||
@ -199,7 +137,7 @@ Application.Controllers.controller('PlansIndexController', ['$scope', '$rootScop
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if it is allowed the change the group of teh selected user
|
||||
* Check if it is allowed the change the group of the selected user
|
||||
*/
|
||||
$scope.isAllowedChangingGroup = function () {
|
||||
return $scope.ctrl.member && !$scope.selectedPlan && !$scope.paid.plan;
|
||||
@ -215,7 +153,6 @@ Application.Controllers.controller('PlansIndexController', ['$scope', '$rootScop
|
||||
if (!AuthService.isAuthorized('admin')) {
|
||||
$scope.ctrl.member = $scope.currentUser;
|
||||
$scope.paid.plan = $scope.currentUser.subscribed_plan;
|
||||
$scope.group.id = $scope.currentUser.group_id;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -13,10 +13,10 @@ export default class UserLib {
|
||||
* Check if the current user has privileged access for resources concerning the provided customer
|
||||
*/
|
||||
isPrivileged = (customer: User): boolean => {
|
||||
if (this.user.role === 'admin') return true;
|
||||
if (this.user?.role === 'admin') return true;
|
||||
|
||||
if (this.user.role === 'manager') {
|
||||
return (this.user.id !== customer.id);
|
||||
if (this.user?.role === 'manager') {
|
||||
return (this.user?.id !== customer.id);
|
||||
}
|
||||
|
||||
return false;
|
||||
|
@ -134,6 +134,7 @@ export enum SettingName {
|
||||
SocialsLastfm = 'lastfm',
|
||||
SocialsFlickr = 'flickr',
|
||||
MachinesModule = 'machines_module',
|
||||
UserChangeGroup = 'user_change_group',
|
||||
}
|
||||
|
||||
export type SettingValue = string|boolean|number;
|
||||
|
@ -525,7 +525,6 @@ angular.module('application.router', ['ui.router'])
|
||||
},
|
||||
resolve: {
|
||||
subscriptionExplicationsPromise: ['Setting', function (Setting) { return Setting.get({ name: 'subscription_explications_alert' }).$promise; }],
|
||||
groupsPromise: ['Group', function (Group) { return Group.query().$promise; }],
|
||||
settingsPromise: ['Setting', function (Setting) { return Setting.query({ names: "['online_payment_module', 'payment_gateway', 'overlapping_categories']" }).$promise; }]
|
||||
}
|
||||
})
|
||||
@ -1072,7 +1071,8 @@ angular.module('application.router', ['ui.router'])
|
||||
"'renew_pack_threshold', 'pack_only_for_subscription', 'overlapping_categories', 'public_registrations'," +
|
||||
"'extended_prices_in_same_day', 'recaptcha_site_key', 'recaptcha_secret_key', 'user_validation_required', 'user_validation_required_machine', " +
|
||||
"'user_validation_required_training', 'user_validation_required_subscription', 'user_validation_required_space'," +
|
||||
"'user_validation_required_event', 'user_validation_required_pack', 'user_validation_required_list', 'machines_module']"
|
||||
"'user_validation_required_event', 'user_validation_required_pack', 'user_validation_required_list'," +
|
||||
"'machines_module', 'user_change_group']"
|
||||
}).$promise;
|
||||
}],
|
||||
privacyDraftsPromise: ['Setting', function (Setting) { return Setting.get({ name: 'privacy_draft', history: true }).$promise; }],
|
||||
|
@ -85,6 +85,7 @@
|
||||
|
||||
@import "modules/abuses";
|
||||
@import "modules/cookies";
|
||||
@import "modules/dashboard";
|
||||
@import "modules/document-filters";
|
||||
@import "modules/event-themes";
|
||||
@import "modules/icalendar";
|
||||
|
51
app/frontend/src/stylesheets/modules/dashboard.scss
Normal file
51
app/frontend/src/stylesheets/modules/dashboard.scss
Normal file
@ -0,0 +1,51 @@
|
||||
.dashboard {
|
||||
.dashboard-change-group {
|
||||
border: 0;
|
||||
margin: 0;
|
||||
|
||||
h3 {
|
||||
padding: 0;
|
||||
text-transform: uppercase;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.display,
|
||||
.change-group-form {
|
||||
padding: 10px 0 0;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.display {
|
||||
.current-group {
|
||||
margin-bottom: 10px;
|
||||
background-color: #fcf8e3;
|
||||
border-color: #faebcc;
|
||||
color: #8a6d3b;
|
||||
}
|
||||
.request-change-btn {
|
||||
width: auto;
|
||||
text-transform: uppercase;
|
||||
border-radius: 3px;
|
||||
font-size: 12px;
|
||||
line-height: 18px;
|
||||
padding: 5px 10px;
|
||||
height: 30px;
|
||||
background-color: var(--secondary);
|
||||
border-color: var(--secondary);
|
||||
|
||||
&:hover {
|
||||
background-color: white;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.actions {
|
||||
.fab-button {
|
||||
font-size: 12px;
|
||||
line-height: 18px;
|
||||
padding: 5px 10px;
|
||||
height: 30px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -480,6 +480,18 @@
|
||||
</boolean-setting>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<h3 class="m-l" translate>{{ 'app.admin.settings.change_group' }}</h3>
|
||||
<p class="alert alert-warning m-h-md" translate>
|
||||
{{ 'app.admin.settings.change_group_info' }}
|
||||
</p>
|
||||
<div class="col-md-10 col-md-offset-1">
|
||||
<boolean-setting name="user_change_group"
|
||||
settings="allSettings"
|
||||
label="app.admin.settings.allow_group_change">
|
||||
</boolean-setting>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -4,7 +4,7 @@
|
||||
|
||||
|
||||
|
||||
<div class="row no-gutter wrapper">
|
||||
<div class="row no-gutter wrapper dashboard">
|
||||
<div class="col-sm-12 col-md-12 col-lg-3">
|
||||
<div class="widget panel b-a m m-t-lg">
|
||||
<div class="panel-heading b-b small text-center">
|
||||
@ -16,24 +16,12 @@
|
||||
<div class="text-xs" ng-show="user.last_sign_in_at"><i>{{ 'app.logged.dashboard.settings.last_activity_on_' | translate:{DATE:(user.last_sign_in_at | amDateFormat: 'LL')} }}</i></div>
|
||||
</div>
|
||||
<div class="widget-content no-bg b-b auto wrapper">
|
||||
<div class="m-b-md">
|
||||
<h3 class="text-u-c" translate>{{ 'app.logged.dashboard.settings.group' }}</h3>
|
||||
<div ng-show="!group.change">
|
||||
<uib-alert type="warning">
|
||||
<span class="text-black font-sbold">{{getUserGroup().name}}</span>
|
||||
</uib-alert>
|
||||
<button class="btn text-black btn-warning-full btn-sm m-t-n-sm"
|
||||
ng-click="group.change = !group.change"
|
||||
ng-hide="user.subscribed_plan.name || user.role === 'admin'"
|
||||
translate>
|
||||
{{ 'app.logged.dashboard.settings.i_want_to_change_group' }}
|
||||
</button>
|
||||
</div>
|
||||
<div ng-show="group.change">
|
||||
<select class="form-control" ng-options="g.id as g.name for g in groups" ng-model="userGroup"></select>
|
||||
<button class="btn btn-success m-t" ng-click="selectGroup()">Changer mon groupe</button>
|
||||
</div>
|
||||
</div>
|
||||
<change-group user="user"
|
||||
on-success="onGroupUpdateSuccess"
|
||||
on-error="onError"
|
||||
class-name="'dashboard-change-group'"
|
||||
allow-change="isAllowedChangingGroup()">
|
||||
</change-group>
|
||||
<div ng-show="$root.modules.plans">
|
||||
<h3 class="text-u-c" translate>{{ 'app.logged.dashboard.settings.subscription' }}</h3>
|
||||
<div ng-show="user.subscribed_plan">
|
||||
|
@ -144,7 +144,8 @@ class Setting < ApplicationRecord
|
||||
pinterest
|
||||
lastfm
|
||||
flickr
|
||||
machines_module] }
|
||||
machines_module
|
||||
user_change_group] }
|
||||
# WARNING: when adding a new key, you may also want to add it in:
|
||||
# - config/locales/en.yml#settings
|
||||
# - app/frontend/src/javascript/models/setting.ts#SettingName
|
||||
|
@ -40,7 +40,8 @@ class SettingPolicy < ApplicationPolicy
|
||||
recaptcha_site_key feature_tour_display disqus_shortname allowed_cad_extensions openlab_app_id openlab_default
|
||||
online_payment_module stripe_public_key confirmation_required wallet_module trainings_module address_required
|
||||
payment_gateway payzen_endpoint payzen_public_key public_agenda_module renew_pack_threshold statistics_module
|
||||
pack_only_for_subscription overlapping_categories public_registrations socials_facebook socials_twitter socials_viadeo socials_linkedin socials_instagram socials_youtube socials_vimeo socials_dailymotion socials_github socials_echosciences socials_pinterest socials_lastfm socials_flickr]
|
||||
pack_only_for_subscription overlapping_categories public_registrations facebook twitter viadeo linkedin instagram
|
||||
youtube vimeo dailymotion github echosciences pinterest lastfm flickr machines_module user_change_group]
|
||||
end
|
||||
|
||||
##
|
||||
|
@ -1408,6 +1408,10 @@ en:
|
||||
account_confirmation: "Account confirmation"
|
||||
confirmation_required_info: "Optionally, you can force the users to confirm their email address before being able to access Fab-manager."
|
||||
confirmation_is_required: "Confirmation required"
|
||||
change_group: "Group change"
|
||||
change_group_info: "After an user has created his account, you can restrict him from changing his group. In that case, only managers and administrators will be able to change the user's group."
|
||||
allow_group_change: "Allow group change"
|
||||
user_change_group: "users can change their group"
|
||||
wallet_module: "wallet module"
|
||||
public_agenda_module: "public agenda module"
|
||||
statistics_module: "statistics module"
|
||||
|
@ -582,3 +582,4 @@ en:
|
||||
lastfm: "lastfm"
|
||||
flickr: "flickr"
|
||||
machines_module: "Machines module"
|
||||
user_change_group: "Allow users to change their group"
|
||||
|
@ -905,6 +905,8 @@ Setting.set('pack_only_for_subscription', true) unless Setting.find_by(name: 'pa
|
||||
|
||||
Setting.set('public_registrations', true) unless Setting.find_by(name: 'public_registrations').try(:value)
|
||||
|
||||
Setting.set('user_change_group', true) unless Setting.find_by(name: 'user_change_group').try(:value)
|
||||
|
||||
unless Setting.find_by(name: 'overlapping_categories').try(:value)
|
||||
Setting.set('overlapping_categories', 'training_reservations,machine_reservations,space_reservations,events_reservations')
|
||||
end
|
||||
|
Loading…
x
Reference in New Issue
Block a user