|
| 1 | +#-- copyright |
| 2 | +# OpenProject is an open source project management software. |
| 3 | +# Copyright (C) 2012-2022 the OpenProject GmbH |
| 4 | +# |
| 5 | +# This program is free software; you can redistribute it and/or |
| 6 | +# modify it under the terms of the GNU General Public License version 3. |
| 7 | +# |
| 8 | +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: |
| 9 | +# Copyright (C) 2006-2013 Jean-Philippe Lang |
| 10 | +# Copyright (C) 2010-2013 the ChiliProject Team |
| 11 | +# |
| 12 | +# This program is free software; you can redistribute it and/or |
| 13 | +# modify it under the terms of the GNU General Public License |
| 14 | +# as published by the Free Software Foundation; either version 2 |
| 15 | +# of the License, or (at your option) any later version. |
| 16 | +# |
| 17 | +# This program is distributed in the hope that it will be useful, |
| 18 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 19 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 20 | +# GNU General Public License for more details. |
| 21 | +# |
| 22 | +# You should have received a copy of the GNU General Public License |
| 23 | +# along with this program; if not, write to the Free Software |
| 24 | +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
| 25 | +# |
| 26 | +# See COPYRIGHT and LICENSE files for more details. |
| 27 | +#++ |
| 28 | + |
| 29 | +module Groups |
| 30 | + # Adds inherited roles to the users provided to mirror the roles the group has. |
| 31 | + # This can be scoped to only a certain project which results in considerably better performance. |
| 32 | + class CreateInheritedRolesService < ::BaseServices::BaseContracted |
| 33 | + using CoreExtensions::SquishSql |
| 34 | + include Groups::Concerns::MembershipManipulation |
| 35 | + |
| 36 | + def initialize(group, current_user:, contract_class: AdminOnlyContract) |
| 37 | + self.model = group |
| 38 | + |
| 39 | + super user: current_user, |
| 40 | + contract_class: |
| 41 | + end |
| 42 | + |
| 43 | + private |
| 44 | + |
| 45 | + def modify_members_and_roles(params) |
| 46 | + sql_query = ::OpenProject::SqlSanitization |
| 47 | + .sanitize add_to_user_and_projects_cte(project_ids: params[:project_ids]), |
| 48 | + group_id: model.id, |
| 49 | + user_ids: params[:user_ids], |
| 50 | + project_ids: params[:project_ids] |
| 51 | + |
| 52 | + execute_query(sql_query) |
| 53 | + end |
| 54 | + |
| 55 | + def add_to_user_and_projects_cte(project_ids: nil) |
| 56 | + project_limit = if project_ids |
| 57 | + "project_id IN (:project_ids)" |
| 58 | + else |
| 59 | + "1=1" |
| 60 | + end |
| 61 | + |
| 62 | + <<~SQL.squish |
| 63 | + -- select existing users from given IDs |
| 64 | + WITH found_users AS ( |
| 65 | + SELECT id as user_id FROM #{User.table_name} WHERE id IN (:user_ids) |
| 66 | + ), |
| 67 | + timestamp AS ( |
| 68 | + SELECT CURRENT_TIMESTAMP as time |
| 69 | + ), |
| 70 | + -- select existing memberships of the group |
| 71 | + group_memberships AS ( |
| 72 | + SELECT project_id, user_id FROM #{Member.table_name} WHERE user_id = :group_id AND #{project_limit} |
| 73 | + ), |
| 74 | + -- select existing member_roles of the group |
| 75 | + group_roles AS ( |
| 76 | + SELECT members.project_id AS project_id, |
| 77 | + members.user_id AS user_id, |
| 78 | + members.id AS member_id, |
| 79 | + member_roles.role_id AS role_id, |
| 80 | + member_roles.id AS member_role_id |
| 81 | + FROM #{MemberRole.table_name} member_roles |
| 82 | + JOIN #{Member.table_name} members |
| 83 | + ON members.id = member_roles.member_id AND members.user_id = :group_id |
| 84 | + ), |
| 85 | + -- find members that already exist |
| 86 | + existing_members AS ( |
| 87 | + SELECT members.id, found_users.user_id, members.project_id |
| 88 | + FROM members, found_users, group_memberships |
| 89 | + WHERE members.user_id = found_users.user_id |
| 90 | + AND members.project_id IS NOT DISTINCT FROM group_memberships.project_id |
| 91 | + AND members.id IS NOT NULL |
| 92 | + ), |
| 93 | + -- insert the group user into members |
| 94 | + new_members AS ( |
| 95 | + INSERT INTO #{Member.table_name} (project_id, user_id, updated_at, created_at) |
| 96 | + SELECT group_memberships.project_id, found_users.user_id, (SELECT time from timestamp), (SELECT time from timestamp) |
| 97 | + FROM found_users, group_memberships |
| 98 | + WHERE NOT EXISTS (SELECT 1 FROM existing_members WHERE existing_members.user_id = found_users.user_id AND existing_members.project_id IS NOT DISTINCT FROM group_memberships.project_id) |
| 99 | + ON CONFLICT(project_id, user_id) DO NOTHING |
| 100 | + RETURNING id, user_id, project_id |
| 101 | + ), |
| 102 | + -- copy the member roles of the group |
| 103 | + add_roles AS ( |
| 104 | + INSERT INTO #{MemberRole.table_name} (member_id, role_id, inherited_from) |
| 105 | + SELECT members.id, group_roles.role_id, group_roles.member_role_id |
| 106 | + FROM group_roles |
| 107 | + JOIN |
| 108 | + (SELECT * FROM new_members UNION SELECT * from existing_members) members ON group_roles.project_id IS NOT DISTINCT FROM members.project_id |
| 109 | + -- Ignore if the role was already inserted by us |
| 110 | + ON CONFLICT DO NOTHING |
| 111 | + RETURNING id, member_id, role_id |
| 112 | + ), |
| 113 | + -- get the ids of members where roles have been added the member did not have before |
| 114 | + members_with_added_roles AS ( |
| 115 | + SELECT DISTINCT add_roles.member_id |
| 116 | + FROM add_roles |
| 117 | + WHERE NOT EXISTS |
| 118 | + (SELECT 1 FROM #{MemberRole.table_name} |
| 119 | + WHERE #{MemberRole.table_name}.member_id = add_roles.member_id |
| 120 | + AND #{MemberRole.table_name}.role_id = add_roles.role_id |
| 121 | + AND #{MemberRole.table_name}.id != add_roles.id) |
| 122 | + ), |
| 123 | + touch_existing_members AS ( |
| 124 | + UPDATE members SET updated_at = CURRENT_TIMESTAMP |
| 125 | + WHERE id IN (SELECT id from existing_members) |
| 126 | + AND id IN (SELECT member_id from members_with_added_roles) |
| 127 | + ) |
| 128 | +
|
| 129 | + SELECT member_id from members_with_added_roles |
| 130 | + SQL |
| 131 | + end |
| 132 | + |
| 133 | + def touch_updated(member_ids) |
| 134 | + # do nothing in this case as we already touch while updating |
| 135 | + end |
| 136 | + end |
| 137 | +end |
0 commit comments