decidim-direct_verifications 0.22.1 → 1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +32 -3
  3. data/app/assets/config/direct_verifications_admin_manifest.css +3 -0
  4. data/app/assets/stylesheets/decidim/direct_verifications/authorizations.scss +17 -0
  5. data/app/commands/decidim/direct_verifications/verification/create_import.rb +50 -0
  6. data/app/controllers/decidim/direct_verifications/verification/admin/authorizations_controller.rb +40 -0
  7. data/app/controllers/decidim/direct_verifications/verification/admin/direct_verifications_controller.rb +20 -18
  8. data/app/controllers/decidim/direct_verifications/verification/admin/imports_controller.rb +55 -0
  9. data/app/forms/decidim/direct_verifications/registration_form.rb +8 -0
  10. data/app/forms/decidim/direct_verifications/verification/create_import_form.rb +43 -0
  11. data/app/jobs/decidim/direct_verifications/authorize_users_job.rb +34 -0
  12. data/app/jobs/decidim/direct_verifications/base_import_job.rb +38 -0
  13. data/app/jobs/decidim/direct_verifications/register_users_job.rb +19 -0
  14. data/app/jobs/decidim/direct_verifications/revoke_users_job.rb +17 -0
  15. data/app/mailers/decidim/direct_verifications/import_mailer.rb +27 -0
  16. data/app/mailers/decidim/direct_verifications/stats.rb +23 -0
  17. data/app/views/decidim/direct_verifications/import_mailer/finished_processing.html.erb +7 -0
  18. data/app/views/decidim/direct_verifications/import_mailer/finished_processing.text.erb +7 -0
  19. data/app/views/decidim/direct_verifications/verification/admin/authorizations/index.html.erb +42 -0
  20. data/app/views/decidim/direct_verifications/verification/admin/direct_verifications/index.html.erb +2 -1
  21. data/app/views/decidim/direct_verifications/verification/admin/imports/new.html.erb +50 -0
  22. data/config/initializers/mail_previews.rb +5 -0
  23. data/config/locales/ca.yml +20 -20
  24. data/config/locales/cs.yml +69 -0
  25. data/config/locales/en.yml +29 -2
  26. data/config/locales/es.yml +20 -20
  27. data/config/locales/fr.yml +69 -0
  28. data/lib/decidim/direct_verifications.rb +24 -1
  29. data/lib/decidim/direct_verifications/authorize_user.rb +64 -0
  30. data/lib/decidim/direct_verifications/instrumenter.rb +58 -0
  31. data/lib/decidim/direct_verifications/parsers.rb +11 -0
  32. data/lib/decidim/direct_verifications/parsers/base_parser.rb +37 -0
  33. data/lib/decidim/direct_verifications/parsers/metadata_parser.rb +54 -0
  34. data/lib/decidim/direct_verifications/parsers/name_parser.rb +40 -0
  35. data/lib/decidim/direct_verifications/register_user.rb +54 -0
  36. data/lib/decidim/direct_verifications/revoke_user.rb +45 -0
  37. data/lib/decidim/direct_verifications/tests/factories.rb +11 -0
  38. data/lib/decidim/direct_verifications/tests/verification_controller_examples.rb +8 -3
  39. data/lib/decidim/direct_verifications/user_processor.rb +17 -101
  40. data/lib/decidim/direct_verifications/user_stats.rb +5 -5
  41. data/lib/decidim/direct_verifications/verification/admin_engine.rb +6 -1
  42. data/lib/decidim/direct_verifications/version.rb +3 -3
  43. metadata +37 -9
  44. data/lib/decidim/direct_verifications/config.rb +0 -23
@@ -0,0 +1,69 @@
1
+ fr:
2
+ decidim:
3
+ admin:
4
+ models:
5
+ user:
6
+ fields:
7
+ roles:
8
+ participant: Participant
9
+ authorization_handlers:
10
+ admin:
11
+ direct_verifications:
12
+ help:
13
+ - 'Permet l''introduction massive des utilisateurs afin de :'
14
+ - Inscription directe au sein de l'organisation et envoie des invitations
15
+ - Vérifiez-les avec n'importe quelle méthode de vérification active
16
+ - Révoquer leur vérification dans toute méthode de vérification active
17
+ direct_verifications:
18
+ explanation: Vérification manuelle par les administrateurs de l'organisation
19
+ name: Vérification directe
20
+ direct_verifications:
21
+ verification:
22
+ admin:
23
+ authorizations:
24
+ index:
25
+ created_at: Créé à
26
+ metadata: Métadonnées
27
+ name: Nom
28
+ title: Autorisations
29
+ user_name: Nom de l'utilisateur
30
+ new_import: Nouvel import
31
+ direct_verifications:
32
+ create:
33
+ authorized: "%{authorized} utilisateurs ont été vérifiés avec succès en utilisant [%{handler}] (%{count} détectés, %{errors} erreurs)"
34
+ info: "%{count} utilisateurs détectés, dont %{registered} sont enregistrés, %{authorized} autorisés à utiliser [%{handler}] (%{unconfirmed} non confirmés)"
35
+ registered: "%{registered} utilisateurs ont été enregistrés avec succès (%{count} détectés, %{errors} erreurs) "
36
+ revoked: La vérification de %{revoked} utilisateurs ont été révoqués en utilisant [%{handler}] (%{count} détectés, %{errors} erreurs)
37
+ gdpr_disclaimer: Faites-le sous votre responsabilité. N'oubliez pas que vous devez avoir le consentement explicite de vos utilisateurs afin de les enregistrer. Dans le cas contraire, vous enfreindrez le règlement du RGPD dans les pays de l'UE.
38
+ index:
39
+ authorizations: Utilisateurs autorisés
40
+ stats: Statistiques des utilisateurs
41
+ title: Inscription et autorisation des utilisateurs
42
+ new:
43
+ authorization_handler: Méthode de vérification
44
+ authorize: Utilisateurs autorisés
45
+ check: Vérifier le statut des utilisateurs
46
+ info: Entrez les e-mails ici, un par ligne. Si les e-mails sont précédés d'un texte, il sera interprété comme le nom de l'utilisateur
47
+ register: Inscrire des utilisateurs sur la plateforme (si ils existent, ils seront ignorés)
48
+ revoke: Révoquer l'autorisation des utilisateurs
49
+ submit: Envoyer et traiter la liste
50
+ textarea: Liste d'emails
51
+ stats:
52
+ index:
53
+ authorized: Autorisé
54
+ authorized_unconfirmed: Autorisé mais non confirmé
55
+ global: "- N'importe quelle méthode de vérification -"
56
+ registered: Inscrits
57
+ unconfirmed: Non confirmé
58
+ authorizations:
59
+ new:
60
+ no_action: Cette méthode nécessite un administrateur qui vous vérifie
61
+ verifications:
62
+ authorizations:
63
+ first_login:
64
+ actions:
65
+ direct_verifications: Vérification directe
66
+ devise:
67
+ mailer:
68
+ direct_invite:
69
+ subject: Instructions d'invitation
@@ -1,12 +1,35 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "direct_verifications/version"
4
- require_relative "direct_verifications/config"
5
4
  require_relative "direct_verifications/user_processor"
6
5
  require_relative "direct_verifications/user_stats"
7
6
  require_relative "direct_verifications/verification"
7
+ require_relative "direct_verifications/parsers"
8
8
 
9
9
  module Decidim
10
10
  module DirectVerifications
11
+ include ActiveSupport::Configurable
12
+
13
+ class InputParserError < StandardError; end
14
+
15
+ # Specify in this variable which authorization methods can be managed by the plugin
16
+ # Be careful to specify only what you really need
17
+ config_accessor :manage_workflows do
18
+ ["direct_verifications"]
19
+ end
20
+
21
+ # The processor for the user uploaded data where to extract emails and other info
22
+ # be default it uses Decidim::DirectVerifications::Parsers::NameParser
23
+ # Currently available are:
24
+ # - :name_parser
25
+ # - :metadata_parser
26
+ # A custom parser can be specified as long it respects the module hierachy
27
+ config_accessor :input_parser do
28
+ :name_parser
29
+ end
30
+
31
+ def self.find_parser_class(manifest)
32
+ "Decidim::DirectVerifications::Parsers::#{manifest.to_s.camelize}".safe_constantize || Decidim::DirectVerifications::Parsers::NameParser
33
+ end
11
34
  end
12
35
  end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module DirectVerifications
5
+ class AuthorizeUser
6
+ # rubocop:disable Metrics/ParameterLists
7
+ def initialize(email, data, session, organization, instrumenter, authorization_handler)
8
+ @email = email
9
+ @data = data
10
+ @session = session
11
+ @organization = organization
12
+ @instrumenter = instrumenter
13
+ @authorization_handler = authorization_handler
14
+ end
15
+ # rubocop:enable Metrics/ParameterLists
16
+
17
+ def call
18
+ unless user
19
+ instrumenter.add_error :authorized, email
20
+ return
21
+ end
22
+
23
+ return unless valid_authorization?
24
+
25
+ Verification::ConfirmUserAuthorization.call(authorization, form, session) do
26
+ on(:ok) do
27
+ instrumenter.add_processed :authorized, email
28
+ end
29
+ on(:invalid) do
30
+ instrumenter.add_error :authorized, email
31
+ end
32
+ end
33
+ end
34
+
35
+ private
36
+
37
+ attr_reader :email, :data, :session, :organization, :instrumenter, :authorization_handler
38
+
39
+ def valid_authorization?
40
+ !authorization.granted? || authorization.expired?
41
+ end
42
+
43
+ def user
44
+ @user ||= User.find_by(email: email, decidim_organization_id: organization.id)
45
+ end
46
+
47
+ def authorization
48
+ @authorization ||=
49
+ begin
50
+ auth = Authorization.find_or_initialize_by(
51
+ user: user,
52
+ name: authorization_handler
53
+ )
54
+ auth.metadata = data
55
+ auth
56
+ end
57
+ end
58
+
59
+ def form
60
+ Verification::DirectVerificationsForm.new(email: user.email, name: user.name)
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module DirectVerifications
5
+ class Instrumenter
6
+ def initialize(current_user)
7
+ @current_user = current_user
8
+ @errors = { registered: Set.new, authorized: Set.new, revoked: Set.new }
9
+ @processed = { registered: Set.new, authorized: Set.new, revoked: Set.new }
10
+ end
11
+
12
+ def add_processed(type, email)
13
+ @processed[type] << email
14
+ end
15
+
16
+ def add_error(type, email)
17
+ @errors[type] << email
18
+ end
19
+
20
+ def processed_count(key)
21
+ processed[key].size
22
+ end
23
+
24
+ def errors_count(key)
25
+ errors[key].size
26
+ end
27
+
28
+ def emails_count(key)
29
+ @processed[key].size + @errors[key].size
30
+ end
31
+
32
+ def track(event, email, user = nil)
33
+ if user
34
+ add_processed event, email
35
+ log_action user
36
+ else
37
+ add_error event, email
38
+ end
39
+ end
40
+
41
+ private
42
+
43
+ attr_reader :current_user, :processed, :errors
44
+
45
+ def log_action(user)
46
+ Decidim.traceability.perform_action!(
47
+ "invite",
48
+ user,
49
+ current_user,
50
+ extra: {
51
+ invited_user_role: "participant",
52
+ invited_user_id: user.id
53
+ }
54
+ )
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module DirectVerifications
5
+ module Parsers
6
+ autoload :BaseParser, "decidim/direct_verifications/parsers/base_parser"
7
+ autoload :NameParser, "decidim/direct_verifications/parsers/name_parser"
8
+ autoload :MetadataParser, "decidim/direct_verifications/parsers/metadata_parser"
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module DirectVerifications
5
+ module Parsers
6
+ # Abstract class all concrete parsers should inherit from. They are expected to implement
7
+ # #header, #lines, and #parse_data methods.
8
+ class BaseParser
9
+ EMAIL_REGEXP = /([A-Z0-9+._-]+@[A-Z0-9._-]+\.[A-Z0-9_-]+)\b/i.freeze
10
+
11
+ def initialize(txt)
12
+ @txt = txt
13
+ @emails = {}
14
+ end
15
+
16
+ def to_h
17
+ lines.each do |line|
18
+ EMAIL_REGEXP.match(line) do |match|
19
+ email = normalize(match[0])
20
+ emails[email] = parse_data(email, line, header)
21
+ end
22
+ end
23
+
24
+ emails
25
+ end
26
+
27
+ private
28
+
29
+ attr_reader :txt, :emails
30
+
31
+ def normalize(value)
32
+ value.to_s.downcase
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "csv"
4
+
5
+ module Decidim
6
+ module DirectVerifications
7
+ module Parsers
8
+ class MetadataParser < BaseParser
9
+ I18N_SCOPE = "decidim.direct_verifications.verification.admin.direct_verifications"
10
+
11
+ def header
12
+ @header ||= begin
13
+ header_row = lines[0].chomp
14
+ header_row = tokenize(header_row)
15
+ normalize_header(header_row)
16
+ end
17
+ end
18
+
19
+ def lines
20
+ @lines ||= StringIO.new(txt).readlines
21
+ end
22
+
23
+ def parse_data(email, line, header)
24
+ tokens = tokenize(line)
25
+
26
+ hash = {}
27
+ header.each_with_index do |column, index|
28
+ value = tokens[index]
29
+ next if value&.include?(email)
30
+
31
+ hash[column] = value
32
+ end
33
+ hash
34
+ end
35
+
36
+ private
37
+
38
+ def tokenize(line)
39
+ CSV.parse_line(line).map do |token|
40
+ token&.strip
41
+ end
42
+ end
43
+
44
+ def normalize_header(line)
45
+ line.map do |field|
46
+ raise InputParserError, I18n.t("#{I18N_SCOPE}.create.missing_header") if field.nil?
47
+
48
+ field.to_sym.downcase
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module DirectVerifications
5
+ module Parsers
6
+ class NameParser < BaseParser
7
+ LINE_DELIMITER = /[\r\n;,]/.freeze
8
+ NON_ALPHA_CHARS = /[^[:print:]]|["$<>|\\]/.freeze
9
+
10
+ def header
11
+ nil
12
+ end
13
+
14
+ def lines
15
+ txt.split(LINE_DELIMITER)
16
+ end
17
+
18
+ def parse_data(email, line, _header)
19
+ name = parse_name(email, line)
20
+ name = strip_non_alpha_chars(name)
21
+ name.presence || fallback_name(email)
22
+ end
23
+
24
+ private
25
+
26
+ def strip_non_alpha_chars(str)
27
+ (str.presence || "").gsub(NON_ALPHA_CHARS, "").strip
28
+ end
29
+
30
+ def parse_name(email, line)
31
+ line.split(email).first
32
+ end
33
+
34
+ def fallback_name(email)
35
+ email.split("@").first
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module DirectVerifications
5
+ class RegisterUser
6
+ def initialize(email, data, organization, current_user, instrumenter)
7
+ @email = email
8
+ @name = data.is_a?(Hash) ? data[:name] : data
9
+ @organization = organization
10
+ @current_user = current_user
11
+ @instrumenter = instrumenter
12
+ end
13
+
14
+ def call
15
+ return if user
16
+
17
+ InviteUser.call(form) do
18
+ on(:ok) do
19
+ instrumenter.track(:registered, email, user)
20
+ end
21
+ on(:invalid) do
22
+ instrumenter.track(:registered, email)
23
+ end
24
+ end
25
+ rescue StandardError => e
26
+ instrumenter.track(:registered, email)
27
+ raise e if Rails.env.test? || Rails.env.development?
28
+ end
29
+
30
+ private
31
+
32
+ attr_reader :email, :name, :organization, :current_user, :instrumenter
33
+
34
+ def user
35
+ @user ||= User.find_by(email: email, decidim_organization_id: organization.id)
36
+ end
37
+
38
+ def form
39
+ RegistrationForm.new(
40
+ name: name.presence || fallback_name,
41
+ email: email.downcase,
42
+ organization: organization,
43
+ admin: false,
44
+ invited_by: current_user,
45
+ invitation_instructions: "direct_invite"
46
+ )
47
+ end
48
+
49
+ def fallback_name
50
+ email.split("@").first
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module DirectVerifications
5
+ class RevokeUser
6
+ def initialize(email, organization, instrumenter, authorization_handler)
7
+ @email = email
8
+ @organization = organization
9
+ @instrumenter = instrumenter
10
+ @authorization_handler = authorization_handler
11
+ end
12
+
13
+ def call
14
+ unless user
15
+ instrumenter.add_error :revoked, email
16
+ return
17
+ end
18
+
19
+ return unless valid_authorization?
20
+
21
+ Verification::DestroyUserAuthorization.call(authorization) do
22
+ on(:ok) do
23
+ instrumenter.add_processed :revoked, email
24
+ end
25
+ end
26
+ end
27
+
28
+ private
29
+
30
+ attr_reader :email, :organization, :instrumenter, :authorization_handler
31
+
32
+ def user
33
+ @user ||= User.find_by(email: email, decidim_organization_id: organization.id)
34
+ end
35
+
36
+ def authorization
37
+ @authorization ||= Authorization.find_by(user: user, name: authorization_handler)
38
+ end
39
+
40
+ def valid_authorization?
41
+ authorization&.granted?
42
+ end
43
+ end
44
+ end
45
+ end