decidim-bulletin_board 0.8.2 → 0.10.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +4 -1
- data/CHANGELOG.md +41 -1
- data/Gemfile.lock +59 -62
- data/app/assets/config/{decidim_bulletin_board_manifest.js → manifest.js} +0 -0
- data/app/assets/javascripts/decidim/bulletin_board/decidim-bulletin_board.js +279 -2
- data/bin/release +10 -0
- data/lib/decidim/bulletin_board.rb +13 -17
- data/lib/decidim/bulletin_board/authority/create_election.rb +121 -16
- data/lib/decidim/bulletin_board/authority/end_vote.rb +57 -0
- data/lib/decidim/bulletin_board/authority/get_election_results.rb +63 -0
- data/lib/decidim/bulletin_board/authority/get_election_status.rb +12 -11
- data/lib/decidim/bulletin_board/authority/publish_results.rb +22 -16
- data/lib/decidim/bulletin_board/authority/start_key_ceremony.rb +57 -0
- data/lib/decidim/bulletin_board/authority/start_tally.rb +22 -16
- data/lib/decidim/bulletin_board/authority/start_vote.rb +57 -0
- data/lib/decidim/bulletin_board/client.rb +58 -48
- data/lib/decidim/bulletin_board/command.rb +11 -26
- data/lib/decidim/bulletin_board/graphql/bb_schema.json +205 -87
- 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 +13 -8
- data/lib/decidim/bulletin_board/voter/get_pending_message_status.rb +1 -1
- metadata +13 -12
- data/app/assets/javascripts/decidim/bulletin_board/decidim-bulletin_board.dev.js +0 -18045
- data/lib/decidim/bulletin_board/authority.rb +0 -8
- data/lib/decidim/bulletin_board/authority/close_ballot_box.rb +0 -51
- data/lib/decidim/bulletin_board/authority/open_ballot_box.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
|
@@ -10,33 +10,138 @@ module Decidim
|
|
10
10
|
@election_data = election_data
|
11
11
|
end
|
12
12
|
|
13
|
+
# Returns the message_id related to the operation
|
14
|
+
def message_id
|
15
|
+
@message_id ||= build_message_id(unique_election_id(election_id), "create_election")
|
16
|
+
end
|
17
|
+
|
13
18
|
def call
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
19
|
+
# arguments used inside the graphql operation
|
20
|
+
args = {
|
21
|
+
message_id: message_id,
|
22
|
+
signed_data: sign_message(message_id, message)
|
23
|
+
}
|
24
|
+
|
25
|
+
response = graphql.query do
|
26
|
+
mutation do
|
27
|
+
createElection(messageId: args[:message_id], signedData: args[:signed_data]) do
|
28
|
+
election do
|
29
|
+
status
|
25
30
|
end
|
31
|
+
error
|
26
32
|
end
|
27
33
|
end
|
34
|
+
end
|
28
35
|
|
29
|
-
|
36
|
+
return broadcast(:error, response.data.create_election.error) if response.data.create_election.error.present?
|
30
37
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
end
|
38
|
+
broadcast(:ok, response.data.create_election.election)
|
39
|
+
rescue Graphlient::Errors::ServerError
|
40
|
+
broadcast(:error, "Sorry, something went wrong")
|
35
41
|
end
|
36
42
|
|
37
43
|
private
|
38
44
|
|
39
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
|
40
145
|
end
|
41
146
|
end
|
42
147
|
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
module BulletinBoard
|
5
|
+
module Authority
|
6
|
+
# This command uses the GraphQL client to request the ending of the voting period.
|
7
|
+
class EndVote < Decidim::BulletinBoard::Command
|
8
|
+
# Public: Initializes the command.
|
9
|
+
#
|
10
|
+
# election_id - The local election identifier
|
11
|
+
def initialize(election_id)
|
12
|
+
@election_id = election_id
|
13
|
+
end
|
14
|
+
|
15
|
+
# Returns the message_id related to the operation
|
16
|
+
def message_id
|
17
|
+
@message_id ||= build_message_id(unique_election_id(election_id), "end_vote")
|
18
|
+
end
|
19
|
+
|
20
|
+
# Executes the command. Broadcasts these events:
|
21
|
+
#
|
22
|
+
# - :ok when everything is valid and the query operation is successful.
|
23
|
+
# - :error if query operation was not successful.
|
24
|
+
#
|
25
|
+
# Returns nothing.
|
26
|
+
def call
|
27
|
+
# arguments used inside the graphql operation
|
28
|
+
args = {
|
29
|
+
message_id: message_id,
|
30
|
+
signed_data: sign_message(message_id, {})
|
31
|
+
}
|
32
|
+
|
33
|
+
response = graphql.query do
|
34
|
+
mutation do
|
35
|
+
endVote(messageId: args[:message_id], signedData: args[:signed_data]) do
|
36
|
+
pendingMessage do
|
37
|
+
status
|
38
|
+
end
|
39
|
+
error
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
return broadcast(:error, response.data.end_vote.error) if response.data.end_vote.error.present?
|
45
|
+
|
46
|
+
broadcast(:ok, response.data.end_vote.pending_message)
|
47
|
+
rescue Graphlient::Errors::FaradayServerError
|
48
|
+
broadcast(:error, "Sorry, something went wrong")
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
attr_reader :election_id
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
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
|
@@ -19,21 +19,22 @@ module Decidim
|
|
19
19
|
#
|
20
20
|
# Returns nothing.
|
21
21
|
def call
|
22
|
-
|
22
|
+
# arguments used inside the graphql operation
|
23
|
+
args = {
|
24
|
+
unique_id: unique_election_id(election_id)
|
25
|
+
}
|
23
26
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
status
|
29
|
-
end
|
27
|
+
response = graphql.query do
|
28
|
+
query do
|
29
|
+
election(uniqueId: args[:unique_id]) do
|
30
|
+
status
|
30
31
|
end
|
31
32
|
end
|
32
|
-
|
33
|
-
broadcast(:ok, response.data.election.status)
|
34
|
-
rescue Graphlient::Errors::ServerError
|
35
|
-
broadcast(:error, "Sorry, something went wrong")
|
36
33
|
end
|
34
|
+
|
35
|
+
broadcast(:ok, response.data.election.status)
|
36
|
+
rescue Graphlient::Errors::ServerError
|
37
|
+
broadcast(:error, "Sorry, something went wrong")
|
37
38
|
end
|
38
39
|
|
39
40
|
private
|
@@ -12,6 +12,11 @@ module Decidim
|
|
12
12
|
@election_id = election_id
|
13
13
|
end
|
14
14
|
|
15
|
+
# Returns the message_id related to the operation
|
16
|
+
def message_id
|
17
|
+
@message_id ||= build_message_id(unique_election_id(election_id), "publish_results")
|
18
|
+
end
|
19
|
+
|
15
20
|
# Executes the command. Broadcasts these events:
|
16
21
|
#
|
17
22
|
# - :ok when everything is valid and the query operation is successful.
|
@@ -19,27 +24,28 @@ module Decidim
|
|
19
24
|
#
|
20
25
|
# Returns nothing.
|
21
26
|
def call
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
27
|
+
# arguments used inside the graphql operation
|
28
|
+
args = {
|
29
|
+
message_id: message_id,
|
30
|
+
signed_data: sign_message(message_id, {})
|
31
|
+
}
|
32
|
+
|
33
|
+
response = graphql.query do
|
34
|
+
mutation do
|
35
|
+
publishResults(messageId: args[:message_id], signedData: args[:signed_data]) do
|
36
|
+
election do
|
37
|
+
status
|
33
38
|
end
|
39
|
+
error
|
34
40
|
end
|
35
41
|
end
|
42
|
+
end
|
36
43
|
|
37
|
-
|
44
|
+
return broadcast(:error, response.data.publish_results.error) if response.data.publish_results.error.present?
|
38
45
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
end
|
46
|
+
broadcast(:ok, response.data.publish_results.election)
|
47
|
+
rescue Graphlient::Errors::ServerError
|
48
|
+
broadcast(:error, "Sorry, something went wrong")
|
43
49
|
end
|
44
50
|
|
45
51
|
private
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
module BulletinBoard
|
5
|
+
module Authority
|
6
|
+
# This command uses the GraphQL client to request the starting of the key ceremony.
|
7
|
+
class StartKeyCeremony < Decidim::BulletinBoard::Command
|
8
|
+
# Public: Initializes the command.
|
9
|
+
#
|
10
|
+
# election_id - The local election identifier
|
11
|
+
def initialize(election_id)
|
12
|
+
@election_id = election_id
|
13
|
+
end
|
14
|
+
|
15
|
+
# Returns the message_id related to the operation
|
16
|
+
def message_id
|
17
|
+
@message_id ||= build_message_id(unique_election_id(election_id), "start_key_ceremony")
|
18
|
+
end
|
19
|
+
|
20
|
+
# Executes the command. Broadcasts these events:
|
21
|
+
#
|
22
|
+
# - :ok when everything is valid and the query operation is successful.
|
23
|
+
# - :error if query operation was not successful.
|
24
|
+
#
|
25
|
+
# Returns nothing.
|
26
|
+
def call
|
27
|
+
# arguments used inside the graphql operation
|
28
|
+
args = {
|
29
|
+
message_id: message_id,
|
30
|
+
signed_data: sign_message(message_id, {})
|
31
|
+
}
|
32
|
+
|
33
|
+
response = graphql.query do
|
34
|
+
mutation do
|
35
|
+
startKeyCeremony(messageId: args[:message_id], signedData: args[:signed_data]) do
|
36
|
+
pendingMessage do
|
37
|
+
status
|
38
|
+
end
|
39
|
+
error
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
return broadcast(:error, response.data.start_key_ceremony.error) if response.data.start_key_ceremony.error.present?
|
45
|
+
|
46
|
+
broadcast(:ok, response.data.start_key_ceremony.pending_message)
|
47
|
+
rescue Graphlient::Errors::FaradayServerError
|
48
|
+
broadcast(:error, "Sorry, something went wrong")
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
attr_reader :election_id
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|