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.
- checksums.yaml +4 -4
- data/README.md +32 -3
- data/app/assets/config/direct_verifications_admin_manifest.css +3 -0
- data/app/assets/stylesheets/decidim/direct_verifications/authorizations.scss +17 -0
- data/app/commands/decidim/direct_verifications/verification/create_import.rb +50 -0
- data/app/controllers/decidim/direct_verifications/verification/admin/authorizations_controller.rb +40 -0
- data/app/controllers/decidim/direct_verifications/verification/admin/direct_verifications_controller.rb +20 -18
- data/app/controllers/decidim/direct_verifications/verification/admin/imports_controller.rb +55 -0
- data/app/forms/decidim/direct_verifications/registration_form.rb +8 -0
- data/app/forms/decidim/direct_verifications/verification/create_import_form.rb +43 -0
- data/app/jobs/decidim/direct_verifications/authorize_users_job.rb +34 -0
- data/app/jobs/decidim/direct_verifications/base_import_job.rb +38 -0
- data/app/jobs/decidim/direct_verifications/register_users_job.rb +19 -0
- data/app/jobs/decidim/direct_verifications/revoke_users_job.rb +17 -0
- data/app/mailers/decidim/direct_verifications/import_mailer.rb +27 -0
- data/app/mailers/decidim/direct_verifications/stats.rb +23 -0
- data/app/views/decidim/direct_verifications/import_mailer/finished_processing.html.erb +7 -0
- data/app/views/decidim/direct_verifications/import_mailer/finished_processing.text.erb +7 -0
- data/app/views/decidim/direct_verifications/verification/admin/authorizations/index.html.erb +42 -0
- data/app/views/decidim/direct_verifications/verification/admin/direct_verifications/index.html.erb +2 -1
- data/app/views/decidim/direct_verifications/verification/admin/imports/new.html.erb +50 -0
- data/config/initializers/mail_previews.rb +5 -0
- data/config/locales/ca.yml +20 -20
- data/config/locales/cs.yml +69 -0
- data/config/locales/en.yml +29 -2
- data/config/locales/es.yml +20 -20
- data/config/locales/fr.yml +69 -0
- data/lib/decidim/direct_verifications.rb +24 -1
- data/lib/decidim/direct_verifications/authorize_user.rb +64 -0
- data/lib/decidim/direct_verifications/instrumenter.rb +58 -0
- data/lib/decidim/direct_verifications/parsers.rb +11 -0
- data/lib/decidim/direct_verifications/parsers/base_parser.rb +37 -0
- data/lib/decidim/direct_verifications/parsers/metadata_parser.rb +54 -0
- data/lib/decidim/direct_verifications/parsers/name_parser.rb +40 -0
- data/lib/decidim/direct_verifications/register_user.rb +54 -0
- data/lib/decidim/direct_verifications/revoke_user.rb +45 -0
- data/lib/decidim/direct_verifications/tests/factories.rb +11 -0
- data/lib/decidim/direct_verifications/tests/verification_controller_examples.rb +8 -3
- data/lib/decidim/direct_verifications/user_processor.rb +17 -101
- data/lib/decidim/direct_verifications/user_stats.rb +5 -5
- data/lib/decidim/direct_verifications/verification/admin_engine.rb +6 -1
- data/lib/decidim/direct_verifications/version.rb +3 -3
- metadata +37 -9
- 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
|