Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GO-426 ÚPVS CEP signing #295

Open
wants to merge 13 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .env
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
ARCHIVER_API_URL=
GB_API_ENV=
GB_API_URL=
SK_API_URL=
SITE_ADMIN_EMAILS=
GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=
Expand Down
4 changes: 2 additions & 2 deletions app/jobs/govbox/authorize_delivery_notification_job.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
class Govbox::AuthorizeDeliveryNotificationJob < ApplicationJob
def perform(message, upvs_client: UpvsEnvironment.upvs_client)
edesk_api = upvs_client.api(message.thread.box).edesk
def perform(message)
edesk_api = UpvsEnvironment.upvs_api(message.thread.box).edesk

success, target_message_id = edesk_api.authorize_delivery_notification(message.metadata["delivery_notification"]["authorize_url"], mode: :sync)

Expand Down
4 changes: 2 additions & 2 deletions app/jobs/govbox/download_message_job.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ module Govbox
class DownloadMessageJob < ApplicationJob
queue_as :default

def perform(govbox_folder, edesk_message_id, upvs_client: UpvsEnvironment.upvs_client)
edesk_api = upvs_client.api(govbox_folder.box).edesk
def perform(govbox_folder, edesk_message_id)
edesk_api = UpvsEnvironment.upvs_api(govbox_folder.box).edesk
response_status, raw_message = edesk_api.fetch_message(edesk_message_id)

raise "Unable to fetch folder messages" if response_status != 200
Expand Down
4 changes: 2 additions & 2 deletions app/jobs/govbox/submit_message_draft_job.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ class TemporarySubmissionError < SubmissionError

retry_on TemporarySubmissionError, wait: 2.minutes, attempts: 5

def perform(message_draft, schedule_sync: true, upvs_client: UpvsEnvironment.upvs_client)
def perform(message_draft, schedule_sync: true)
message_draft_data = {
posp_id: message_draft.metadata["posp_id"],
posp_version: message_draft.metadata["posp_version"],
Expand All @@ -21,7 +21,7 @@ def perform(message_draft, schedule_sync: true, upvs_client: UpvsEnvironment.upv
objects: build_objects(message_draft)
}.compact

sktalk_api = upvs_client.api(message_draft.thread.box).sktalk
sktalk_api = UpvsEnvironment.upvs_api(message_draft.thread.box).sktalk
success, response_status, response_body = sktalk_api.receive_and_save_to_outbox(message_draft_data)

if success
Expand Down
4 changes: 2 additions & 2 deletions app/jobs/govbox/sync_box_job.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ module Govbox
class SyncBoxJob < ApplicationJob
queue_as :default

def perform(box, upvs_client: UpvsEnvironment.upvs_client)
def perform(box)
return unless box.syncable?

edesk_api = upvs_client.api(box).edesk
edesk_api = UpvsEnvironment.upvs_api(box).edesk
response_status, raw_folders = edesk_api.fetch_folders

raise "Unable to fetch folders" if response_status != 200
Expand Down
4 changes: 2 additions & 2 deletions app/jobs/govbox/sync_folder_job.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ module Govbox
class SyncFolderJob < ApplicationJob
queue_as :default

def perform(folder, upvs_client: UpvsEnvironment.upvs_client, batch_size: 1000)
edesk_api = upvs_client.api(folder.box).edesk
def perform(folder, batch_size: 1000)
edesk_api = UpvsEnvironment.upvs_api(folder.box).edesk
new_messages_ids = []

0.step do |k|
Expand Down
63 changes: 63 additions & 0 deletions app/lib/signer.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
module Signer
def self.sign(message_object, signing_option)
cep_api = UpvsEnvironment.upvs_api(message_object.message.thread.box).cep
cep_api_connection = ApiConnection.find(signing_option.settings["api_connection_id"])

if message_object.mimetype == 'application/pdf'
data = {
objects: [
{
certificate_type: 'Subject',
certificate_subject: signing_option.settings["certificate_subject"],
signature_type: 'PAdES',
class: 'http://schemas.gov.sk/attachment/pdf',
mime_type: 'application/pdf',
encoding: 'Base64',
content: Base64.strict_encode64(message_object.content)
}
]
}
signed_objects = cep_api.sign(data, cep_api_connection)
signed_data = signed_objects&.first
else
data = {
object_groups: [
{
id: SecureRandom.uuid,
signing_certificate: {
type: 'Subject',
subject: signing_option.settings["certificate_subject"],
},
unsigned_objects: [
{
id: message_object_id(message_object),
data: Base64.strict_encode64(message_object.content)
}
],
}
]
}
signed_objects = cep_api.sign_v2(data, cep_api_connection)
signed_data = signed_objects&.first
end

signed_data
end

private

def message_object_id(object)
if object.form?
"http://schemas.gov.sk/form/#{object.message.metadata["posp_id"]}/#{object.message.metadata["posp_version"]}/form.xsd"
else
case object.mimetype
when 'text/plain'
'http://schemas.gov.sk/attachment/txt'
when 'image/png'
'http://schemas.gov.sk/attachment/png'
else
raise "Unsupported MimeType"
end
end
end
end
15 changes: 14 additions & 1 deletion app/lib/upvs/govbox_api.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

module Upvs
class GovboxApi < Api
attr_reader :sub, :obo, :api_token_private_key, :url, :edesk, :sktalk
attr_reader :sub, :obo, :api_token_private_key, :url, :edesk, :sktalk, :cep

def initialize(url, box:, handler: Faraday)
@sub = box.api_connection.sub
Expand All @@ -11,6 +11,7 @@ def initialize(url, box:, handler: Faraday)
@url = url
@edesk = Edesk.new(self)
@sktalk = SkTalk.new(self)
@cep = Cep.new(self)
@handler = handler
@handler.options.timeout = 900_000
end
Expand Down Expand Up @@ -67,6 +68,18 @@ def submit_successful?(response_status, receive_result, save_to_outbox_result)
end
end

class Cep < Namespace
def sign(data, api_connection)
cep_sk_api = Upvs::SkApiClient.new.api(api_connection: api_connection).cep
cep_sk_api.sign(data)
end

def sign_v2(data, api_connection)
cep_sk_api = Upvs::SkApiClient.new.api(api_connection: api_connection).cep
cep_sk_api.sign_v2(data)
end
end

class Error < StandardError
attr_accessor :resource

Expand Down
56 changes: 56 additions & 0 deletions app/lib/upvs/sk_api.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
require 'jwt'

module Upvs
class SkApi < Api
attr_reader :sub, :obo, :api_token_private_key, :url, :cep

def initialize(url, box: nil, api_connection: nil, handler: Faraday)
@sub = api_connection&.sub || box&.api_connection&.sub
@obo = api_connection&.box_obo(box) || box&.api_connection&.box_obo(box)
@api_token_private_key = OpenSSL::PKey::RSA.new(api_connection&.api_token_private_key || box&.api_connection.api_token_private_key)
@url = url
@cep = Cep.new(self)
@handler = handler
@handler.options.timeout = 900_000
end

class Cep < Namespace
def sign(data)
response_status, response_body = @api.request(:post, "#{@api.url}/api/cep/sign", data.to_json, header)
response_body['signed_objects'] if sign_successful?(response_status, response_body)
end

def sign_v2(data)
response_status, response_body = @api.request(:post, "#{@api.url}/api/cep/sign_v2", data.to_json, header)
response_body['signed_object_groups'] if sign_successful?(response_status, response_body)
end

private

def header
{
"Authorization": authorization_payload,
"Content-Type": "application/json"
}
end

def sign_successful?(response_status, response_body)
response_status == 200 && response_body['sign_description'] == 'OK'
end
end

class Error < StandardError
attr_accessor :resource

attr_reader :response

def initialize(response)
@response = response
end

def to_s
cause ? cause.to_s : 'Unknown error'
end
end
end
end
4 changes: 2 additions & 2 deletions app/lib/upvs_environment.rb
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
module UpvsEnvironment
extend self

def upvs_client
@upvs_client ||= Upvs::GovboxApiClient.new
def upvs_api(box)
@upvs_api ||= box.api_connection.upvs_api(box)
end

def sso_settings
Expand Down
4 changes: 4 additions & 0 deletions app/models/api_connection.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ class ApiConnection < ApplicationRecord
belongs_to :tenant, optional: true
has_many :boxes

def upvs_api(box)
raise NotImplementedError
end

def box_obo(box)
raise NotImplementedError
end
Expand Down
13 changes: 13 additions & 0 deletions app/models/autogram_signing_option.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# == Schema Information
#
# Table name: signing_options
#
# id :bigint not null, primary key
# settings :jsonb
# type :string
# created_at :datetime not null
# updated_at :datetime not null
# tenant_id :bigint not null
#
class AutogramSigningOption < SigningOption
end
4 changes: 4 additions & 0 deletions app/models/govbox/api_connection.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@
class Govbox::ApiConnection < ::ApiConnection
validates :tenant_id, presence: false

def upvs_api(box)
Upvs::GovboxApiClient.new.api(box)
end

def box_obo(box)
raise "OBO not allowed!" if invalid_obo?(box)
obo
Expand Down
4 changes: 4 additions & 0 deletions app/models/govbox/api_connection_with_obo_support.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@
class Govbox::ApiConnectionWithOboSupport < ::ApiConnection
validates :tenant_id, presence: true

def upvs_api(box)
Upvs::GovboxApiClient.new.api(box)
end

def box_obo(box)
raise "OBO not allowed!" if obo.present?

Expand Down
21 changes: 21 additions & 0 deletions app/models/seal_signing_option.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# == Schema Information
#
# Table name: signing_options
#
# id :bigint not null, primary key
# settings :jsonb
# type :string
# created_at :datetime not null
# updated_at :datetime not null
# tenant_id :bigint not null
#
class SealSigningOption < SigningOption
validate :validate_settings

private

def validate_settings
errors.add(:settings, :invalid) unless settings["certificate_subject"].present?
errors.add(:settings, :invalid) unless settings["api_connection_id"].present?
end
end
14 changes: 14 additions & 0 deletions app/models/signing_option.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# == Schema Information
#
# Table name: signing_options
#
# id :bigint not null, primary key
# settings :jsonb
# type :string
# created_at :datetime not null
# updated_at :datetime not null
# tenant_id :bigint not null
#
class SigningOption < ApplicationRecord
belongs_to :tenant
end
9 changes: 7 additions & 2 deletions app/models/sk_api/api_connection_with_obo_support.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,15 @@
class SkApi::ApiConnectionWithOboSupport < ::ApiConnection
validates :tenant_id, presence: true

def upvs_api(box)
Upvs::SkApiClient.new.api(box: box)
end

def box_obo(box)
raise "OBO not allowed!" if invalid_obo?(box)
raise "OBO not allowed!" if box && invalid_obo?(box)

box.settings["obo"] if box.settings
box.settings["obo"] if box&.settings
obo
end

def destroy_with_box?
Expand Down
10 changes: 10 additions & 0 deletions app/models/tenant.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ class Tenant < ApplicationRecord
has_many :groups, dependent: :destroy
has_many :custom_groups

has_many :signing_options, dependent: :destroy

has_one :draft_tag
has_one :everything_tag
has_one :signature_requested_tag
Expand Down Expand Up @@ -106,5 +108,13 @@ def create_default_objects
create_signed_externally_tag!(name: "Externe podpísané", visible: false, color: "purple", icon: "shield-check")

make_admins_see_everything!

create_default_signing_options!
end

def create_default_signing_options!
signing_options.create!(
type: 'AutogramSigningOption',
)
end
end
14 changes: 14 additions & 0 deletions app/models/tenant_signing_option.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# == Schema Information
#
# Table name: tenant_signing_options
#
# id :bigint not null, primary key
# created_at :datetime not null
# updated_at :datetime not null
# signing_option_id :bigint not null
# tenant_id :bigint not null
#
class TenantSigningOption < ApplicationRecord
belongs_to :tenant
belongs_to :signing_option
end
9 changes: 9 additions & 0 deletions app/services/upvs/sk_api_client.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
class Upvs::SkApiClient
def initialize(host: ENV.fetch('SK_API_URL'))
@host = host
end

def api(box: nil, api_connection: nil)
Upvs::SkApi.new(@host, box: box, api_connection: api_connection)
end
end
Loading
Loading