oydid 0.9.2 → 0.9.4
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/VERSION +1 -1
- data/lib/oydid/basic.rb +26 -7
- data/lib/oydid/log.rb +42 -18
- data/spec/oydid_spec.rb +153 -0
- metadata +3 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 0f96270f4b2465c3dadb499841094e20c27c1e6512e18f69c2e2f7dbb2c6559f
|
|
4
|
+
data.tar.gz: 0aff913041f3105b4569c41c88918439b50754431e24c85e51197f851fd436d9
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 2505aeb0d16fc4a7786b24bbd1dcd607e7ac71122738ddc0bff9c6880d813813c5e6f2c134977a4bbf92b765565b8899822ae5d66dfe1977f214b89960ed6733
|
|
7
|
+
data.tar.gz: 49b7a640acf6793d33a504b45c4f69b30767a86d95fe4f7fd3236c26e4494456ccf4e799effe12c856b85a2ab675fa9887664c934bdcdd93710381466af27145
|
data/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.9.
|
|
1
|
+
0.9.4
|
data/lib/oydid/basic.rb
CHANGED
|
@@ -1248,6 +1248,30 @@ class Oydid
|
|
|
1248
1248
|
end
|
|
1249
1249
|
end
|
|
1250
1250
|
|
|
1251
|
+
# Marks a failure of the queried repository itself (HTTP 5xx) as opposed to
|
|
1252
|
+
# a DID that is simply not stored there. Without the distinction a caller
|
|
1253
|
+
# turns a broken upstream into "not found", which claims the identifier
|
|
1254
|
+
# never existed - see upstream_error?.
|
|
1255
|
+
UPSTREAM_ERROR_PREFIX = "upstream error"
|
|
1256
|
+
|
|
1257
|
+
def self.upstream_error?(message)
|
|
1258
|
+
message.to_s.start_with?(UPSTREAM_ERROR_PREFIX)
|
|
1259
|
+
end
|
|
1260
|
+
|
|
1261
|
+
# Failure message for a non-200 response: the repository's own error field
|
|
1262
|
+
# when it sent one, otherwise a message naming status and URL. A 5xx gets
|
|
1263
|
+
# the prefix above so callers can tell it apart from a missing DID.
|
|
1264
|
+
def self.http_error_message(response, url)
|
|
1265
|
+
msg = (response.parsed_response["error"].to_s rescue "")
|
|
1266
|
+
return msg unless msg.to_s == ""
|
|
1267
|
+
code = response.code.to_i rescue 0
|
|
1268
|
+
if code >= 500
|
|
1269
|
+
UPSTREAM_ERROR_PREFIX + " " + code.to_s + " from " + url.to_s
|
|
1270
|
+
else
|
|
1271
|
+
"invalid response from " + url.to_s
|
|
1272
|
+
end
|
|
1273
|
+
end
|
|
1274
|
+
|
|
1251
1275
|
def self.retrieve_document(doc_identifier, doc_file, doc_location, options)
|
|
1252
1276
|
# in-process callers can supply the DID document directly (e.g. read from
|
|
1253
1277
|
# a local database) to avoid any HTTP/file lookup
|
|
@@ -1271,11 +1295,7 @@ class Oydid
|
|
|
1271
1295
|
end
|
|
1272
1296
|
retVal = HTTParty.get(doc_location + "/doc/" + doc_identifier + option_str)
|
|
1273
1297
|
if retVal.code != 200
|
|
1274
|
-
|
|
1275
|
-
if msg.to_s == ""
|
|
1276
|
-
msg = "invalid response from " + doc_location.to_s + "/doc/" + doc_identifier.to_s
|
|
1277
|
-
end
|
|
1278
|
-
return [nil, msg]
|
|
1298
|
+
return [nil, http_error_message(retVal, doc_location.to_s + "/doc/" + doc_identifier.to_s)]
|
|
1279
1299
|
end
|
|
1280
1300
|
if options.transform_keys(&:to_s)["trace"]
|
|
1281
1301
|
if options[:silent].nil? || !options[:silent]
|
|
@@ -1324,8 +1344,7 @@ class Oydid
|
|
|
1324
1344
|
doc_location = doc_location.sub("%3A%2F%2F","://").sub("%3A", ":")
|
|
1325
1345
|
retVal = HTTParty.get(doc_location + "/doc_raw/" + doc_hash)
|
|
1326
1346
|
if retVal.code != 200
|
|
1327
|
-
|
|
1328
|
-
return [nil, msg]
|
|
1347
|
+
return [nil, http_error_message(retVal, doc_location.to_s + "/doc_raw/" + doc_hash.to_s)]
|
|
1329
1348
|
end
|
|
1330
1349
|
if options.transform_keys(&:to_s)["trace"]
|
|
1331
1350
|
if options[:silent].nil? || !options[:silent]
|
data/lib/oydid/log.rb
CHANGED
|
@@ -44,10 +44,7 @@ class Oydid
|
|
|
44
44
|
log_location = log_location.gsub("%2F%2F","//")
|
|
45
45
|
retVal = HTTParty.get(log_location + "/log/" + did_hash)
|
|
46
46
|
if retVal.code != 200
|
|
47
|
-
|
|
48
|
-
"invalid response from " + log_location.to_s + "/log/" + did_hash.to_s
|
|
49
|
-
|
|
50
|
-
return [nil, msg]
|
|
47
|
+
return [nil, http_error_message(retVal, log_location.to_s + "/log/" + did_hash.to_s)]
|
|
51
48
|
end
|
|
52
49
|
if options.transform_keys(&:to_s)["trace"]
|
|
53
50
|
if options[:silent].nil? || !options[:silent]
|
|
@@ -79,9 +76,7 @@ class Oydid
|
|
|
79
76
|
log_location = log_location.gsub("%2F%2F","//")
|
|
80
77
|
retVal = HTTParty.get(log_location + "/log/" + log_hash + "/item")
|
|
81
78
|
if retVal.code != 200
|
|
82
|
-
|
|
83
|
-
"invalid response from " + log_location.to_s + "/log/" + log_hash.to_s + "/item"
|
|
84
|
-
return [nil, msg]
|
|
79
|
+
return [nil, http_error_message(retVal, log_location.to_s + "/log/" + log_hash.to_s + "/item")]
|
|
85
80
|
end
|
|
86
81
|
if options.transform_keys(&:to_s)["trace"]
|
|
87
82
|
if options[:silent].nil? || !options[:silent]
|
|
@@ -132,7 +127,7 @@ class Oydid
|
|
|
132
127
|
if !position.nil?
|
|
133
128
|
dag.add_edge from: dag_log[position], to: dag_log[i]
|
|
134
129
|
end
|
|
135
|
-
end unless el["previous"] == []
|
|
130
|
+
end unless el["previous"].nil? || el["previous"] == []
|
|
136
131
|
i += 1
|
|
137
132
|
end unless logs.nil?
|
|
138
133
|
|
|
@@ -212,7 +207,7 @@ class Oydid
|
|
|
212
207
|
dag.add_edge from: dag_log[position], to: dag_log[i]
|
|
213
208
|
end
|
|
214
209
|
end
|
|
215
|
-
end unless el["previous"] == []
|
|
210
|
+
end unless el["previous"].nil? || el["previous"] == []
|
|
216
211
|
i += 1
|
|
217
212
|
end unless logs.nil?
|
|
218
213
|
|
|
@@ -472,10 +467,20 @@ class Oydid
|
|
|
472
467
|
message = log_el["doc"].to_s
|
|
473
468
|
signature = log_el["sig"]
|
|
474
469
|
# public_key = current_public_doc_key.to_s
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
#
|
|
478
|
-
|
|
470
|
+
# SECURITY (fail-closed on delegation): an UPDATE
|
|
471
|
+
# is authorised only by the document key of the
|
|
472
|
+
# version it supersedes - the key continuity that
|
|
473
|
+
# anchors the chain. Delegated keys (op=5) are NOT
|
|
474
|
+
# honoured at resolution: they were collected from
|
|
475
|
+
# the raw log with no authentication whatsoever, so
|
|
476
|
+
# honouring them let anyone able to write a log
|
|
477
|
+
# entry take over a revoked DID by injecting a
|
|
478
|
+
# DELEGATE plus a self-signed UPDATE. No production
|
|
479
|
+
# DID relies on a delegated key to resolve
|
|
480
|
+
# (audit 2026-09-01), so this closes the hole at
|
|
481
|
+
# zero cost.
|
|
482
|
+
pubKeys = [current_public_doc_key.to_s]
|
|
483
|
+
msg = ""
|
|
479
484
|
signature_verification = false
|
|
480
485
|
used_pubkey = ""
|
|
481
486
|
pubKeys.each do |key|
|
|
@@ -520,7 +525,7 @@ class Oydid
|
|
|
520
525
|
new_did_hash = new_doc_did.delete_prefix("did:oyd:")
|
|
521
526
|
new_did_hash = new_did_hash.split(LOCATION_PREFIX).first.split(CGI.escape LOCATION_PREFIX).first
|
|
522
527
|
new_did10 = new_did_hash[0,10]
|
|
523
|
-
new_doc = retrieve_document(new_doc_did, new_did10 + ".doc", new_doc_location,
|
|
528
|
+
new_doc = retrieve_document(new_doc_did, new_did10 + ".doc", new_doc_location, options).first
|
|
524
529
|
currentDID["verification"] += "found UPDATE log record:" + "\n"
|
|
525
530
|
currentDID["verification"] += JSON.pretty_generate(log_el) + "\n"
|
|
526
531
|
currentDID["verification"] += "⛔ none of available public keys (" + pubKeys.join(", ") + ")\n"
|
|
@@ -558,9 +563,22 @@ class Oydid
|
|
|
558
563
|
# handle DID Rotation
|
|
559
564
|
if (i == (currentDID["log"].length-1))
|
|
560
565
|
if options[:followAlsoKnownAs]
|
|
566
|
+
# the payload of a DID Document is arbitrary JSON: an
|
|
567
|
+
# Array or a scalar is valid and simply carries no
|
|
568
|
+
# alsoKnownAs, so only a Hash can be inspected for it.
|
|
569
|
+
# Without this guard a revoked DID whose document holds
|
|
570
|
+
# e.g. [5] raised NoMethodError here, the repository
|
|
571
|
+
# answered 500 with an empty body, and the resolver
|
|
572
|
+
# degraded that to "not found" instead of "deactivated".
|
|
561
573
|
current_doc = currentDID["doc"]
|
|
562
|
-
|
|
563
|
-
|
|
574
|
+
doc_payload = current_doc.is_a?(Hash) ? current_doc["doc"] : nil
|
|
575
|
+
doc_payload = doc_payload.transform_keys(&:to_s) if doc_payload.is_a?(Hash)
|
|
576
|
+
if doc_payload.is_a?(Hash) && doc_payload.has_key?("alsoKnownAs")
|
|
577
|
+
# DID Core defines alsoKnownAs as a set; older
|
|
578
|
+
# documents wrote a bare String
|
|
579
|
+
rotate_DID = doc_payload["alsoKnownAs"]
|
|
580
|
+
rotate_DID = rotate_DID.first if rotate_DID.is_a?(Array)
|
|
581
|
+
rotate_DID = rotate_DID.to_s
|
|
564
582
|
if rotate_DID.start_with?("did:")
|
|
565
583
|
rotate_DID_method = rotate_DID.split(":").take(2).join(":")
|
|
566
584
|
did_orig = currentDID["did"]
|
|
@@ -586,8 +604,14 @@ class Oydid
|
|
|
586
604
|
currentDID["verification"] += "(Details: https://ownyourdata.github.io/oydid/#did_rotation)" + "\n\n"
|
|
587
605
|
end
|
|
588
606
|
when "did:oyd"
|
|
589
|
-
|
|
590
|
-
|
|
607
|
+
# DID Rotation to another did:oyd identifier
|
|
608
|
+
# is not implemented yet: resolving it needs
|
|
609
|
+
# a second pass through this resolver.
|
|
610
|
+
if verification_output
|
|
611
|
+
currentDID["verification"] += "DID rotation to: " + rotate_DID.to_s + "\n"
|
|
612
|
+
currentDID["verification"] += "⚠️ rotation to another did:oyd identifier is not supported yet\n"
|
|
613
|
+
currentDID["verification"] += "(Details: https://ownyourdata.github.io/oydid/#did_rotation)" + "\n\n"
|
|
614
|
+
end
|
|
591
615
|
else
|
|
592
616
|
# do nothing: DID Rotation is not supported for this DID method yet
|
|
593
617
|
end
|
data/spec/oydid_spec.rb
CHANGED
|
@@ -519,6 +519,159 @@ describe "OYDID handling" do
|
|
|
519
519
|
# end
|
|
520
520
|
# end
|
|
521
521
|
|
|
522
|
+
# regression: the payload of a DID Document is arbitrary JSON, not
|
|
523
|
+
# necessarily an object. A revoked DID whose payload is an Array used to
|
|
524
|
+
# raise NoMethodError in the alsoKnownAs lookup - the repository then
|
|
525
|
+
# answered 500 with an empty body and the resolver reported "not found"
|
|
526
|
+
# instead of "deactivated".
|
|
527
|
+
describe "dag_update with a non-object DID Document payload" do
|
|
528
|
+
def revoked_state(payload)
|
|
529
|
+
{ "did" => "did:oyd:zQmPyjnkL52gQxBBhPuCFkf167MzpCKquqbu5Qt5ia2JGTr",
|
|
530
|
+
"doc" => { "doc" => payload, "key" => "", "log" => "" },
|
|
531
|
+
"log" => [{ "ts" => 1647732475, "op" => 1, "doc" => "zQmcVLwEtzU8By7TTeRFGvVUKm7HhZS6GgRTDFt51QNnans",
|
|
532
|
+
"sig" => "", "previous" => [] }] }
|
|
533
|
+
end
|
|
534
|
+
|
|
535
|
+
it "does not raise for an Array payload" do
|
|
536
|
+
expect {
|
|
537
|
+
Oydid.dag_update(revoked_state([5]), { followAlsoKnownAs: true })
|
|
538
|
+
}.not_to raise_error
|
|
539
|
+
end
|
|
540
|
+
|
|
541
|
+
it "does not raise for a scalar payload" do
|
|
542
|
+
expect {
|
|
543
|
+
Oydid.dag_update(revoked_state("plain"), { followAlsoKnownAs: true })
|
|
544
|
+
}.not_to raise_error
|
|
545
|
+
end
|
|
546
|
+
|
|
547
|
+
it "accepts alsoKnownAs written as a set" do
|
|
548
|
+
state = revoked_state({ "alsoKnownAs" => ["did:oyd:zQmaBZTghndXTgxNwfbdpVLWdFf6faYE4oeuN2zzXdQt1kh"] })
|
|
549
|
+
expect {
|
|
550
|
+
Oydid.dag_update(state, { followAlsoKnownAs: true })
|
|
551
|
+
}.not_to raise_error
|
|
552
|
+
end
|
|
553
|
+
end
|
|
554
|
+
|
|
555
|
+
# regression: a log entry with a missing "previous" field (e.g. junk op=5
|
|
556
|
+
# entries POSTed to the unauthenticated /log endpoint) made dag_did call
|
|
557
|
+
# nil.each - the repository then answered 500 with an empty body for every
|
|
558
|
+
# request to that DID. previous is optional; a missing one means no
|
|
559
|
+
# back-reference, exactly like an empty array.
|
|
560
|
+
describe "dag_did with a log entry missing 'previous'" do
|
|
561
|
+
let(:log) do
|
|
562
|
+
[ { "ts" => 0, "op" => 5, "doc" => "doc:123", "sig" => "z6M234" }, # no "previous"
|
|
563
|
+
{ "ts" => 1, "op" => 2, "doc" => "zQmSVzALxVDs2Tqf5gh9XDWSL4L57YLi8H1XpvpizQPd79c", "sig" => "z", "previous" => [] },
|
|
564
|
+
{ "ts" => 1, "op" => 0, "doc" => "zQmXRVzdjMnTz1WQVkVdJU1ZF9DPjcK5LM2GynpGzxbNAz6", "sig" => "z", "previous" => [] } ]
|
|
565
|
+
end
|
|
566
|
+
|
|
567
|
+
it "does not raise on a missing previous field" do
|
|
568
|
+
expect { Oydid.dag_did(log, { silent: true }) }.not_to raise_error
|
|
569
|
+
end
|
|
570
|
+
|
|
571
|
+
it "still resolves the DAG and finds the single CREATE entry" do
|
|
572
|
+
dag, create_index, _terminate_index, msg = Oydid.dag_did(log, { silent: true })
|
|
573
|
+
expect(msg).to eq ""
|
|
574
|
+
expect(dag).not_to be_nil
|
|
575
|
+
expect(create_index).to eq 1
|
|
576
|
+
end
|
|
577
|
+
end
|
|
578
|
+
|
|
579
|
+
# SECURITY regression (fail-closed on delegation): a DELEGATE entry (op=5) is
|
|
580
|
+
# collected from the raw log with no authentication. Honouring it at resolution
|
|
581
|
+
# let anyone able to write a log entry take over a revoked DID by injecting a
|
|
582
|
+
# DELEGATE plus a self-signed UPDATE. Resolution must authorise an UPDATE only
|
|
583
|
+
# by the superseded version's own document key - and must keep resolving a
|
|
584
|
+
# legitimate owner-signed update.
|
|
585
|
+
describe "DELEGATE keys are not honoured at resolution" do
|
|
586
|
+
HOPTS = { digest: "sha2-256", encode: "base58btc" }
|
|
587
|
+
class MemStore
|
|
588
|
+
def initialize(h); @h = h; end
|
|
589
|
+
def get(k); @h[k]; end
|
|
590
|
+
end
|
|
591
|
+
def h(e); Oydid.multi_hash(Oydid.canonical(e.slice("ts","op","doc","sig","previous")), HOPTS).first; end
|
|
592
|
+
def sub(e); Oydid.multi_hash(Oydid.canonical(e.slice("ts","op","doc","sig")), HOPTS).first; end
|
|
593
|
+
|
|
594
|
+
# generate_base builds the CREATE/TERMINATE/REVOKE records and the keys
|
|
595
|
+
# without the network round-trip that Oydid.create makes via w3c (which
|
|
596
|
+
# WebMock blocks in the test env). doc_location "local" keeps the log
|
|
597
|
+
# reference free of an @location suffix.
|
|
598
|
+
def build_revoked_victim
|
|
599
|
+
dd, dk, dl, = Oydid.generate_base({ "purpose" => "victim" }, nil, "create",
|
|
600
|
+
{ key_type: "ed25519", doc_location: "local" })
|
|
601
|
+
create = dl[:l1]
|
|
602
|
+
term1 = dl[:l2]
|
|
603
|
+
revoke = dl[:r1].merge("previous" => [h(create), h(term1)])
|
|
604
|
+
{ vdid: dd[:did].delete_prefix("did:oyd:"), vdoc: dd[:didDocument], vpriv: dk[:privateKey],
|
|
605
|
+
create: create, term1: term1, revoke: revoke }
|
|
606
|
+
end
|
|
607
|
+
|
|
608
|
+
def build_v2
|
|
609
|
+
np = { "service" => [{ "id" => "#x", "serviceEndpoint" => "https://new.example" }] }
|
|
610
|
+
apriv = Oydid.generate_private_key("", "ed25519-priv", {}).first
|
|
611
|
+
apub = Oydid.public_key(apriv, {}).first
|
|
612
|
+
arpub = Oydid.public_key(Oydid.generate_private_key("", "ed25519-priv", {}).first, {}).first
|
|
613
|
+
nkey = "#{apub}:#{arpub}"
|
|
614
|
+
r2 = { "ts" => 3, "op" => 1, "sig" => "z",
|
|
615
|
+
"doc" => Oydid.multi_hash(Oydid.canonical({ "doc" => np, "key" => nkey }.to_json), HOPTS).first }
|
|
616
|
+
term2 = { "ts" => 3, "op" => 0, "doc" => sub(r2), "sig" => Oydid.sign(sub(r2), apriv, HOPTS).first, "previous" => [] }
|
|
617
|
+
ndoc = { "doc" => np, "key" => nkey, "log" => h(term2) }
|
|
618
|
+
{ np: np, apriv: apriv, apub: apub, term2: term2, ndoc: ndoc,
|
|
619
|
+
ndid: Oydid.multi_hash(Oydid.canonical(ndoc.to_json), HOPTS).first }
|
|
620
|
+
end
|
|
621
|
+
|
|
622
|
+
def resolve(vic, v2, log)
|
|
623
|
+
store = MemStore.new(vic[:vdid] => { "doc" => vic[:vdoc], "log" => log },
|
|
624
|
+
v2[:ndid] => { "doc" => v2[:ndoc], "log" => log })
|
|
625
|
+
r, = Oydid.read("did:oyd:#{vic[:vdid]}",
|
|
626
|
+
{ local_doc: vic[:vdoc], local_log: log, local_store: store, silent: true })
|
|
627
|
+
[r["error"].to_i, r.dig("doc", "doc")]
|
|
628
|
+
end
|
|
629
|
+
|
|
630
|
+
it "rejects a takeover via an injected DELEGATE + self-signed UPDATE" do
|
|
631
|
+
vic = build_revoked_victim; v2 = build_v2
|
|
632
|
+
deleg = { "ts" => 2, "op" => 5, "doc" => "doc:#{v2[:apub]}", "sig" => "", "previous" => [h(vic[:term1])] }
|
|
633
|
+
upd = { "ts" => 3, "op" => 3, "doc" => v2[:ndid],
|
|
634
|
+
"sig" => Oydid.sign(v2[:ndid], v2[:apriv], HOPTS).first, "previous" => [h(vic[:revoke])] }
|
|
635
|
+
_err, doc = resolve(vic, v2, [vic[:create], vic[:term1], vic[:revoke], deleg, upd, v2[:term2]])
|
|
636
|
+
expect(doc).not_to eq v2[:np]
|
|
637
|
+
end
|
|
638
|
+
|
|
639
|
+
it "still resolves a legitimate owner-signed UPDATE" do
|
|
640
|
+
vic = build_revoked_victim; v2 = build_v2
|
|
641
|
+
upd = { "ts" => 3, "op" => 3, "doc" => v2[:ndid],
|
|
642
|
+
"sig" => Oydid.sign(v2[:ndid], vic[:vpriv], HOPTS).first, "previous" => [h(vic[:revoke])] }
|
|
643
|
+
err, doc = resolve(vic, v2, [vic[:create], vic[:term1], vic[:revoke], upd, v2[:term2]])
|
|
644
|
+
expect(err).to eq 0
|
|
645
|
+
expect(doc).to eq v2[:np]
|
|
646
|
+
end
|
|
647
|
+
end
|
|
648
|
+
|
|
649
|
+
# a broken repository (5xx) has to stay distinguishable from a DID that is
|
|
650
|
+
# not stored there - otherwise the caller reports "not found" and thereby
|
|
651
|
+
# claims the identifier never existed
|
|
652
|
+
describe "failure messages for non-200 responses" do
|
|
653
|
+
it "marks a 5xx as an upstream error" do
|
|
654
|
+
reply = double(code: 500, parsed_response: "")
|
|
655
|
+
msg = Oydid.http_error_message(reply, "https://oydid.ownyourdata.eu/doc/zQmXY")
|
|
656
|
+
expect(Oydid.upstream_error?(msg)).to be true
|
|
657
|
+
expect(msg).to include("500")
|
|
658
|
+
end
|
|
659
|
+
|
|
660
|
+
it "passes the repository's own error through" do
|
|
661
|
+
reply = double(code: 410, parsed_response: { "error" => "revoked" })
|
|
662
|
+
msg = Oydid.http_error_message(reply, "https://oydid.ownyourdata.eu/doc/zQmXY")
|
|
663
|
+
expect(msg).to eq "revoked"
|
|
664
|
+
expect(Oydid.upstream_error?(msg)).to be false
|
|
665
|
+
end
|
|
666
|
+
|
|
667
|
+
it "does not mark a 4xx without an error field as upstream error" do
|
|
668
|
+
reply = double(code: 404, parsed_response: nil)
|
|
669
|
+
msg = Oydid.http_error_message(reply, "https://oydid.ownyourdata.eu/doc/zQmXY")
|
|
670
|
+
expect(msg).to start_with("invalid response from")
|
|
671
|
+
expect(Oydid.upstream_error?(msg)).to be false
|
|
672
|
+
end
|
|
673
|
+
end
|
|
674
|
+
|
|
522
675
|
# main functionds
|
|
523
676
|
Dir.glob(File.expand_path("../input/main/*_read.doc", __FILE__)).each do |input|
|
|
524
677
|
it "execute read for #{input.split('/').last}" do
|
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: oydid
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.9.
|
|
4
|
+
version: 0.9.4
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Christoph Fabianek
|
|
8
8
|
bindir: bin
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date: 2026-
|
|
10
|
+
date: 2026-09-01 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: simple_dag
|
|
@@ -433,7 +433,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
433
433
|
- !ruby/object:Gem::Version
|
|
434
434
|
version: '0'
|
|
435
435
|
requirements: []
|
|
436
|
-
rubygems_version: 4.0.
|
|
436
|
+
rubygems_version: 4.0.15
|
|
437
437
|
specification_version: 4
|
|
438
438
|
summary: Own Your Decentralized Identifier for Ruby.
|
|
439
439
|
test_files:
|