Compare commits

...

8 Commits

  1. 3
      .rspec
  2. 2
      Gemfile
  3. 10
      Gemfile-2.3.lock
  4. 2
      Procfile
  5. 2
      Procfile.sidekiq
  6. 4
      app/controllers/check_controller.rb
  7. 1
      app/controllers/site_controller.rb
  8. 14
      app/controllers/sites_controller.rb
  9. 13
      app/controllers/statistics_controller.rb
  10. 2
      app/helpers/application_helper.rb
  11. 17
      app/helpers/check_helper.rb
  12. 48
      app/helpers/sites_helper.rb
  13. 2
      app/helpers/statistics_helper.rb
  14. 12
      app/javascript/css/application.scss
  15. 40
      app/javascript/js/stats/grades.js
  16. 72
      app/javascript/js/stats/index.js
  17. 7
      app/javascript/packs/application.js
  18. 24
      app/lib/matomo.rb
  19. 99
      app/models/analysis.rb
  20. 50
      app/views/application/sites.html.erb
  21. 15
      app/views/check/show.html.erb
  22. 13
      app/views/ssh/show.html.erb
  23. 7
      app/views/statistics/grade.html.erb
  24. 15
      app/views/statistics/index.html.erb
  25. 29
      bin/rspec
  26. 116
      bin/stats
  27. 8
      config/locales/en.yml
  28. 3
      config/locales/fr.yml
  29. 8
      config/routes.rb
  30. 67
      config/sites.yml
  31. 16
      db/migrate/20220626133648_add_refresh_to_analysis.rb
  32. 26
      db/schema.rb
  33. 3
      package.json
  34. 55
      spec/models/analysis_spec.rb

@ -0,0 +1,3 @@
--require spec_helper
--format progress
--format html --out tmp/rspec.html

@ -17,6 +17,8 @@ gem 'sidekiq-workflow', git: 'https://git.imirhil.fr/aeris/sidekiq-workflow.git'
gem 'simpleidn'
gem 'http_accept_language'
gem 'recursive-open-struct'
gem 'ruby-progressbar'
gem 'public_suffix'
gem 'uglifier'
gem 'sass-rails'

@ -29,8 +29,6 @@ GEM
colorize (0.8.1)
connection_pool (2.2.5)
dotenv (2.7.6)
foreman (0.87.2)
http_accept_language (2.1.1)
httparty (0.20.0)
mime-types (~> 3.0)
multi_xml (>= 0.5.2)
@ -42,11 +40,9 @@ GEM
nokogiri (1.10.10)
mini_portile2 (~> 2.4.0)
parallel (1.19.2)
pg (1.2.3)
rack (2.0.9)
rack-protection (2.2.0)
rack
recursive-open-struct (1.1.3)
redis (4.4.0)
redlock (1.2.2)
redis (>= 3.0.0, < 5.0)
@ -73,13 +69,9 @@ DEPENDENCIES
amazing_print
cryptcheck (~> 2.0.0)!
dotenv
foreman
http_accept_language
pg
recursive-open-struct
sidekiq
sidekiq-workflow!
simpleidn
BUNDLED WITH
2.3.9
2.3.10

@ -1,4 +1,2 @@
web: bundle exec guard -i
webpack: bundle exec webpack-dev-server
sidekiq: bundle exec sidekiq -q default
sidekiq_1_0: BUNDLE_GEMFILE=Gemfile-2.3 bin/sidekiq 1.0 -q tls_1_0

@ -0,0 +1,2 @@
sidekiq: bundle exec sidekiq -q default
sidekiq_1_0: BUNDLE_GEMFILE=Gemfile-2.3 bin/sidekiq 1.0 -q tls_1_0

@ -18,8 +18,8 @@ class CheckController < ApplicationController
def refresh
unless @analysis.pending
if Rails.env == 'production'
refresh_allowed = @analysis.updated_at + Rails.configuration.refresh_delay
if Time.now < refresh_allowed
refresh_at = @analysis.refresh_at
if refresh_at || Time.now < refresh_at
flash[:warning] = "Merci d’attendre au moins #{l refresh_allowed} pour rafraîchir"
return redirect_to action: :show, id: @host
end

@ -36,7 +36,6 @@ class SiteController < ApplicationController
end
def help
end
def about

@ -0,0 +1,14 @@
class SitesController < ApplicationController
@@sites = YAML.load_file Rails.root.join 'config/sites.yml'
@@sites.keys.each do |name|
define_method(name) { sites name }
end
private
def sites(name)
@name = name
@sites = Stat[:"sites_#{name}"].data
render :sites
end
end

@ -0,0 +1,13 @@
class StatisticsController < ApplicationController
TODAY = Date.today
def show
service = params.fetch :id
respond_to do |format|
format.json do
json = Stat.where(name: "grades_for_#{service}").order(date: :desc).first.dataset
render json: json, status: :ok
end
end
end
end

@ -1,2 +0,0 @@
module ApplicationHelper
end

@ -1,16 +1,14 @@
module CheckHelper
private def __label(value, color, state = true)
color = :default unless color
color = "state-#{color}" if state
"<span class=\"badge badge-#{color}\">#{value}</span>"
end
include ActionView::Helpers::TagHelper
def label(value, color, state = true)
__label(value, color, state).html_safe
color = :default unless color
color = "state-#{color}" if state
content_tag :span, value.to_s.html_safe, class: [:badge, :"badge-#{color}"]
end
def cell(value, color, state = true)
"<td class=\"badge-state-#{color}\">#{value}</td>".html_safe
content_tag :td, value, class: :"table-#{color}"
end
def labels(level, states, state = true)
@ -22,7 +20,7 @@ module CheckHelper
else
value ? :success : :danger
end
__label name, color, state
label name, color, state
end.join(' ').html_safe
end
@ -30,7 +28,7 @@ module CheckHelper
::CryptCheck::State.collect do |level|
states[level].each_pair
.select { |_, v| v == true }
.collect { |name, _| __label name, level }
.collect { |name, _| label name, level }
end.flatten(1).join(' ').html_safe
end
@ -54,6 +52,7 @@ module CheckHelper
end
def rank_label(rank)
rank = rank&.to_sym
l = %i(0 V T X).include? rank
label rank, rank_color(rank), !l
end

@ -0,0 +1,48 @@
module SitesHelper
include CheckHelper
def domain_cell(domain, grade)
link = link_to domain, https_show_path(domain)
content_tag :th, (rank_label(grade) + ' ' + link).html_safe
end
def tls_cell(tls)
return unless tls
color = case tls.to_sym
when :tls1_2_only
:success
when :tls1_2
:error
else
:critical
end
content = content_tag :div, color, class: %i[sr-only]
content_tag :td, label(' ' + content, color), class: %i[text-center]
end
def ciphers_cell(ciphers)
return unless ciphers
color = case ciphers.to_sym
when :good
:success
else
:critical
end
content = content_tag :div, color, class: %i[sr-only]
content_tag :td, label(' ' + content, color), class: %i[text-center]
end
def pfs_cell(pfs)
return unless pfs
color = case pfs.to_sym
when :pfs_only
:success
when :pfs
:error
else
:critical
end
content = content_tag :div, color, class: %i[sr-only]
content_tag :td, label(' ' + content, color), class: %i[text-center]
end
end

@ -0,0 +1,2 @@
module StatisticsHelper
end

@ -91,27 +91,27 @@ $color-good: #beb052;
$color-best: #8db457;
$color-great: #5cb85c;
.badge-state-critical, td.badge-state-critical {
.badge-state-critical, td.table-critical {
background-color: $color-critical;
}
.badge-state-error, td.badge-state-error {
.badge-state-error, td.table-error {
background-color: $color-error;
}
.badge-state-warning, td.badge-state-warning {
.badge-state-warning {
background-color: $color-warning;
}
.badge-state-good, td.badge-state-good {
.badge-state-good, td.table-good {
background-color: $color-good;
}
.badge-state-best, td.badge-state-best {
.badge-state-best, td.table-best {
background-color: $color-best;
}
.badge-state-great, td.badge-state-great {
.badge-state-great, td.table-great {
background-color: $color-great;
}

@ -0,0 +1,40 @@
document.addEventListener("DOMContentLoaded", () => {
let gradesChart = document.getElementById('gradesChart').getContext('2d')
let background = [
'rgba(255, 99, 132, 1)',
'rgba(54, 162, 235, 1)',
'rgba(255, 206, 86, 1)',
'rgba(75, 192, 192, 1)',
'rgba(153, 102, 255, 1)',
'rgba(255, 159, 64, 1)'
]
let createGradesChart = new Chart(gradesChart, {
type: 'bar'
})
let generateGraphs = function () {
fetch(window.location.href, {
method: "GET",
headers: {
"Accept": "application/json",
"Content-Type": "application/json",
},
}).then((response) => {
response.json().then((data) => {
if (response.status === 200) {
createGradesChart.data.labels = data["labels"]
createGradesChart.data.datasets = [{
label: 'Number',
data: data["dataset"],
backgroundColor: background
}]
createGradesChart.update()
}
})
});
}
generateGraphs()
});

@ -0,0 +1,72 @@
document.addEventListener("DOMContentLoaded", () => {
let background = [
'#5cb85c', // A+
'#5cb85c', // A
'#8db457', // B+
'#8db457', // B
'#beb052', // C+
'#beb052', // C
'#6c757d', // D
'#f0ad4e', // E
'#e4804e', // F
'#d9534f' // G
]
for (const service of ["https", "smtp", "tls", "xmpp"]) {
const name = service.replace(/^\w/, c => c.toUpperCase())
console.info(`grades${name}Chart`)
const canvas = document.getElementById(`grades${name}Chart`).getContext('2d')
// const chart = new Chart(canvas, {
// type: 'pie',
// options: {
// interaction: {
// intersect: false,
// mode: 'dataset',
// }
// }
// })
const chart = new Chart(canvas, {
type: 'bar',
options: {
interaction: {
intersect: false,
mode: 'dataset',
},
plugins: {
datalabels: {
anchor: 'end',
align: 'top',
formatter: ((value, context) => {
const index = context.dataIndex
const keys = Object.keys(context.dataset.data)
value = context.dataset.data[keys[index]]
return value
}),
color: "black",
font: {
weight: "bold",
}
}
}
}
})
fetch(`/statistics/${service}.json`).then((response) => {
if (response.status === 200) {
response.json().then((data) => {
const labels = ["A+", "A", "B+", "B", "C+", "C", "D", "E", "F", "G"]
const dataset = JSON.parse(JSON.stringify(data, labels, 0))
chart.data.labels = labels
chart.data.datasets = [{
label: 'Number of request',
data: dataset,
backgroundColor: background
}]
chart.update()
})
}
});
}
}
)
;

@ -1 +1,8 @@
import 'css/application'
import Chart from 'chart.js/auto'
import ChartDataLabels from 'chartjs-plugin-datalabels'
global.Chart = Chart
Chart.register(ChartDataLabels)
import 'js/stats/index'

@ -0,0 +1,24 @@
class Matomo
module Helpers
def matomo_tag
config = Matomo
return unless config.enabled?
render partial: 'matomo', locals: { config: config }
end
end
cattr_reader :url, :path, :site
def self.load
@@url = ENV['MATOMO_URL']
@@site = ENV['MATOMO_SITE']
@@disabled = ENV['MATOMO_DISABLED']
end
def self.enabled?
@@disabled.nil? && self.url && self.site
end
ActionView::Base.include Helpers
self.load
end

@ -1,36 +1,67 @@
class Analysis < ApplicationRecord
enum service: %i[https smtp xmpp tls ssh].collect { |e| [e, e.to_s] }.to_h
validates :service, presence: true
validates :host, presence: true
def self.[](service, host, args)
key = self.key service, host, args
self.find_by key
end
def self.pending!(service, host, args)
key = self.key service, host, args
analysis = self.find_or_create_by! key
analysis.pending!
end
def pending!
self.update! pending: true
self
end
def self.post!(service, host, args, result)
analysis = self[service, host, args]
analysis.post! result
end
def post!(result)
self.update! pending: false, result: result
end
private
def self.key(service, host, args)
{ service: service, host: host, args: args }
end
enum service: %i[https smtp xmpp tls ssh].collect { |e| [e, e.to_s] }.to_h
validates :service, presence: true
validates :host, presence: true
def self.[](service, host, args)
key = self.key service, host, args
self.find_by key
end
def self.pending!(service, host, args)
key = self.key service, host, args
analysis = self.find_or_create_by! key
analysis.pending!
end
def pending!
self.update! pending: true
self
end
def self.post!(service, host, args, result)
analysis = self[service, host, args]
analysis.post! result
end
RESOLVER = ::Resolv::DNS.new
DNS_TXT_FORMAT = /^cryptcheck=(.*)/
DEBUG = 'debug'
DEFAULT_REFRESH_DELAY = Rails.configuration.refresh_delay
def find_refresh_delay
host = self.host
loop do
break unless host && PublicSuffix.valid?(host)
RESOLVER.getresources(host, ::Resolv::DNS::Resource::IN::TXT).each do |txt|
txt.strings.each do |value|
if match = DNS_TXT_FORMAT.match(value)
match = match[1]
return if match == DEBUG
begin
delay = ::ActiveSupport::Duration.parse match
delay = DEFAULT_REFRESH_DELAY if delay > DEFAULT_REFRESH_DELAY
return delay
rescue ActiveSupport::Duration::ISO8601Parser::ParsingError
end
end
end
end
_, host = host.split '.', 2
end
DEFAULT_REFRESH_DELAY
rescue
DEFAULT_REFRESH_DELAY
end
def post!(result)
refresh_at = self.find_refresh_delay&.since
self.update! pending: false, refresh_at: refresh_at, result: result
end
private
def self.key(service, host, args)
{ service: service, host: host, args: args }
end
end

@ -0,0 +1,50 @@
<h1><%= @name.to_s.capitalize %></h1>
<h2>Columns meaning</h2>
<div class="container">
<div class="form-group row">
<div class="col-sm-1">TLS:</div>
<div class="col-sm-11">
<%= label(' ' + content_tag(:span, :success, class: %i[sr-only]), :success) %> TLS1.2 only supported
<%= label(' ' + content_tag(:span, :error, class: %i[sr-only]), :error) %> No TLS1.2 supported
<%= label(' ' + content_tag(:span, :critical, class: %i[sr-only]), :critical) %> SSLv2 or SSLv3 supported
</div>
</div>
<div class="row">
<div class="col-sm-1">Ciphers:</div>
<div class="col-sm-11">
<%= label(' ' + content_tag(:span, :success, class: %i[sr-only]), :success) %> Only safe cipher supported
<%= label(' ' + content_tag(:span, :critical, class: %i[sr-only]), :critical) %> Unsafe cipher supported
</div>
</div>
<div class="row">
<div class="col-sm-1">PFS:</div>
<div class="col-sm-11">
<%= label(' ' + content_tag(:span, :success, class: %i[sr-only]), :success) %> Only PFS cipher supported
<%= label(' ' + content_tag(:span, :error, class: %i[sr-only]), :error) %> PFS cipher but also no-PFS cipher supported
<%= label(' ' + content_tag(:span, :critical, class: %i[sr-only]), :critical) %> No PFS supported
</div>
</div>
<br/>
<table class="table table-sm">
<thead>
<tr>
<%= content_tag :th, t('.domain') %>
<%= content_tag :th, t('.tls'), class: %i[text-center] %>
<%= content_tag :th, t('.ciphers'), class: %i[text-center] %>
<%= content_tag :th, t('.pfs'), class: %i[text-center] %>
</tr>
</thead>
<tbody>
<% @sites.sort_by { _2.fetch('grade') || 'Z' }.each do |domain, stat| %>
<tr>
<%= domain_cell domain, stat.fetch('grade') %>
<%= tls_cell stat.fetch 'tls' %>
<%= ciphers_cell stat.fetch 'ciphers' %>
<%= pfs_cell stat.fetch 'pfs' %>
</tr>
<% end %>
</tbody>
</table>

@ -1,16 +1,19 @@
<div class="container">
<div class="row">
<div class="col-sm-11">
<div class="col-sm-10">
<h1>
[<%= self.type.to_s.upcase %>] <%= @host %>
<span class="small">(<%= l @analysis.updated_at %>)</span>
</h1>
</div>
<% if Time.now - @analysis.updated_at >= Rails.configuration.refresh_delay %>
<div class="col-sm-1">
<%= link_to t('Refresh'), { action: :refresh }, class: %i(btn btn-outline-secondary) %>
</div>
<div class="col-sm-2 text-end">
<% if @analysis.refresh_at.nil? || Time.now >= @analysis.refresh_at %>
<%= link_to t('Refresh'), { action: :refresh }, class: %i(btn btn-outline-secondary) %>
<% else %>
<%= button_tag t('Refresh not available', date: l(@analysis.refresh_at, format: :time)),
type: :button, class: %i(btn btn-outline-secondary), disabled: true %>
<% end %>
</div>
</div>
<% @result.each do |host|
@ -30,7 +33,7 @@
<div class="row">
<div class="col-sm-12">
<h2>
<%= rank_label host[:grade].to_sym %>
<%= rank_label host[:grade] %>
<%= host[:ip] %> : <%= host[:port] %>
<span class="small">(<%= host[:hostname] %>)</span></h2>
</div>

@ -1,15 +1,18 @@
<div class="container">
<div class="row">
<div class="col-sm-11">
<div class="col-sm-10">
<h1>
[SSH] <%= @host %> <span class="small">(<%= l @analysis.updated_at %>)</span>
</h1>
</div>
<% if Time.now - @analysis.updated_at >= Rails.configuration.refresh_delay %>
<div class="col-sm-1">
<%= link_to t('Refresh'), {action: :refresh}, class: %i(btn btn-default) %>
<div class="col-sm-2 text-end">
<% if Time.now >= @analysis.refresh_at %>
<%= link_to t('Refresh'), { action: :refresh }, class: %i(btn btn-outline-secondary) %>
<% else %>
<%= button_tag t('Refresh not available', date: l(@analysis.refresh_at, format: :time)),
type: :button, class: %i(btn btn-outline-secondary), disabled: true %>
<% end %>
</div>
<% end %>
</div>
<%
@result.each do |host|

@ -0,0 +1,7 @@
<div>
Nombre de recherche <%= params[:service] %> : <%= @services.size %>
</div>
<div>
<canvas id="gradesChart"></canvas>
</div>

@ -0,0 +1,15 @@
<div class="row">
<!-- <div class="col-4">-->
<!-- <h2>Requests per service</h2>-->
<!-- <canvas id="servicesChart" aria-label="Pie chart for number of requests per service" role="img"></canvas>-->
<!-- </div>-->
<% %i[https smtp tls xmpp].each do |s| %>
<div class="col-6">
<h2>Grades for service <%= s.to_s.upcase %></h2>
<canvas id="<%= "grades" + s.to_s.upcase_first + "Chart" %>" aria-label="Bar chart for number of grades for service <%= s.to_s.upcase %>" role="img"></canvas>
</div>
<% end %>
</div>

@ -0,0 +1,29 @@
#!/usr/bin/env ruby
# frozen_string_literal: true
#
# This file was generated by Bundler.
#
# The application 'rspec' is installed as part of a gem, and
# this file is here to facilitate running it.
#
require "pathname"
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
Pathname.new(__FILE__).realpath)
bundle_binstub = File.expand_path("../bundle", __FILE__)
if File.file?(bundle_binstub)
if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
load(bundle_binstub)
else
abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
end
end
require "rubygems"
require "bundler/setup"
load Gem.bin_path("rspec-core", "rspec")

@ -0,0 +1,116 @@
#!./bin/rails runner
# Profit from open class to add stats methods only on this script
class Analysis
def grade
grades = self.result.collect { _1['grade'] }.compact
CryptCheck::Grade.worst grades
end
def tls
return unless (result = self.result)
protocols = result.collect { |r| r.dig('handshakes', 'protocols')
&.collect { |p| p['protocol'].to_sym } }
.compact.flatten.uniq
return :ssl unless (protocols & %i[SSLv2 SSLv3]).empty?
return :tls unless protocols.include? :TLSv1_2
return :tls1_2_only if protocols == %i[TLSv1_2]
:tls1_2
end
def ciphers
return unless (result = self.result)
status = result.collect do |r|
r.dig('handshakes', 'ciphers')&.collect do |c|
s = CryptCheck::Tls::Cipher
.new(nil, c.fetch('name')).status
CryptCheck::State.good_or_bad s
end
end.compact.flatten.uniq
return :bad if status.include? :bad
:good
end
def pfs
return unless (result = self.result)
ciphers = result.collect do |r|
r.dig('handshakes', 'ciphers')&.collect do |c|
CryptCheck::Tls::Cipher
.new(nil, c.fetch('name'))
.pfs?
end
end.compact.flatten.uniq
return :no_pfs unless ciphers.include? true
return :pfs_only unless ciphers.include? false
:pfs
end
end
sites = YAML.load_file Rails.root.join 'config/sites.yml'
workflows = []
sites.each do |type, domains|
domains.each do |domain|
puts "Refreshing #{domain}"
@analysis = Analysis.pending! :https, domain, 443
workflows << CheckWorkflow.start!(:https, @analysis.host, *@analysis.args)
end
end
workflows.each &:wait
sites.each do |type, domains|
domains = domains.collect do |domain|
analysis = Analysis[:https, domain, 443]
stats = {
grade: analysis.grade,
tls: analysis.tls,
ciphers: analysis.ciphers,
pfs: analysis.pfs
}
[domain, stats]
end.to_h
Stat.create! :"sites_#{type}", domains
end
# general stat
services = Analysis.group(:service).order(service: :asc).count
Stat.create! :request_per_service, { labels: services.keys, dataset: services.values }
# grade per service for https, smtp, tls and xmpp
%i[https smtp tls xmpp].each do |service_name|
services = Analysis.where service: service_name, pending: false
services.each do |service|
if (g = service.grade)
grades[g] += 1
end
if (t = service.tls)
tls[t] += 1
end
if (c = service.ciphers)
ciphers[c] += 1
end
if (p = service.pfs)
pfs[p] += 1
end
end
ap "grades_for_#{service}" => grades
Stat.create! "grades_for_#{service}", grades
ap "tls_for_#{service}" => tls
Stat.create! "tls_for_#{service}", tls
ap "ciphers_for_#{service}" => ciphers
Stat.create! "ciphers_for_#{service}", ciphers
ap "pfs_for_#{service}" => pfs
Stat.create! "pfs_for_#{service}", pfs
end

@ -18,6 +18,7 @@ en:
"Error during analysis:": "Error during analysis:"
Refresh: Refresh
Refresh not available: Refresh available at %{date}
Protocol: Protocol
Protocols: Protocols
@ -78,3 +79,10 @@ en:
great:
hsts: This server supports HSTS with long duration
time:
formats:
default: "%d/%m/%Y %H:%M:%S %:z"
long: "%d/%m/%Y %H:%M:%S"
short: "%d %b %Hh%M"
time: "%H:%M:%S"

@ -18,6 +18,7 @@ fr:
"Error during analysis:": "Erreur durant l’analyse :"
Refresh: Rafraîchir
Refresh not available: Rafraîchissement disponible à %{date}
Protocol: Protocole
Protocols: Protocoles
@ -262,5 +263,7 @@ fr:
am: am
formats:
default: "%d/%m/%Y %H:%M:%S %:z"
long: "%d/%m/%Y %H:%M:%S"
short: "%d %b %Hh%M"
time: "%H:%M:%S"
pm: pm

@ -2,7 +2,7 @@ Rails.application.routes.draw do
%i[https smtp xmpp tls ssh].each do |type|
namespace type, id: /[^\/]+/ do
get '/', action: :index
get ':id/', action: :show
get ':id/', action: :show, as: :show
get ':id/refresh', action: :refresh, as: :refresh
end
end
@ -16,8 +16,14 @@ Rails.application.routes.draw do
root 'site#index'
post '/' => 'site#check'
resources :statistics, only: %i[index show]
get 'sites' => 'site#sites'
%i[banks insurances gouv.fr].each do |name|
get name, controller: :sites, action: name
end
if Rails.env.development?
require 'sidekiq/web'
mount Sidekiq::Web => '/sidekiq'

@ -0,0 +1,67 @@
banks:
- admin.vybecard.com
- app.n26.com
- app.nickel.eu
- app.qonto.com
- clients.boursorama.com
- clients.cmavignon.com
- connexion-mabanque.bnpparibas
- ebanking-ch3.ubs.com
- epargnants.interepargne.natixis.fr
- espace-client.hellobank.fr
- espace-client.lanef.com
- espaceclient.axa.fr
- linxea-zen.avepargne.fr
- m.ing.fr
- mabanque.bnpparibas
- mabanque.fortuneo.fr
- mon.cmb.fr
- monespace.lcl.fr
- particuliers.societegenerale.fr
- secure.bforbank.com
- transatplan.banquetransatlantique.com
- voscomptesenligne.labanquepostale.fr
- www.altaprofits.com
- www.aviva.fr
- www.banque-rhone-alpes.fr
- www.banquepopulaire.fr
- www.bred.fr
- www.caisse-epargne.fr
- www.cic.fr
- www.credit-agricole.fr
- www.credit-cooperatif.coop
- www.creditmutuel.fr
- www.hsbc.fr
- www.ibps.sud.banquepopulaire.fr
- www.icgauth.banquebcp.fr
- www.labanquepostale.fr
- www.mgen.fr
- www.monabanq.com
- www.previ-direct.com
insurances:
- adherent.gie-afer.fr
- authentification.groupama.fr
- connect.axa.fr
- connect.maif.fr
- connect.sogarep.fr
- epargnant.amundi-ee.com
- espace-assure.gmf.fr
- espace-client.allianz.fr
- espace-client.mma.fr
- espace-personnel.direct-assurance.fr
- espaceperso.mutuelledesmotards.fr
- harmonie-et-moi.fr
- myswisslife.fr
- www.acommeassure.com
- www.assu2000.fr
- www.assurances-collectives.cm-cic.com
- www.aviva.fr
- www.creditmutuel-epargnesalariale.fr
- www.lolivier.fr
- www.maaf.fr
- www.mgen.fr
- www.monabanq.com
- www.monespace.generali.fr
- www.mutavie.fr
- www.sylvea.fr

@ -0,0 +1,16 @@
class AddRefreshToAnalysis < ActiveRecord::Migration[7.0]
def up
add_column :analyses, :refresh_at, :timestamp
delay = Rails.configuration.refresh_delay
progress = ProgressBar.create total: Analysis.count, format: '%t (%c/%C) %W %e'
Analysis.all.each do |analysis|
analysis.update_column :refresh_at, analysis.updated_at + delay
progress.increment
end
end
def down
remove_column :analyses, :refresh_at
end
end

@ -2,29 +2,35 @@
# of editing this file, please use the migrations feature of Active Record to
# incrementally modify your database, and then regenerate this schema definition.
#
# Note that this schema.rb definition is the authoritative source for your
# database schema. If you need to create the application database on another
# system, you should be using db:schema:load, not running all the migrations
# from scratch. The latter is a flawed and unsustainable approach (the more migrations
# you'll amass, the slower it'll run and the greater likelihood for issues).
# This file is the source Rails uses to define your schema when running `bin/rails
# db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to
# be faster and is potentially less error prone than running all of your
# migrations from scratch. Old migrations may fail to apply correctly if those
# migrations use external dependencies or application code.
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema[6.1].define(version: 2019_12_01_192510) do
ActiveRecord::Schema[7.0].define(version: 2022_06_26_133648) do
# These are extensions that must be enabled in order to support this database
enable_extension "pgcrypto"
enable_extension "plpgsql"
create_table "analyses", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
create_table "analyses", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t|
t.string "service", null: false
t.string "host", null: false
t.boolean "pending", default: true, null: false
t.jsonb "result"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.datetime "created_at", precision: nil, null: false
t.datetime "updated_at", precision: nil, null: false
t.jsonb "args"
t.datetime "refresh_at", precision: nil
t.index ["service", "host", "args"], name: "index_analyses_on_service_and_host_and_args", unique: true
end
create_table "stats", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.string "name"
t.date "date"
t.jsonb "data"
t.index ["name", "date"], name: "index_stats_on_name_and_date", unique: true
end
end

@ -5,8 +5,11 @@
"author": "aeris <aeris@imirhil.fr>",
"license": "AGPL-3.0-or-later",
"dependencies": {
"@popperjs/core": "^2.11.5",
"@rails/webpacker": "5.4.3",
"bootstrap": "^5.1.3",
"chart.js": "^3.7.1",
"chartjs-plugin-datalabels": "^2.0.0",
"font-awesome": "^4.7.0",
"webpack": "^4.46.0",
"webpack-cli": "^3.3.12"

@ -0,0 +1,55 @@
require 'rails_helper'
describe Analysis do
describe '::find_refresh_delay' do
it 'must return host value' do
expect(Analysis::RESOLVER).to receive(:getresources)
.with('www.example.org', ::Resolv::DNS::Resource::IN::TXT)
.and_return([::Resolv::DNS::Resource::IN::TXT.new('cryptcheck=PT1M')])
allow(Analysis::RESOLVER).to receive(:getresources)
.with('example.org', ::Resolv::DNS::Resource::IN::TXT)
.and_raise(Exception, :must_not_be_called)
host = Analysis.new host: 'www.example.org'
expect(host.find_refresh_delay).to eq 1.minute
end
it 'must recurse domain value' do
expect(Analysis::RESOLVER).to receive(:getresources)
.with('www.example.org', ::Resolv::DNS::Resource::IN::TXT)
.and_return([])
expect(Analysis::RESOLVER).to receive(:getresources)
.with('example.org', ::Resolv::DNS::Resource::IN::TXT)
.and_return([::Resolv::DNS::Resource::IN::TXT.new('cryptcheck=PT1M')])
allow(Analysis::RESOLVER).to receive(:getresources)
.with('org', ::Resolv::DNS::Resource::IN::TXT)
.and_raise(Exception, :must_not_be_called)
host = Analysis.new host: 'www.example.org'
expect(host.find_refresh_delay).to eq 1.minute
end
it 'must not test TLD' do
expect(Analysis::RESOLVER).to receive(:getresources)
.with('www.example.org', ::Resolv::DNS::Resource::IN::TXT)
.and_return([])
expect(Analysis::RESOLVER).to receive(:getresources)
.with('example.org', ::Resolv::DNS::Resource::IN::TXT)
.and_return([])
allow(Analysis::RESOLVER).to receive(:getresources)
.with('org', ::Resolv::DNS::Resource::IN::TXT)
.and_raise(Exception, :must_not_be_called)
host = Analysis.new host: 'www.example.org'
expect(host.find_refresh_delay).to eq 1.hour
end
it 'must support debug mode' do
expect(Analysis::RESOLVER).to receive(:getresources)
.with('www.example.org', ::Resolv::DNS::Resource::IN::TXT)
.and_return([::Resolv::DNS::Resource::IN::TXT.new('cryptcheck=debug')])
allow(Analysis::RESOLVER).to receive(:getresources)
.with('example.org', ::Resolv::DNS::Resource::IN::TXT)
.and_raise(Exception, :must_not_be_called)
host = Analysis.new host: 'www.example.org'
expect(host.find_refresh_delay).to be_nil
end
end
end
Loading…
Cancel
Save