decidim-direct_verifications 0.17.8 → 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 +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
|