isds 0.1.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/.rspec +3 -0
- data/.rubocop.yml +44 -0
- data/.ruby-version +1 -0
- data/LICENSE.txt +21 -0
- data/README.md +346 -0
- data/Rakefile +16 -0
- data/lib/isds/attachment.rb +66 -0
- data/lib/isds/authentication/access_interface.rb +20 -0
- data/lib/isds/authentication/base.rb +34 -0
- data/lib/isds/authentication/basic.rb +17 -0
- data/lib/isds/authentication/certificate.rb +58 -0
- data/lib/isds/client.rb +81 -0
- data/lib/isds/configuration.rb +154 -0
- data/lib/isds/connection.rb +113 -0
- data/lib/isds/databox.rb +121 -0
- data/lib/isds/digital_signature/verifier.rb +64 -0
- data/lib/isds/error.rb +59 -0
- data/lib/isds/large_messages/downloader.rb +61 -0
- data/lib/isds/large_messages/uploader.rb +65 -0
- data/lib/isds/message.rb +208 -0
- data/lib/isds/search.rb +111 -0
- data/lib/isds/status.rb +65 -0
- data/lib/isds/timestamp/verifier.rb +36 -0
- data/lib/isds/types.rb +19 -0
- data/lib/isds/user.rb +71 -0
- data/lib/isds/version.rb +5 -0
- data/lib/isds.rb +57 -0
- metadata +213 -0
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ISDS
|
|
4
|
+
module LargeMessages
|
|
5
|
+
class Uploader
|
|
6
|
+
DEFAULT_CHUNK_SIZE = 10 * 1024 * 1024 # 10MB
|
|
7
|
+
|
|
8
|
+
attr_reader :connection, :chunk_size
|
|
9
|
+
|
|
10
|
+
def initialize(connection:, chunk_size: DEFAULT_CHUNK_SIZE)
|
|
11
|
+
@connection = connection
|
|
12
|
+
@chunk_size = chunk_size
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def upload(file_path, message_envelope, &progress_callback) # rubocop:disable Metrics/AbcSize
|
|
16
|
+
validate_file!(file_path)
|
|
17
|
+
|
|
18
|
+
file_size = File.size(file_path)
|
|
19
|
+
chunks = (file_size.to_f / chunk_size).ceil
|
|
20
|
+
uploaded_bytes = 0
|
|
21
|
+
|
|
22
|
+
File.open(file_path, 'rb') do |file|
|
|
23
|
+
chunks.times do |i|
|
|
24
|
+
chunk_data = file.read(chunk_size)
|
|
25
|
+
upload_chunk(message_envelope, chunk_data, i + 1, chunks)
|
|
26
|
+
|
|
27
|
+
uploaded_bytes += chunk_data.bytesize
|
|
28
|
+
progress_callback&.call(uploaded_bytes, file_size, i + 1, chunks)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
finalize_upload(message_envelope)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
def validate_file!(file_path)
|
|
38
|
+
raise AttachmentError, "File not found: #{file_path}" unless File.exist?(file_path)
|
|
39
|
+
|
|
40
|
+
file_size = File.size(file_path)
|
|
41
|
+
return unless file_size > Attachment::LARGE_MAX_FILE_SIZE
|
|
42
|
+
|
|
43
|
+
raise FileSizeExceededError, 'File exceeds maximum size of 100MB for large messages'
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def upload_chunk(_envelope, chunk_data, sequence, total)
|
|
47
|
+
encoded = Base64.strict_encode64(chunk_data)
|
|
48
|
+
|
|
49
|
+
connection.call(:large_messages, :upload_chunk, {
|
|
50
|
+
'isds:dmChunk' => {
|
|
51
|
+
'isds:dmEncodedContent' => encoded,
|
|
52
|
+
'isds:dmChunkSeq' => sequence,
|
|
53
|
+
'isds:dmChunksTotal' => total
|
|
54
|
+
}
|
|
55
|
+
})
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def finalize_upload(envelope)
|
|
59
|
+
connection.call(:large_messages, :finalize_upload, {
|
|
60
|
+
'isds:dmEnvelope' => envelope
|
|
61
|
+
})
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
data/lib/isds/message.rb
ADDED
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ISDS
|
|
4
|
+
class Message # rubocop:disable Metrics/ClassLength
|
|
5
|
+
attr_reader :id, :databox_id, :subject, :sender, :recipient,
|
|
6
|
+
:status, :delivery_time, :acceptance_time,
|
|
7
|
+
:attachments, :annotation, :raw_data
|
|
8
|
+
|
|
9
|
+
ENVELOPE_OPTION_MAPPINGS = {
|
|
10
|
+
sender_org_unit: 'isds:dmSenderOrgUnit',
|
|
11
|
+
sender_org_unit_num: 'isds:dmSenderOrgUnitNum',
|
|
12
|
+
recipient_org_unit: 'isds:dmRecipientOrgUnit',
|
|
13
|
+
recipient_org_unit_num: 'isds:dmRecipientOrgUnitNum',
|
|
14
|
+
to_hands: 'isds:dmToHands',
|
|
15
|
+
personal_delivery: 'isds:dmPersonalDelivery',
|
|
16
|
+
allow_subst_delivery: 'isds:dmAllowSubstDelivery',
|
|
17
|
+
legal_title_law: 'isds:dmLegalTitleLaw',
|
|
18
|
+
legal_title_year: 'isds:dmLegalTitleYear',
|
|
19
|
+
legal_title_sect: 'isds:dmLegalTitleSect',
|
|
20
|
+
legal_title_par: 'isds:dmLegalTitlePar',
|
|
21
|
+
legal_title_point: 'isds:dmLegalTitlePoint',
|
|
22
|
+
type: 'isds:dmType'
|
|
23
|
+
}.freeze
|
|
24
|
+
private_constant :ENVELOPE_OPTION_MAPPINGS
|
|
25
|
+
|
|
26
|
+
def initialize(connection: nil, **attributes)
|
|
27
|
+
@connection = connection
|
|
28
|
+
@id = attributes[:id]
|
|
29
|
+
@databox_id = attributes[:databox_id]
|
|
30
|
+
@subject = attributes[:subject]
|
|
31
|
+
@sender = attributes[:sender]
|
|
32
|
+
@recipient = attributes[:recipient]
|
|
33
|
+
@status = attributes[:status]
|
|
34
|
+
@delivery_time = attributes[:delivery_time]
|
|
35
|
+
@acceptance_time = attributes[:acceptance_time]
|
|
36
|
+
@attachments = attributes[:attachments] || []
|
|
37
|
+
@annotation = attributes[:annotation]
|
|
38
|
+
@raw_data = attributes[:raw_data]
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def create(to_databox_id, subject:, attachments: [], **options)
|
|
42
|
+
envelope = build_envelope(to_databox_id, subject, options)
|
|
43
|
+
files = build_attachments(attachments)
|
|
44
|
+
|
|
45
|
+
response = @connection.call(:operations, :create_message, {
|
|
46
|
+
'isds:dmEnvelope' => envelope,
|
|
47
|
+
'isds:dmFiles' => files
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
extract_message_id(response)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def delivered?
|
|
54
|
+
[4, 5].include?(status)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def read?
|
|
58
|
+
status == 6
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def large_message?
|
|
62
|
+
attachments.any? { |a| a.respond_to?(:size) && a.size > 10 * 1024 * 1024 }
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def download_attachment(attachment_id, path = nil)
|
|
66
|
+
response = @connection.call(:info, :message_download, { 'isds:dmID' => id })
|
|
67
|
+
extract_attachment(response, attachment_id, path)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def download_all_attachments(directory)
|
|
71
|
+
response = @connection.call(:info, :message_download, { 'isds:dmID' => id })
|
|
72
|
+
extract_all_attachments(response, directory)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def signed_content
|
|
76
|
+
response = @connection.call(:info, :signed_message_download, { 'isds:dmID' => id })
|
|
77
|
+
response.dig(:signed_message_download_response, :dm_signature)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
class << self
|
|
81
|
+
def find(message_id, connection:)
|
|
82
|
+
response = connection.call(:info, :message_download, { 'isds:dmID' => message_id })
|
|
83
|
+
from_response(response, connection: connection)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def received(connection:, limit: 50, unread_only: true, **filters)
|
|
87
|
+
params = build_list_params(limit: limit, **filters)
|
|
88
|
+
params['isds:dmStatusFilter'] = unread_only ? '4' : '-1'
|
|
89
|
+
|
|
90
|
+
response = connection.call(:info, :get_list_of_received_messages, params)
|
|
91
|
+
parse_message_list(response, connection: connection)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def sent(connection:, limit: 50, **filters)
|
|
95
|
+
params = build_list_params(limit: limit, **filters)
|
|
96
|
+
|
|
97
|
+
response = connection.call(:info, :get_list_of_sent_messages, params)
|
|
98
|
+
parse_message_list(response, connection: connection)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def mark_as_read(message_id, connection:)
|
|
102
|
+
connection.call(:info, :mark_message_as_downloaded, { 'isds:dmID' => message_id })
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
private
|
|
106
|
+
|
|
107
|
+
def build_list_params(limit:, **filters)
|
|
108
|
+
params = { 'isds:dmLimit' => limit }
|
|
109
|
+
params['isds:dmFromTime'] = format_time_filter(filters[:from_time]) if filters[:from_time]
|
|
110
|
+
params['isds:dmToTime'] = format_time_filter(filters[:to_time]) if filters[:to_time]
|
|
111
|
+
params['isds:dmOffset'] = filters[:offset] if filters[:offset]
|
|
112
|
+
params
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def format_time_filter(value)
|
|
116
|
+
value.is_a?(Time) ? value.iso8601 : value
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def parse_message_list(response, connection:)
|
|
120
|
+
records_key = response.keys.first
|
|
121
|
+
records = response.dig(records_key, :dm_records, :dm_record)
|
|
122
|
+
return [] if records.nil?
|
|
123
|
+
|
|
124
|
+
records = [records] unless records.is_a?(Array)
|
|
125
|
+
records.map { |record| from_record(record, connection: connection) }
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def from_response(response, connection:)
|
|
129
|
+
data = response.dig(:message_download_response, :dm_return_data_message)
|
|
130
|
+
return nil unless data
|
|
131
|
+
|
|
132
|
+
build_from_envelope(data[:dm_dm] || {}, data, connection)
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def from_record(record, connection:)
|
|
136
|
+
build_from_envelope(record, record, connection)
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def build_from_envelope(envelope, raw, connection)
|
|
140
|
+
new(
|
|
141
|
+
connection: connection,
|
|
142
|
+
id: envelope[:dm_id],
|
|
143
|
+
subject: envelope[:dm_annotation],
|
|
144
|
+
sender: envelope[:dm_sender],
|
|
145
|
+
recipient: envelope[:dm_recipient],
|
|
146
|
+
status: envelope[:dm_message_status]&.to_i,
|
|
147
|
+
delivery_time: envelope[:dm_delivery_time],
|
|
148
|
+
acceptance_time: envelope[:dm_acceptance_time],
|
|
149
|
+
raw_data: raw
|
|
150
|
+
)
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
private
|
|
155
|
+
|
|
156
|
+
def build_envelope(to_databox_id, subject, options)
|
|
157
|
+
envelope = {
|
|
158
|
+
'isds:dbIDRecipient' => to_databox_id,
|
|
159
|
+
'isds:dmAnnotation' => subject
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
ENVELOPE_OPTION_MAPPINGS.each do |option_key, xml_key|
|
|
163
|
+
envelope[xml_key] = options[option_key] if options[option_key]
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
envelope
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def build_attachments(attachments)
|
|
170
|
+
return {} if attachments.empty?
|
|
171
|
+
|
|
172
|
+
{ 'isds:dmFile' => attachments.map { |a| Attachment.build_soap_element(a) } }
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def extract_message_id(response)
|
|
176
|
+
response.dig(:create_message_response, :dm_id) ||
|
|
177
|
+
response.dig(:create_message_response, :message_id)
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def extract_attachment(response, attachment_id, path)
|
|
181
|
+
files = extract_files(response)
|
|
182
|
+
file = files.find { |f| f[:dm_file_meta_type] == attachment_id || f[:dm_file_descr] == attachment_id }
|
|
183
|
+
return nil unless file
|
|
184
|
+
|
|
185
|
+
content = Base64.decode64(file[:dm_encoded_content])
|
|
186
|
+
path ? File.binwrite(path, content) && path : content
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
def extract_all_attachments(response, directory)
|
|
190
|
+
FileUtils.mkdir_p(directory)
|
|
191
|
+
|
|
192
|
+
extract_files(response).filter_map do |file|
|
|
193
|
+
next unless file[:dm_encoded_content]
|
|
194
|
+
|
|
195
|
+
content = Base64.decode64(file[:dm_encoded_content])
|
|
196
|
+
filename = file[:dm_file_descr] || "attachment_#{file[:dm_file_meta_type]}"
|
|
197
|
+
path = File.join(directory, filename)
|
|
198
|
+
File.binwrite(path, content)
|
|
199
|
+
path
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
def extract_files(response)
|
|
204
|
+
files = response.dig(:message_download_response, :dm_return_data_message, :dm_files, :dm_file)
|
|
205
|
+
files.is_a?(Array) ? files : [files]
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
end
|
data/lib/isds/search.rb
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ISDS
|
|
4
|
+
class Search
|
|
5
|
+
DATABOX_TYPES = {
|
|
6
|
+
fo: 'FO',
|
|
7
|
+
pfo: 'PFO',
|
|
8
|
+
po: 'PO',
|
|
9
|
+
ovm: 'OVM'
|
|
10
|
+
}.freeze
|
|
11
|
+
|
|
12
|
+
def self.databoxes(query, connection:, **options)
|
|
13
|
+
criteria = build_search_criteria(query, **options)
|
|
14
|
+
response = connection.call(:search, :find_data_box, criteria)
|
|
15
|
+
parse_search_response(response)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def self.by_name(name, connection:, exact: false)
|
|
19
|
+
criteria = {
|
|
20
|
+
'isds:dbOwnerInfo' => {
|
|
21
|
+
'isds:firmName' => name
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
response = connection.call(:search, :find_data_box, criteria)
|
|
26
|
+
results = parse_search_response(response)
|
|
27
|
+
|
|
28
|
+
if exact
|
|
29
|
+
results.select { |db| db.name.casecmp?(name) }
|
|
30
|
+
else
|
|
31
|
+
results
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def self.by_ico(ico, connection:)
|
|
36
|
+
Databox.find_by_ico(ico, connection: connection)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def self.by_address(connection:, **address_parts)
|
|
40
|
+
criteria = { 'isds:dbOwnerInfo' => {} }
|
|
41
|
+
criteria['isds:dbOwnerInfo']['isds:adCity'] = address_parts[:city] if address_parts[:city]
|
|
42
|
+
criteria['isds:dbOwnerInfo']['isds:adStreet'] = address_parts[:street] if address_parts[:street]
|
|
43
|
+
criteria['isds:dbOwnerInfo']['isds:adZipCode'] = address_parts[:zip_code] if address_parts[:zip_code]
|
|
44
|
+
|
|
45
|
+
response = connection.call(:search, :find_data_box, criteria)
|
|
46
|
+
parse_search_response(response)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def self.by_type(databox_type, connection:, query: nil)
|
|
50
|
+
type_value = DATABOX_TYPES[databox_type.to_sym] || databox_type.to_s
|
|
51
|
+
criteria = {
|
|
52
|
+
'isds:dbOwnerInfo' => {
|
|
53
|
+
'isds:dbType' => type_value
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
criteria['isds:dbOwnerInfo']['isds:firmName'] = query if query
|
|
57
|
+
|
|
58
|
+
response = connection.call(:search, :find_data_box, criteria)
|
|
59
|
+
parse_search_response(response)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def self.ovm_only(connection:, query: nil)
|
|
63
|
+
by_type(:ovm, connection: connection, query: query)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def self.paginated_search(query, connection:, page: 1, per_page: 100)
|
|
67
|
+
criteria = build_search_criteria(query)
|
|
68
|
+
criteria['isds:dbOffset'] = (page - 1) * per_page
|
|
69
|
+
criteria['isds:dbLimit'] = per_page
|
|
70
|
+
|
|
71
|
+
response = connection.call(:search, :find_data_box, criteria)
|
|
72
|
+
results = parse_search_response(response)
|
|
73
|
+
|
|
74
|
+
{
|
|
75
|
+
results: results,
|
|
76
|
+
page: page,
|
|
77
|
+
per_page: per_page,
|
|
78
|
+
total: results.length
|
|
79
|
+
}
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def self.build_search_criteria(query, **options)
|
|
83
|
+
criteria = { 'isds:dbOwnerInfo' => {} }
|
|
84
|
+
|
|
85
|
+
case query
|
|
86
|
+
when Hash
|
|
87
|
+
query.each do |key, value|
|
|
88
|
+
criteria['isds:dbOwnerInfo']["isds:#{key}"] = value
|
|
89
|
+
end
|
|
90
|
+
when String
|
|
91
|
+
criteria['isds:dbOwnerInfo']['isds:firmName'] = query
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
criteria['isds:dbOwnerInfo']['isds:dbType'] = options[:type] if options[:type]
|
|
95
|
+
criteria
|
|
96
|
+
end
|
|
97
|
+
private_class_method :build_search_criteria
|
|
98
|
+
|
|
99
|
+
def self.parse_search_response(response)
|
|
100
|
+
key = response.keys.first
|
|
101
|
+
records = response.dig(key, :db_results, :db_owner_info)
|
|
102
|
+
return [] if records.nil?
|
|
103
|
+
|
|
104
|
+
records = [records] unless records.is_a?(Array)
|
|
105
|
+
records.map do |record|
|
|
106
|
+
Databox.send(:from_record, record)
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
private_class_method :parse_search_response
|
|
110
|
+
end
|
|
111
|
+
end
|
data/lib/isds/status.rb
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ISDS
|
|
4
|
+
class Status
|
|
5
|
+
CODES = {
|
|
6
|
+
1 => :submitted,
|
|
7
|
+
2 => :stamped,
|
|
8
|
+
3 => :infected,
|
|
9
|
+
4 => :delivered,
|
|
10
|
+
5 => :delivered_by_fiction,
|
|
11
|
+
6 => :read,
|
|
12
|
+
7 => :undeliverable,
|
|
13
|
+
8 => :removed,
|
|
14
|
+
9 => :in_vault,
|
|
15
|
+
10 => :in_vault_delivered
|
|
16
|
+
}.freeze
|
|
17
|
+
|
|
18
|
+
LABELS = {
|
|
19
|
+
submitted: 'Podána',
|
|
20
|
+
stamped: 'Opatřena časovým razítkem',
|
|
21
|
+
infected: 'Infikována virem',
|
|
22
|
+
delivered: 'Dodána',
|
|
23
|
+
delivered_by_fiction: 'Doručena fikcí',
|
|
24
|
+
read: 'Přečtena',
|
|
25
|
+
undeliverable: 'Nedoručitelná',
|
|
26
|
+
removed: 'Odstraněna',
|
|
27
|
+
in_vault: 'V datovém trezoru',
|
|
28
|
+
in_vault_delivered: 'V datovém trezoru - dodána'
|
|
29
|
+
}.freeze
|
|
30
|
+
|
|
31
|
+
attr_reader :code
|
|
32
|
+
|
|
33
|
+
def initialize(code)
|
|
34
|
+
@code = code.to_i
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def name
|
|
38
|
+
CODES[code]
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def label
|
|
42
|
+
LABELS[name]
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def delivered?
|
|
46
|
+
%i[delivered delivered_by_fiction read in_vault_delivered].include?(name)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def read?
|
|
50
|
+
name == :read
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def problematic?
|
|
54
|
+
%i[infected undeliverable removed].include?(name)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def final?
|
|
58
|
+
%i[read undeliverable removed in_vault in_vault_delivered].include?(name)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def to_s
|
|
62
|
+
"#{code} - #{label || 'Unknown'}"
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ISDS
|
|
4
|
+
module Timestamp
|
|
5
|
+
class Verifier
|
|
6
|
+
attr_reader :connection
|
|
7
|
+
|
|
8
|
+
def initialize(connection:)
|
|
9
|
+
@connection = connection
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def verify(message_id)
|
|
13
|
+
response = connection.call(:info, :get_message_state_changes, {
|
|
14
|
+
'isds:dmID' => message_id
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
key = response.keys.first
|
|
18
|
+
events = response.dig(key, :dm_events, :dm_event)
|
|
19
|
+
return [] if events.nil?
|
|
20
|
+
|
|
21
|
+
events = [events] unless events.is_a?(Array)
|
|
22
|
+
events.map { |event| parse_event(event) }
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
private
|
|
26
|
+
|
|
27
|
+
def parse_event(event)
|
|
28
|
+
{
|
|
29
|
+
time: event[:dm_event_time],
|
|
30
|
+
type: event[:dm_event_type],
|
|
31
|
+
description: event[:dm_event_descr]
|
|
32
|
+
}
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
data/lib/isds/types.rb
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ISDS
|
|
4
|
+
module Types
|
|
5
|
+
include Dry.Types()
|
|
6
|
+
|
|
7
|
+
DataboxType = Types::Coercible::String.enum('FO', 'PFO', 'PFO_ADVOK', 'PFO_DANPOR', 'PFO_INSSPR',
|
|
8
|
+
'PFO_AUDITOR', 'PO', 'PO_ZAK', 'PO_REQ', 'OVM', 'OVM_NOTAR',
|
|
9
|
+
'OVM_EXEKUT', 'OVM_REQ')
|
|
10
|
+
|
|
11
|
+
MessageStatus = Types::Coercible::Integer.enum(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
|
|
12
|
+
# 1 = submitted, 2 = stamped, 3 = infected, 4 = delivered,
|
|
13
|
+
# 5 = delivered by fiction, 6 = read, 7 = undeliverable,
|
|
14
|
+
# 8 = removed, 9 = in data vault, 10 = in data vault delivered
|
|
15
|
+
|
|
16
|
+
Environment = Types::Coercible::Symbol.enum(:test, :production)
|
|
17
|
+
AuthMethod = Types::Coercible::Symbol.enum(:basic, :certificate, :access_interface)
|
|
18
|
+
end
|
|
19
|
+
end
|
data/lib/isds/user.rb
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ISDS
|
|
4
|
+
class User
|
|
5
|
+
attr_reader :id, :first_name, :last_name, :type, :privileges, :raw_data
|
|
6
|
+
|
|
7
|
+
def initialize(**attributes)
|
|
8
|
+
@id = attributes[:id]
|
|
9
|
+
@first_name = attributes[:first_name]
|
|
10
|
+
@last_name = attributes[:last_name]
|
|
11
|
+
@type = attributes[:type]
|
|
12
|
+
@privileges = attributes[:privileges] || {}
|
|
13
|
+
@raw_data = attributes[:raw_data]
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def full_name
|
|
17
|
+
[first_name, last_name].compact.join(' ')
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def self.list(connection:)
|
|
21
|
+
response = connection.call(:access, :get_data_box_users, {})
|
|
22
|
+
parse_users(response)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def self.add(connection:, databox_id:, **user_info)
|
|
26
|
+
Databox.validate_databox_id!(databox_id)
|
|
27
|
+
|
|
28
|
+
connection.call(:access, :add_data_box_user, {
|
|
29
|
+
'isds:dbID' => databox_id,
|
|
30
|
+
'isds:dbUserInfo' => build_user_info(user_info)
|
|
31
|
+
})
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def self.remove(connection:, user_id:)
|
|
35
|
+
connection.call(:access, :delete_data_box_user, {
|
|
36
|
+
'isds:dbUserID' => user_id
|
|
37
|
+
})
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def self.build_user_info(info)
|
|
41
|
+
user = {}
|
|
42
|
+
user['isds:pnFirstName'] = info[:first_name] if info[:first_name]
|
|
43
|
+
user['isds:pnLastName'] = info[:last_name] if info[:last_name]
|
|
44
|
+
user['isds:userPrivils'] = info[:privileges] if info[:privileges]
|
|
45
|
+
user
|
|
46
|
+
end
|
|
47
|
+
private_class_method :build_user_info
|
|
48
|
+
|
|
49
|
+
def self.parse_users(response)
|
|
50
|
+
key = response.keys.first
|
|
51
|
+
records = response.dig(key, :db_users, :db_user_info)
|
|
52
|
+
return [] if records.nil?
|
|
53
|
+
|
|
54
|
+
records = [records] unless records.is_a?(Array)
|
|
55
|
+
records.map { |record| from_record(record) }
|
|
56
|
+
end
|
|
57
|
+
private_class_method :parse_users
|
|
58
|
+
|
|
59
|
+
def self.from_record(record)
|
|
60
|
+
new(
|
|
61
|
+
id: record[:user_id],
|
|
62
|
+
first_name: record[:pn_first_name],
|
|
63
|
+
last_name: record[:pn_last_name],
|
|
64
|
+
type: record[:user_type],
|
|
65
|
+
privileges: record[:user_privils],
|
|
66
|
+
raw_data: record
|
|
67
|
+
)
|
|
68
|
+
end
|
|
69
|
+
private_class_method :from_record
|
|
70
|
+
end
|
|
71
|
+
end
|
data/lib/isds/version.rb
ADDED
data/lib/isds.rb
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'zeitwerk'
|
|
4
|
+
|
|
5
|
+
loader = Zeitwerk::Loader.for_gem
|
|
6
|
+
loader.inflector.inflect('isds' => 'ISDS')
|
|
7
|
+
loader.setup
|
|
8
|
+
|
|
9
|
+
require 'savon'
|
|
10
|
+
require 'nokogiri'
|
|
11
|
+
require 'faraday'
|
|
12
|
+
require 'faraday/retry'
|
|
13
|
+
require 'dry-configurable'
|
|
14
|
+
require 'dry-validation'
|
|
15
|
+
require 'dry-struct'
|
|
16
|
+
require 'dry-types'
|
|
17
|
+
|
|
18
|
+
module ISDS
|
|
19
|
+
ISDS_NAMESPACE = 'http://isds.czechpoint.cz/v20'
|
|
20
|
+
|
|
21
|
+
ENDPOINTS = {
|
|
22
|
+
production: {
|
|
23
|
+
base_url: 'https://ws1.mojedatovaschranka.cz/',
|
|
24
|
+
ws_url: 'https://ws1.mojedatovaschranka.cz/soap',
|
|
25
|
+
ws2_url: 'https://ws1.mojedatovaschranka.cz/vdz_ws/'
|
|
26
|
+
},
|
|
27
|
+
test: {
|
|
28
|
+
base_url: 'https://www.czebox.cz/',
|
|
29
|
+
ws_url: 'https://www.czebox.cz/soap',
|
|
30
|
+
ws2_url: 'https://www.czebox.cz/vdz_ws/',
|
|
31
|
+
wsdl_base: 'https://www.czebox.cz/static/wsdl/v20/'
|
|
32
|
+
}
|
|
33
|
+
}.freeze
|
|
34
|
+
|
|
35
|
+
SERVICE_ENDPOINT_MAP = {
|
|
36
|
+
operations: :ws_url,
|
|
37
|
+
info: :ws_url,
|
|
38
|
+
search: :ws_url,
|
|
39
|
+
access: :ws_url,
|
|
40
|
+
supplementary: :ws_url,
|
|
41
|
+
large_messages: :ws2_url
|
|
42
|
+
}.freeze
|
|
43
|
+
|
|
44
|
+
class << self
|
|
45
|
+
def configure
|
|
46
|
+
yield(configuration)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def configuration
|
|
50
|
+
@configuration ||= Configuration.new
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def reset_configuration!
|
|
54
|
+
@configuration = Configuration.new
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|