diff --git a/app/mailers/notifications_mailer.rb b/app/mailers/notifications_mailer.rb index 814d526..31a4677 100644 --- a/app/mailers/notifications_mailer.rb +++ b/app/mailers/notifications_mailer.rb @@ -19,4 +19,16 @@ class NotificationsMailer < ApplicationMailer subject = t(".subject", domain: @check.domain) mail subject: subject end + + def ssl_expires_soon + @expire_in_days = Integer(@check.domain_expires_at.to_date - Date.today) + + subject = t(".subject", domain: @check.domain, count: @expire_in_days) + mail subject: subject + end + + def ssl_recurrent_failures + subject = t(".subject", domain: @check.domain) + mail subject: subject + end end diff --git a/app/services/notifier/channels/base.rb b/app/services/notifier/channels/base.rb index b63a52d..a3e9586 100644 --- a/app/services/notifier/channels/base.rb +++ b/app/services/notifier/channels/base.rb @@ -11,6 +11,10 @@ module Notifier domain_notify_expires_soon(notification) when [:domain, :recurrent_failures] domain_notify_recurrent_failures(notification) + when [:ssl, :expires_soon] + ssl_notify_expires_soon(notification) + when [:ssl, :recurrent_failures] + ssl_notify_recurrent_failures(notification) else fail ArgumentError, "Invalid notification reason `#{reason}` for check kind `#{notification.check.kind}`." @@ -25,15 +29,16 @@ module Notifier "#{self.class.name} channel did not implemented method #{__callee__}" end - # domain notifications - def domain_notify_expires_soon(_notification) - fail NotImplementedError, - "Channel #{self.class.name} does not implement #{__callee__}" - end - - def domain_notify_recurrent_failures(_notification) - fail NotImplementedError, - "Channel #{self.class.name} does not implement #{__callee__}" + %i[ + domain_notify_expires_soon + domain_notify_recurrent_failures + ssl_notify_expires_soon + ssl_notify_recurrent_failures + ].each do |method| + define_method(method) do + fail NotImplementedError, + "Channel #{self.class.name} does not implement method #{method}." + end end # :nocov: end diff --git a/app/services/notifier/channels/email.rb b/app/services/notifier/channels/email.rb index 17bbdab..9243b7d 100644 --- a/app/services/notifier/channels/email.rb +++ b/app/services/notifier/channels/email.rb @@ -16,6 +16,14 @@ module Notifier def domain_notify_recurrent_failures(notification) NotificationsMailer.with(notification: notification).domain_recurrent_failures.deliver_now end + + def ssl_notify_expires_soon(notification) + NotificationsMailer.with(notification: notification).ssl_expires_soon.deliver_now + end + + def ssl_notify_recurrent_failures(notification) + NotificationsMailer.with(notification: notification).ssl_recurrent_failures.deliver_now + end end end end diff --git a/app/views/notifications_mailer/ssl_expires_soon.en.html.erb b/app/views/notifications_mailer/ssl_expires_soon.en.html.erb new file mode 100644 index 0000000..401df10 --- /dev/null +++ b/app/views/notifications_mailer/ssl_expires_soon.en.html.erb @@ -0,0 +1,12 @@ +

+ Hi, +
+
+ the SSL certificate for <%= @check.domain %> will expire + <%= format_utc(@check.domain_expires_at) %>. +

+
+ +<%= render "check_comment_vendor" %> + +<%= render "footer_expires_soon", interval: @notification.interval, check: @check %> diff --git a/app/views/notifications_mailer/ssl_expires_soon.en.text.erb b/app/views/notifications_mailer/ssl_expires_soon.en.text.erb new file mode 100644 index 0000000..a1d1c56 --- /dev/null +++ b/app/views/notifications_mailer/ssl_expires_soon.en.text.erb @@ -0,0 +1,9 @@ +Hi, + +the SSL certificate for <%= @check.domain %> will expire <%= format_utc(@check.domain_expires_at) %>. + + +<%= render "check_comment_vendor" %> + + +<%= render "footer_expires_soon", interval: @notification.interval, check: @check %> diff --git a/app/views/notifications_mailer/ssl_expires_soon.fr.html.erb b/app/views/notifications_mailer/ssl_expires_soon.fr.html.erb new file mode 100644 index 0000000..dfc1c3f --- /dev/null +++ b/app/views/notifications_mailer/ssl_expires_soon.fr.html.erb @@ -0,0 +1,12 @@ +

+ Salut, +
+
+ le certificat SSL pour <%= @check.domain %> va expirer le + <%= format_utc(@check.domain_expires_at) %>. +

+
+ +<%= render "check_comment_vendor" %> + +<%= render "footer_expires_soon", interval: @notification.interval, check: @check %> diff --git a/app/views/notifications_mailer/ssl_expires_soon.fr.text.erb b/app/views/notifications_mailer/ssl_expires_soon.fr.text.erb new file mode 100644 index 0000000..0664fb5 --- /dev/null +++ b/app/views/notifications_mailer/ssl_expires_soon.fr.text.erb @@ -0,0 +1,9 @@ +Salut, + +le certificat SSL pour <%= @check.domain %> va expirer le <%= format_utc(@check.domain_expires_at) %>. + + +<%= render "check_comment_vendor" %> + + +<%= render "footer_expires_soon", interval: @notification.interval, check: @check %> diff --git a/app/views/notifications_mailer/ssl_recurrent_failures.en.html.erb b/app/views/notifications_mailer/ssl_recurrent_failures.en.html.erb new file mode 100644 index 0000000..602c32d --- /dev/null +++ b/app/views/notifications_mailer/ssl_recurrent_failures.en.html.erb @@ -0,0 +1,31 @@ +

+ Hi, +
+
+ + We had recurrent failures while checking the SSL certificate for + <%= @check.domain %>. As of today, we can no longer verify the certificate + expiry date. +

+ +

+ <%- if @check.domain_expires_at.present? %> + Our last known expiry date is <%= format_utc(@check.domain_expires_at) %>. +
+ <%- end %> + + <%- if @check.last_success_at.present? %> + Our last successful check occured <%= format_utc(@check.last_success_at) %>. + <%- end %> +

+ +

+ If there is no more SSL endpoint for this domain, please disable + or delete the check by following this link:

+ <%= link_to nil, edit_check_url(@check) %> +

+
+<%= render "check_comment_vendor" %> + + +<%= render "footer_recurrent_failures", interval: @notification.interval, check: @check %> diff --git a/app/views/notifications_mailer/ssl_recurrent_failures.en.text.erb b/app/views/notifications_mailer/ssl_recurrent_failures.en.text.erb new file mode 100644 index 0000000..2bb6dd3 --- /dev/null +++ b/app/views/notifications_mailer/ssl_recurrent_failures.en.text.erb @@ -0,0 +1,22 @@ +Hi, + +We had recurrent failures while checking the SSL certificate for +<%= @check.domain %>. As of today, we can no longer verify the certificate +expiry date. + +<%- if @check.domain_expires_at.present? %> +The last known expiry date is <%= format_utc(@check.domain_expires_at) %>. +<%- end %> + +<%- if @check.last_success_at.present? %> +The last successful check occured <%= format_utc(@check.last_success_at) %>. +<%- end %> + +If there is no more SSL endpoint for this domain, please disable +or delete the check by following this link: + +<%= edit_check_url(@check) %> + +<%= render "check_comment_vendor" %> + +<%= render "footer_recurrent_failures", interval: @notification.interval, check: @check %> diff --git a/app/views/notifications_mailer/ssl_recurrent_failures.fr.html.erb b/app/views/notifications_mailer/ssl_recurrent_failures.fr.html.erb new file mode 100644 index 0000000..ea05abc --- /dev/null +++ b/app/views/notifications_mailer/ssl_recurrent_failures.fr.html.erb @@ -0,0 +1,35 @@ +

+ Salut, +
+
+ + Nous avons rencontré de multiples erreurs + pendant l'exécution des vérifications du certificat SSL + du site <%= @check.domain %>. + Nous ne pouvons plus vérifier la date d'expiration du certificat + en nous connectant au site. +

+ +

+ <%- if @check.domain_expires_at.present? %> + La dernière date d'expiration connue est le <%= format_utc(@check.domain_expires_at) %>. +
+ <% end %> + + <%- if @check.last_success_at.present? %> + Notre dernière vérification réussie a eu lieu le <%= format_utc(@check.last_success_at) %>. + <% end %> +

+ +

+ S'il n'y a plus de terminaison SSL pour ce site ou s'il n'existe plus, + merci de désactiver la vérification associée, avec ce lien : +
+
+ <%= link_to nil, edit_check_url(@check) %> +

+
+<%= render "check_comment_vendor" %> + + +<%= render "footer_recurrent_failures", interval: @notification.interval, check: @check %> diff --git a/app/views/notifications_mailer/ssl_recurrent_failures.fr.text.erb b/app/views/notifications_mailer/ssl_recurrent_failures.fr.text.erb new file mode 100644 index 0000000..fe15787 --- /dev/null +++ b/app/views/notifications_mailer/ssl_recurrent_failures.fr.text.erb @@ -0,0 +1,22 @@ +Salut, + +Nous avons rencontré de multiples erreurs pendant l'exécution des vérifications +du certificat SSL pour le site <%= @check.domain %>. +Nous ne pouvons plus vérifier la date d'expiration du certificat +en nous connectant au site. + +<%- if @check.domain_expires_at.present? %> +La dernière date d'expiration connue est le <%= format_utc(@check.domain_expires_at) %>. +<% end %> + +<%- if @check.last_success_at.present? %> +Notre dernière vérification réussie a eu lieu le <%= format_utc(@check.last_success_at) %>. +<% end %> + +S'il n'y a plus de terminaison SSL pour ce site ou s'il n'existe plus, +merci de désactiver la vérification associée, avec ce lien : +<%= edit_check_url(@check) %> + +<%= render "check_comment_vendor" %> + +<%= render "footer_recurrent_failures", interval: @notification.interval, check: @check %> diff --git a/config/locales/en.yml b/config/locales/en.yml index d93597e..9a34ad9 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -38,6 +38,15 @@ en: domain_recurrent_failures: subject: "Recurrent failures in %{domain} domain expiry check" + ssl_expires_soon: + subject: + zero: "SSL certificate for %{domain} expires TODAY!" + one: "SSL certificate for %{domain} expires TOMORROW!" + other: "SSL certificate for %{domain} expires in %{count} days" + + ssl_recurrent_failures: + subject: "Recurrent failures in %{domain} SSL certificate expiry check" + shared: locales: diff --git a/config/locales/fr.yml b/config/locales/fr.yml index 4e4b71c..ed5926f 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -12,6 +12,14 @@ fr: check: past: "ne peut être dans le futur" + time: + am: am + formats: + default: "%a %d %b %Y %H:%M:%S %z" + long: "%A %d %B %Y %H:%M" + short: "%d %b %H:%M" + pm: pm + devise: registrations: new: @@ -36,7 +44,16 @@ fr: other: "Le domaine %{domain} expire dans %{count} jours" domain_recurrent_failures: - subject: "Multiples erreur dans la vérification d'expiration du domaine %{domain}" + subject: "Erreurs dans la vérification d'expiration du domaine %{domain}" + + ssl_expires_soon: + subject: + zero: "Le certificat SSL pour %{domain} expire AUJOURD'HUI !" + one: "Le certificat SSL pour %{domain} expire DEMAIN !" + other: "Le certificat SSL pour %{domain} expire dans %{count} jours" + + ssl_recurrent_failures: + subject: "Erreurs dans la vérification d'expiration du certificat SSL %{domain}" shared: diff --git a/db/seeds.rb b/db/seeds.rb index c89deb6..2bf199b 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -1,3 +1,4 @@ +CheckLog.destroy_all Notification.destroy_all Check.destroy_all User.destroy_all @@ -33,6 +34,31 @@ check_chexpire_org_error = Check.create!( last_success_at: 4.days.ago, ) +ssl_check_chexpire_org = Check.create!( + user: user1, + kind: :ssl, + domain: "www.chexpire.org", + domain_expires_at: 1.week.from_now, + domain_updated_at: 6.months.ago, + domain_created_at: Time.new(2016, 8, 4, 12, 15, 1), + comment: "The date are fake, this is a seed !", + vendor: "Some random registrar", +) + +ssl_check_chexpire_org_error = Check.create!( + user: user1, + kind: :ssl, + domain: "chexpire.org", + domain_expires_at: 1.week.from_now, + domain_updated_at: 6.months.ago, + domain_created_at: Time.new(2016, 8, 4, 12, 15, 1), + comment: "The date are fake, this is a seed !", + vendor: "Some random registrar", + last_run_at: 20.minutes.ago, + last_success_at: 4.days.ago, +) + + Notification.create!( check: check_chexpire_org, interval: 15, @@ -49,6 +75,22 @@ Notification.create!( status: :pending, ) +Notification.create!( + check: ssl_check_chexpire_org, + interval: 15, + channel: :email, + recipient: "colin@example.org", + status: :pending, +) + +Notification.create!( + check: ssl_check_chexpire_org_error, + interval: 15, + channel: :email, + recipient: "colin@example.org", + status: :pending, +) + puts "\e[0;32mDone 👌\e[0m" puts " " puts "--------------------" diff --git a/test/mailers/notifications_mailer_test.rb b/test/mailers/notifications_mailer_test.rb index ddae512..c261131 100644 --- a/test/mailers/notifications_mailer_test.rb +++ b/test/mailers/notifications_mailer_test.rb @@ -1,6 +1,6 @@ require "test_helper" -class NotificationsMailerTest < ActionMailer::TestCase +class NotificationsMailerTest < ActionMailer::TestCase # rubocop:disable Metrics/ClassLength test "domain_expires_soon" do check = create(:check, domain_expires_at: Time.new(2018, 6, 10, 12, 0, 5, "+02:00")) notification = build(:notification, interval: 10, check: check, recipient: "colin@example.org") @@ -17,7 +17,7 @@ class NotificationsMailerTest < ActionMailer::TestCase assert_equal ["colin@example.org"], mail.to assert_equal [Rails.configuration.chexpire.fetch("mailer_default_from")], mail.from - parts = [mail.text_part.body.to_s, mail.html_part.to_s] + parts = [mail.text_part.decode_body, mail.html_part.decode_body] parts.each do |part| assert_match "domain.fr", part @@ -70,7 +70,7 @@ class NotificationsMailerTest < ActionMailer::TestCase mail = NotificationsMailer.with(notification: notification).domain_expires_soon - parts = [mail.text_part.body.to_s, mail.html_part.to_s] + parts = [mail.text_part.decode_body, mail.html_part.decode_body] parts.each do |part| assert_match "My comment", part @@ -114,7 +114,7 @@ class NotificationsMailerTest < ActionMailer::TestCase assert_equal ["recipient@domain.fr"], mail.to assert_equal [Rails.configuration.chexpire.fetch("mailer_default_from")], mail.from - parts = [mail.text_part.body.to_s, mail.html_part.to_s] + parts = [mail.text_part.decode_body, mail.html_part.decode_body] parts.each do |part| assert_match "invalid-domain.fr", part @@ -156,4 +156,128 @@ class NotificationsMailerTest < ActionMailer::TestCase end end end + + test "ssl_expires_soon" do + check = create(:check, :ssl, domain_expires_at: Time.new(2018, 6, 10, 12, 0, 5, "+02:00")) + notification = build(:notification, interval: 10, check: check, recipient: "colin@example.org") + + Date.stub :today, Date.new(2018, 6, 2) do + mail = NotificationsMailer.with(notification: notification).ssl_expires_soon + + assert_emails 1 do + mail.deliver_now + end + + assert_match "domain.fr", mail.subject + assert_match "SSL", mail.subject + assert_match "in 8 days", mail.subject + assert_equal ["colin@example.org"], mail.to + assert_equal [Rails.configuration.chexpire.fetch("mailer_default_from")], mail.from + + parts = [mail.text_part.decode_body, mail.html_part.decode_body] + + parts.each do |part| + assert_match "domain.fr", part + assert_match "Sun, 10 Jun 2018 10:00:05 +0000", part + assert_match "10 days", part + assert_match "/checks/#{check.id}/edit", part + assert_no_match "comment", part + assert_no_match "vendor", part + end + end + end + + test "ssl_expires_soon - FR" do + check = create(:check, :ssl, domain_expires_at: Time.new(2018, 6, 10, 12, 0, 5, "+02:00")) + notification = build(:notification, interval: 10, check: check, recipient: "colin@example.org") + + I18n.with_locale :fr do + Date.stub :today, Date.new(2018, 6, 2) do + mail = NotificationsMailer.with(notification: notification).ssl_expires_soon + + assert_emails 1 do + mail.deliver_now + end + + assert_match "domain.fr", mail.subject + assert_match "SSL", mail.subject + assert_match "dans 8 jours", mail.subject + assert_equal ["colin@example.org"], mail.to + assert_equal [Rails.configuration.chexpire.fetch("mailer_default_from")], mail.from + + parts = [mail.text_part.decode_body, mail.html_part.decode_body] + + parts.each do |part| + assert_match "domain.fr", part + assert_match "dim 10 juin 2018 10:00:05 +0000", part + assert_match "10 jours", part + assert_match "/checks/#{check.id}/edit", part + assert_no_match "commentaire", part + assert_no_match "fournisseur", part + end + end + end + end + + test "ssl_recurrent_failures" do + last_success_at = Time.new(2018, 5, 30, 6, 10, 0, "+00:00") + domain_expires_at = Time.new(2018, 10, 10, 7, 20, 0, "+04:00") + check = build(:check, :ssl, :last_runs_failed, + domain: "invalid-domain.fr", + last_success_at: last_success_at, + domain_expires_at: domain_expires_at, + comment: "My comment") + notification = create(:notification, check: check) + + mail = NotificationsMailer.with(notification: notification).ssl_recurrent_failures + assert_match "failures", mail.subject + assert_match "invalid-domain.fr", mail.subject + assert_match "SSL", mail.subject + + assert_equal ["recipient@domain.fr"], mail.to + assert_equal [Rails.configuration.chexpire.fetch("mailer_default_from")], mail.from + + parts = [mail.text_part.decode_body, mail.html_part.decode_body] + + parts.each do |part| + assert_match "invalid-domain.fr", part + assert_match "recurrent failures", part + assert_match(/success[a-z ]+ Wed, 30 May 2018 06:10:00 \+0000/, part) + assert_match(/expiry[a-z ]+ Wed, 10 Oct 2018 03:20:00 \+0000/, part) + assert_match "My comment", part + assert_match "/checks/#{check.id}/edit", part + end + end + + test "ssl_recurrent_failures - FR" do + last_success_at = Time.new(2018, 5, 30, 6, 10, 0, "+00:00") + domain_expires_at = Time.new(2018, 10, 10, 7, 20, 0, "+04:00") + check = build(:check, :ssl, :last_runs_failed, + domain: "invalid-domain.fr", + last_success_at: last_success_at, + domain_expires_at: domain_expires_at, + comment: "My comment") + notification = create(:notification, check: check) + + I18n.with_locale :fr do + mail = NotificationsMailer.with(notification: notification).ssl_recurrent_failures + assert_match "Erreurs", mail.subject + assert_match "invalid-domain.fr", mail.subject + assert_match "SSL", mail.subject + + assert_equal ["recipient@domain.fr"], mail.to + assert_equal [Rails.configuration.chexpire.fetch("mailer_default_from")], mail.from + + parts = [mail.text_part.decode_body, mail.html_part.decode_body] + + parts.each do |part| + assert_match "invalid-domain.fr", part + assert_match "erreurs", part + assert_match(/réussie[a-z ]+ mer 30 mai 2018 06:10:00 \+0000/, part) + assert_match(/expiration[a-z ]+ mer 10 oct. 2018 03:20:00 \+0000/, part) + assert_match "commentaire", part + assert_match "/checks/#{check.id}/edit", part + end + end + end end diff --git a/test/mailers/previews/notifications_mailer_preview.rb b/test/mailers/previews/notifications_mailer_preview.rb index f9c2898..01f5085 100644 --- a/test/mailers/previews/notifications_mailer_preview.rb +++ b/test/mailers/previews/notifications_mailer_preview.rb @@ -2,12 +2,25 @@ class NotificationsMailerPreview < ActionMailer::Preview # Preview this email at http://localhost:3000/rails/mailers/notifications_mailer/domain_expires_soon def domain_expires_soon - NotificationsMailer.with(notification: Notification.first).domain_expires_soon + check = Check.domain.first + NotificationsMailer.with(notification: check.notifications.first).domain_expires_soon end # Preview this email at http://localhost:3000/rails/mailers/notifications_mailer/domain_recurrent_failures def domain_recurrent_failures - check = Check.where("last_run_at != last_success_at").limit(1).first + check = Check.domain.where("last_run_at != last_success_at").first NotificationsMailer.with(notification: check.notifications.first).domain_recurrent_failures end + + # Preview this email at http://localhost:3000/rails/mailers/notifications_mailer/ssl_expires_soon + def ssl_expires_soon + check = Check.ssl.first + NotificationsMailer.with(notification: check.notifications.first).ssl_expires_soon + end + + # Preview this email at http://localhost:3000/rails/mailers/notifications_mailer/ssl_recurrent_failures + def ssl_recurrent_failures + check = Check.ssl.where("last_run_at != last_success_at").first + NotificationsMailer.with(notification: check.notifications.first).ssl_recurrent_failures + end end