rest_pki 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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