decidim-direct_verifications 0.17.8 → 1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +43 -2
  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 +71 -46
  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 +18 -17
  21. data/app/views/decidim/direct_verifications/verification/admin/imports/new.html.erb +50 -0
  22. data/app/views/decidim/direct_verifications/verification/admin/stats/index.html.erb +3 -3
  23. data/config/initializers/mail_previews.rb +5 -0
  24. data/config/locales/ca.yml +25 -20
  25. data/config/locales/cs.yml +69 -0
  26. data/config/locales/en.yml +34 -2
  27. data/config/locales/es.yml +25 -20
  28. data/config/locales/fr.yml +69 -0
  29. data/lib/decidim/direct_verifications.rb +24 -1
  30. data/lib/decidim/direct_verifications/authorize_user.rb +64 -0
  31. data/lib/decidim/direct_verifications/instrumenter.rb +58 -0
  32. data/lib/decidim/direct_verifications/parsers.rb +11 -0
  33. data/lib/decidim/direct_verifications/parsers/base_parser.rb +37 -0
  34. data/lib/decidim/direct_verifications/parsers/metadata_parser.rb +54 -0
  35. data/lib/decidim/direct_verifications/parsers/name_parser.rb +40 -0
  36. data/lib/decidim/direct_verifications/register_user.rb +54 -0
  37. data/lib/decidim/direct_verifications/revoke_user.rb +45 -0
  38. data/lib/decidim/direct_verifications/tests/factories.rb +11 -0
  39. data/lib/decidim/direct_verifications/tests/verification_controller_examples.rb +83 -0
  40. data/lib/decidim/direct_verifications/user_processor.rb +18 -98
  41. data/lib/decidim/direct_verifications/user_stats.rb +9 -6
  42. data/lib/decidim/direct_verifications/verification/admin_engine.rb +6 -1
  43. data/lib/decidim/direct_verifications/version.rb +3 -2
  44. metadata +38 -9
  45. data/lib/decidim/direct_verifications/config.rb +0 -23
@@ -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
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "decidim/core/test/factories"
4
+
5
+ FactoryBot.modify do
6
+ factory :authorization do
7
+ trait :direct_verification do
8
+ name { "direct_verifications" }
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ shared_examples_for "checking users" do |params|
4
+ context "when check without mails" do
5
+ before { params[:userslist] = "" }
6
+
7
+ it "renders the index with info message" do
8
+ perform_enqueued_jobs do
9
+ post :create, params: params
10
+ expect(flash[:info]).not_to be_empty
11
+ expect(flash[:info]).to include("0 users detected")
12
+ expect(subject).to render_template("decidim/direct_verifications/verification/admin/direct_verifications/index")
13
+ end
14
+ end
15
+ end
16
+
17
+ context "when check with mails" do
18
+ before { params[:userslist] = "mail@example.com" }
19
+
20
+ it "renders the index with info message" do
21
+ perform_enqueued_jobs do
22
+ post :create, params: params
23
+ expect(flash[:info]).not_to be_empty
24
+ expect(flash[:info]).to include("1 users detected")
25
+ expect(subject).to render_template("decidim/direct_verifications/verification/admin/direct_verifications/index")
26
+ end
27
+ end
28
+ end
29
+ end
30
+
31
+ shared_examples_for "registering users" do |params|
32
+ context "when there are valid emails" do
33
+ it "creates warning message" do
34
+ perform_enqueued_jobs do
35
+ post :create, params: params
36
+ expect(flash[:warning]).not_to be_empty
37
+ expect(flash[:warning]).to include("1 detected")
38
+ expect(flash[:warning]).to include("0 errors")
39
+ expect(flash[:warning]).to include("1 users")
40
+ expect(flash[:warning]).to include("registered")
41
+ end
42
+ end
43
+ end
44
+ end
45
+
46
+ shared_examples_for "authorizing users" do |params|
47
+ context "when there are valid emails" do
48
+ it "creates notice message" do
49
+ perform_enqueued_jobs do
50
+ post :create, params: params
51
+ expect(flash[:notice]).not_to be_empty
52
+ expect(flash[:notice]).to include("1 detected")
53
+ expect(flash[:notice]).to include("0 errors")
54
+ expect(flash[:notice]).to include("1 users")
55
+ expect(flash[:notice]).to include("verified")
56
+ end
57
+ end
58
+ end
59
+ end
60
+
61
+ shared_examples_for "revoking users" do |params|
62
+ context "when there are valid emails" do
63
+ before do
64
+ create(
65
+ :authorization,
66
+ :granted,
67
+ name: verification_type,
68
+ user: authorized_user
69
+ )
70
+ end
71
+
72
+ it "creates notice message" do
73
+ perform_enqueued_jobs do
74
+ post :create, params: params
75
+ expect(flash[:notice]).not_to be_empty
76
+ expect(flash[:notice]).to include("1 detected")
77
+ expect(flash[:notice]).to include("0 errors")
78
+ expect(flash[:notice]).to include("1 users")
79
+ expect(flash[:notice]).to include("revoked")
80
+ end
81
+ end
82
+ end
83
+ end
@@ -1,127 +1,47 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "decidim/direct_verifications/register_user"
4
+ require "decidim/direct_verifications/authorize_user"
5
+ require "decidim/direct_verifications/revoke_user"
6
+ require "decidim/direct_verifications/instrumenter"
7
+
3
8
  module Decidim
4
9
  module DirectVerifications
5
10
  class UserProcessor
6
- def initialize(organization, current_user)
11
+ def initialize(organization, current_user, session, instrumenter)
7
12
  @organization = organization
8
13
  @current_user = current_user
9
14
  @authorization_handler = :direct_verifications
10
- @errors = { registered: [], authorized: [], revoked: [] }
11
- @processed = { registered: [], authorized: [], revoked: [] }
15
+
12
16
  @emails = {}
17
+ @session = session
18
+ @instrumenter = instrumenter
13
19
  end
14
20
 
15
- attr_reader :organization, :current_user, :errors, :processed, :emails
16
- attr_accessor :authorization_handler
17
-
18
- def emails=(email_list)
19
- @emails = email_list.map { |k, v| [k.to_s.downcase, v.presence || k.split("@").first] }.to_h
20
- end
21
+ attr_reader :organization, :current_user, :session, :errors, :processed
22
+ attr_accessor :authorization_handler, :emails
21
23
 
22
24
  def register_users
23
- @emails.each do |email, name|
24
- next if find_user(email)
25
- form = register_form(email, name)
26
- begin
27
- InviteUser.call(form) do
28
- on(:ok) do
29
- add_processed :registered, email
30
- log_action find_user(email)
31
- end
32
- on(:invalid) do
33
- add_error :registered, email
34
- end
35
- end
36
- rescue StandardError
37
- add_error :registered, email
38
- end
25
+ emails.each do |email, data|
26
+ RegisterUser.new(email, data, organization, current_user, instrumenter).call
39
27
  end
40
28
  end
41
29
 
42
30
  def authorize_users
43
- @emails.each do |email, _name|
44
- if (u = find_user(email))
45
- auth = authorization(u)
46
- next unless !auth.granted? || auth.expired?
47
- Verification::ConfirmUserAuthorization.call(auth, authorize_form(u)) do
48
- on(:ok) do
49
- add_processed :authorized, email
50
- end
51
- on(:invalid) do
52
- add_error :authorized, email
53
- end
54
- end
55
- else
56
- add_error :authorized, email
57
- end
31
+ emails.each do |email, data|
32
+ AuthorizeUser.new(email, data, session, organization, instrumenter, authorization_handler).call
58
33
  end
59
34
  end
60
35
 
61
36
  def revoke_users
62
- @emails.each do |email, _name|
63
- if (u = find_user(email))
64
- auth = authorization(u)
65
- next unless auth.granted?
66
- Verification::DestroyUserAuthorization.call(auth) do
67
- on(:ok) do
68
- add_processed :revoked, email
69
- end
70
- on(:invalid) do
71
- add_error :revoked, email
72
- end
73
- end
74
- else
75
- add_error :revoked, email
76
- end
37
+ emails.each do |email, _name|
38
+ RevokeUser.new(email, organization, instrumenter, authorization_handler).call
77
39
  end
78
40
  end
79
41
 
80
42
  private
81
43
 
82
- def find_user(email)
83
- User.find_by(email: email, decidim_organization_id: @organization.id)
84
- end
85
-
86
- def register_form(email, name)
87
- OpenStruct.new(name: name.presence || email.split("@").first,
88
- email: email.downcase,
89
- organization: organization,
90
- admin: false,
91
- invited_by: current_user,
92
- invitation_instructions: "direct_invite")
93
- end
94
-
95
- def authorization(user)
96
- Authorization.find_or_initialize_by(
97
- user: user,
98
- name: authorization_handler
99
- )
100
- end
101
-
102
- def authorize_form(user)
103
- Verification::DirectVerificationsForm.new(email: user.email, name: user.name)
104
- end
105
-
106
- def add_processed(type, email)
107
- @processed[type] << email unless @processed[type].include? email
108
- end
109
-
110
- def add_error(type, email)
111
- @errors[type] << email unless @errors[type].include? email
112
- end
113
-
114
- def log_action(user)
115
- Decidim.traceability.perform_action!(
116
- "invite",
117
- user,
118
- current_user,
119
- extra: {
120
- invited_user_role: "participant",
121
- invited_user_id: user.id
122
- }
123
- )
124
- end
44
+ attr_reader :instrumenter
125
45
  end
126
46
  end
127
47
  end