decidim-bulletin_board 0.9.2 → 0.10.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +13 -1
- data/Gemfile.lock +56 -56
- data/app/assets/javascripts/decidim/bulletin_board/decidim-bulletin_board.js +279 -2
- data/bin/release +8 -0
- data/lib/decidim/bulletin_board.rb +13 -17
- data/lib/decidim/bulletin_board/authority/create_election.rb +101 -2
- data/lib/decidim/bulletin_board/authority/end_vote.rb +1 -1
- data/lib/decidim/bulletin_board/authority/get_election_results.rb +63 -0
- data/lib/decidim/bulletin_board/authority/get_election_status.rb +1 -1
- data/lib/decidim/bulletin_board/authority/publish_results.rb +1 -1
- data/lib/decidim/bulletin_board/authority/start_key_ceremony.rb +1 -1
- data/lib/decidim/bulletin_board/authority/start_tally.rb +1 -1
- data/lib/decidim/bulletin_board/authority/start_vote.rb +1 -1
- data/lib/decidim/bulletin_board/client.rb +33 -38
- data/lib/decidim/bulletin_board/command.rb +11 -26
- data/lib/decidim/bulletin_board/graphql/bb_schema.json +19 -1
- data/lib/decidim/bulletin_board/graphql/factory.rb +18 -0
- data/lib/decidim/bulletin_board/settings.rb +49 -0
- data/lib/decidim/bulletin_board/version.rb +1 -1
- data/lib/decidim/bulletin_board/voter/cast_vote.rb +1 -1
- data/lib/decidim/bulletin_board/voter/get_pending_message_status.rb +1 -1
- metadata +6 -6
- data/lib/decidim/bulletin_board/authority.rb +0 -10
- data/lib/decidim/bulletin_board/authority/get_election_log_entries_by_types.rb +0 -51
- data/lib/decidim/bulletin_board/graphql/client.rb +0 -18
- data/lib/decidim/bulletin_board/voter.rb +0 -4
data/bin/release
ADDED
@@ -7,14 +7,11 @@ require "jwt"
|
|
7
7
|
require "graphlient"
|
8
8
|
require "wisper"
|
9
9
|
|
10
|
+
require "decidim/bulletin_board/client"
|
10
11
|
require "decidim/bulletin_board/engine"
|
11
12
|
require "decidim/bulletin_board/jwk_utils"
|
12
13
|
require "decidim/bulletin_board/message_identifier"
|
13
14
|
|
14
|
-
require "decidim/bulletin_board/client"
|
15
|
-
require "decidim/bulletin_board/authority"
|
16
|
-
require "decidim/bulletin_board/voter"
|
17
|
-
|
18
15
|
module Decidim
|
19
16
|
# This module holds all the logic for the Bulletin Board Ruby Client to connect
|
20
17
|
# a Decidim instance with a Bulletin Board server
|
@@ -27,26 +24,25 @@ module Decidim
|
|
27
24
|
# The BulletinBoard server (String)
|
28
25
|
config_accessor :server
|
29
26
|
|
27
|
+
# The public key (JSON) of the Bulletin Board server
|
28
|
+
config_accessor :server_public_key
|
29
|
+
|
30
30
|
# The api key generated by the Bulletin Board for the Decidim authority (String)
|
31
31
|
config_accessor :api_key
|
32
32
|
|
33
|
-
# The scheme: scheme name and scheme parameters, e.g. quorum for Electionguard
|
34
|
-
# Example:
|
35
|
-
# {
|
36
|
-
# name: "Dummy",
|
37
|
-
# parameters: {
|
38
|
-
# quorum: 2
|
39
|
-
# }
|
40
|
-
# }
|
41
|
-
config_accessor :scheme
|
42
|
-
|
43
33
|
# The authority name (String)
|
44
34
|
config_accessor :authority_name
|
45
35
|
|
46
|
-
# The number of trustees for an election (Int). Must be higher than the schemes' quorum
|
47
|
-
config_accessor :number_of_trustees
|
48
|
-
|
49
36
|
# The identification private key (JSON) for your Decidim instance
|
50
37
|
config_accessor :identification_private_key
|
38
|
+
|
39
|
+
# The voting scheme name
|
40
|
+
config_accessor :scheme_name
|
41
|
+
|
42
|
+
# The number of trustees for an election (Int). Must be higher than the quorum
|
43
|
+
config_accessor :number_of_trustees
|
44
|
+
|
45
|
+
# The quorum needed to start the tally
|
46
|
+
config_accessor :quorum
|
51
47
|
end
|
52
48
|
end
|
@@ -19,10 +19,10 @@ module Decidim
|
|
19
19
|
# arguments used inside the graphql operation
|
20
20
|
args = {
|
21
21
|
message_id: message_id,
|
22
|
-
signed_data: sign_message(message_id,
|
22
|
+
signed_data: sign_message(message_id, message)
|
23
23
|
}
|
24
24
|
|
25
|
-
response =
|
25
|
+
response = graphql.query do
|
26
26
|
mutation do
|
27
27
|
createElection(messageId: args[:message_id], signedData: args[:signed_data]) do
|
28
28
|
election do
|
@@ -43,6 +43,105 @@ module Decidim
|
|
43
43
|
private
|
44
44
|
|
45
45
|
attr_reader :election_data, :election_id
|
46
|
+
|
47
|
+
def message
|
48
|
+
{
|
49
|
+
scheme: scheme,
|
50
|
+
bulletin_board: bulletin_board,
|
51
|
+
authority: authority,
|
52
|
+
trustees: trustees,
|
53
|
+
description: {
|
54
|
+
name: text(election_data[:title]),
|
55
|
+
start_date: election_data[:start_date].iso8601,
|
56
|
+
end_date: election_data[:end_date].iso8601,
|
57
|
+
candidates: candidates,
|
58
|
+
contests: contests
|
59
|
+
}
|
60
|
+
}
|
61
|
+
end
|
62
|
+
|
63
|
+
def scheme
|
64
|
+
{
|
65
|
+
name: settings.scheme_name,
|
66
|
+
quorum: settings.quorum
|
67
|
+
}
|
68
|
+
end
|
69
|
+
|
70
|
+
def bulletin_board
|
71
|
+
{
|
72
|
+
name: "Bulletin Board",
|
73
|
+
slug: "bulletin-board",
|
74
|
+
public_key: settings.server_public_key
|
75
|
+
}
|
76
|
+
end
|
77
|
+
|
78
|
+
def authority
|
79
|
+
{
|
80
|
+
name: settings.authority_name,
|
81
|
+
slug: settings.authority_slug,
|
82
|
+
public_key: settings.public_key
|
83
|
+
}
|
84
|
+
end
|
85
|
+
|
86
|
+
def trustees
|
87
|
+
election_data[:trustees].map do |trustee|
|
88
|
+
{
|
89
|
+
name: trustee[:name],
|
90
|
+
slug: trustee[:slug],
|
91
|
+
public_key: trustee[:public_key]
|
92
|
+
}
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def contests
|
97
|
+
election_data[:questions].map do |question|
|
98
|
+
{
|
99
|
+
"@type": "CandidateContest",
|
100
|
+
object_id: question[:slug],
|
101
|
+
sequence_order: question[:weight],
|
102
|
+
vote_variation: question[:max_selections] == 1 ? "one_of_m" : "n_of_m",
|
103
|
+
name: default_text(question[:title]),
|
104
|
+
number_elected: question[:answers].count,
|
105
|
+
votes_allowed: 1,
|
106
|
+
ballot_title: text(question[:title]),
|
107
|
+
ballot_subtitle: text(question[:description]),
|
108
|
+
ballot_selections: contest_answers(question)
|
109
|
+
}
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def contest_answers(question)
|
114
|
+
question[:answers].map do |answer|
|
115
|
+
{
|
116
|
+
object_id: "#{question[:slug]}_#{answer[:slug]}",
|
117
|
+
sequence_order: answer[:weight],
|
118
|
+
candidate_id: answer[:slug]
|
119
|
+
}
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def candidates
|
124
|
+
election_data[:answers].map do |answer|
|
125
|
+
{
|
126
|
+
object_id: answer[:slug],
|
127
|
+
ballot_name: text(answer[:title])
|
128
|
+
}
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
def default_text(field)
|
133
|
+
field[default_locale]
|
134
|
+
end
|
135
|
+
|
136
|
+
def text(field)
|
137
|
+
{
|
138
|
+
text: field.map { |locale, value| { language: locale.to_s, value: value } }
|
139
|
+
}
|
140
|
+
end
|
141
|
+
|
142
|
+
def default_locale
|
143
|
+
@default_locale ||= election_data[:default_locale].to_sym
|
144
|
+
end
|
46
145
|
end
|
47
146
|
end
|
48
147
|
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
module BulletinBoard
|
5
|
+
module Authority
|
6
|
+
# This command uses the GraphQL client to get the results of an election.
|
7
|
+
class GetElectionResults < Decidim::BulletinBoard::Command
|
8
|
+
# Public: Initializes the command.
|
9
|
+
#
|
10
|
+
# election_id [String] - The local election identifier
|
11
|
+
def initialize(election_id)
|
12
|
+
@election_id = election_id
|
13
|
+
end
|
14
|
+
|
15
|
+
# Executes the command. Broadcasts these events:
|
16
|
+
#
|
17
|
+
# - :ok when everything is valid and the query operation is successful.
|
18
|
+
# - :error if query operation was not successful.
|
19
|
+
#
|
20
|
+
# Returns nothing.
|
21
|
+
def call
|
22
|
+
# arguments used inside the graphql operation
|
23
|
+
# unique_id [String] as election identifier
|
24
|
+
# types [Array of Strings] to filter election log entries by their type
|
25
|
+
args = {
|
26
|
+
unique_id: unique_election_id(election_id),
|
27
|
+
types: ["end_tally"]
|
28
|
+
}
|
29
|
+
|
30
|
+
response = graphql.query do
|
31
|
+
query do
|
32
|
+
election(uniqueId: args[:unique_id]) do
|
33
|
+
logEntries(types: args[:types]) do
|
34
|
+
signedData
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
return broadcast(:error, "There aren't any log entries with type: 'end_tally' for this election.") if response.data.election.log_entries.empty?
|
41
|
+
|
42
|
+
@signed_data = response.data.election.log_entries.first.signed_data
|
43
|
+
|
44
|
+
broadcast(:ok, decoded_data["results"])
|
45
|
+
rescue Graphlient::Errors::ServerError
|
46
|
+
broadcast(:error, "Sorry, something went wrong")
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
attr_reader :election_id, :types, :signed_data
|
52
|
+
|
53
|
+
def decoded_data
|
54
|
+
@decoded_data ||= begin
|
55
|
+
JWT.decode(signed_data, settings.server_public_key_rsa, true, algorithm: "RS256").first
|
56
|
+
rescue JWT::VerificationError, JWT::DecodeError, JWT::InvalidIatError, JWT::InvalidPayload => e
|
57
|
+
{ error: e.message }
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -1,39 +1,33 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "decidim/bulletin_board/command"
|
4
|
+
require "decidim/bulletin_board/graphql/factory"
|
5
|
+
require "decidim/bulletin_board/settings"
|
6
|
+
|
7
|
+
require "decidim/bulletin_board/authority/create_election"
|
8
|
+
require "decidim/bulletin_board/authority/end_vote"
|
9
|
+
require "decidim/bulletin_board/authority/get_election_status"
|
10
|
+
require "decidim/bulletin_board/authority/start_key_ceremony"
|
11
|
+
require "decidim/bulletin_board/authority/start_tally"
|
12
|
+
require "decidim/bulletin_board/authority/start_vote"
|
13
|
+
require "decidim/bulletin_board/authority/publish_results"
|
14
|
+
require "decidim/bulletin_board/authority/get_election_results"
|
15
|
+
require "decidim/bulletin_board/voter/cast_vote"
|
16
|
+
require "decidim/bulletin_board/voter/get_pending_message_status"
|
4
17
|
|
5
18
|
module Decidim
|
6
19
|
module BulletinBoard
|
7
20
|
# The Bulletin Board client
|
8
21
|
class Client
|
9
|
-
def initialize
|
10
|
-
@
|
11
|
-
@
|
12
|
-
@scheme = BulletinBoard.scheme.presence
|
13
|
-
@authority_name = BulletinBoard.authority_name.presence
|
14
|
-
@number_of_trustees = BulletinBoard.number_of_trustees.presence
|
15
|
-
@identification_private_key = BulletinBoard.identification_private_key.presence
|
16
|
-
@private_key = identification_private_key_content if identification_private_key
|
22
|
+
def initialize(config = Decidim::BulletinBoard)
|
23
|
+
@settings = Settings.new(config)
|
24
|
+
@graphql = Graphql::Factory.client_for(settings)
|
17
25
|
end
|
18
26
|
|
19
|
-
|
20
|
-
|
21
|
-
delegate :authority_slug, to: Decidim::BulletinBoard::Command
|
22
|
-
|
23
|
-
def quorum
|
24
|
-
@scheme.dig(:parameters, :quorum) || number_of_trustees
|
25
|
-
end
|
26
|
-
|
27
|
-
def public_key
|
28
|
-
private_key&.export
|
29
|
-
end
|
30
|
-
|
31
|
-
def configured?
|
32
|
-
private_key && server && api_key
|
33
|
-
end
|
27
|
+
delegate :configured?, :server, :public_key, :authority_name, :number_of_trustees, :quorum, to: :settings
|
34
28
|
|
35
29
|
def create_election(election_id, election_data)
|
36
|
-
create_election =
|
30
|
+
create_election = configure Authority::CreateElection.new(election_id, election_data)
|
37
31
|
yield create_election.message_id if block_given?
|
38
32
|
create_election.on(:ok) { |election| return election }
|
39
33
|
create_election.on(:error) { |error_message| raise StandardError, error_message }
|
@@ -41,7 +35,7 @@ module Decidim
|
|
41
35
|
end
|
42
36
|
|
43
37
|
def start_key_ceremony(election_id)
|
44
|
-
start_key_ceremony =
|
38
|
+
start_key_ceremony = configure Authority::StartKeyCeremony.new(election_id)
|
45
39
|
yield start_key_ceremony.message_id if block_given?
|
46
40
|
start_key_ceremony.on(:ok) { |pending_message| return pending_message }
|
47
41
|
start_key_ceremony.on(:error) { |error_message| raise StandardError, error_message }
|
@@ -49,7 +43,7 @@ module Decidim
|
|
49
43
|
end
|
50
44
|
|
51
45
|
def start_vote(election_id)
|
52
|
-
start_vote =
|
46
|
+
start_vote = configure Authority::StartVote.new(election_id)
|
53
47
|
yield start_vote.message_id if block_given?
|
54
48
|
start_vote.on(:ok) { |pending_message| return pending_message }
|
55
49
|
start_vote.on(:error) { |error_message| raise StandardError, error_message }
|
@@ -57,7 +51,7 @@ module Decidim
|
|
57
51
|
end
|
58
52
|
|
59
53
|
def cast_vote(election_id, voter_id, encrypted_vote)
|
60
|
-
cast_vote =
|
54
|
+
cast_vote = configure Voter::CastVote.new(election_id, voter_id, encrypted_vote)
|
61
55
|
yield cast_vote.message_id if block_given?
|
62
56
|
cast_vote.on(:ok) { |pending_message| return pending_message }
|
63
57
|
cast_vote.on(:error) { |error_message| raise StandardError, error_message }
|
@@ -65,14 +59,14 @@ module Decidim
|
|
65
59
|
end
|
66
60
|
|
67
61
|
def get_pending_message_status(message_id)
|
68
|
-
get_pending_message_status =
|
62
|
+
get_pending_message_status = configure Voter::GetPendingMessageStatus.new(message_id)
|
69
63
|
get_pending_message_status.on(:ok) { |status| return status }
|
70
64
|
get_pending_message_status.on(:error) { |error_message| raise StandardError, error_message }
|
71
65
|
get_pending_message_status.call
|
72
66
|
end
|
73
67
|
|
74
68
|
def end_vote(election_id)
|
75
|
-
end_vote =
|
69
|
+
end_vote = configure Authority::EndVote.new(election_id)
|
76
70
|
yield end_vote.message_id if block_given?
|
77
71
|
end_vote.on(:ok) { |pending_message| return pending_message }
|
78
72
|
end_vote.on(:error) { |error_message| raise StandardError, error_message }
|
@@ -80,29 +74,29 @@ module Decidim
|
|
80
74
|
end
|
81
75
|
|
82
76
|
def get_election_status(election_id)
|
83
|
-
get_election_status =
|
77
|
+
get_election_status = configure Authority::GetElectionStatus.new(election_id)
|
84
78
|
get_election_status.on(:ok) { |status| return status }
|
85
79
|
get_election_status.on(:error) { |error_message| raise StandardError, error_message }
|
86
80
|
get_election_status.call
|
87
81
|
end
|
88
82
|
|
89
83
|
def start_tally(election_id)
|
90
|
-
start_tally =
|
84
|
+
start_tally = configure Authority::StartTally.new(election_id)
|
91
85
|
yield start_tally.message_id if block_given?
|
92
86
|
start_tally.on(:ok) { |pending_message| return pending_message }
|
93
87
|
start_tally.on(:error) { |error_message| raise StandardError, error_message }
|
94
88
|
start_tally.call
|
95
89
|
end
|
96
90
|
|
97
|
-
def
|
98
|
-
get_log_entries =
|
99
|
-
get_log_entries.on(:ok) { |
|
91
|
+
def get_election_results(election_id)
|
92
|
+
get_log_entries = configure Authority::GetElectionResults.new(election_id)
|
93
|
+
get_log_entries.on(:ok) { |result| return result }
|
100
94
|
get_log_entries.on(:error) { |error_message| raise StandardError, error_message }
|
101
95
|
get_log_entries.call
|
102
96
|
end
|
103
97
|
|
104
98
|
def publish_results(election_id)
|
105
|
-
publish_results =
|
99
|
+
publish_results = configure Authority::PublishResults.new(election_id)
|
106
100
|
yield publish_results.message_id if block_given?
|
107
101
|
publish_results.on(:ok) { |status| return status }
|
108
102
|
publish_results.on(:error) { |error_message| raise StandardError, error_message }
|
@@ -111,10 +105,11 @@ module Decidim
|
|
111
105
|
|
112
106
|
private
|
113
107
|
|
114
|
-
attr_reader :
|
108
|
+
attr_reader :settings, :graphql
|
115
109
|
|
116
|
-
def
|
117
|
-
|
110
|
+
def configure(command)
|
111
|
+
command.configure(settings, graphql)
|
112
|
+
command
|
118
113
|
end
|
119
114
|
end
|
120
115
|
end
|