decidim-bulletin_board 0.9.2 → 0.12.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (29) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +55 -0
  3. data/Gemfile.lock +56 -56
  4. data/app/assets/config/{decidim_bulletin_board_manifest.js → manifest.js} +0 -0
  5. data/app/assets/javascripts/decidim/bulletin_board/decidim-bulletin_board.js +279 -2
  6. data/bin/release +10 -0
  7. data/lib/decidim/bulletin_board.rb +16 -20
  8. data/lib/decidim/bulletin_board/authority/create_election.rb +100 -2
  9. data/lib/decidim/bulletin_board/authority/end_vote.rb +1 -1
  10. data/lib/decidim/bulletin_board/authority/get_election_results.rb +63 -0
  11. data/lib/decidim/bulletin_board/authority/get_election_status.rb +1 -1
  12. data/lib/decidim/bulletin_board/authority/publish_results.rb +1 -1
  13. data/lib/decidim/bulletin_board/authority/start_key_ceremony.rb +1 -1
  14. data/lib/decidim/bulletin_board/authority/start_tally.rb +1 -1
  15. data/lib/decidim/bulletin_board/authority/start_vote.rb +1 -1
  16. data/lib/decidim/bulletin_board/client.rb +36 -38
  17. data/lib/decidim/bulletin_board/command.rb +11 -26
  18. data/lib/decidim/bulletin_board/graphql/bb_schema.json +19 -1
  19. data/lib/decidim/bulletin_board/graphql/factory.rb +18 -0
  20. data/lib/decidim/bulletin_board/message_identifier.rb +7 -3
  21. data/lib/decidim/bulletin_board/settings.rb +41 -0
  22. data/lib/decidim/bulletin_board/version.rb +1 -1
  23. data/lib/decidim/bulletin_board/voter/cast_vote.rb +1 -1
  24. data/lib/decidim/bulletin_board/voter/get_pending_message_status.rb +1 -1
  25. metadata +7 -7
  26. data/lib/decidim/bulletin_board/authority.rb +0 -10
  27. data/lib/decidim/bulletin_board/authority/get_election_log_entries_by_types.rb +0 -51
  28. data/lib/decidim/bulletin_board/graphql/client.rb +0 -18
  29. data/lib/decidim/bulletin_board/voter.rb +0 -4
data/bin/release ADDED
@@ -0,0 +1,10 @@
1
+ #!/bin/bash
2
+
3
+ cd ../decidim-bulletin_board-app && \
4
+ bundle install && \
5
+ npm run release && \
6
+ cd ../decidim-bulletin_board-ruby && \
7
+ bundle install && \
8
+ git add .. && \
9
+ git commit --amend && \
10
+ 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
@@ -24,29 +21,28 @@ module Decidim
24
21
  # Configure the following variables inside your
25
22
  # decidim_bulletin_board.rb initializer
26
23
 
27
- # The BulletinBoard server (String)
28
- config_accessor :server
24
+ # The Bulletin Board server (String)
25
+ config_accessor :bulletin_board_server
26
+
27
+ # The public key (JSON) of the Bulletin Board server
28
+ config_accessor :bulletin_board_public_key
29
29
 
30
30
  # The api key generated by the Bulletin Board for the Decidim authority (String)
31
- config_accessor :api_key
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
31
+ config_accessor :authority_api_key
42
32
 
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
36
+ # The identification private key (JSON) for your Decidim instance
37
+ config_accessor :authority_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
47
43
  config_accessor :number_of_trustees
48
44
 
49
- # The identification private key (JSON) for your Decidim instance
50
- config_accessor :identification_private_key
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, election_data)
22
+ signed_data: sign_message(message_id, message)
23
23
  }
24
24
 
25
- response = client.query do
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,104 @@ 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
+ pretty_name: "Bulletin Board",
74
+ public_key: settings.bulletin_board_public_key
75
+ }
76
+ end
77
+
78
+ def authority
79
+ {
80
+ name: settings.authority_slug,
81
+ pretty_name: settings.authority_name,
82
+ public_key: settings.authority_public_key
83
+ }
84
+ end
85
+
86
+ def trustees
87
+ election_data[:trustees].map do |trustee|
88
+ {
89
+ name: trustee[:slug],
90
+ pretty_name: trustee[:name],
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
+ ballot_title: text(question[:title]),
106
+ ballot_subtitle: text(question[:description]),
107
+ ballot_selections: contest_answers(question)
108
+ }
109
+ end
110
+ end
111
+
112
+ def contest_answers(question)
113
+ question[:answers].map do |answer|
114
+ {
115
+ object_id: "#{question[:slug]}_#{answer[:slug]}",
116
+ sequence_order: answer[:weight],
117
+ candidate_id: answer[:slug]
118
+ }
119
+ end
120
+ end
121
+
122
+ def candidates
123
+ election_data[:answers].map do |answer|
124
+ {
125
+ object_id: answer[:slug],
126
+ ballot_name: text(answer[:title])
127
+ }
128
+ end
129
+ end
130
+
131
+ def default_text(field)
132
+ field[default_locale]
133
+ end
134
+
135
+ def text(field)
136
+ {
137
+ text: field.map { |locale, value| { language: locale.to_s, value: value } }
138
+ }
139
+ end
140
+
141
+ def default_locale
142
+ @default_locale ||= election_data[:default_locale].to_sym
143
+ end
46
144
  end
47
145
  end
48
146
  end
@@ -30,7 +30,7 @@ module Decidim
30
30
  signed_data: sign_message(message_id, {})
31
31
  }
32
32
 
33
- response = client.query do
33
+ response = graphql.query do
34
34
  mutation do
35
35
  endVote(messageId: args[:message_id], signedData: args[:signed_data]) do
36
36
  pendingMessage do
@@ -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.bulletin_board_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
@@ -24,7 +24,7 @@ module Decidim
24
24
  unique_id: unique_election_id(election_id)
25
25
  }
26
26
 
27
- response = client.query do
27
+ response = graphql.query do
28
28
  query do
29
29
  election(uniqueId: args[:unique_id]) do
30
30
  status
@@ -30,7 +30,7 @@ module Decidim
30
30
  signed_data: sign_message(message_id, {})
31
31
  }
32
32
 
33
- response = client.query do
33
+ response = graphql.query do
34
34
  mutation do
35
35
  publishResults(messageId: args[:message_id], signedData: args[:signed_data]) do
36
36
  election do
@@ -30,7 +30,7 @@ module Decidim
30
30
  signed_data: sign_message(message_id, {})
31
31
  }
32
32
 
33
- response = client.query do
33
+ response = graphql.query do
34
34
  mutation do
35
35
  startKeyCeremony(messageId: args[:message_id], signedData: args[:signed_data]) do
36
36
  pendingMessage do
@@ -30,7 +30,7 @@ module Decidim
30
30
  signed_data: sign_message(message_id, {})
31
31
  }
32
32
 
33
- response = client.query do
33
+ response = graphql.query do
34
34
  mutation do
35
35
  startTally(messageId: args[:message_id], signedData: args[:signed_data]) do
36
36
  pendingMessage do
@@ -30,7 +30,7 @@ module Decidim
30
30
  signed_data: sign_message(message_id, {})
31
31
  }
32
32
 
33
- response = client.query do
33
+ response = graphql.query do
34
34
  mutation do
35
35
  startVote(messageId: args[:message_id], signedData: args[:signed_data]) do
36
36
  pendingMessage do
@@ -1,39 +1,36 @@
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
- @server = BulletinBoard.server.presence
11
- @api_key = BulletinBoard.api_key.presence
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
- attr_reader :server, :scheme, :api_key, :number_of_trustees, :authority_name
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?,
28
+ :bulletin_board_server, :bulletin_board_public_key,
29
+ :authority_public_key, :authority_name, :authority_slug,
30
+ :scheme_name, :number_of_trustees, :quorum, to: :settings
34
31
 
35
32
  def create_election(election_id, election_data)
36
- create_election = Decidim::BulletinBoard::Authority::CreateElection.new(election_id, election_data)
33
+ create_election = configure Authority::CreateElection.new(election_id, election_data)
37
34
  yield create_election.message_id if block_given?
38
35
  create_election.on(:ok) { |election| return election }
39
36
  create_election.on(:error) { |error_message| raise StandardError, error_message }
@@ -41,7 +38,7 @@ module Decidim
41
38
  end
42
39
 
43
40
  def start_key_ceremony(election_id)
44
- start_key_ceremony = Decidim::BulletinBoard::Authority::StartKeyCeremony.new(election_id)
41
+ start_key_ceremony = configure Authority::StartKeyCeremony.new(election_id)
45
42
  yield start_key_ceremony.message_id if block_given?
46
43
  start_key_ceremony.on(:ok) { |pending_message| return pending_message }
47
44
  start_key_ceremony.on(:error) { |error_message| raise StandardError, error_message }
@@ -49,7 +46,7 @@ module Decidim
49
46
  end
50
47
 
51
48
  def start_vote(election_id)
52
- start_vote = Decidim::BulletinBoard::Authority::StartVote.new(election_id)
49
+ start_vote = configure Authority::StartVote.new(election_id)
53
50
  yield start_vote.message_id if block_given?
54
51
  start_vote.on(:ok) { |pending_message| return pending_message }
55
52
  start_vote.on(:error) { |error_message| raise StandardError, error_message }
@@ -57,7 +54,7 @@ module Decidim
57
54
  end
58
55
 
59
56
  def cast_vote(election_id, voter_id, encrypted_vote)
60
- cast_vote = Decidim::BulletinBoard::Voter::CastVote.new(election_id, voter_id, encrypted_vote)
57
+ cast_vote = configure Voter::CastVote.new(election_id, voter_id, encrypted_vote)
61
58
  yield cast_vote.message_id if block_given?
62
59
  cast_vote.on(:ok) { |pending_message| return pending_message }
63
60
  cast_vote.on(:error) { |error_message| raise StandardError, error_message }
@@ -65,14 +62,14 @@ module Decidim
65
62
  end
66
63
 
67
64
  def get_pending_message_status(message_id)
68
- get_pending_message_status = Decidim::BulletinBoard::Voter::GetPendingMessageStatus.new(message_id)
65
+ get_pending_message_status = configure Voter::GetPendingMessageStatus.new(message_id)
69
66
  get_pending_message_status.on(:ok) { |status| return status }
70
67
  get_pending_message_status.on(:error) { |error_message| raise StandardError, error_message }
71
68
  get_pending_message_status.call
72
69
  end
73
70
 
74
71
  def end_vote(election_id)
75
- end_vote = Decidim::BulletinBoard::Authority::EndVote.new(election_id)
72
+ end_vote = configure Authority::EndVote.new(election_id)
76
73
  yield end_vote.message_id if block_given?
77
74
  end_vote.on(:ok) { |pending_message| return pending_message }
78
75
  end_vote.on(:error) { |error_message| raise StandardError, error_message }
@@ -80,29 +77,29 @@ module Decidim
80
77
  end
81
78
 
82
79
  def get_election_status(election_id)
83
- get_election_status = Decidim::BulletinBoard::Authority::GetElectionStatus.new(election_id)
80
+ get_election_status = configure Authority::GetElectionStatus.new(election_id)
84
81
  get_election_status.on(:ok) { |status| return status }
85
82
  get_election_status.on(:error) { |error_message| raise StandardError, error_message }
86
83
  get_election_status.call
87
84
  end
88
85
 
89
86
  def start_tally(election_id)
90
- start_tally = Decidim::BulletinBoard::Authority::StartTally.new(election_id)
87
+ start_tally = configure Authority::StartTally.new(election_id)
91
88
  yield start_tally.message_id if block_given?
92
89
  start_tally.on(:ok) { |pending_message| return pending_message }
93
90
  start_tally.on(:error) { |error_message| raise StandardError, error_message }
94
91
  start_tally.call
95
92
  end
96
93
 
97
- def get_election_log_entries_by_types(election_id, types)
98
- get_log_entries = Decidim::BulletinBoard::Authority::GetElectionLogEntriesByTypes.new(election_id, types)
99
- get_log_entries.on(:ok) { |log_entries| return log_entries }
94
+ def get_election_results(election_id)
95
+ get_log_entries = configure Authority::GetElectionResults.new(election_id)
96
+ get_log_entries.on(:ok) { |result| return result }
100
97
  get_log_entries.on(:error) { |error_message| raise StandardError, error_message }
101
98
  get_log_entries.call
102
99
  end
103
100
 
104
101
  def publish_results(election_id)
105
- publish_results = Decidim::BulletinBoard::Authority::PublishResults.new(election_id)
102
+ publish_results = configure Authority::PublishResults.new(election_id)
106
103
  yield publish_results.message_id if block_given?
107
104
  publish_results.on(:ok) { |status| return status }
108
105
  publish_results.on(:error) { |error_message| raise StandardError, error_message }
@@ -111,10 +108,11 @@ module Decidim
111
108
 
112
109
  private
113
110
 
114
- attr_reader :identification_private_key, :private_key
111
+ attr_reader :settings, :graphql
115
112
 
116
- def identification_private_key_content
117
- @identification_private_key_content ||= JwkUtils.import_private_key(identification_private_key)
113
+ def configure(command)
114
+ command.configure(settings, graphql)
115
+ command
118
116
  end
119
117
  end
120
118
  end