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.
- checksums.yaml +4 -4
- data/README.md +43 -2
- 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 +71 -46
- 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 +18 -17
- data/app/views/decidim/direct_verifications/verification/admin/imports/new.html.erb +50 -0
- data/app/views/decidim/direct_verifications/verification/admin/stats/index.html.erb +3 -3
- data/config/initializers/mail_previews.rb +5 -0
- data/config/locales/ca.yml +25 -20
- data/config/locales/cs.yml +69 -0
- data/config/locales/en.yml +34 -2
- data/config/locales/es.yml +25 -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 +83 -0
- data/lib/decidim/direct_verifications/user_processor.rb +18 -98
- data/lib/decidim/direct_verifications/user_stats.rb +9 -6
- data/lib/decidim/direct_verifications/verification/admin_engine.rb +6 -1
- data/lib/decidim/direct_verifications/version.rb +3 -2
- metadata +38 -9
- 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,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
|
-
|
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, :
|
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
|
-
|
24
|
-
|
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
|
-
|
44
|
-
|
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
|
-
|
63
|
-
|
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
|
-
|
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
|