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.
Files changed (42) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +28 -0
  3. data/CHANGELOG.md +12 -0
  4. data/Gemfile +4 -0
  5. data/LICENSE.txt +22 -0
  6. data/README.md +19 -0
  7. data/Rakefile +2 -0
  8. data/lib/rest_pki.rb +35 -0
  9. data/lib/rest_pki/authentication.rb +43 -0
  10. data/lib/rest_pki/cades_signature_finisher.rb +48 -0
  11. data/lib/rest_pki/cades_signature_starter.rb +148 -0
  12. data/lib/rest_pki/full_xml_signature_starter.rb +51 -0
  13. data/lib/rest_pki/namespace_manager.rb +16 -0
  14. data/lib/rest_pki/object.rb +126 -0
  15. data/lib/rest_pki/pades_signature_finisher.rb +50 -0
  16. data/lib/rest_pki/pades_signature_starter.rb +110 -0
  17. data/lib/rest_pki/pades_visual_positioning_presets.rb +32 -0
  18. data/lib/rest_pki/resources/authentication_model.rb +5 -0
  19. data/lib/rest_pki/resources/cades_model.rb +5 -0
  20. data/lib/rest_pki/resources/pades_model.rb +5 -0
  21. data/lib/rest_pki/resources/response_model.rb +5 -0
  22. data/lib/rest_pki/resources/xml_model.rb +5 -0
  23. data/lib/rest_pki/rest_base_error.rb +12 -0
  24. data/lib/rest_pki/rest_error.rb +16 -0
  25. data/lib/rest_pki/rest_unreachable_error.rb +13 -0
  26. data/lib/rest_pki/restpki_client.rb +95 -0
  27. data/lib/rest_pki/restpki_error.rb +16 -0
  28. data/lib/rest_pki/signature_finisher.rb +33 -0
  29. data/lib/rest_pki/signature_starter.rb +59 -0
  30. data/lib/rest_pki/standard_security_contexts.rb +10 -0
  31. data/lib/rest_pki/standard_signature_policies.rb +25 -0
  32. data/lib/rest_pki/validation_error.rb +11 -0
  33. data/lib/rest_pki/validation_item.rb +33 -0
  34. data/lib/rest_pki/validation_results.rb +107 -0
  35. data/lib/rest_pki/version.rb +3 -0
  36. data/lib/rest_pki/xml_element_signature_starter.rb +68 -0
  37. data/lib/rest_pki/xml_id_resolution_table.rb +40 -0
  38. data/lib/rest_pki/xml_insertion_options.rb +11 -0
  39. data/lib/rest_pki/xml_signature_finisher.rb +52 -0
  40. data/lib/rest_pki/xml_signature_starter.rb +84 -0
  41. data/rest_pki.gemspec +30 -0
  42. 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,5 @@
1
+ module RestPki
2
+ class AuthenticationModel < RestPkiObject
3
+
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ module RestPki
2
+ class CadesModel < RestPkiObject
3
+
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ module RestPki
2
+ class PadesModel < RestPkiObject
3
+
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ module RestPki
2
+ class ResponseModel < RestPkiObject
3
+
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+
2
+ module RestPki
3
+ class XmlModel < RestPkiObject
4
+ end
5
+ end
@@ -0,0 +1,12 @@
1
+
2
+ module RestPki
3
+ class RestBaseError < StandardError
4
+ attr_reader :verb, :url
5
+
6
+ def initialize(message, verb, url)
7
+ super(message)
8
+ @verb = verb
9
+ @url = url
10
+ end
11
+ end
12
+ 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