ksef_client 0.1.0.rc1 → 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 +4 -4
- data/CHANGELOG.md +1424 -0
- data/CONTRIBUTING.md +45 -6
- data/README.md +377 -25
- data/SECURITY.md +25 -2
- data/docs/REFERENCE.md +3099 -34
- data/docs/errors.md +33 -4
- data/docs/field_mapping.md +390 -0
- data/lib/ksef/auth/access_token.rb +150 -0
- data/lib/ksef/auth/authorization_policy.rb +88 -0
- data/lib/ksef/auth/challenge.rb +53 -0
- data/lib/ksef/auth/client.rb +143 -0
- data/lib/ksef/auth/initiation.rb +22 -0
- data/lib/ksef/auth/operation_status.rb +34 -0
- data/lib/ksef/auth/schema/schemat_auth_v2-0.xsd +109 -0
- data/lib/ksef/auth/schema/schemat_auth_v2-1.xsd +109 -0
- data/lib/ksef/auth/signature_template.rb +120 -0
- data/lib/ksef/auth/signer.rb +132 -0
- data/lib/ksef/auth/status.rb +69 -0
- data/lib/ksef/auth/token.rb +121 -0
- data/lib/ksef/auth/token_info.rb +27 -0
- data/lib/ksef/auth/token_request.rb +142 -0
- data/lib/ksef/auth/tokens.rb +24 -0
- data/lib/ksef/auth/validator.rb +84 -0
- data/lib/ksef/auth/xades.rb +40 -0
- data/lib/ksef/auth.rb +43 -0
- data/lib/ksef/client/receipt.rb +37 -0
- data/lib/ksef/client/session.rb +68 -0
- data/lib/ksef/client.rb +314 -0
- data/lib/ksef/crypto/certificate.rb +78 -0
- data/lib/ksef/crypto/digest.rb +25 -0
- data/lib/ksef/crypto/encryptor.rb +131 -0
- data/lib/ksef/crypto/public_keys.rb +146 -0
- data/lib/ksef/crypto.rb +66 -0
- data/lib/ksef/environments.rb +1 -1
- data/lib/ksef/errors.rb +35 -2
- data/lib/ksef/fa3/address.rb +64 -0
- data/lib/ksef/fa3/advance_checks.rb +55 -0
- data/lib/ksef/fa3/advance_invoice.rb +55 -0
- data/lib/ksef/fa3/advance_reader.rb +59 -0
- data/lib/ksef/fa3/attachment.rb +43 -0
- data/lib/ksef/fa3/attachment_checks.rb +121 -0
- data/lib/ksef/fa3/attachment_reader.rb +99 -0
- data/lib/ksef/fa3/attachment_table.rb +98 -0
- data/lib/ksef/fa3/builder/advances.rb +57 -0
- data/lib/ksef/fa3/builder/corrections.rb +46 -0
- data/lib/ksef/fa3/builder/subjects.rb +71 -0
- data/lib/ksef/fa3/builder.rb +149 -0
- data/lib/ksef/fa3/business_validator.rb +134 -0
- data/lib/ksef/fa3/canonical.rb +44 -0
- data/lib/ksef/fa3/corrected_invoice.rb +53 -0
- data/lib/ksef/fa3/correction.rb +151 -0
- data/lib/ksef/fa3/correction_checks.rb +106 -0
- data/lib/ksef/fa3/correction_reader.rb +88 -0
- data/lib/ksef/fa3/data_block.rb +75 -0
- data/lib/ksef/fa3/document_mapping.rb +142 -0
- data/lib/ksef/fa3/document_validator.rb +174 -0
- data/lib/ksef/fa3/element_tree.rb +49 -0
- data/lib/ksef/fa3/field_checks.rb +151 -0
- data/lib/ksef/fa3/formatting.rb +273 -0
- data/lib/ksef/fa3/generated/enums.rb +652 -0
- data/lib/ksef/fa3/generated/types.rb +3308 -0
- data/lib/ksef/fa3/invoice.rb +259 -0
- data/lib/ksef/fa3/issue.rb +32 -0
- data/lib/ksef/fa3/line.rb +162 -0
- data/lib/ksef/fa3/meta_entry.rb +45 -0
- data/lib/ksef/fa3/model_validator.rb +219 -0
- data/lib/ksef/fa3/nip.rb +59 -0
- data/lib/ksef/fa3/node_reader.rb +46 -0
- data/lib/ksef/fa3/order.rb +55 -0
- data/lib/ksef/fa3/order_line.rb +70 -0
- data/lib/ksef/fa3/parser.rb +208 -0
- data/lib/ksef/fa3/provenance.rb +137 -0
- data/lib/ksef/fa3/rounding_inference.rb +87 -0
- data/lib/ksef/fa3/row_reader.rb +66 -0
- data/lib/ksef/fa3/serializer.rb +140 -0
- data/lib/ksef/fa3/subject.rb +128 -0
- data/lib/ksef/fa3/subject_checks.rb +126 -0
- data/lib/ksef/fa3/subject_reader.rb +76 -0
- data/lib/ksef/fa3/summaries.rb +107 -0
- data/lib/ksef/fa3/summary_checks.rb +75 -0
- data/lib/ksef/fa3/table_column.rb +49 -0
- data/lib/ksef/fa3/totals.rb +119 -0
- data/lib/ksef/fa3/validator.rb +79 -0
- data/lib/ksef/fa3/vat_rate.rb +94 -0
- data/lib/ksef/fa3.rb +59 -0
- data/lib/ksef/http/connection.rb +47 -4
- data/lib/ksef/http/json_decoder.rb +53 -0
- data/lib/ksef/http/retry.rb +105 -0
- data/lib/ksef/invoices/client.rb +72 -0
- data/lib/ksef/ksef_number.rb +150 -0
- data/lib/ksef/sessions/invoice_codes.rb +70 -0
- data/lib/ksef/sessions/invoice_state.rb +68 -0
- data/lib/ksef/sessions/online.rb +169 -0
- data/lib/ksef/sessions/session_codes.rb +73 -0
- data/lib/ksef/sessions/session_state.rb +48 -0
- data/lib/ksef/sessions/status.rb +138 -0
- data/lib/ksef/sessions/upo_page.rb +36 -0
- data/lib/ksef/sessions.rb +91 -0
- data/lib/ksef/upo/client.rb +154 -0
- data/lib/ksef/upo/document.rb +74 -0
- data/lib/ksef/upo/schema/upo-v4-3.xsd +293 -0
- data/lib/ksef/upo/validation.rb +37 -0
- data/lib/ksef/upo/validator.rb +124 -0
- data/lib/ksef/upo.rb +55 -0
- data/lib/ksef/version.rb +1 -1
- data/lib/ksef.rb +18 -0
- metadata +105 -4
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ksef
|
|
4
|
+
module Auth
|
|
5
|
+
# The optional `AuthorizationPolicy` of an {TokenRequest} — a whitelist restricting
|
|
6
|
+
# which client IPs may use the resulting `accessToken`.
|
|
7
|
+
#
|
|
8
|
+
# Its own class rather than a Hash inside {TokenRequest} because the schema treats it
|
|
9
|
+
# as a distinct structure with its own rules: three list kinds, each capped at ten
|
|
10
|
+
# entries, in a fixed order, wrapped in an `AllowedIps` element that is mandatory once
|
|
11
|
+
# the policy is present (docs/REFERENCE.md §4.1).
|
|
12
|
+
#
|
|
13
|
+
# The IP values are deliberately **not** pattern-checked here. v2.1's patterns are
|
|
14
|
+
# correct and the schema will catch a malformed address, whereas v2.0's are broken
|
|
15
|
+
# outright (§14.4) — duplicating either in Ruby would mean maintaining a second,
|
|
16
|
+
# divergent source of truth for no gain.
|
|
17
|
+
class AuthorizationPolicy
|
|
18
|
+
MAX_IPS = 10
|
|
19
|
+
|
|
20
|
+
# Ordered as the schema sequences them; the caller's Hash order is irrelevant.
|
|
21
|
+
IP_ELEMENTS = { addresses: "Ip4Address", ranges: "Ip4Range", masks: "Ip4Mask" }.freeze
|
|
22
|
+
|
|
23
|
+
# The same three lists as JSON, for the KSeF-token flow's `authorizationPolicy`.
|
|
24
|
+
# Both authentication methods accept the policy; only their encodings differ, so the
|
|
25
|
+
# rules above are shared rather than restated. The OpenAPI contract caps each of
|
|
26
|
+
# these arrays at ten entries too, which independently corroborates {MAX_IPS}.
|
|
27
|
+
IP_FIELDS = { addresses: :ip4Addresses, ranges: :ip4Ranges, masks: :ip4Masks }.freeze
|
|
28
|
+
|
|
29
|
+
attr_reader :addresses, :ranges, :masks
|
|
30
|
+
|
|
31
|
+
# @return [AuthorizationPolicy, nil] passes `nil` and an existing policy through
|
|
32
|
+
# @raise [Ksef::ValidationError] on an unknown key
|
|
33
|
+
def self.coerce(value)
|
|
34
|
+
return value if value.nil? || value.is_a?(self)
|
|
35
|
+
|
|
36
|
+
unknown = value.keys - IP_ELEMENTS.keys
|
|
37
|
+
unless unknown.empty?
|
|
38
|
+
raise ValidationError,
|
|
39
|
+
"Unknown allowed_ips key(s) #{unknown.map(&:inspect).join(", ")}. " \
|
|
40
|
+
"Permitted: #{IP_ELEMENTS.keys.map(&:inspect).join(", ")}."
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
new(**value)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def initialize(addresses: [], ranges: [], masks: [])
|
|
47
|
+
@addresses = Array(addresses).freeze
|
|
48
|
+
@ranges = Array(ranges).freeze
|
|
49
|
+
@masks = Array(masks).freeze
|
|
50
|
+
validate!
|
|
51
|
+
freeze
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# @return [Array<Array(String, String)>] `[element name, value]` pairs in schema order
|
|
55
|
+
def entries
|
|
56
|
+
IP_ELEMENTS.flat_map { |key, name| public_send(key).map { |value| [name, value] } }
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# @return [Hash] the contract's `AllowedIps`, omitting the lists that are empty —
|
|
60
|
+
# all three are nullable, and sending `[]` says nothing the absence does not
|
|
61
|
+
def to_h
|
|
62
|
+
IP_FIELDS.each_with_object({}) do |(key, field), json|
|
|
63
|
+
values = public_send(key)
|
|
64
|
+
json[field] = values unless values.empty?
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def empty? = addresses.empty? && ranges.empty? && masks.empty?
|
|
69
|
+
|
|
70
|
+
private
|
|
71
|
+
|
|
72
|
+
def validate!
|
|
73
|
+
# AllowedIps is mandatory inside AuthorizationPolicy, so an empty policy cannot be
|
|
74
|
+
# made schema-valid. Saying so beats emitting a document KSeF will reject.
|
|
75
|
+
raise ValidationError, "allowed_ips was given but lists no addresses" if empty?
|
|
76
|
+
|
|
77
|
+
IP_ELEMENTS.each_key { |key| check_count(key, public_send(key)) }
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def check_count(key, list)
|
|
81
|
+
return if list.size <= MAX_IPS
|
|
82
|
+
|
|
83
|
+
raise ValidationError,
|
|
84
|
+
"allowed_ips[#{key.inspect}] has #{list.size} entries; the schema permits #{MAX_IPS}"
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ksef
|
|
4
|
+
module Auth
|
|
5
|
+
# The response to `POST /auth/challenge` — the prologue to both authentication methods.
|
|
6
|
+
#
|
|
7
|
+
# `timestamp_ms` is not decoration: the KSeF-token flow encrypts `{token}|{timestampMs}`
|
|
8
|
+
# and the timestamp acts as a nonce, so it must be the server's value rather than a
|
|
9
|
+
# locally generated one (docs/REFERENCE.md §4.5).
|
|
10
|
+
Challenge = Data.define(:challenge, :timestamp, :timestamp_ms, :client_ip)
|
|
11
|
+
|
|
12
|
+
# Reopened rather than using a `Data.define` block, matching {Ksef::FA3::Invoice}: a
|
|
13
|
+
# constant inside that block would be defined on `Object`, not on the class.
|
|
14
|
+
class Challenge
|
|
15
|
+
# Ten minutes from issue (§4).
|
|
16
|
+
LIFETIME = 600
|
|
17
|
+
|
|
18
|
+
# Length 36, `\d{8}-CR-[A-F0-9]{10}-[A-F0-9]{10}-[A-F0-9]{2}` (§4.1). Lives here
|
|
19
|
+
# rather than on the request documents because it describes a *challenge*, and both
|
|
20
|
+
# authentication methods consume the same one — the XAdES document and the KSeF-token
|
|
21
|
+
# JSON body would otherwise each carry their own copy.
|
|
22
|
+
FORMAT = /\A\d{8}-CR-[A-F0-9]{10}-[A-F0-9]{10}-[A-F0-9]{2}\z/
|
|
23
|
+
|
|
24
|
+
# Checked before a request is spent on the challenge, since a malformed or
|
|
25
|
+
# copy-pasted one is the cheapest failure to catch locally.
|
|
26
|
+
#
|
|
27
|
+
# @param value [String]
|
|
28
|
+
# @raise [Ksef::ValidationError]
|
|
29
|
+
def self.validate_format!(value)
|
|
30
|
+
return value if value.is_a?(String) && FORMAT.match?(value)
|
|
31
|
+
|
|
32
|
+
raise ValidationError,
|
|
33
|
+
"Challenge #{value.inspect} is malformed. Expected 36 characters as " \
|
|
34
|
+
"YYYYMMDD-CR-XXXXXXXXXX-XXXXXXXXXX-XX with uppercase hex, exactly as returned by " \
|
|
35
|
+
"POST /auth/challenge."
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def self.from(payload)
|
|
39
|
+
new(
|
|
40
|
+
challenge: payload["challenge"],
|
|
41
|
+
timestamp: Ksef::Auth.time(payload["timestamp"]),
|
|
42
|
+
timestamp_ms: payload["timestampMs"],
|
|
43
|
+
client_ip: payload["clientIp"]
|
|
44
|
+
)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Worth checking before spending a signature on a challenge that has already lapsed.
|
|
48
|
+
def expires_at = timestamp && (timestamp + LIFETIME)
|
|
49
|
+
def expired?(now = Time.now) = !expires_at.nil? && expires_at <= now
|
|
50
|
+
def to_s = challenge.to_s
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "faraday"
|
|
4
|
+
|
|
5
|
+
module Ksef
|
|
6
|
+
module Auth
|
|
7
|
+
# The six authentication endpoint calls: the five HTTP steps of §4.2's flow — its
|
|
8
|
+
# sixth step, signing, is offline — plus `POST /auth/ksef-token` for the other
|
|
9
|
+
# authentication method (docs/REFERENCE.md §4.2, §4.5).
|
|
10
|
+
#
|
|
11
|
+
# Deliberately thin: it maps requests and responses and does nothing else. Deciding
|
|
12
|
+
# *which* credential to present, and caching the result, belongs a layer up in
|
|
13
|
+
# {Ksef::Client}, which does exactly that.
|
|
14
|
+
#
|
|
15
|
+
# Namespaced `Ksef::Auth::Client` rather than folded into `Ksef::Client` so that each
|
|
16
|
+
# subsystem's endpoints stay together, matching how the official clients are organised.
|
|
17
|
+
#
|
|
18
|
+
# Three of these calls are **unauthenticated** — the challenge and both authentication
|
|
19
|
+
# submissions declare no `security` in the contract. Of the rest, {#status} and
|
|
20
|
+
# {#redeem} take the temporary *authentication* token and {#refresh} takes the refresh
|
|
21
|
+
# token, so the bearer is passed per call rather than held by the instance.
|
|
22
|
+
class Client
|
|
23
|
+
# Polling defaults. There is deliberately no timeout: on DEMO and PROD the operation
|
|
24
|
+
# legitimately stays "in progress" while the certificate's status is checked with its
|
|
25
|
+
# issuer over OCSP/CRL, and the docs say the duration depends on that issuer. A client
|
|
26
|
+
# that gives up after a fixed interval reports failure for authentications that were
|
|
27
|
+
# about to succeed (§4.2).
|
|
28
|
+
POLL_INTERVAL = 2
|
|
29
|
+
|
|
30
|
+
# @param connection [Faraday::Connection] usually from {Ksef::HTTP::Connection.build}
|
|
31
|
+
def initialize(connection)
|
|
32
|
+
@connection = connection
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# @return [Challenge]
|
|
36
|
+
def challenge
|
|
37
|
+
Challenge.from(post("auth/challenge").body)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Submits the XAdES-signed `AuthTokenRequest`. Returns `202 Accepted`; the operation
|
|
41
|
+
# is asynchronous from here.
|
|
42
|
+
#
|
|
43
|
+
# @param signed_xml [String]
|
|
44
|
+
# @param verify_certificate_chain [Boolean, nil] the contract's optional query flag
|
|
45
|
+
# @return [Initiation]
|
|
46
|
+
def submit_xades(signed_xml, verify_certificate_chain: nil)
|
|
47
|
+
response = @connection.post("auth/xades-signature") do |request|
|
|
48
|
+
request.params["verifyCertificateChain"] = verify_certificate_chain unless verify_certificate_chain.nil?
|
|
49
|
+
request.headers["Content-Type"] = "application/xml"
|
|
50
|
+
request.body = signed_xml
|
|
51
|
+
end
|
|
52
|
+
Initiation.from(response.body)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Submits a KSeF-token authentication. Like {#submit_xades} this takes an
|
|
56
|
+
# already-built request — {Token#authentication_request} assembles it, because the
|
|
57
|
+
# encryption depends on which published key was selected and that is not this
|
|
58
|
+
# layer's decision.
|
|
59
|
+
#
|
|
60
|
+
# A `400` here carries `21111` for a bad challenge or `21470` for a stale key
|
|
61
|
+
# identifier; the latter is worth wrapping in
|
|
62
|
+
# {Ksef::Crypto::PublicKeys#with_key_rotation}.
|
|
63
|
+
#
|
|
64
|
+
# @param request [Hash] the contract's `InitTokenAuthenticationRequest`
|
|
65
|
+
# @return [Initiation]
|
|
66
|
+
def submit_ksef_token(request)
|
|
67
|
+
Initiation.from(post("auth/ksef-token", body: request).body)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# @param token [String, TokenInfo] the *authentication* token from {#submit_xades}
|
|
71
|
+
# @return [OperationStatus]
|
|
72
|
+
def status(reference_number, token:)
|
|
73
|
+
OperationStatus.from(get("auth/#{reference_number}", token: token).body)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Exchanges a completed authentication for the token pair. **Single-use** — a second
|
|
77
|
+
# call with the same authentication token is a 400, so this is never auto-retried
|
|
78
|
+
# (it is a POST, which the retry policy already excludes).
|
|
79
|
+
#
|
|
80
|
+
# @return [Tokens]
|
|
81
|
+
def redeem(token:)
|
|
82
|
+
Tokens.from(post("auth/token/redeem", token: token).body)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# @return [TokenInfo] a fresh access token
|
|
86
|
+
def refresh(refresh_token:)
|
|
87
|
+
TokenInfo.from(post("auth/token/refresh", token: refresh_token).body["accessToken"])
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Polls until the operation stops being in progress.
|
|
91
|
+
#
|
|
92
|
+
# @param interval [Numeric] seconds between polls
|
|
93
|
+
# @param sleeper [#call] injected for tests; receives the interval
|
|
94
|
+
# @yieldparam status [OperationStatus] after each poll, for progress reporting
|
|
95
|
+
# @return [OperationStatus] the terminal status, successful or not
|
|
96
|
+
def wait_until_complete(reference_number, token:, interval: POLL_INTERVAL, sleeper: method(:sleep))
|
|
97
|
+
loop do
|
|
98
|
+
current = status(reference_number, token: token)
|
|
99
|
+
yield current if block_given?
|
|
100
|
+
return current if current.terminal?
|
|
101
|
+
|
|
102
|
+
sleeper.call(interval)
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# As {#wait_until_complete}, but insists on success.
|
|
107
|
+
#
|
|
108
|
+
# @raise [Ksef::AuthenticationError] carrying the server's own wording and details
|
|
109
|
+
def authenticate!(reference_number, token:, **, &)
|
|
110
|
+
result = wait_until_complete(reference_number, token: token, **, &)
|
|
111
|
+
return result if result.success?
|
|
112
|
+
|
|
113
|
+
detail = result.details.empty? ? "" : " (#{result.details.join("; ")})"
|
|
114
|
+
raise AuthenticationError,
|
|
115
|
+
"Authentication #{reference_number} failed with status #{result.code}: #{result.explain}#{detail}"
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
private
|
|
119
|
+
|
|
120
|
+
def get(path, token: nil)
|
|
121
|
+
@connection.get(path) { |request| authorize(request, token) }
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def post(path, token: nil, body: nil)
|
|
125
|
+
@connection.post(path) do |request|
|
|
126
|
+
authorize(request, token)
|
|
127
|
+
request.body = body unless body.nil?
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# The bearer differs per call: the authentication token for status and redemption,
|
|
132
|
+
# the refresh token for refresh, and none at all for the first two. A {TokenInfo} or
|
|
133
|
+
# a bare String both work — but the value has to be pulled out explicitly, because
|
|
134
|
+
# `TokenInfo#to_s` redacts itself and interpolating one would send "[REDACTED]".
|
|
135
|
+
def authorize(request, token)
|
|
136
|
+
return if token.nil?
|
|
137
|
+
|
|
138
|
+
value = token.is_a?(TokenInfo) ? token.token : token.to_s
|
|
139
|
+
request.headers["Authorization"] = "Bearer #{value}"
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ksef
|
|
4
|
+
module Auth
|
|
5
|
+
# The `202 Accepted` from `POST /auth/xades-signature` — the contract's
|
|
6
|
+
# `AuthenticationInitResponse`.
|
|
7
|
+
#
|
|
8
|
+
# The token here is the short-lived *authentication* token, not an access token. It
|
|
9
|
+
# authorises exactly two follow-up calls: checking the operation's status, and redeeming
|
|
10
|
+
# it for the real pair. Confusing the two is the obvious mistake in this flow (docs/REFERENCE.md §4.2).
|
|
11
|
+
Initiation = Data.define(:reference_number, :authentication_token) do
|
|
12
|
+
def self.from(payload)
|
|
13
|
+
new(
|
|
14
|
+
reference_number: payload["referenceNumber"],
|
|
15
|
+
authentication_token: TokenInfo.from(payload["authenticationToken"])
|
|
16
|
+
)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def token = authentication_token&.token
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ksef
|
|
4
|
+
module Auth
|
|
5
|
+
# The response to `GET /auth/{referenceNumber}` — the contract's
|
|
6
|
+
# `AuthenticationOperationStatusResponse`.
|
|
7
|
+
OperationStatus = Data.define(
|
|
8
|
+
:code, :description, :details, :authentication_method, :started_at,
|
|
9
|
+
:token_redeemed, :last_refresh_at, :refresh_token_valid_until
|
|
10
|
+
) do
|
|
11
|
+
def self.from(payload)
|
|
12
|
+
status = payload["status"] || {}
|
|
13
|
+
new(
|
|
14
|
+
code: status["code"],
|
|
15
|
+
description: status["description"],
|
|
16
|
+
details: Array(status["details"]).freeze,
|
|
17
|
+
authentication_method: payload["authenticationMethod"],
|
|
18
|
+
started_at: Ksef::Auth.time(payload["startDate"]),
|
|
19
|
+
token_redeemed: payload["isTokenRedeemed"],
|
|
20
|
+
last_refresh_at: Ksef::Auth.time(payload["lastTokenRefreshDate"]),
|
|
21
|
+
refresh_token_valid_until: Ksef::Auth.time(payload["refreshTokenValidUntil"])
|
|
22
|
+
)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def in_progress? = Status.in_progress?(code)
|
|
26
|
+
def success? = Status.success?(code)
|
|
27
|
+
def terminal? = Status.terminal?(code)
|
|
28
|
+
def retryable? = Status.retryable?(code)
|
|
29
|
+
|
|
30
|
+
# Prefers the server's own wording; falls back to our table when it sends none.
|
|
31
|
+
def explain = description.to_s.empty? ? Status.describe(code) : description
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="utf-8"?>
|
|
2
|
+
<xsd:schema
|
|
3
|
+
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
|
4
|
+
xmlns:tns="http://ksef.mf.gov.pl/auth/token/2.0"
|
|
5
|
+
targetNamespace="http://ksef.mf.gov.pl/auth/token/2.0"
|
|
6
|
+
elementFormDefault="qualified" attributeFormDefault="unqualified">
|
|
7
|
+
<xsd:element name="AuthTokenRequest">
|
|
8
|
+
<xsd:complexType>
|
|
9
|
+
<xsd:sequence>
|
|
10
|
+
<xsd:element name="Challenge">
|
|
11
|
+
<xsd:simpleType>
|
|
12
|
+
<xsd:restriction base="xsd:token">
|
|
13
|
+
<xsd:length value="36"/>
|
|
14
|
+
<xsd:pattern value="\d{8}-CR-[A-F0-9]{10}-[A-F0-9]{10}-[A-F0-9]{2}"/>
|
|
15
|
+
</xsd:restriction>
|
|
16
|
+
</xsd:simpleType>
|
|
17
|
+
</xsd:element>
|
|
18
|
+
<xsd:element name="ContextIdentifier" type="tns:TContextIdentifier"/>
|
|
19
|
+
<xsd:element name="SubjectIdentifierType" type="tns:SubjectIdentifierTypeEnum"/>
|
|
20
|
+
<xsd:element name="AuthorizationPolicy" minOccurs="0">
|
|
21
|
+
<xsd:complexType>
|
|
22
|
+
<xsd:sequence>
|
|
23
|
+
<xsd:element name="AllowedIps">
|
|
24
|
+
<xsd:complexType>
|
|
25
|
+
<xsd:sequence minOccurs="1" maxOccurs="1">
|
|
26
|
+
<xsd:element name="Ip4Address" minOccurs="0" maxOccurs="10">
|
|
27
|
+
<xsd:simpleType>
|
|
28
|
+
<xsd:restriction base="xsd:token">
|
|
29
|
+
<xsd:pattern value="^((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)\.?\b){4}$"/>
|
|
30
|
+
</xsd:restriction>
|
|
31
|
+
</xsd:simpleType>
|
|
32
|
+
</xsd:element>
|
|
33
|
+
<xsd:element name="Ip4Range" minOccurs="0" maxOccurs="10">
|
|
34
|
+
<xsd:simpleType>
|
|
35
|
+
<xsd:restriction base="xsd:token">
|
|
36
|
+
<xsd:pattern value="^((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)\.?\b){4}-((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)\.?\b){4}$"/>
|
|
37
|
+
</xsd:restriction>
|
|
38
|
+
</xsd:simpleType>
|
|
39
|
+
</xsd:element>
|
|
40
|
+
<xsd:element name="Ip4Mask" minOccurs="0" maxOccurs="10">
|
|
41
|
+
<xsd:simpleType>
|
|
42
|
+
<xsd:restriction base="xsd:token">
|
|
43
|
+
<xsd:pattern value="^((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)\.?\b){4}\/(0|[1-9]|1[0-9]|2[0-9]|3[0-2])$"/>
|
|
44
|
+
</xsd:restriction>
|
|
45
|
+
</xsd:simpleType>
|
|
46
|
+
</xsd:element>
|
|
47
|
+
</xsd:sequence>
|
|
48
|
+
</xsd:complexType>
|
|
49
|
+
</xsd:element>
|
|
50
|
+
</xsd:sequence>
|
|
51
|
+
</xsd:complexType>
|
|
52
|
+
</xsd:element>
|
|
53
|
+
</xsd:sequence>
|
|
54
|
+
</xsd:complexType>
|
|
55
|
+
</xsd:element>
|
|
56
|
+
<xsd:simpleType name="SubjectIdentifierTypeEnum">
|
|
57
|
+
<xsd:restriction base="xsd:token">
|
|
58
|
+
<xsd:enumeration value="certificateSubject"/>
|
|
59
|
+
<xsd:enumeration value="certificateFingerprint"/>
|
|
60
|
+
</xsd:restriction>
|
|
61
|
+
</xsd:simpleType>
|
|
62
|
+
|
|
63
|
+
<xsd:complexType name="TContextIdentifier">
|
|
64
|
+
<xsd:choice>
|
|
65
|
+
<xsd:element name="Nip" type="tns:TNIP">
|
|
66
|
+
<xsd:annotation>
|
|
67
|
+
<xsd:documentation>Numer Identyfikacyjny NIP</xsd:documentation>
|
|
68
|
+
</xsd:annotation>
|
|
69
|
+
</xsd:element>
|
|
70
|
+
<xsd:element name="InternalId" type="tns:TIID">
|
|
71
|
+
<xsd:annotation>
|
|
72
|
+
<xsd:documentation>Identyfikator wewnętrzny</xsd:documentation>
|
|
73
|
+
</xsd:annotation>
|
|
74
|
+
</xsd:element>
|
|
75
|
+
<xsd:element name="NipVatUe" type="tns:TNipVatUE">
|
|
76
|
+
<xsd:annotation>
|
|
77
|
+
<xsd:documentation>Kontekst złożony, będący połaczniem numeru NIP oraz numeru Vat UE</xsd:documentation>
|
|
78
|
+
</xsd:annotation>
|
|
79
|
+
</xsd:element>
|
|
80
|
+
<xsd:element name="PeppolId" type="tns:TPeppolId">
|
|
81
|
+
<xsd:annotation>
|
|
82
|
+
<xsd:documentation>Identyfikator dostawcy usług Peppol</xsd:documentation>
|
|
83
|
+
</xsd:annotation>
|
|
84
|
+
</xsd:element>
|
|
85
|
+
</xsd:choice>
|
|
86
|
+
</xsd:complexType>
|
|
87
|
+
|
|
88
|
+
<xsd:simpleType name="TIID">
|
|
89
|
+
<xsd:restriction base="xsd:string">
|
|
90
|
+
<xsd:pattern value="[1-9]((\d[1-9])|([1-9]\d))\d{7}-\d{5}"/>
|
|
91
|
+
</xsd:restriction>
|
|
92
|
+
</xsd:simpleType>
|
|
93
|
+
<xsd:simpleType name="TNIP">
|
|
94
|
+
<xsd:restriction base="xsd:string">
|
|
95
|
+
<xsd:pattern value="[1-9]((\d[1-9])|([1-9]\d))\d{7}"/>
|
|
96
|
+
</xsd:restriction>
|
|
97
|
+
</xsd:simpleType>
|
|
98
|
+
<xsd:simpleType name="TNipVatUE">
|
|
99
|
+
<xsd:restriction base="xsd:string">
|
|
100
|
+
<!-- based on: https://ec.europa.eu/taxation_customs/vies/#/faq Q11 -->
|
|
101
|
+
<xsd:pattern value="([1-9]((\d[1-9])|([1-9]\d))\d{7}-((AT)(U\d{8})|(BE)([01]{1}\d{9})|(BG)(\d{9,10})|(CY)(\d{8}[A-Z])|(CZ)(\d{8,10})|(DE)(\d{9})|(DK)(\d{8})|(EE)(\d{9})|(EL)(\d{9})|(ES)([A-Z]\d{8}|\d{8}[A-Z]|[A-Z]\d{7}[A-Z])|(FI)(\d{8})|(FR)[A-Z0-9]{2}\d{9}|(HR)(\d{11})|(HU)(\d{8})|(IE)(\d{7}[A-Z]{2}|\d[A-Z0-9+*]\d{5}[A-Z])|(IT)(\d{11})|(LT)(\d{9}|\d{12})|(LU)(\d{8})|(LV)(\d{11})|(MT)(\d{8})|(NL)([A-Z0-9+*]{12})|(PT)(\d{9})|(RO)(\d{2,10})|(SE)(\d{12})|(SI)(\d{8})|(SK)(\d{10})|(XI)((\d{9}|(\d{12}))|(GD|HA)(\d{3}))))$"/>
|
|
102
|
+
</xsd:restriction>
|
|
103
|
+
</xsd:simpleType>
|
|
104
|
+
<xsd:simpleType name="TPeppolId">
|
|
105
|
+
<xsd:restriction base="xsd:string">
|
|
106
|
+
<xsd:pattern value="^P[A-Z]{2}[0-9]{6}$"/>
|
|
107
|
+
</xsd:restriction>
|
|
108
|
+
</xsd:simpleType>
|
|
109
|
+
</xsd:schema>
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="utf-8"?>
|
|
2
|
+
<xsd:schema
|
|
3
|
+
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
|
4
|
+
xmlns:tns="http://ksef.mf.gov.pl/auth/token/2.1"
|
|
5
|
+
targetNamespace="http://ksef.mf.gov.pl/auth/token/2.1"
|
|
6
|
+
elementFormDefault="qualified" attributeFormDefault="unqualified">
|
|
7
|
+
<xsd:element name="AuthTokenRequest">
|
|
8
|
+
<xsd:complexType>
|
|
9
|
+
<xsd:sequence>
|
|
10
|
+
<xsd:element name="Challenge">
|
|
11
|
+
<xsd:simpleType>
|
|
12
|
+
<xsd:restriction base="xsd:token">
|
|
13
|
+
<xsd:length value="36"/>
|
|
14
|
+
<xsd:pattern value="\d{8}-CR-[A-F0-9]{10}-[A-F0-9]{10}-[A-F0-9]{2}"/>
|
|
15
|
+
</xsd:restriction>
|
|
16
|
+
</xsd:simpleType>
|
|
17
|
+
</xsd:element>
|
|
18
|
+
<xsd:element name="ContextIdentifier" type="tns:TContextIdentifier"/>
|
|
19
|
+
<xsd:element name="SubjectIdentifierType" type="tns:SubjectIdentifierTypeEnum"/>
|
|
20
|
+
<xsd:element name="AuthorizationPolicy" minOccurs="0">
|
|
21
|
+
<xsd:complexType>
|
|
22
|
+
<xsd:sequence>
|
|
23
|
+
<xsd:element name="AllowedIps">
|
|
24
|
+
<xsd:complexType>
|
|
25
|
+
<xsd:sequence minOccurs="1" maxOccurs="1">
|
|
26
|
+
<xsd:element name="Ip4Address" minOccurs="0" maxOccurs="10">
|
|
27
|
+
<xsd:simpleType>
|
|
28
|
+
<xsd:restriction base="xsd:token">
|
|
29
|
+
<xsd:pattern value="((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\.){3}(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])"/>
|
|
30
|
+
</xsd:restriction>
|
|
31
|
+
</xsd:simpleType>
|
|
32
|
+
</xsd:element>
|
|
33
|
+
<xsd:element name="Ip4Range" minOccurs="0" maxOccurs="10">
|
|
34
|
+
<xsd:simpleType>
|
|
35
|
+
<xsd:restriction base="xsd:token">
|
|
36
|
+
<xsd:pattern value="((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\.){3}(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])-((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\.){3}(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])"/>
|
|
37
|
+
</xsd:restriction>
|
|
38
|
+
</xsd:simpleType>
|
|
39
|
+
</xsd:element>
|
|
40
|
+
<xsd:element name="Ip4Mask" minOccurs="0" maxOccurs="10">
|
|
41
|
+
<xsd:simpleType>
|
|
42
|
+
<xsd:restriction base="xsd:token">
|
|
43
|
+
<xsd:pattern value="((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\.){3}(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])/(0|[1-9]|[12][0-9]|3[0-2])"/>
|
|
44
|
+
</xsd:restriction>
|
|
45
|
+
</xsd:simpleType>
|
|
46
|
+
</xsd:element>
|
|
47
|
+
</xsd:sequence>
|
|
48
|
+
</xsd:complexType>
|
|
49
|
+
</xsd:element>
|
|
50
|
+
</xsd:sequence>
|
|
51
|
+
</xsd:complexType>
|
|
52
|
+
</xsd:element>
|
|
53
|
+
</xsd:sequence>
|
|
54
|
+
</xsd:complexType>
|
|
55
|
+
</xsd:element>
|
|
56
|
+
<xsd:simpleType name="SubjectIdentifierTypeEnum">
|
|
57
|
+
<xsd:restriction base="xsd:token">
|
|
58
|
+
<xsd:enumeration value="certificateSubject"/>
|
|
59
|
+
<xsd:enumeration value="certificateFingerprint"/>
|
|
60
|
+
</xsd:restriction>
|
|
61
|
+
</xsd:simpleType>
|
|
62
|
+
|
|
63
|
+
<xsd:complexType name="TContextIdentifier">
|
|
64
|
+
<xsd:choice>
|
|
65
|
+
<xsd:element name="Nip" type="tns:TNIP">
|
|
66
|
+
<xsd:annotation>
|
|
67
|
+
<xsd:documentation>Numer Identyfikacyjny NIP</xsd:documentation>
|
|
68
|
+
</xsd:annotation>
|
|
69
|
+
</xsd:element>
|
|
70
|
+
<xsd:element name="InternalId" type="tns:TIID">
|
|
71
|
+
<xsd:annotation>
|
|
72
|
+
<xsd:documentation>Identyfikator wewnętrzny</xsd:documentation>
|
|
73
|
+
</xsd:annotation>
|
|
74
|
+
</xsd:element>
|
|
75
|
+
<xsd:element name="NipVatUe" type="tns:TNipVatUE">
|
|
76
|
+
<xsd:annotation>
|
|
77
|
+
<xsd:documentation>Kontekst złożony, będący połaczniem numeru NIP oraz numeru Vat UE</xsd:documentation>
|
|
78
|
+
</xsd:annotation>
|
|
79
|
+
</xsd:element>
|
|
80
|
+
<xsd:element name="PeppolId" type="tns:TPeppolId">
|
|
81
|
+
<xsd:annotation>
|
|
82
|
+
<xsd:documentation>Identyfikator dostawcy usług Peppol</xsd:documentation>
|
|
83
|
+
</xsd:annotation>
|
|
84
|
+
</xsd:element>
|
|
85
|
+
</xsd:choice>
|
|
86
|
+
</xsd:complexType>
|
|
87
|
+
|
|
88
|
+
<xsd:simpleType name="TIID">
|
|
89
|
+
<xsd:restriction base="xsd:string">
|
|
90
|
+
<xsd:pattern value="[1-9]((\d[1-9])|([1-9]\d))\d{7}-\d{5}"/>
|
|
91
|
+
</xsd:restriction>
|
|
92
|
+
</xsd:simpleType>
|
|
93
|
+
<xsd:simpleType name="TNIP">
|
|
94
|
+
<xsd:restriction base="xsd:string">
|
|
95
|
+
<xsd:pattern value="[1-9]((\d[1-9])|([1-9]\d))\d{7}"/>
|
|
96
|
+
</xsd:restriction>
|
|
97
|
+
</xsd:simpleType>
|
|
98
|
+
<xsd:simpleType name="TNipVatUE">
|
|
99
|
+
<xsd:restriction base="xsd:string">
|
|
100
|
+
<!-- based on: https://ec.europa.eu/taxation_customs/vies/#/faq Q11 -->
|
|
101
|
+
<xsd:pattern value="([1-9]((\d[1-9])|([1-9]\d))\d{7}-((AT)(U\d{8})|(BE)([01]{1}\d{9})|(BG)(\d{9,10})|(CY)(\d{8}[A-Z])|(CZ)(\d{8,10})|(DE)(\d{9})|(DK)(\d{8})|(EE)(\d{9})|(EL)(\d{9})|(ES)([A-Z]\d{8}|\d{8}[A-Z]|[A-Z]\d{7}[A-Z])|(FI)(\d{8})|(FR)[A-Z0-9]{2}\d{9}|(HR)(\d{11})|(HU)(\d{8})|(IE)(\d{7}[A-Z]{2}|\d[A-Z0-9+*]\d{5}[A-Z])|(IT)(\d{11})|(LT)(\d{9}|\d{12})|(LU)(\d{8})|(LV)(\d{11})|(MT)(\d{8})|(NL)([A-Z0-9+*]{12})|(PT)(\d{9})|(RO)(\d{2,10})|(SE)(\d{12})|(SI)(\d{8})|(SK)(\d{10})|(XI)((\d{9}|(\d{12}))|(GD|HA)(\d{3}))))$"/>
|
|
102
|
+
</xsd:restriction>
|
|
103
|
+
</xsd:simpleType>
|
|
104
|
+
<xsd:simpleType name="TPeppolId">
|
|
105
|
+
<xsd:restriction base="xsd:string">
|
|
106
|
+
<xsd:pattern value="^P[A-Z]{2}[0-9]{6}$"/>
|
|
107
|
+
</xsd:restriction>
|
|
108
|
+
</xsd:simpleType>
|
|
109
|
+
</xsd:schema>
|