rest_pki 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +28 -0
- data/CHANGELOG.md +12 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +19 -0
- data/Rakefile +2 -0
- data/lib/rest_pki.rb +35 -0
- data/lib/rest_pki/authentication.rb +43 -0
- data/lib/rest_pki/cades_signature_finisher.rb +48 -0
- data/lib/rest_pki/cades_signature_starter.rb +148 -0
- data/lib/rest_pki/full_xml_signature_starter.rb +51 -0
- data/lib/rest_pki/namespace_manager.rb +16 -0
- data/lib/rest_pki/object.rb +126 -0
- data/lib/rest_pki/pades_signature_finisher.rb +50 -0
- data/lib/rest_pki/pades_signature_starter.rb +110 -0
- data/lib/rest_pki/pades_visual_positioning_presets.rb +32 -0
- data/lib/rest_pki/resources/authentication_model.rb +5 -0
- data/lib/rest_pki/resources/cades_model.rb +5 -0
- data/lib/rest_pki/resources/pades_model.rb +5 -0
- data/lib/rest_pki/resources/response_model.rb +5 -0
- data/lib/rest_pki/resources/xml_model.rb +5 -0
- data/lib/rest_pki/rest_base_error.rb +12 -0
- data/lib/rest_pki/rest_error.rb +16 -0
- data/lib/rest_pki/rest_unreachable_error.rb +13 -0
- data/lib/rest_pki/restpki_client.rb +95 -0
- data/lib/rest_pki/restpki_error.rb +16 -0
- data/lib/rest_pki/signature_finisher.rb +33 -0
- data/lib/rest_pki/signature_starter.rb +59 -0
- data/lib/rest_pki/standard_security_contexts.rb +10 -0
- data/lib/rest_pki/standard_signature_policies.rb +25 -0
- data/lib/rest_pki/validation_error.rb +11 -0
- data/lib/rest_pki/validation_item.rb +33 -0
- data/lib/rest_pki/validation_results.rb +107 -0
- data/lib/rest_pki/version.rb +3 -0
- data/lib/rest_pki/xml_element_signature_starter.rb +68 -0
- data/lib/rest_pki/xml_id_resolution_table.rb +40 -0
- data/lib/rest_pki/xml_insertion_options.rb +11 -0
- data/lib/rest_pki/xml_signature_finisher.rb +52 -0
- data/lib/rest_pki/xml_signature_starter.rb +84 -0
- data/rest_pki.gemspec +30 -0
- metadata +146 -0
@@ -0,0 +1,126 @@
|
|
1
|
+
module RestPki
|
2
|
+
class RestPkiObject
|
3
|
+
attr_reader :attributes
|
4
|
+
|
5
|
+
RESOURCES = Dir[File.expand_path('../resources/*.rb', __FILE__)].map do |path|
|
6
|
+
File.basename(path, '.rb').to_sym
|
7
|
+
end
|
8
|
+
|
9
|
+
def initialize(response = {}, object)
|
10
|
+
@attributes = Hash.new
|
11
|
+
@unsaved_attributes = Set.new
|
12
|
+
update(response, object)
|
13
|
+
end
|
14
|
+
|
15
|
+
def []=(key,value)
|
16
|
+
@attributes[key] = value
|
17
|
+
@unsaved_attributes.add key
|
18
|
+
end
|
19
|
+
|
20
|
+
def unsaved_attributes
|
21
|
+
Hash[@unsaved_attributes.map do |key|
|
22
|
+
[ key, to_hash_value(self[key], :unsaved_attributes) ]
|
23
|
+
end]
|
24
|
+
end
|
25
|
+
|
26
|
+
def empty?
|
27
|
+
@attributes.empty?
|
28
|
+
end
|
29
|
+
|
30
|
+
def ==(other)
|
31
|
+
self.class == other.class && id == other.id
|
32
|
+
end
|
33
|
+
|
34
|
+
def to_hash
|
35
|
+
Hash[@attributes.map do |key, value|
|
36
|
+
[ key, to_hash_value(value, :to_hash) ]
|
37
|
+
end]
|
38
|
+
end
|
39
|
+
|
40
|
+
def respond_to?(name, include_all = false)
|
41
|
+
return true if name.to_s.end_with? '='
|
42
|
+
|
43
|
+
@attributes.has_key?(name.to_s) || super
|
44
|
+
end
|
45
|
+
|
46
|
+
protected
|
47
|
+
def update(attributes, object)
|
48
|
+
removed_attributes = @attributes.keys - attributes.keys
|
49
|
+
|
50
|
+
removed_attributes.each do |key|
|
51
|
+
@attributes.delete key
|
52
|
+
end
|
53
|
+
|
54
|
+
attributes.each do |key, value|
|
55
|
+
key = key.to_s
|
56
|
+
|
57
|
+
@attributes[key] = RestPkiObject.convert(value, object)
|
58
|
+
@unsaved_attributes.delete key
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def to_hash_value(value, type)
|
63
|
+
case value
|
64
|
+
when RestPkiObject
|
65
|
+
value.send type
|
66
|
+
when Array
|
67
|
+
value.map do |v|
|
68
|
+
to_hash_value v, type
|
69
|
+
end
|
70
|
+
else
|
71
|
+
value
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def method_missing(name, *args, &block)
|
76
|
+
name = name.to_s
|
77
|
+
|
78
|
+
unless block_given?
|
79
|
+
if name.end_with?('=') && args.size == 1
|
80
|
+
attribute_name = name[0...-1]
|
81
|
+
return self[attribute_name] = args[0]
|
82
|
+
end
|
83
|
+
|
84
|
+
if args.size == 0
|
85
|
+
return self[name]
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
if attributes.respond_to? name
|
90
|
+
return attributes.public_send name, *args, &block
|
91
|
+
end
|
92
|
+
|
93
|
+
super name, *args, &block
|
94
|
+
end
|
95
|
+
|
96
|
+
class << self
|
97
|
+
|
98
|
+
def convert(response, object)
|
99
|
+
case response
|
100
|
+
when Array
|
101
|
+
response.map{ |i| convert(i, object) }
|
102
|
+
when Hash
|
103
|
+
resource_class_for(object).new(response, object) #response['object']).new(response)
|
104
|
+
else
|
105
|
+
response
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
protected
|
110
|
+
def resource_class_for(resource_name)
|
111
|
+
return RestPki::RestPkiObject if resource_name.nil?
|
112
|
+
|
113
|
+
if RESOURCES.include? resource_name.to_sym
|
114
|
+
Object.const_get "RestPki::#{capitalize_name resource_name}"
|
115
|
+
else
|
116
|
+
RestPki::RestPkiObject
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def capitalize_name(name)
|
121
|
+
name.gsub(/(\A\w|\_\w)/){ |str| str.gsub('_', '').upcase }
|
122
|
+
end
|
123
|
+
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
|
2
|
+
module RestPki
|
3
|
+
class PadesSignatureFinisher < SignatureFinisher
|
4
|
+
|
5
|
+
def initialize(restpki_client)
|
6
|
+
super(restpki_client)
|
7
|
+
@signed_pdf_content = nil
|
8
|
+
end
|
9
|
+
|
10
|
+
def finish
|
11
|
+
if @token.to_s.blank?
|
12
|
+
raise 'The token was not set'
|
13
|
+
end
|
14
|
+
|
15
|
+
if @signature.to_s.blank?
|
16
|
+
response = @restpki_client.post("Api/PadesSignatures/#{@token}/Finalize", nil, 'pades_model')
|
17
|
+
else
|
18
|
+
request = { signature: @signature }
|
19
|
+
response = @restpki_client.post("Api/PadesSignatures/#{@token}/SignedBytes", request, 'pades_model')
|
20
|
+
end
|
21
|
+
|
22
|
+
@signed_pdf_content = Base64.decode64(response['signedPdf'])
|
23
|
+
@callback_argument = response['callbackArgument']
|
24
|
+
@certificate_info = response['certificate']
|
25
|
+
@done = true
|
26
|
+
|
27
|
+
@signed_pdf_content
|
28
|
+
end
|
29
|
+
|
30
|
+
def signed_pdf_content
|
31
|
+
unless @done
|
32
|
+
raise 'The field "signed_pdf_content" can only be accessed after calling the finish method'
|
33
|
+
end
|
34
|
+
|
35
|
+
@signed_pdf_content
|
36
|
+
end
|
37
|
+
|
38
|
+
def write_signed_pdf(pdf_path)
|
39
|
+
unless @done
|
40
|
+
raise 'The method write_signed_pdf can only be called after calling the finish method'
|
41
|
+
end
|
42
|
+
|
43
|
+
file = File.open(pdf_path, 'wb')
|
44
|
+
file.write(@signed_pdf_content)
|
45
|
+
file.close
|
46
|
+
|
47
|
+
nil # No value is returned.
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
require 'base64'
|
2
|
+
|
3
|
+
module RestPki
|
4
|
+
class PadesSignatureStarter < SignatureStarter
|
5
|
+
attr_accessor :visual_representation
|
6
|
+
|
7
|
+
def initialize(restpki_client)
|
8
|
+
super(restpki_client)
|
9
|
+
@pdf_content_base64 = nil
|
10
|
+
@visual_representation = nil
|
11
|
+
end
|
12
|
+
|
13
|
+
#region set_pdf_tosign
|
14
|
+
|
15
|
+
def set_pdf_tosign_from_path(pdf_path)
|
16
|
+
file = File.open(pdf_path, 'rb')
|
17
|
+
@pdf_content_base64 = Base64.encode64(file.read)
|
18
|
+
file.close
|
19
|
+
|
20
|
+
@pdf_content_base64
|
21
|
+
end
|
22
|
+
|
23
|
+
def set_pdf_tosign_from_raw(content_raw)
|
24
|
+
@pdf_content_base64 = Base64.encode64(content_raw)
|
25
|
+
end
|
26
|
+
|
27
|
+
def set_pdf_tosign_from_base64(content_base64)
|
28
|
+
@pdf_content_base64 = content_base64
|
29
|
+
end
|
30
|
+
|
31
|
+
def set_pdf_content_tosign(content_raw)
|
32
|
+
set_pdf_tosign_from_raw(content_raw)
|
33
|
+
end
|
34
|
+
|
35
|
+
def set_pdf_file_tosign(pdf_path)
|
36
|
+
set_pdf_tosign_from_path(pdf_path)
|
37
|
+
end
|
38
|
+
|
39
|
+
#endregion
|
40
|
+
|
41
|
+
def start_with_webpki
|
42
|
+
if @pdf_content_base64.to_s.blank?
|
43
|
+
raise 'The PDF to sign was not set'
|
44
|
+
end
|
45
|
+
if @signature_policy_id.to_s.blank?
|
46
|
+
raise 'The signature policy was not set'
|
47
|
+
end
|
48
|
+
|
49
|
+
request = {
|
50
|
+
securityContextId: @security_context_id,
|
51
|
+
pdfToSign: @pdf_content_base64,
|
52
|
+
signaturePolicyId: @signature_policy_id,
|
53
|
+
callbackArgument: @callback_argument,
|
54
|
+
visualRepresentation: @visual_representation,
|
55
|
+
ignoreRevocationStatusUnknown: @ignore_revocation_status_unknown
|
56
|
+
}
|
57
|
+
unless @signer_certificate_base64.to_s.blank?
|
58
|
+
request['certificate'] = Base64.encode64(@signer_certificate_base64)
|
59
|
+
end
|
60
|
+
|
61
|
+
response = @restpki_client.post('Api/PadesSignatures', request, 'pades_model')
|
62
|
+
|
63
|
+
unless response['certificate'].nil?
|
64
|
+
@certificate = response['certificate']
|
65
|
+
end
|
66
|
+
@done = true
|
67
|
+
|
68
|
+
response['token']
|
69
|
+
end
|
70
|
+
|
71
|
+
def start
|
72
|
+
if @pdf_content_base64.to_s.blank?
|
73
|
+
raise 'The PDF to sign was not set'
|
74
|
+
end
|
75
|
+
if @signature_policy_id.to_s.blank?
|
76
|
+
raise 'The signature policy was not set'
|
77
|
+
end
|
78
|
+
if @signer_certificate_base64.to_s.blank?
|
79
|
+
raise 'The signer certificate was not set'
|
80
|
+
end
|
81
|
+
|
82
|
+
request = {
|
83
|
+
securityContextId: @security_context_id,
|
84
|
+
pdfToSign: @pdf_content_base64,
|
85
|
+
signaturePolicyId: @signature_policy_id,
|
86
|
+
callbackArgument: @callback_argument,
|
87
|
+
visualRepresentation: @visual_representation,
|
88
|
+
ignoreRevocationStatusUnknown: @ignore_revocation_status_unknown
|
89
|
+
}
|
90
|
+
unless @signer_certificate_base64.to_s.blank?
|
91
|
+
request['certificate'] = Base64.encode64(@signer_certificate_base64)
|
92
|
+
end
|
93
|
+
|
94
|
+
response = @restpki_client.post('Api/PadesSignatures', request, 'pades_model')
|
95
|
+
|
96
|
+
unless response['certificate'].nil?
|
97
|
+
@certificate = response['certificate']
|
98
|
+
end
|
99
|
+
@done = true
|
100
|
+
|
101
|
+
{
|
102
|
+
:token => response['token'],
|
103
|
+
:to_sign_data => response['toSignData'],
|
104
|
+
:to_sign_hash => response['toSignHash'],
|
105
|
+
:digest_algorithm_oid => response['digestAlgorithmOid'],
|
106
|
+
:signature_algorithm => get_signature_algorithm(response['digestAlgorithmOid'])
|
107
|
+
}
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
|
2
|
+
module RestPki
|
3
|
+
class PadesVisualPositioningPresets
|
4
|
+
@@cached_presets = Hash.new
|
5
|
+
|
6
|
+
def self.get_footnote(restpki_client, page_number=nil, rows=nil)
|
7
|
+
url_segment = 'Footnote'
|
8
|
+
unless page_number.to_s.blank?
|
9
|
+
url_segment += '?pageNumber=' + page_number
|
10
|
+
end
|
11
|
+
unless rows.to_s.blank?
|
12
|
+
url_segment += '?rows=' + rows
|
13
|
+
end
|
14
|
+
get_preset(restpki_client, url_segment)
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.get_new_page(restpki_client)
|
18
|
+
get_preset(restpki_client, 'NewPage')
|
19
|
+
end
|
20
|
+
|
21
|
+
|
22
|
+
private
|
23
|
+
def self.get_preset(restpki_client, url_segment)
|
24
|
+
if @@cached_presets.to_hash.has_key? url_segment
|
25
|
+
return @@cached_presets[url_segment]
|
26
|
+
end
|
27
|
+
preset = restpki_client.get("Api/PadesVisualPositioningPresets/#{url_segment}", 'pades_model')
|
28
|
+
@@cached_presets[url_segment] = preset
|
29
|
+
preset
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
|
2
|
+
module RestPki
|
3
|
+
class RestError < RestBaseError
|
4
|
+
attr_reader :status_code, :error_message
|
5
|
+
|
6
|
+
def initialize(verb, url, status_code, error_message=nil)
|
7
|
+
message = "REST action #{verb} #{url} returned HTTP error #{status_code}"
|
8
|
+
unless error_message.to_s.blank?
|
9
|
+
message += ": #{error_message}"
|
10
|
+
end
|
11
|
+
super(message, verb, url)
|
12
|
+
@status_code = status_code
|
13
|
+
@error_message = error_message
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
|
2
|
+
module RestPki
|
3
|
+
class RestUnreachableError < RestBaseError
|
4
|
+
|
5
|
+
def initialize(verb, url, error_message=nil)
|
6
|
+
message = "REST action #{verb} #{url} unreachable"
|
7
|
+
unless error_message.to_s.blank?
|
8
|
+
message += ": #{error_message}"
|
9
|
+
end
|
10
|
+
super(message, verb, url)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
require 'uri'
|
2
|
+
require 'rest_client'
|
3
|
+
require 'multi_json'
|
4
|
+
|
5
|
+
module RestPki
|
6
|
+
class RestPkiClient
|
7
|
+
|
8
|
+
def initialize(endpoint_url, access_token)
|
9
|
+
@endpoint_url = endpoint_url
|
10
|
+
@access_token = access_token
|
11
|
+
end
|
12
|
+
|
13
|
+
def get(url, object_model)
|
14
|
+
verb = 'GET'
|
15
|
+
params = get_rest_params(verb, url)
|
16
|
+
|
17
|
+
begin
|
18
|
+
response = RestClient::Request.execute params
|
19
|
+
rescue RestClient::Exception => ex
|
20
|
+
response = RestPkiObject.convert({
|
21
|
+
:code => ex.http_code,
|
22
|
+
:body => ex.response
|
23
|
+
}, 'response_model')
|
24
|
+
rescue Exception => ex
|
25
|
+
raise RestUnreachableError.new(verb, url, ex.message)
|
26
|
+
end
|
27
|
+
check_response(verb, url, response)
|
28
|
+
|
29
|
+
RestPkiObject.convert(MultiJson.decode(response.body), object_model)
|
30
|
+
end
|
31
|
+
|
32
|
+
def post(url, data, object_model)
|
33
|
+
verb = 'POST'
|
34
|
+
params = get_rest_params(verb, url, data)
|
35
|
+
|
36
|
+
begin
|
37
|
+
response = RestClient::Request.execute params
|
38
|
+
rescue RestClient::Exception => ex
|
39
|
+
response = RestPkiObject.convert({
|
40
|
+
:code => ex.http_code,
|
41
|
+
:body => ex.response
|
42
|
+
}, 'response_model')
|
43
|
+
rescue Exception => ex
|
44
|
+
raise RestUnreachableError.new(verb, url, ex.message)
|
45
|
+
end
|
46
|
+
check_response(verb, url, response)
|
47
|
+
|
48
|
+
RestPkiObject.convert(MultiJson.decode(response.body), object_model)
|
49
|
+
end
|
50
|
+
|
51
|
+
def get_authentication
|
52
|
+
Authentication.new(self)
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
def get_rest_params(method, url, params=nil)
|
57
|
+
{
|
58
|
+
method: method,
|
59
|
+
url: @endpoint_url + url,
|
60
|
+
payload: params ? MultiJson.encode(params) : nil,
|
61
|
+
open_timeout: 30,
|
62
|
+
timeout: 90,
|
63
|
+
headers: {
|
64
|
+
'Content-Type': 'application/json; charset=utf8',
|
65
|
+
Accept: 'application/json',
|
66
|
+
Authorization: "Bearer #{@access_token}",
|
67
|
+
'X-RestPki-Client': "Ruby #{RestPki::VERSION}"
|
68
|
+
}
|
69
|
+
}
|
70
|
+
end
|
71
|
+
|
72
|
+
def check_response(verb, url, http_response)
|
73
|
+
status_code = http_response.code
|
74
|
+
if status_code < 200 || status_code > 299
|
75
|
+
ex = nil
|
76
|
+
begin
|
77
|
+
response = MultiJson.decode http_response.body
|
78
|
+
if status_code == 422 && response['code'].to_s.blank?
|
79
|
+
if response['code'] == 'ValidationError'
|
80
|
+
vr = ValidationResults.new(response['validationResults'])
|
81
|
+
ex = ValidationError.new(verb, url, vr)
|
82
|
+
else
|
83
|
+
ex = RestPkiError.new(verb, url, response['code'], response['detail'])
|
84
|
+
end
|
85
|
+
else
|
86
|
+
ex = RestError.new(verb, url, status_code, response['message'])
|
87
|
+
end
|
88
|
+
rescue => e
|
89
|
+
ex = RestError.new(verb, url, status_code)
|
90
|
+
end
|
91
|
+
raise ex
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|