Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
113 changes: 85 additions & 28 deletions app/assets/javascripts/admin/commons/users.js
Original file line number Diff line number Diff line change
@@ -1,34 +1,91 @@
/*global $ */
$(function () {
'use strict';
// Affiche la cible d'un rôle et n'y propose que les options du bon type.
// Le type attendu est porté par l'option de rôle sélectionnée
// (data-scope-type, vide = rôle global) ; chaque option de cible porte aussi
// son type. Aucun mapping en dur : tout vient du HTML rendu.
window.osuny.userRoles = {
init: function () {
'use strict';
var rows,
i;
if (!document.body.matches('.users-edit, .users-new, .users-update, .users-create')) {
return;
}
rows = document.querySelectorAll('[data-user-role]');
for (i = 0; i < rows.length; i += 1) {
this.refreshRow(rows[i]);
}
this.initEvents();
},

initEvents: function () {
'use strict';
document.addEventListener('change', this.onChangeRole.bind(this));
$('#user_roles').on('cocoon:after-insert', this.onAfterInsert.bind(this));
},

onChangeRole: function (event) {
'use strict';
var roleSelect = event.target.closest('[data-user-role-role]');
if (roleSelect !== null) {
this.refreshRow(roleSelect.closest('[data-user-role]'));
}
},

onAfterInsert: function (event, row) {
'use strict';
this.refreshRow(row[0]);
},

refreshRow: function (row) {
'use strict';
var roleSelect = row.querySelector('[data-user-role-role]'),
roleOption = roleSelect && roleSelect.options[roleSelect.selectedIndex],
type = roleOption && roleOption.getAttribute('data-scope-type'),
container = row.querySelector('[data-user-role-scope-container]'),
scope = row.querySelector('[data-user-role-scope]');

var changeRole = function () {
var value = $('select[name="user[role]"]').val(),
showForRoles,
required;

$('*[data-show-for-roles]').each(function () {
showForRoles = $(this)
.attr('data-show-for-roles')
.split(',');
if ($.inArray(value, showForRoles) > -1) {
required = $(this).attr('data-required');
if (required) {
$('input, select', this).attr('required', 'required');
} else {
$('input, select', this).removeAttr('required');
}
$(this).show();
} else {
$(this).hide();
// hidden field cannot be required
$('input, select', this).removeAttr('required');
if (!type) {
container.classList.add('d-none');
return;
}
container.classList.remove('d-none');
this.filterScopeOptions(scope, type);
},

// N'affiche que les cibles du bon type et, si la cible courante n'est pas
// du bon type, sélectionne la première valide.
filterScopeOptions: function (scope, type) {
'use strict';
var options = scope.options,
firstMatch = null,
selected = scope.options[scope.selectedIndex],
matches,
i;

for (i = 0; i < options.length; i += 1) {
matches = options[i].getAttribute('data-scope-type') === type;
options[i].hidden = !matches;
options[i].disabled = !matches;
if (matches && firstMatch === null) {
firstMatch = options[i].value;
}
});
};
}

if (!selected || selected.getAttribute('data-scope-type') !== type) {
scope.value = firstMatch;
}
Comment thread
arnaudlevy marked this conversation as resolved.
Comment thread
qltysh[bot] marked this conversation as resolved.
},

if ($('body').is('.users-edit, .users-new, .users-update, .users-create')) {
changeRole();
$('select[name="user[role]"]').change(changeRole);
invoke: function () {
'use strict';
return {
init: this.init.bind(this)
};
}
}.invoke();

window.addEventListener('DOMContentLoaded', function () {
'use strict';
window.osuny.userRoles.init();
});
3 changes: 2 additions & 1 deletion app/controllers/admin/users_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,8 @@ def breadcrumb

def user_params
params.require(:user)
.permit(:email, :first_name, :last_name, :role, :language_id, :picture, :picture_delete, :picture_infos, :mobile_phone, programs_to_manage_ids: [], websites_to_manage_ids: [])
.permit(:email, :first_name, :last_name, :language_id, :picture, :picture_delete, :picture_infos, :mobile_phone,
roles_attributes: [:id, :role, :scope_choice, :_destroy])
.merge(university_id: current_university.id)
end

Expand Down
2 changes: 1 addition & 1 deletion app/controllers/application_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ def render_as_plain_text(partial: nil)
end

def current_ability
@current_ability ||= Ability.for(current_user)
@current_ability ||= User::Ability.for(current_user)
end

def ensure_university
Expand Down
6 changes: 1 addition & 5 deletions app/controllers/extranet/application_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,7 @@ def authorize_extranet_access!
end

def user_is_authorized?
user_is_more_than_visitor || user_is_alumnus || user_is_contact
end

def user_is_more_than_visitor
!current_user.visitor?
current_ability.can? :read, current_extranet
end

def user_is_alumnus
Expand Down
19 changes: 0 additions & 19 deletions app/models/ability.rb

This file was deleted.

2 changes: 0 additions & 2 deletions app/models/ability/visitor.rb

This file was deleted.

2 changes: 1 addition & 1 deletion app/models/communication/website.rb
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ class Communication::Website < ApplicationRecord

def self.organized_for(user, language, limit: 6)
university = user.university
ability = ::Ability.for(user)
ability = ::User::Ability.for(user)
# Favorites first
favorites_ids = user.favorites.websites.pluck(:about_id)
websites = university.websites
Expand Down
3 changes: 2 additions & 1 deletion app/models/emergency_message.rb
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,8 @@ def to_s

def target
users = User.all
users = users.where(university_id: university_id) if university_id.present?
users = users.where(university_id: university_id) if university_id.present?
# TODO(multiroles) @pabois
users = users.where(role: role) if role.present?
# next lines are to prevent to send the message to multiple occurrences of the same email (as for server_admin!)
target_user_ids = users.select("DISTINCT ON (users.email) users.email, users.id").map(&:id)
Expand Down
1 change: 1 addition & 0 deletions app/models/user.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
# reset_password_token :string uniquely indexed
# role :integer default("visitor")
# second_factor_attempts_count :integer default(0)
# server_admin :boolean default(FALSE)
# session_token :string
# sign_in_count :integer default(0), not null
# totp_timestamp :datetime
Expand Down
36 changes: 36 additions & 0 deletions app/models/user/ability.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# frozen_string_literal: true

class User::Ability
include CanCan::Ability

attr_reader :user

def self.for(user)
user ||= User.new # guest user (not logged in)
new(user)
end

def initialize(user)
@user = user ||= User.new # guest user (not logged in)
if user.server_admin?
can :manage, :all
else
# Les rôles sont fusionnés du moins au plus privilégié (cf. User#ability_roles)
# pour que, en cas de conflit, la règle du rôle le plus puissant — déclarée
# en dernier — l'emporte (CanCanCan : last rule wins).
user.ability_roles.each do |role_name|
merge "User::Ability::#{role_name.classify}".constantize.new(user)
end
end
end

protected

# Ids des cibles attachées à un rôle donné de l'utilisateur.
# Chaque ability scope ses règles aux cibles de SON rôle (cf. User#scopes_for),
# ce qui permet d'être par exemple author du site A et website_manager du site B
# sans que les périmètres ne débordent l'un sur l'autre.
def scoped_ids(role_name)
@user.scopes_for(role_name).map(&:id)
end
end
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
class Ability::Admin < Ability
class User::Ability::Admin < User::Ability

def initialize(user)
super
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
class Ability::AlumniManager < Ability
class User::Ability::AlumniManager < User::Ability

def initialize(user)
super
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
class Ability::Author < Ability
class User::Ability::Author < User::Ability

def initialize(user)
super
Expand Down Expand Up @@ -27,6 +27,12 @@ def initialize(user)

protected

# Sites sur lesquels l'utilisateur est author. Surchargé en :contributor par
# Ability::Contributor, qui hérite des mêmes règles mais sur ses propres sites.
def managed_websites_ids
@managed_websites_ids ||= scoped_ids(:author)
end

def manage_records_based_on_abouts(record_class)
can :manage, record_class, university_id: @user.university_id, about_type: 'Communication::Website::Agenda::Event::Localization', about_id: managed_agenda_event_localization_ids
can :manage, record_class, university_id: @user.university_id, about_type: 'Communication::Website::Agenda::Exhibition::Localization', about_id: managed_agenda_exhibition_localization_ids
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
class Ability::Contributor < Ability::Author
class User::Ability::Contributor < User::Ability::Author

def initialize(user)
super
Expand All @@ -9,4 +9,12 @@ def initialize(user)
cannot :publish, Communication::Website::Jobboard::Job
end

protected

# Hérite des règles d'author, mais scopées sur les sites où l'utilisateur est
# contributor (l'override est résolu dynamiquement, y compris pendant `super`).
def managed_websites_ids
@managed_websites_ids ||= scoped_ids(:contributor)
end

end
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
class Ability::ProgramManager < Ability
class User::Ability::ProgramManager < User::Ability

def initialize(user)
super
Expand Down Expand Up @@ -96,7 +96,7 @@ def managed_post_category_localization_ids
end

def managed_programs_ids
@managed_programs_ids ||= @user.programs_to_manage.pluck(:id)
@managed_programs_ids ||= scoped_ids(:program_manager)
end

def managed_program_localization_ids
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
class Ability::ServerAdmin < Ability
class User::Ability::ServerAdmin < User::Ability

def initialize(user)
super
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
class Ability::Teacher < Ability
class User::Ability::Teacher < User::Ability

def initialize(user)
super
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
class Ability::WebsiteManager < Ability
class User::Ability::WebsiteManager < User::Ability

def initialize(user)
super
Expand Down Expand Up @@ -38,6 +38,11 @@ def initialize(user)

protected

# Sites dont l'utilisateur est responsable (rôle website_manager).
def managed_websites_ids
@managed_websites_ids ||= scoped_ids(:website_manager)
end

def managed_agenda_category_localization_ids
@managed_agenda_category_localization_ids ||= begin
Communication::Website::Agenda::Category::Localization
Expand Down
Loading
Loading