Skip to content

Commit

Permalink
Merge pull request #8 from slovensko-digital/add-integrations
Browse files Browse the repository at this point in the history
Add integrations and other update
  • Loading branch information
celuchmarek authored Jun 13, 2024
2 parents aafbce7 + 4521533 commit 9ee71ad
Show file tree
Hide file tree
Showing 39 changed files with 1,586 additions and 91 deletions.
7 changes: 7 additions & 0 deletions .gitlab/auto-deploy-values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,10 @@ persistence:
accessMode: ReadWriteMany
size: 1Gi
storageClass: nfs-client
- name: autogram-server-well-known
mount:
path: /app/public/.well-known
claim:
accessMode: ReadWriteMany
size: 1Mi
storageClass: nfs-client
3 changes: 3 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ gem "rack-cors"

gem "faraday"
gem "base64"
gem "jwt"
gem "fcm"

group :development, :test do
# See https://guides.rubyonrails.org/debugging_rails_applications.html#debugging-with-the-debug-gem
Expand All @@ -51,3 +53,4 @@ group :development do
# gem "spring"
end

gem "good_job", "~> 3.28"
45 changes: 45 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ GEM
minitest (>= 5.1)
mutex_m
tzinfo (~> 2.0)
addressable (2.8.6)
public_suffix (>= 2.0.2, < 6.0)
base64 (0.2.0)
bigdecimal (3.1.6)
bootsnap (1.18.3)
Expand All @@ -94,12 +96,36 @@ GEM
drb (2.2.0)
ruby2_keywords
erubi (1.12.0)
et-orbi (1.2.11)
tzinfo
faraday (2.9.0)
faraday-net_http (>= 2.0, < 3.2)
faraday-net_http (3.1.0)
net-http
fcm (1.0.8)
faraday (>= 1.0.0, < 3.0)
googleauth (~> 1)
fugit (1.10.1)
et-orbi (~> 1, >= 1.2.7)
raabro (~> 1.4)
globalid (1.2.1)
activesupport (>= 6.1)
good_job (3.28.0)
activejob (>= 6.0.0)
activerecord (>= 6.0.0)
concurrent-ruby (>= 1.0.2)
fugit (>= 1.1)
railties (>= 6.0.0)
thor (>= 0.14.1)
google-cloud-env (2.1.1)
faraday (>= 1.0, < 3.a)
googleauth (1.11.0)
faraday (>= 1.0, < 3.a)
google-cloud-env (~> 2.1)
jwt (>= 1.4, < 3.0)
multi_json (~> 1.11)
os (>= 0.9, < 2.0)
signet (>= 0.16, < 2.a)
i18n (1.14.1)
concurrent-ruby (~> 1.0)
io-console (0.7.2)
Expand All @@ -109,6 +135,8 @@ GEM
jbuilder (2.11.5)
actionview (>= 5.0.0)
activesupport (>= 5.0.0)
jwt (2.8.1)
base64
loofah (2.22.0)
crass (~> 1.0.2)
nokogiri (>= 1.12.0)
Expand All @@ -119,8 +147,10 @@ GEM
net-smtp
marcel (1.0.2)
mini_mime (1.1.5)
mini_portile2 (2.8.5)
minitest (5.22.2)
msgpack (1.7.2)
multi_json (1.15.0)
mutex_m (0.2.0)
net-http (0.4.1)
uri
Expand All @@ -134,6 +164,9 @@ GEM
net-smtp (0.4.0.1)
net-protocol
nio4r (2.7.0)
nokogiri (1.16.2)
mini_portile2 (~> 2.8.2)
racc (~> 1.4)
nokogiri (1.16.2-aarch64-linux)
racc (~> 1.4)
nokogiri (1.16.2-arm-linux)
Expand All @@ -146,11 +179,14 @@ GEM
racc (~> 1.4)
nokogiri (1.16.2-x86_64-linux)
racc (~> 1.4)
os (1.1.4)
pg (1.5.5)
psych (5.1.2)
stringio
public_suffix (5.0.5)
puma (6.4.2)
nio4r (~> 2.0)
raabro (1.4.0)
racc (1.7.3)
rack (3.0.9.1)
rack-cors (2.0.1)
Expand Down Expand Up @@ -197,6 +233,11 @@ GEM
reline (0.4.3)
io-console (~> 0.5)
ruby2_keywords (0.0.5)
signet (0.19.0)
addressable (~> 2.8)
faraday (>= 0.17.5, < 3.a)
jwt (>= 1.5, < 3.0)
multi_json (~> 1.10)
stringio (3.1.0)
thor (1.3.0)
timeout (0.4.1)
Expand All @@ -213,6 +254,7 @@ PLATFORMS
aarch64-linux
arm-linux
arm64-darwin
ruby
x86-linux
x86_64-darwin
x86_64-linux
Expand All @@ -223,7 +265,10 @@ DEPENDENCIES
debug (>= 1.6.2)
dotenv-rails
faraday
fcm
good_job (~> 3.28)
jbuilder
jwt
pg
puma (>= 5.0)
rack-cors
Expand Down
25 changes: 25 additions & 0 deletions app/controllers/api/v1/device_integrations_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
class Api::V1::DeviceIntegrationsController < ApiController
before_action :set_device

def create
integration = ApiEnvironment.integration_token_authenticator.verify_token(params.require(:integration_pairing_token), expected_aud: 'device', max_exp_in: 24.hours)
@device.integrations << integration

head 204
end

def index
@integrations = @device.integrations
end

def destroy
@device.integrations.delete(Integration.find(params.require(:id)))
head 204
end

private

def set_device
@device = ApiEnvironment.device_token_authenticator.verify_token(authenticity_token)
end
end
11 changes: 11 additions & 0 deletions app/controllers/api/v1/devices_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
class Api::V1::DevicesController < ApiController
def create
@device = Device.create!(device_params)
end

private

def device_params
params.permit(:display_name, :platform, :public_key, :registration_id)
end
end
123 changes: 101 additions & 22 deletions app/controllers/api/v1/documents_controller.rb
Original file line number Diff line number Diff line change
@@ -1,21 +1,39 @@
class Api::V1::DocumentsController < ApplicationController
before_action :set_document, only: %i[ show datatosign sign visualization destroy ]
before_action :set_key, only: %i[ show create datatosign sign visualization ]
before_action :decrypt_document_content, only: %i[ show sign datatosign visualization destroy]
before_action :set_document, only: %i[ show datatosign sign visualization destroy parameters ]
before_action :set_key, only: %i[ create datatosign sign visualization ]
before_action :decrypt_document_content, only: %i[ sign datatosign visualization destroy]

# GET /documents/1
def show
@signers = @document.signers
modified_since = request.headers.to_h['HTTP_IF_MODIFIED_SINCE']

response.set_header('Last-Modified', @document.last_signed_at + 1.seconds)
if modified_since && Time.zone.parse(modified_since) >= @document.last_signed_at
render json: nil, status: 304
else
# expecting 1s polling on this operation
set_key
decrypt_document_content

@signers = @document.signers
end
end

# POST /documents
def create
p = document_params
@document = Document.new(parameters: p[:parameters])
@document.encrypt_file(@key, p[:document][:filename], p[:payloadMimeType], p[:document][:content])
@document.validate_parameters(p[:document][:content])
filename, mimetype = create_filename_and_mimetype(p[:document][:filename], p[:payload_mime_type])
mimetype, content, parameters = Document.convert_to_b64(mimetype, p[:document][:content], p[:parameters])

render json: @document.errors, status: :unprocessable_entity unless @document.save
@document = Document.new(parameters: parameters)
@document.encrypt_file(@key, filename, mimetype, content)
@document.validate_parameters(content, mimetype)

unless @document.save
render json: @document.errors, status: :unprocessable_entity
else
response.set_header('Last-Modified', @document.last_signed_at + 1.seconds)
end
end

# POST /documents/1/visualization
Expand All @@ -25,14 +43,14 @@ def visualization

# POST /documents/1/datatosign
def datatosign
@document.set_add_timestamp if datatosign_params[:addTimestamp]
@signing_certificate = datatosign_params.require(:signingCertificate)
@document.set_add_timestamp if datatosign_params[:add_timestamp]
@signing_certificate = datatosign_params.require(:signing_certificate)
@result = @document.datatosign(@signing_certificate)
end

# POST /documents/1/sign
def sign
@signer = @document.sign(@key, sign_params[:dataToSignStructure], sign_params[:signedData])
@signer = @document.sign(@key, sign_params[:data_to_sign_structure], sign_params[:signed_data])
render json: @document.errors, status: :unprocessable_entity unless @signer

@document = Document.find(params[:id])
Expand All @@ -44,20 +62,34 @@ def destroy
@document.destroy!
end

# GET /documents/1/parameters
def parameters
end

private
def set_document
@document = Document.find(params[:id])
end

def set_key
@key = request.headers.to_h['HTTP_X_ENCRYPTION_KEY'] || params[:encryptionKey]
key_b64 = request.headers.to_h['HTTP_X_ENCRYPTION_KEY'] || params[:encryption_key]
begin
begin
# AVM app somehow sends urlsafe base64 even if its source code doesn't seem so
@key = Base64.urlsafe_decode64(key_b64)
rescue ArgumentError
@key = Base64.strict_decode64(key_b64)
end
rescue => e
raise AvmUnauthorizedError.new("ENCRYPTION_KEY_MALFORMED", "Encryption key Base64 decryption failed.", e.message)
end

raise AvmUnauthorizedError.new("ENCRYPTION_KEY_MISSING", "Encryption key not provided.", "Encryption key must be provided either in X-Encryption-Key header or as encryptionKey query parameter.") unless @key
# TODO
# raise AvmUnauthorizedError.new("ENCRYPTION_KEY_MALFORMED", "Encryption key invalid.", "Encryption key must be a 64 character long hexadecimal string.") unless validate_key(@key)
raise AvmUnauthorizedError.new("ENCRYPTION_KEY_MALFORMED", "Encryption key invalid.", "Encryption key must be a base64 string encoding 32 bytes long key.") unless validate_key(@key)
end

def validate_key(key)
key.length == 64 and !key[/\H/]
key.length == 32
end

def decrypt_document_content
Expand All @@ -68,22 +100,69 @@ def document_params
params.require(:parameters)
d = params.require(:document)
d.require(:content)
d.require(:filename)
params.require(:payloadMimeType)
params.permit(:encryptionKey, :payloadMimeType, :key, :document => [:filename, :content], :parameters => [:level, :container])
params.permit(
:encryption_key,
:payload_mime_type,
:document => [:filename, :content],
:parameters => [
:checkPDFACompliance,
:autoLoadEform,
:level,
:container,
:containerXmlns,
:embedUsedSchemas,
:identifier,
:packaging,
:digestAlgorithm,
:en319132,
:infoCanonicalization,
:propertiesCanonicalization,
:keyInfoCanonicalization,
:schema,
:schemaIdentifier,
:transformation,
:transformationIdentifier,
:transformationLanguage,
:transformationMediaDestinationTypeDescription,
:transformationTargetEnvironment
]
)
end

def datatosign_params
params.permit(:encryptionKey, :id, :signingCertificate, :addTimestamp)
params.permit(:encryption_key, :id, :signing_certificate, :add_timestamp)
end

def sign_params
params.require(:signedData)
dts = params.require(:dataToSignStructure)
params.require(:signed_data)
dts = params.require(:data_to_sign_structure)
dts.require(:dataToSign)
dts.require(:signingTime)
dts.require(:signingCertificate)

params.permit(:encryptionKey, :id, :signedData, :returnSignedDocument, :dataToSignStructure => [:dataToSign, :signingTime, :signingCertificate])
params.permit(:encryption_key, :id, :signed_data, :return_signed_document, :data_to_sign_structure => [:dataToSign, :signingTime, :signingCertificate])
end

def create_filename_and_mimetype(filename, mimetype)
return [filename, mimetype] if filename && mimetype

Mime::Type.register("application/vnd.etsi.asic-e+zip", "asice", [], [".sce"])
Mime::Type.register("application/vnd.etsi.asic-s+zip", "asics", [], [".scs"])
Mime::Type.register("application/vnd.gov.sk.xmldatacontainer+xml", "xdcf")
Mime::Type.register("application/msword", "doc")
Mime::Type.register("application/vnd.openxmlformats-officedocument.wordprocessingml.document", "docx")
Mime::Type.register("application/vnd.ms-excel", "xls")
Mime::Type.register("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "xlsx")
Mime::Type.register("application/vnd.ms-powerpoint", "ppt")
Mime::Type.register("application/vnd.openxmlformats-officedocument.presentationml.presentation", "pptx")

unless filename
filename = 'document.' + Mime::Type.lookup(mimetype).symbol.to_s
else
mimetype = Mime::Type.lookup_by_extension(File.extname(filename).downcase.gsub('.', '')).to_s
raise AvmServiceBadRequestError.new({code: "FAILED_PARSING_MIMETYPE", message: "Could not parse mimetype", details: "Could not parse mimetype from: #{filename}"}.to_json) if mimetype.empty?
end

[filename, mimetype]
end
end
18 changes: 18 additions & 0 deletions app/controllers/api/v1/integration_devices_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
class Api::V1::IntegrationDevicesController < ApiController
before_action :set_integration

def index
@devices = @integration.devices
end

def destroy
@integration.devices.delete(Device.find(params.require(:id)))
render :head
end

private

def set_integration
@integration = ApiEnvironment.integration_token_authenticator.verify_token(authenticity_token)
end
end
11 changes: 11 additions & 0 deletions app/controllers/api/v1/integrations_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
class Api::V1::IntegrationsController < ApiController
def create
@integration = Integration.create!(integration_params)
end

private

def integration_params
params.permit(:display_name, :platform, :public_key, :pushkey)
end
end
Loading

0 comments on commit 9ee71ad

Please sign in to comment.