From fb1d9089c65612fc46c7538d8fcbeda431159488 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?S=C3=A9bastien=20Gaya?= <sebastien.gaya@gmail.com>
Date: Mon, 4 Sep 2023 15:31:47 +0200
Subject: [PATCH] invalidate access token

---
 app/mailers/notification_mailer.rb                |  9 +++++++++
 app/models/communication/website.rb               |  1 +
 .../communication/website/with_git_repository.rb  | 10 ++++++++++
 app/models/communication/website/with_managers.rb | 11 +++++++++++
 app/services/git/providers/abstract.rb            | 15 ++++++++-------
 app/services/git/providers/github.rb              | 11 +++++++++++
 app/services/git/providers/gitlab.rb              | 13 ++++++++++++-
 app/services/git/repository.rb                    |  5 +----
 .../website_invalid_access_token.html.erb         |  3 +++
 config/locales/en.yml                             |  8 ++++++--
 config/locales/fr.yml                             |  8 ++++++--
 11 files changed, 78 insertions(+), 16 deletions(-)
 create mode 100644 app/models/communication/website/with_managers.rb
 create mode 100644 app/views/mailers/notifications/website_invalid_access_token.html.erb

diff --git a/app/mailers/notification_mailer.rb b/app/mailers/notification_mailer.rb
index 26efb189a..5b4fded48 100644
--- a/app/mailers/notification_mailer.rb
+++ b/app/mailers/notification_mailer.rb
@@ -21,4 +21,13 @@ class NotificationMailer < ApplicationMailer
     mail(from: user.university.mail_from[:full], to: user.email, subject: subject)
   end
 
+  def website_invalid_access_token(website, user)
+    @website = website
+    @url = edit_admin_communication_website_path(website)
+    merge_with_university_infos(user.university, {})
+    I18n.locale = user.language.iso_code
+    subject = t('mailers.notifications.website_invalid_access_token.subject', website: website)
+    mail(from: user.university.mail_from[:full], to: user.email, subject: subject)
+  end
+
 end
diff --git a/app/models/communication/website.rb b/app/models/communication/website.rb
index c35d720f6..1ae31f1e2 100644
--- a/app/models/communication/website.rb
+++ b/app/models/communication/website.rb
@@ -47,6 +47,7 @@ class Communication::Website < ApplicationRecord
   include WithGitRepository
   include WithImport
   include WithLanguages
+  include WithManagers
   include WithProgramCategories
   include WithReferences
   include WithSpecialPages
diff --git a/app/models/communication/website/with_git_repository.rb b/app/models/communication/website/with_git_repository.rb
index 43a5d6c14..93abed016 100644
--- a/app/models/communication/website/with_git_repository.rb
+++ b/app/models/communication/website/with_git_repository.rb
@@ -38,6 +38,16 @@ module Communication::Website::WithGitRepository
   end
   handle_asynchronously :destroy_obsolete_git_files, queue: :default
 
+  def invalidate_access_token!
+    # Nullify the expired token
+    update_column :access_token, nil
+    # Notify admins and website managers managing this website.
+    users_to_notify = university.users.admin + university.users.website_manager.where(id: manager_ids)
+    users_to_notify.each do |user|
+      NotificationMailer.website_invalid_access_token(self, user).deliver_later
+    end
+  end
+
   # Le website devient data/website.yml
   # Les configs héritent du modèle website et s'exportent en différents fichiers
   def exportable_to_git?
diff --git a/app/models/communication/website/with_managers.rb b/app/models/communication/website/with_managers.rb
new file mode 100644
index 000000000..c98cf2c21
--- /dev/null
+++ b/app/models/communication/website/with_managers.rb
@@ -0,0 +1,11 @@
+module Communication::Website::WithManagers
+  extend ActiveSupport::Concern
+
+  included do
+    has_and_belongs_to_many :managers,
+                            class_name: 'User',
+                            join_table: :communication_websites_users,
+                            foreign_key: :communication_website_id,
+                            association_foreign_key: :user_id
+  end
+end
diff --git a/app/services/git/providers/abstract.rb b/app/services/git/providers/abstract.rb
index ceb8a3423..3debfe848 100644
--- a/app/services/git/providers/abstract.rb
+++ b/app/services/git/providers/abstract.rb
@@ -1,11 +1,12 @@
 class Git::Providers::Abstract
-  attr_reader :endpoint, :branch, :access_token, :repository
-
-  def initialize(endpoint, branch, access_token, repository)
-    @endpoint = endpoint
-    @branch = branch
-    @access_token = access_token
-    @repository = repository
+  attr_reader :git_repository; :endpoint, :branch, :access_token, :repository
+
+  def initialize(git_repository)
+    @git_repository = git_repository
+    @endpoint = git_repository.website.git_endpoint
+    @branch = git_repository.website.git_branch
+    @access_token = git_repository.website.access_token
+    @repository = git_repository.website.repository
   end
 
   def valid?
diff --git a/app/services/git/providers/github.rb b/app/services/git/providers/github.rb
index 58dae24af..882b936b0 100644
--- a/app/services/git/providers/github.rb
+++ b/app/services/git/providers/github.rb
@@ -86,6 +86,17 @@ class Git::Providers::Github < Git::Providers::Abstract
     nil
   end
 
+  def valid?
+    return false unless super
+    begin
+      client.repository(repository)
+      true
+    rescue Octokit::Unauthorized
+      git_repository.website.invalidate_access_token!
+      false
+    end
+  end
+
   protected
 
   def client
diff --git a/app/services/git/providers/gitlab.rb b/app/services/git/providers/gitlab.rb
index 39c928078..44cdfe59c 100644
--- a/app/services/git/providers/gitlab.rb
+++ b/app/services/git/providers/gitlab.rb
@@ -63,6 +63,17 @@ class Git::Providers::Gitlab < Git::Providers::Abstract
     sha
   end
 
+  def valid?
+    return false unless super
+    begin
+      client.project(repository)
+      true
+    rescue Gitlab::Error::Unauthorized
+      git_repository.website.invalidate_access_token!
+      false
+    end
+  end
+
   def branch
     super.present?  ? super
                     : 'main'
@@ -76,7 +87,7 @@ class Git::Providers::Gitlab < Git::Providers::Abstract
   end
 
   def client
-    @client ||= Gitlab.client(
+    @client ||= Gitlab.client(
       endpoint: endpoint,
       private_token: access_token
     )
diff --git a/app/services/git/repository.rb b/app/services/git/repository.rb
index 7863c6de0..9ba136cf2 100644
--- a/app/services/git/repository.rb
+++ b/app/services/git/repository.rb
@@ -48,10 +48,7 @@ class Git::Repository
   protected
 
   def provider
-    @provider ||= provider_class.new  website&.git_endpoint,
-                                      website&.git_branch,
-                                      website&.access_token,
-                                      website&.repository
+    @provider ||= provider_class.new self
   end
 
   def provider_class
diff --git a/app/views/mailers/notifications/website_invalid_access_token.html.erb b/app/views/mailers/notifications/website_invalid_access_token.html.erb
new file mode 100644
index 000000000..c4fdbbeb6
--- /dev/null
+++ b/app/views/mailers/notifications/website_invalid_access_token.html.erb
@@ -0,0 +1,3 @@
+<p><%= t('mailers.notifications.website_invalid_access_token.text_line_1_html', website: @website) %></p>
+<p><%= t('mailers.notifications.import.text_line_2_html', url: @url) %></p>
+<p><%= t('mailers.yours') %></p>
diff --git a/config/locales/en.yml b/config/locales/en.yml
index fc5648c67..2d621b809 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -141,7 +141,7 @@ en:
     successfully_quit_html: "<i>%{model}</i> successfully quit <i>%{target}</i>."
     successfully_removed_html: "<i>%{model}</i> was successfully removed."
     successfully_updated_html: "<i>%{model}</i> was successfully updated."
-    summary: 
+    summary:
       label: Summary
       hint: A short text, like a heading for an article. Don't write all the content here, there are blocks for that.
     users_alerts:
@@ -269,6 +269,10 @@ en:
         text_line_3_html: "Number of lines in the file: %{number}."
         text_error_msg: "Line %{line}: %{error}"
         text_errors_title: "Some errors have occured:"
+      website_invalid_access_token:
+        subject: "Expired access token for \"%{website}\""
+        text_line_1_html: "The access token used for the website \"%{website}\" has expired and does not allow the website to be updated anymore."
+        text_line_2_html: "To solve this issue, please fill in a new access token by clicking <a href=\"%{url}\">here</a>."
     yours: Yours.
   menu:
     admin: Admin
@@ -307,7 +311,7 @@ en:
       delivered: Your message has been sent
       filters: Filters
       target: Target
-      users: 
+      users:
         one: "%{count} user"
         other: "%{count} users"
     websites:
diff --git a/config/locales/fr.yml b/config/locales/fr.yml
index c7e4dc9de..7a7578d75 100644
--- a/config/locales/fr.yml
+++ b/config/locales/fr.yml
@@ -141,7 +141,7 @@ fr:
     successfully_quit_html: "<i>%{model}</i> a bien quitté <i>%{target}</i>."
     successfully_removed_html: "<i>%{model}</i> a bien été retiré(e)."
     successfully_updated_html: "<i>%{model}</i> a bien été mis(e) à jour."
-    summary: 
+    summary:
       label: Résumé
       hint: Un texte court, comme un chapô pour un article. Ne mettez pas tout le contenu ici, pour ça, il y a les blocs !
     users_alerts:
@@ -269,6 +269,10 @@ fr:
         text_line_3_html: "Nombre de lignes traitées : %{number}."
         text_error_msg: "Ligne %{line} : %{error}"
         text_errors_title: "Des erreurs sont survenues :"
+      website_invalid_access_token:
+        subject: Jeton d'accès expiré pour « %{website} »
+        text_line_1_html: Le jeton d'accès utilisé pour le site « %{website} » a expiré et ne permet plus la mise à jour du site.
+        text_line_2_html: Pour résoudre ce problème, veuillez renseigner un nouveau jeton d'accès en cliquant <a href=\"%{url}\">ici</a>.
     yours: Cordialement.
   menu:
     admin: Admin
@@ -307,7 +311,7 @@ fr:
       delivered: Votre message a bien été envoyé
       filters: Filtres
       target: Cible
-      users: 
+      users:
         one: "%{count} utilisateur"
         other: "%{count} utilisateurs"
     websites:
-- 
GitLab