oydid 0.9.2 → 0.9.3

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 072b6ccce2a1b7bf3c607aa01d3a3c97cfd37f8e41d52bb9db529d539ccb6eda
4
- data.tar.gz: 75ccf1a0357d60e629dac7ea3a046cfe0933693a04b5179bf0db7b54929abc6b
3
+ metadata.gz: b941c83aec6492dfb37d272fd83a77f1b036902b70aab64634fed657d1ccaf6a
4
+ data.tar.gz: 0ba0f288c5c30e2cd7d7d831bd072ef0c4924082d70fba44634e1412578a8dc7
5
5
  SHA512:
6
- metadata.gz: c936a18cf7106ae73e76fb30fc5ee789a50330a342dfa9624f29bd03574cde42b9c8a4b6a710e0f7e3ed9c6865fb8ec49aaac2cf7cd24c5d5330405e4f20cd81
7
- data.tar.gz: 3d6c15c81d795cd470f75031722f211aac478dc239a1ece245a64e1b00144d7d37eadfe3c5db8dab4ebfd487e685011f50615f5c40d05ea502d59762cbb51a1f
6
+ metadata.gz: ddc6020c44371fee58f2e527513e4d94974ee5d4a3ebabd22a01b5cc039f5c91cfe1a0ab0f4046c50a356ab0784db6a626c971c5ee18e10b076e03625250605e
7
+ data.tar.gz: eaba43282881c1239cde7af7733707d5beff7670b27388327926b4e7a5c739e9c221c1f9b70565a629adb3e7b4ee43dd3ebb3cb977bf6b2dd885ff6f55a34e93
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.9.2
1
+ 0.9.3
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
- msg = retVal.parsed_response["error"].to_s rescue ""
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
- msg = retVal.parsed_response("error").to_s rescue "invalid response from " + doc_location.to_s + "/doc/" + doc_hash.to_s
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
- msg = retVal.parsed_response("error").to_s rescue
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
- msg = retVal.parsed_response("error").to_s rescue
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]
@@ -558,9 +553,22 @@ class Oydid
558
553
  # handle DID Rotation
559
554
  if (i == (currentDID["log"].length-1))
560
555
  if options[:followAlsoKnownAs]
556
+ # the payload of a DID Document is arbitrary JSON: an
557
+ # Array or a scalar is valid and simply carries no
558
+ # alsoKnownAs, so only a Hash can be inspected for it.
559
+ # Without this guard a revoked DID whose document holds
560
+ # e.g. [5] raised NoMethodError here, the repository
561
+ # answered 500 with an empty body, and the resolver
562
+ # degraded that to "not found" instead of "deactivated".
561
563
  current_doc = currentDID["doc"]
562
- if current_doc["doc"].transform_keys(&:to_s).has_key?("alsoKnownAs")
563
- rotate_DID = current_doc["doc"].transform_keys(&:to_s)["alsoKnownAs"]
564
+ doc_payload = current_doc.is_a?(Hash) ? current_doc["doc"] : nil
565
+ doc_payload = doc_payload.transform_keys(&:to_s) if doc_payload.is_a?(Hash)
566
+ if doc_payload.is_a?(Hash) && doc_payload.has_key?("alsoKnownAs")
567
+ # DID Core defines alsoKnownAs as a set; older
568
+ # documents wrote a bare String
569
+ rotate_DID = doc_payload["alsoKnownAs"]
570
+ rotate_DID = rotate_DID.first if rotate_DID.is_a?(Array)
571
+ rotate_DID = rotate_DID.to_s
564
572
  if rotate_DID.start_with?("did:")
565
573
  rotate_DID_method = rotate_DID.split(":").take(2).join(":")
566
574
  did_orig = currentDID["did"]
@@ -586,8 +594,14 @@ class Oydid
586
594
  currentDID["verification"] += "(Details: https://ownyourdata.github.io/oydid/#did_rotation)" + "\n\n"
587
595
  end
588
596
  when "did:oyd"
589
- puts "try to resolve did:oyd with our own resolver"
590
- puts "add verification text"
597
+ # DID Rotation to another did:oyd identifier
598
+ # is not implemented yet: resolving it needs
599
+ # a second pass through this resolver.
600
+ if verification_output
601
+ currentDID["verification"] += "DID rotation to: " + rotate_DID.to_s + "\n"
602
+ currentDID["verification"] += "⚠️ rotation to another did:oyd identifier is not supported yet\n"
603
+ currentDID["verification"] += "(Details: https://ownyourdata.github.io/oydid/#did_rotation)" + "\n\n"
604
+ end
591
605
  else
592
606
  # do nothing: DID Rotation is not supported for this DID method yet
593
607
  end
data/spec/oydid_spec.rb CHANGED
@@ -519,6 +519,65 @@ 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
+ # a broken repository (5xx) has to stay distinguishable from a DID that is
556
+ # not stored there - otherwise the caller reports "not found" and thereby
557
+ # claims the identifier never existed
558
+ describe "failure messages for non-200 responses" do
559
+ it "marks a 5xx as an upstream error" do
560
+ reply = double(code: 500, parsed_response: "")
561
+ msg = Oydid.http_error_message(reply, "https://oydid.ownyourdata.eu/doc/zQmXY")
562
+ expect(Oydid.upstream_error?(msg)).to be true
563
+ expect(msg).to include("500")
564
+ end
565
+
566
+ it "passes the repository's own error through" do
567
+ reply = double(code: 410, parsed_response: { "error" => "revoked" })
568
+ msg = Oydid.http_error_message(reply, "https://oydid.ownyourdata.eu/doc/zQmXY")
569
+ expect(msg).to eq "revoked"
570
+ expect(Oydid.upstream_error?(msg)).to be false
571
+ end
572
+
573
+ it "does not mark a 4xx without an error field as upstream error" do
574
+ reply = double(code: 404, parsed_response: nil)
575
+ msg = Oydid.http_error_message(reply, "https://oydid.ownyourdata.eu/doc/zQmXY")
576
+ expect(msg).to start_with("invalid response from")
577
+ expect(Oydid.upstream_error?(msg)).to be false
578
+ end
579
+ end
580
+
522
581
  # main functionds
523
582
  Dir.glob(File.expand_path("../input/main/*_read.doc", __FILE__)).each do |input|
524
583
  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.2
4
+ version: 0.9.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Christoph Fabianek
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2026-08-28 00:00:00.000000000 Z
10
+ date: 2026-08-29 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: simple_dag