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.
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
@@ -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,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
@@ -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.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
@@ -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,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
- @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?, :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 = Decidim::BulletinBoard::Authority::CreateElection.new(election_id, election_data)
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 = Decidim::BulletinBoard::Authority::StartKeyCeremony.new(election_id)
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 = Decidim::BulletinBoard::Authority::StartVote.new(election_id)
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 = Decidim::BulletinBoard::Voter::CastVote.new(election_id, voter_id, encrypted_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 = Decidim::BulletinBoard::Voter::GetPendingMessageStatus.new(message_id)
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 = Decidim::BulletinBoard::Authority::EndVote.new(election_id)
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 = Decidim::BulletinBoard::Authority::GetElectionStatus.new(election_id)
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 = Decidim::BulletinBoard::Authority::StartTally.new(election_id)
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 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 }
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 = Decidim::BulletinBoard::Authority::PublishResults.new(election_id)
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 :identification_private_key, :private_key
108
+ attr_reader :settings, :graphql
115
109
 
116
- def identification_private_key_content
117
- @identification_private_key_content ||= JwkUtils.import_private_key(identification_private_key)
110
+ def configure(command)
111
+ command.configure(settings, graphql)
112
+ command
118
113
  end
119
114
  end
120
115
  end