diff --git a/app/controllers/admin/education/school/role/people_controller.rb b/app/controllers/admin/education/school/role/people_controller.rb
new file mode 100644
index 0000000000000000000000000000000000000000..d1929ee372dcd7a4edfcf5fac4ed3b470479cb7c
--- /dev/null
+++ b/app/controllers/admin/education/school/role/people_controller.rb
@@ -0,0 +1,56 @@
+class Admin::Education::School::Role::PeopleController < Admin::Education::School::ApplicationController
+  load_and_authorize_resource :role, class: University::Role, through: :school, param: :role_id, through_association: :university_roles
+  load_and_authorize_resource :involvement, class: University::Person::Involvement, through: :role, parent: false
+
+  def new
+    breadcrumb
+  end
+
+  def edit
+    breadcrumb
+    add_breadcrumb t('edit')
+  end
+
+  def create
+    if @involvement.save
+      redirect_to admin_education_school_role_path(@role, { school_id: @school.id }), notice: t('admin.successfully_created_html', model: @involvement.to_s)
+    else
+      breadcrumb
+      render :new, status: :unprocessable_entity
+    end
+  end
+
+  def update
+    if @involvement.update(involvement_params)
+      redirect_to admin_education_school_role_path(@role, { school_id: @school.id }), notice: t('admin.successfully_updated_html', model: @involvement.to_s)
+    else
+      breadcrumb
+      render :edit, status: :unprocessable_entity
+      add_breadcrumb t('edit')
+    end
+  end
+
+  def destroy
+    @involvement.destroy
+    redirect_to admin_education_school_role_path(@role, { school_id: @school.id }), notice: t('admin.successfully_destroyed_html', model: @involvement.to_s)
+  end
+
+  protected
+
+  def breadcrumb
+    super
+    add_breadcrumb University::Role.model_name.human(count: 2), admin_education_school_roles_path(@school)
+    @role.persisted?  ? add_breadcrumb(@role, admin_education_school_role_path(@role, { school_id: @school.id }))
+                      : add_breadcrumb(t('create'))
+    if @involvement
+      @involvement.persisted?  ? add_breadcrumb(@involvement, admin_education_school_role_person_path(@involvement, { school_id: @school.id, role_id: @role.id }))
+                               : add_breadcrumb(t('create'))
+    end
+  end
+
+  def involvement_params
+    params.require(:university_person_involvement)
+          .permit(:description, :position, :person_id)
+          .merge(university_id: @school.university_id)
+  end
+end
diff --git a/app/controllers/admin/education/school/roles_controller.rb b/app/controllers/admin/education/school/roles_controller.rb
new file mode 100644
index 0000000000000000000000000000000000000000..f795f26cc454e78a325ecb2ad2f2fbd36a58dedf
--- /dev/null
+++ b/app/controllers/admin/education/school/roles_controller.rb
@@ -0,0 +1,62 @@
+class Admin::Education::School::RolesController < Admin::Education::School::ApplicationController
+  load_and_authorize_resource class: University::Role, through: :school, through_association: :university_roles
+
+  def index
+    breadcrumb
+  end
+
+  def show
+    @involvements = @role.involvements.ordered
+    breadcrumb
+  end
+
+  def new
+    breadcrumb
+  end
+
+  def edit
+    breadcrumb
+    add_breadcrumb t('edit')
+  end
+
+  def create
+    if @role.save
+      redirect_to admin_education_school_role_path(@role), notice: t('admin.successfully_created_html', model: @role.to_s)
+    else
+      breadcrumb
+      render :new, status: :unprocessable_entity
+    end
+  end
+
+  def update
+    if @role.update(role_params)
+      redirect_to admin_education_school_role_path(@role), notice: t('admin.successfully_updated_html', model: @role.to_s)
+    else
+      breadcrumb
+      render :edit, status: :unprocessable_entity
+      add_breadcrumb t('edit')
+    end
+  end
+
+  def destroy
+    @role.destroy
+    redirect_to admin_education_school_role_path(@role), notice: t('admin.successfully_destroyed_html', model: @role.to_s)
+  end
+
+  protected
+
+  def breadcrumb
+    super
+    add_breadcrumb University::Role.model_name.human(count: 2), admin_education_school_roles_path(@school)
+    if @role
+      @role.persisted?  ? add_breadcrumb(@role, admin_education_school_role_path(@role, { school_id: @school.id }))
+                        : add_breadcrumb(t('create'))
+    end
+  end
+
+  def role_params
+    params.require(:university_role)
+          .permit(:description, :position)
+          .merge(target: @school, university_id: @school.university_id)
+  end
+end
diff --git a/app/controllers/admin/education/schools_controller.rb b/app/controllers/admin/education/schools_controller.rb
index e1368257c8d1520570065c6c0a9817a75a720f91..7152bbd2748ae5a6622823a90566cf1fea61c082 100644
--- a/app/controllers/admin/education/schools_controller.rb
+++ b/app/controllers/admin/education/schools_controller.rb
@@ -8,6 +8,7 @@ class Admin::Education::SchoolsController < Admin::Education::ApplicationControl
   end
 
   def show
+    @roles = @school.university_roles.ordered
     breadcrumb
   end
 
diff --git a/app/models/ability.rb b/app/models/ability.rb
index 905dd2f6a0db3038439066dcc1c2fec1f76f2273..a193260c541486260a98a86d914a28d86f99d151 100644
--- a/app/models/ability.rb
+++ b/app/models/ability.rb
@@ -36,6 +36,8 @@ class Ability
     can :manage, Education::Program::Teacher, person_id: @user.person&.id
     can :read, Education::Program::Role, university_id: @user.university_id
     can :manage, Education::Program::Role::Person, person_id: @user.person&.id
+    can :read, University::Role, university_id: @user.university_id
+    can :manage, University::Person::Involvement, person_id: @user.person&.id
   end
 
   def program_manager
@@ -66,6 +68,8 @@ class Ability
     can :manage, Research::Journal::Article, university_id: @user.university_id
     can :manage, Research::Journal::Volume, university_id: @user.university_id
     can :manage, Research::Laboratory, university_id: @user.university_id
+    can :manage, University::Role, university_id: @user.university_id
+    can :manage, University::Person::Involvement, university_id: @user.university_id
     can :read, User, university_id: @user.university_id
     can :manage, User, university_id: @user.university_id, role: @user.managed_roles
   end
diff --git a/app/models/education/school.rb b/app/models/education/school.rb
index 6f79e2f3beb978ade47e0fe8d554ec8fcb5824c6..c62ab123bddf850dd8e24b5e55e2ca4a39f767fb 100644
--- a/app/models/education/school.rb
+++ b/app/models/education/school.rb
@@ -32,6 +32,10 @@ class Education::School < ApplicationRecord
   has_many  :university_people_through_administrators,
             through: :administrators,
             source: :person
+  has_many  :university_roles, class_name: 'University::Role', as: :target, dependent: :destroy
+  has_many  :university_people_through_roles,
+            through: :university_roles,
+            source: :person
   has_and_belongs_to_many :programs,
                           class_name: 'Education::Program',
                           join_table: 'education_programs_schools',
diff --git a/app/models/university/person/involvement.rb b/app/models/university/person/involvement.rb
index 83c1b444a5dc914519773f8f35a3c593cf96ea18..d555cccbc012d2f623fa4e27be461cfd32c7dd47 100644
--- a/app/models/university/person/involvement.rb
+++ b/app/models/university/person/involvement.rb
@@ -27,15 +27,25 @@
 class University::Person::Involvement < ApplicationRecord
   include WithPosition
 
+  enum kind: { administrator: 10, researcher: 20, teacher: 30 }
+
   belongs_to :university
   belongs_to :person
   belongs_to :target, polymorphic: true
 
-  enum kind: { administrator: 10, researcher: 20, teacher: 30 }
+  after_commit :sync_target
+
+  def to_s
+    "#{person}"
+  end
 
   protected
 
   def last_ordered_element
     self.class.unscoped.where(university_id: university_id, target: target).ordered.last
   end
+
+  def sync_target
+    target.sync_with_git
+  end
 end
diff --git a/app/models/university/role.rb b/app/models/university/role.rb
index d63eb7041925492af796c72c6b8585b4f7140ce6..2e4987077a6a12d1636514fb28a83ea95a7fdd3b 100644
--- a/app/models/university/role.rb
+++ b/app/models/university/role.rb
@@ -25,6 +25,15 @@ class University::Role < ApplicationRecord
 
   belongs_to :university
   belongs_to :target, polymorphic: true, optional: true
+  has_many :involvements, class_name: 'University::Person::Involvement', as: :target
+
+  def to_s
+    "#{description}"
+  end
+
+  def sync_with_git
+    target.sync_with_git
+  end
 
   protected
 
diff --git a/app/views/admin/education/school/role/people/_form.html.erb b/app/views/admin/education/school/role/people/_form.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..81fb739c7ed7bc375864537be8b2f930b576a61a
--- /dev/null
+++ b/app/views/admin/education/school/role/people/_form.html.erb
@@ -0,0 +1,23 @@
+<%= simple_form_for [:admin, involvement],
+                    url: involvement.new_record? ? admin_education_school_role_people_path(@role, { school_id: @school.id })
+                                                 : admin_education_school_role_person_path(involvement, { school_id: @school.id, role_id: @role.id }) do |f| %>
+  <div class="card flex-fill w-100">
+    <div class="card-header">
+      <h5 class="card-title mb-0"><%= t('admin.infos') %></h5>
+    </div>
+    <div class="card-body">
+      <div class="row">
+        <div class="col-md-6">
+          <% used_person_ids = @role.involvements.where.not(id: involvement.id).pluck(:person_id) %>
+          <%= f.association :person, collection: current_university.people.administration.where.not(id: used_person_ids).ordered %>
+        </div>
+        <div class="col-md-6">
+          <%= f.input :description %>
+        </div>
+      </div>
+    </div>
+  </div>
+  <% content_for :action_bar_right do %>
+    <%= submit f %>
+  <% end %>
+<% end %>
diff --git a/app/views/admin/education/school/role/people/_list.html.erb b/app/views/admin/education/school/role/people/_list.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..5f10eb42cbab4105f2846d69ae881574bedaba5a
--- /dev/null
+++ b/app/views/admin/education/school/role/people/_list.html.erb
@@ -0,0 +1,33 @@
+<% if involvements.any? %>
+  <table class="table table-sortable">
+    <thead>
+      <tr>
+        <th class="ps-0"><%= University::Person.model_name.human %></th>
+        <th><%= University::Person::Involvement.human_attribute_name("description") %></th>
+        <th></th>
+      </tr>
+    </thead>
+    <tbody data-reorder-url="<%= reorder_admin_education_school_role_people_path(@role, { school_id: @school.id }) %>">
+      <% involvements.each do |involvement| %>
+        <tr class="handle" data-id="<%= involvement.id %>">
+          <td class="ps-0">
+            <%= involvement %>
+          </td>
+          <td><%= involvement.description %></td>
+          <td class="text-end pe-0">
+            <div class="btn-group" role="group">
+              <%= link_to t('edit'),
+                          edit_admin_education_school_role_person_path(involvement, { school_id: @school.id, role_id: @role.id }),
+                          class: button_classes if can?(:edit, involvement) %>
+              <%= link_to t('delete'),
+                          admin_education_school_role_person_path(involvement, { school_id: @school.id, role_id: @role.id }),
+                          method: :delete,
+                          data: { confirm: t('please_confirm') },
+                          class: button_classes_danger if can?(:destroy, involvement) %>
+            </div>
+          </td>
+        </tr>
+      <% end %>
+    </tbody>
+  </table>
+<% end %>
diff --git a/app/views/admin/education/school/role/people/edit.html.erb b/app/views/admin/education/school/role/people/edit.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..da924cd052e14cfa55c1f75b9fe56ee8d15a1b8a
--- /dev/null
+++ b/app/views/admin/education/school/role/people/edit.html.erb
@@ -0,0 +1,3 @@
+<% content_for :title, @involvement %>
+
+<%= render 'form', involvement: @involvement %>
diff --git a/app/views/admin/education/school/role/people/new.html.erb b/app/views/admin/education/school/role/people/new.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..f07ff39c18ffb7d9e598688bb41a9361413d804f
--- /dev/null
+++ b/app/views/admin/education/school/role/people/new.html.erb
@@ -0,0 +1,3 @@
+<% content_for :title, University::Person.model_name.human %>
+
+<%= render 'form', involvement: @involvement %>
diff --git a/app/views/admin/education/school/roles/_form.html.erb b/app/views/admin/education/school/roles/_form.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..f09aa1ee30ec84c1949af714a645c5764944507e
--- /dev/null
+++ b/app/views/admin/education/school/roles/_form.html.erb
@@ -0,0 +1,17 @@
+<%= simple_form_for [:admin, role],
+                    url: role.new_record? ? admin_education_school_roles_path(@school)
+                                          : admin_education_school_role_path(role, { school_id: @school.id }) do |f| %>
+  <div class="card flex-fill w-100">
+    <div class="card-header">
+      <h5 class="card-title mb-0"><%= t('admin.infos') %></h5>
+    </div>
+    <div class="card-body">
+      <div class="row">
+        <%= f.input :description %>
+      </div>
+    </div>
+  </div>
+  <% content_for :action_bar_right do %>
+    <%= submit f %>
+  <% end %>
+<% end %>
diff --git a/app/views/admin/education/school/roles/_list.html.erb b/app/views/admin/education/school/roles/_list.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..084f107a7b852baeb73d305aa13de692e8eb4f04
--- /dev/null
+++ b/app/views/admin/education/school/roles/_list.html.erb
@@ -0,0 +1,35 @@
+<% if roles.any? %>
+  <table class="table table-sortable">
+    <thead>
+      <tr>
+        <th class="ps-0"><%= University::Role.model_name.human %></th>
+        <th><%= University::Role.human_attribute_name('people') %></th>
+        <th></th>
+      </tr>
+    </thead>
+    <tbody data-reorder-url="<%= reorder_admin_education_school_roles_path(school_id: @school.id) %>">
+      <% roles.each do |role| %>
+        <tr class="handle" data-id="<%= role.id %>">
+          <td class="ps-0">
+            <%= link_to_if  can?(:read, role),
+                            role,
+                            admin_education_school_role_path(role, { school_id: @school.id }) %>
+          </td>
+          <td><%= role.involvements.includes(:person).ordered.map { |involvement| involvement.person.to_s }.to_sentence %></td>
+          <td class="text-end pe-0">
+            <div class="btn-group" role="group">
+              <%= link_to t('edit'),
+                          edit_admin_education_school_role_path(role, { school_id: @school.id }),
+                          class: button_classes if can?(:edit, role) %>
+              <%= link_to t('delete'),
+                          admin_education_school_role_path(role, { school_id: @school.id }),
+                          method: :delete,
+                          data: { confirm: t('please_confirm') },
+                          class: button_classes_danger if can?(:destroy, role) %>
+            </div>
+          </td>
+        </tr>
+      <% end %>
+    </tbody>
+  </table>
+<% end %>
diff --git a/app/views/admin/education/school/roles/edit.html.erb b/app/views/admin/education/school/roles/edit.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..ff1ec73bd6f1465a96497606080d481b6fd6ca51
--- /dev/null
+++ b/app/views/admin/education/school/roles/edit.html.erb
@@ -0,0 +1,3 @@
+<% content_for :title, @role %>
+
+<%= render 'form', role: @role %>
diff --git a/app/views/admin/education/school/roles/index.html.erb b/app/views/admin/education/school/roles/index.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..d8dda13f8f93992acd13d167d6f47bc7f15b2b94
--- /dev/null
+++ b/app/views/admin/education/school/roles/index.html.erb
@@ -0,0 +1,3 @@
+<% content_for :title, University::Role.model_name.human(count: 2) %>
+<%= link_to t('create'), new_admin_education_school_role_path(school_id: @school.id), class: button_classes %>
+<%= render 'admin/education/school/roles/list', roles: @roles %>
diff --git a/app/views/admin/education/school/roles/new.html.erb b/app/views/admin/education/school/roles/new.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..1e11d9157b4247984613930c39a66a5f5287ec23
--- /dev/null
+++ b/app/views/admin/education/school/roles/new.html.erb
@@ -0,0 +1,3 @@
+<% content_for :title, University::Role.model_name.human %>
+
+<%= render 'form', role: @role %>
diff --git a/app/views/admin/education/school/roles/show.html.erb b/app/views/admin/education/school/roles/show.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..f4c18babfa4490bf24910f2b1bae4923db9ca539
--- /dev/null
+++ b/app/views/admin/education/school/roles/show.html.erb
@@ -0,0 +1,4 @@
+<% content_for :title, @role %>
+
+<%= link_to t('create'), new_admin_education_school_role_person_path(school_id: @school.id, role_id: @role.id), class: button_classes %>
+<%= render 'admin/education/school/role/people/list', involvements: @involvements %>
diff --git a/app/views/admin/education/schools/show.html.erb b/app/views/admin/education/schools/show.html.erb
index 5367e4558ab1221f8c894ae179fcc1a437f85a94..bb527954dd006e6011623b753ae77b0737abe5cb 100644
--- a/app/views/admin/education/schools/show.html.erb
+++ b/app/views/admin/education/schools/show.html.erb
@@ -58,11 +58,36 @@
 
 <div class="card flex-fill w-100">
   <div class="card-header">
-    <h2 class="card-title mb-0 h5"><%= Education::School.human_attribute_name('administrators') %></h2>
+    <div class="float-end">
+      <%= link_to "Gérer les rôles",
+                  admin_education_school_roles_path(school_id: @school.id),
+                  class: button_classes if can?(:update, University::Role) %>
+    </div>
+    <h2 class="card-title mb-0 h5">Rôles</h2>
   </div>
   <div class="card-body">
-    <%= render 'admin/education/school/administrators/list', administrators: @school.administrators.includes(:person).ordered %>
-    <%= link_to t('create'), new_admin_education_school_administrator_path(school_id: @school.id), class: button_classes %>
+    <% if @roles.any? %>
+      <table class="table">
+        <thead>
+          <tr>
+            <th class="ps-0"><%= University::Role.model_name.human %></th>
+            <th><%= University::Role.human_attribute_name('people') %></th>
+          </tr>
+        </thead>
+        <tbody>
+          <% @roles.each do |role| %>
+            <tr>
+              <td class="ps-0">
+                <%= link_to_if  can?(:read, role),
+                                role,
+                                admin_education_school_role_path(role, { school_id: @school.id }) %>
+              </td>
+              <td><%= role.involvements.includes(:person).ordered.map { |involvement| involvement.person.to_s }.to_sentence %></td>
+            </tr>
+          <% end %>
+        </tbody>
+      </table>
+    <% end %>
   </div>
 </div>
 
diff --git a/config/locales/university/en.yml b/config/locales/university/en.yml
index 8d690dbb687aa5dd1691327ed3ba5c9e9de97061..6d8df213d4338944fbe5161cd3b46ed5c0f5945b 100644
--- a/config/locales/university/en.yml
+++ b/config/locales/university/en.yml
@@ -42,6 +42,12 @@ en:
       university/person:
         one: Person
         other: People
+      university/person/involvement:
+        one: Involvement
+        other: Involvements
+      university/role:
+        one: Role
+        other: Roles
   simple_form:
     hints:
       university:
diff --git a/config/locales/university/fr.yml b/config/locales/university/fr.yml
index d40b74e837c1e021fdc7e0d26fce0c6da9046248..a47bf3c9b2fb881b33ce7f26f90d6118d379cd50 100644
--- a/config/locales/university/fr.yml
+++ b/config/locales/university/fr.yml
@@ -42,6 +42,12 @@ fr:
       university/person:
         one: Personne
         other: Personnes
+      university/person/involvement:
+        one: Involvement
+        other: Involvements
+      university/role:
+        one: Rôle
+        other: Rôles
   simple_form:
     hints:
       university:
diff --git a/config/routes/admin/education.rb b/config/routes/admin/education.rb
index c3ac5899ec1bdaae385534f3d240cf8716955689..125c44a9e158ce0a19657879bc68b3146097921b 100644
--- a/config/routes/admin/education.rb
+++ b/config/routes/admin/education.rb
@@ -1,6 +1,16 @@
 namespace :education do
   resources :teachers, only: [:index, :show]
   resources :schools do
+    resources :roles, controller: 'school/roles' do
+      resources :people, controller: 'school/role/people', except: [:index, :show] do
+        collection do
+          post :reorder
+        end
+      end
+      collection do
+        post :reorder
+      end
+    end
     resources :administrators, controller: 'school/administrators', except: [:index, :show]
   end
   resources :programs do
diff --git a/lib/tasks/app.rake b/lib/tasks/app.rake
index 0e862f5d679e6e5f6ed78b930a645e03915d1316..4899fc6617b6d82b6d9d51fe5bc970b0181ec9dd 100644
--- a/lib/tasks/app.rake
+++ b/lib/tasks/app.rake
@@ -53,7 +53,7 @@ namespace :app do
 
     Education::Program::Role.find_each { |program_role|
       university_role = University::Role.where(
-        description: program_role.description,
+        description: program_role.title,
         target: program_role.program,
         position: program_role.position,
         university_id: program_role.university_id