decidim-bulletin_board 0.8.0 → 0.10.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (30) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +4 -1
  3. data/CHANGELOG.md +41 -1
  4. data/Gemfile.lock +59 -58
  5. data/app/assets/javascripts/decidim/bulletin_board/decidim-bulletin_board.js +279 -2
  6. data/bin/release +8 -0
  7. data/lib/decidim/bulletin_board.rb +13 -17
  8. data/lib/decidim/bulletin_board/authority/create_election.rb +121 -16
  9. data/lib/decidim/bulletin_board/authority/end_vote.rb +57 -0
  10. data/lib/decidim/bulletin_board/authority/get_election_results.rb +63 -0
  11. data/lib/decidim/bulletin_board/authority/get_election_status.rb +12 -11
  12. data/lib/decidim/bulletin_board/authority/publish_results.rb +22 -16
  13. data/lib/decidim/bulletin_board/authority/start_key_ceremony.rb +57 -0
  14. data/lib/decidim/bulletin_board/authority/start_tally.rb +22 -16
  15. data/lib/decidim/bulletin_board/authority/start_vote.rb +57 -0
  16. data/lib/decidim/bulletin_board/client.rb +58 -48
  17. data/lib/decidim/bulletin_board/command.rb +11 -26
  18. data/lib/decidim/bulletin_board/graphql/bb_schema.json +205 -87
  19. data/lib/decidim/bulletin_board/graphql/factory.rb +18 -0
  20. data/lib/decidim/bulletin_board/settings.rb +49 -0
  21. data/lib/decidim/bulletin_board/version.rb +1 -1
  22. data/lib/decidim/bulletin_board/voter/cast_vote.rb +13 -8
  23. data/lib/decidim/bulletin_board/voter/get_pending_message_status.rb +1 -1
  24. metadata +9 -8
  25. data/app/assets/javascripts/decidim/bulletin_board/decidim-bulletin_board.dev.js +0 -18038
  26. data/lib/decidim/bulletin_board/authority.rb +0 -8
  27. data/lib/decidim/bulletin_board/authority/close_ballot_box.rb +0 -51
  28. data/lib/decidim/bulletin_board/authority/open_ballot_box.rb +0 -51
  29. data/lib/decidim/bulletin_board/graphql/client.rb +0 -18
  30. data/lib/decidim/bulletin_board/voter.rb +0 -4
data/bin/release ADDED
@@ -0,0 +1,8 @@
1
+ #!/bin/bash
2
+
3
+ cd ../decidim-bulletin_board-app && \
4
+ npm run release && \
5
+ git add . && \
6
+ git commit --amend && \
7
+ cd ../decidim-bulletin_board-ruby && \
8
+ bundle exec rake release
@@ -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
- message_id = message_id(unique_election_id(election_id), "create_election")
15
- signed_data = sign_message(message_id, election_data)
16
-
17
- begin
18
- response = client.query do
19
- mutation do
20
- createElection(messageId: message_id, signedData: signed_data) do
21
- election do
22
- status
23
- end
24
- error
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
- return broadcast(:error, response.data.create_election.error) if response.data.create_election.error.present?
36
+ return broadcast(:error, response.data.create_election.error) if response.data.create_election.error.present?
30
37
 
31
- broadcast(:ok, response.data.create_election.election)
32
- rescue Graphlient::Errors::ServerError
33
- broadcast(:error, "Sorry, something went wrong")
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
- unique_id = unique_election_id(election_id)
22
+ # arguments used inside the graphql operation
23
+ args = {
24
+ unique_id: unique_election_id(election_id)
25
+ }
23
26
 
24
- begin
25
- response = client.query do
26
- query do
27
- election(uniqueId: unique_id) do
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
- message_id = message_id(unique_election_id(election_id), "publish_results")
23
- signed_data = sign_message(message_id, {})
24
-
25
- begin
26
- response = client.query do
27
- mutation do
28
- publishResults(messageId: message_id, signedData: signed_data) do
29
- election do
30
- status
31
- end
32
- error
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
- return broadcast(:error, response.data.publish_results.error) if response.data.publish_results.error.present?
44
+ return broadcast(:error, response.data.publish_results.error) if response.data.publish_results.error.present?
38
45
 
39
- broadcast(:ok, response.data.publish_results.election)
40
- rescue Graphlient::Errors::ServerError
41
- broadcast(:error, "Sorry, something went wrong")
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