| 520 |
522
|
|
# something a person can read — a repository path, a topic and board slug, an
|
| 521 |
523
|
|
# issue number, an agent handle — rather than only by a UUID this forge would
|
| 522 |
524
|
|
# have to resolve. Replacing any of those with an id alone turns this red.
|
|
525
|
+ |
describe "a reputation attestation's subject" do
|
|
526
|
+ |
test "an attestation naming a subject this account established comes back" do
|
|
527
|
+ |
user = github_user("account-export-attest", "export-attest")
|
|
528
|
+ |
repository = private_repository_with_member("export-attest-private", user)
|
|
529
|
+ |
issue = OpenAgents.IssuesFixtures.issue_fixture(repository, %{title: "Attested"})
|
|
530
|
+ |
|
|
531
|
+ |
link_subject!(user, "user:" <> user.id)
|
|
532
|
+ |
attestation = attest!(repository, issue, "user:" <> user.id, "repository")
|
|
533
|
+ |
|
|
534
|
+ |
assert {:ok, export} = AccountExport.build(user)
|
|
535
|
+ |
work = export["repository_work"]
|
|
536
|
+ |
|
|
537
|
+ |
assert [record] = work["attestations"]["records"]
|
|
538
|
+ |
assert record["id"] == attestation.id
|
|
539
|
+ |
assert record["subject_id"] == "user:" <> user.id
|
|
540
|
+ |
assert record["repository"] == repository.owner <> "/" <> repository.name
|
|
541
|
+ |
assert record["issue_number"] == issue.number
|
|
542
|
+ |
assert record["transparency_tier"] == "repository"
|
|
543
|
+ |
refute work["attestations"]["records_truncated"]
|
|
544
|
+ |
|
|
545
|
+ |
# The signed claim travels verbatim, so the recipient checks it offline.
|
|
546
|
+ |
assert record["claim"]["subject"]["actor_id"] == "user:" <> user.id
|
|
547
|
+ |
assert record["claim_digest"] == attestation.claim_digest
|
|
548
|
+ |
|
|
549
|
+ |
assert OpenAgents.Reputation.Claim.valid_signature?(
|
|
550
|
+ |
record["claim"],
|
|
551
|
+ |
record["signature"],
|
|
552
|
+ |
issuer_public_key(attestation)
|
|
553
|
+ |
)
|
|
554
|
+ |
|
|
555
|
+ |
assert work["attestations"]["established_subjects"] == ["user:" <> user.id]
|
|
556
|
+ |
assert work["attestations"]["subject_resolution_rule"] =~ "linked"
|
|
557
|
+ |
end
|
|
558
|
+ |
|
|
559
|
+ |
# Without the linked-claim filter this attestation would come back to
|
|
560
|
+ |
# whoever asked. Dropping `Reputation.linked_subject_ids/1` from
|
|
561
|
+ |
# `attestations_export/2` turns this red.
|
|
562
|
+ |
test "an attestation whose subject the account has not established stays behind" do
|
|
563
|
+ |
user = github_user("account-export-attest-none", "export-attest-none")
|
|
564
|
+ |
repository = private_repository_with_member("export-attest-none-private", user)
|
|
565
|
+ |
issue = OpenAgents.IssuesFixtures.issue_fixture(repository, %{title: "Not mine"})
|
|
566
|
+ |
|
|
567
|
+ |
_attestation = attest!(repository, issue, "actor:someone-else", "repository")
|
|
568
|
+ |
|
|
569
|
+ |
assert {:ok, export} = AccountExport.build(user)
|
|
570
|
+ |
assert export["repository_work"]["attestations"]["records"] == []
|
|
571
|
+ |
assert export["repository_work"]["attestations"]["established_subjects"] == []
|
|
572
|
+ |
end
|
|
573
|
+ |
|
|
574
|
+ |
test "a pending claim resolves nothing, and only the operator's decision does" do
|
|
575
|
+ |
user = github_user("account-export-attest-pending", "export-attest-pending")
|
|
576
|
+ |
repository = private_repository_with_member("export-attest-pending-private", user)
|
|
577
|
+ |
issue = OpenAgents.IssuesFixtures.issue_fixture(repository, %{title: "Pending"})
|
|
578
|
+ |
|
|
579
|
+ |
{:ok, claim} =
|
|
580
|
+ |
Reputation.claim_subject(user, %{
|
|
581
|
+ |
subject_kind: "account",
|
|
582
|
+ |
subject_id: "user:" <> user.id
|
|
583
|
+ |
})
|
|
584
|
+ |
|
|
585
|
+ |
_attestation = attest!(repository, issue, "user:" <> user.id, "repository")
|
|
586
|
+ |
|
|
587
|
+ |
assert {:ok, pending_export} = AccountExport.build(user)
|
|
588
|
+ |
assert pending_export["repository_work"]["attestations"]["records"] == []
|
|
589
|
+ |
|
|
590
|
+ |
{:ok, _linked} = Reputation.approve_subject_claim(claim)
|
|
591
|
+ |
|
|
592
|
+ |
assert {:ok, export} = AccountExport.build(user)
|
|
593
|
+ |
assert [_record] = export["repository_work"]["attestations"]["records"]
|
|
594
|
+ |
|
|
595
|
+ |
assert [projected] = export["identities"]["reputation_subject_claims"]
|
|
596
|
+ |
assert projected["status"] == "linked"
|
|
597
|
+ |
assert projected["subject_kind"] == "account"
|
|
598
|
+ |
end
|
|
599
|
+ |
|
|
600
|
+ |
# Disclosure does not widen. `readable_by/2` admits a public repository to
|
|
601
|
+ |
# a non-member, and the controller shows such a reader `public` only.
|
|
602
|
+ |
# Dropping the membership test from `attestations_export/2` turns this red.
|
|
603
|
+ |
test "a repository or private tier attestation stays behind for a non-member" do
|
|
604
|
+ |
user = github_user("account-export-attest-tier", "export-attest-tier")
|
|
605
|
+ |
public_repository = OpenAgents.AccountsFixtures.repository_fixture()
|
|
606
|
+ |
issue = OpenAgents.IssuesFixtures.issue_fixture(public_repository, %{title: "Tiered"})
|
|
607
|
+ |
other = OpenAgents.IssuesFixtures.issue_fixture(public_repository, %{title: "Open"})
|
|
608
|
+ |
|
|
609
|
+ |
link_subject!(user, "user:" <> user.id)
|
|
610
|
+ |
refute Repositories.member?(public_repository, user)
|
|
611
|
+ |
|
|
612
|
+ |
withheld = attest!(public_repository, issue, "user:" <> user.id, "repository")
|
|
613
|
+ |
disclosed = attest!(public_repository, other, "user:" <> user.id, "public")
|
|
614
|
+ |
|
|
615
|
+ |
assert {:ok, export} = AccountExport.build(user)
|
|
616
|
+ |
ids = Enum.map(export["repository_work"]["attestations"]["records"], & &1["id"])
|
|
617
|
+ |
|
|
618
|
+ |
assert disclosed.id in ids
|
|
619
|
+ |
refute withheld.id in ids
|
|
620
|
+ |
end
|
|
621
|
+ |
|
|
622
|
+ |
# A public-tier attestation in a repository that went private afterwards is
|
|
623
|
+ |
# the one case the tier test admits and `readable_by/2` does not, so this is
|
|
624
|
+ |
# what isolates the join: replacing `subquery(readable)` with `Repository`
|
|
625
|
+ |
# in `attestations_export/2` turns this red and nothing else in this file.
|
|
626
|
+ |
test "a public tier attestation in a repository the account cannot read stays behind" do
|
|
627
|
+ |
user = github_user("account-export-attest-shut", "export-attest-shut")
|
|
628
|
+ |
other = github_user("account-export-attest-shut-owner", "export-attest-shut-owner")
|
|
629
|
+ |
repository = OpenAgents.AccountsFixtures.repository_fixture()
|
|
630
|
+ |
issue = OpenAgents.IssuesFixtures.issue_fixture(repository, %{title: "Was public"})
|
|
631
|
+ |
|
|
632
|
+ |
link_subject!(user, "user:" <> user.id)
|
|
633
|
+ |
attestation = attest!(repository, issue, "user:" <> user.id, "public")
|
|
634
|
+ |
|
|
635
|
+ |
assert {:ok, before} = AccountExport.build(user)
|
|
636
|
+ |
assert [%{"id" => id}] = before["repository_work"]["attestations"]["records"]
|
|
637
|
+ |
assert id == attestation.id
|
|
638
|
+ |
|
|
639
|
+ |
# The repository closes. The attestation's tier still says `public`, so
|
|
640
|
+ |
# only the repository visibility predicate withholds it now.
|
|
641
|
+ |
repository
|
|
642
|
+ |
|> Ecto.Changeset.change(visibility: "private")
|
|
643
|
+ |
|> Repo.update!()
|
|
644
|
+ |
|
|
645
|
+ |
{:ok, _membership} = Repositories.add_member(repository, other, "owner")
|
|
646
|
+ |
refute Repositories.member?(repository, user)
|
|
647
|
+ |
|
|
648
|
+ |
assert {:ok, export} = AccountExport.build(user)
|
|
649
|
+ |
assert export["repository_work"]["attestations"]["records"] == []
|
|
650
|
+ |
end
|
|
651
|
+ |
|
|
652
|
+ |
# Both gates withhold this one, and that is the honest description: for a
|
|
653
|
+ |
# private repository at the `repository` tier the membership test and
|
|
654
|
+ |
# `readable_by/2` coincide, so this asserts the outcome rather than
|
|
655
|
+ |
# isolating one join. The test above isolates it.
|
|
656
|
+ |
test "an attestation in a private repository the account cannot read stays behind" do
|
|
657
|
+ |
user = github_user("account-export-attest-closed", "export-attest-closed")
|
|
658
|
+ |
other = github_user("account-export-attest-owner", "export-attest-owner")
|
|
659
|
+ |
repository = private_repository_with_member("export-attest-closed-private", other)
|
|
660
|
+ |
issue = OpenAgents.IssuesFixtures.issue_fixture(repository, %{title: "Closed"})
|
|
661
|
+ |
|
|
662
|
+ |
link_subject!(user, "user:" <> user.id)
|
|
663
|
+ |
_attestation = attest!(repository, issue, "user:" <> user.id, "repository")
|
|
664
|
+ |
|
|
665
|
+ |
refute Repositories.member?(repository, user)
|
|
666
|
+ |
|
|
667
|
+ |
assert {:ok, export} = AccountExport.build(user)
|
|
668
|
+ |
assert export["repository_work"]["attestations"]["records"] == []
|
|
669
|
+ |
end
|
|
670
|
+ |
end
|
|
671
|
+ |
|
| 523 |
672
|
|
describe "the document resolves without the forge" do
|
| 524 |
673
|
|
test "every record names its context in readable terms", %{board: board} do
|
| 525 |
674
|
|
user = github_user("account-export-standalone", "export-standalone")
|