Skip to content

Update validate_lola_access() to enforce actor binding consistently across dual-mode endpoints - #267

Merged
aaronjae22 merged 7 commits into
mainfrom
aaronaej/validate_lola_access_review
Aug 4, 2026
Merged

Update validate_lola_access() to enforce actor binding consistently across dual-mode endpoints#267
aaronjae22 merged 7 commits into
mainfrom
aaronaej/validate_lola_access_review

Conversation

@aaronjae22

@aaronjae22 aaronjae22 commented Jun 18, 2026

Copy link
Copy Markdown
Collaborator

Closes #266

This PR updates the existing validate_lola_access() endpoint to continue enforcing LOLA §5 token-to-actor binding consistently across every actor-scoped endpoint.

Currently, a portability token bound to actor A could reactor actor B's LOLA augmented dual-mode responses, including B's private outbox activities. This doesn't respect LOLA §5.

Every actor-scoped LOLA endpoint falls into one of two behavioral categories:

Strict (LOLA-gated) endpoints — these serve only private/privacy-sensitive data and have no meaningful public version:

  • followerscontentlikedblocked

They each call validate_lola_access(request, required_scope=True). No portability scope → 403 insufficient_scope. They already enforce token-to-actor binding.

Dual-mode endpoints — these serve a public ActivityPub response to anonymous callers, and an augmented response when a portability token is present:

  • actor_detail — public: basic Actor; with scope: adds the migration object + outbox/following/followers/liked/blocked URLs.
  • portability_outbox_detail — public: public activities only; with scope: all activities including private.
  • following_collection— public: following list; with scope: nested actor objects gain migration augmentation.

These three do not call validate_lola_access at all. They look at request.has_portability_scope directly (inside the JSON-LD builders) and augment the response — but they never check which actor the token was issued for.

@aaronjae22 aaronjae22 changed the title Aaronaej/validate lola access review Update validate_lola_access() to enforce actor binding consistently across dual-mode endpoints Jun 18, 2026
@aaronjae22
aaronjae22 marked this pull request as ready for review July 1, 2026 23:14
@aaronjae22
aaronjae22 requested a review from lisad July 6, 2026 21:40

@lisad lisad left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have some suggestions you might enjoy, but overall this is readable and sane

otherwise the factory creates a fresh token user. Only the token<->actor binding is what
validate_lola_access checks, so both shapes satisfy the gate.
"""
def bind_portability_token(actor, user=None):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is a useful test util. At some point it makes sense to organize test utilities some other way than putting them in conftest.py, as a matter of taste/readability

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I noticed that myself too, that work is plan for an upcoming PR

request.path,
url_pk,
)
return {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there any time when "valid" would be True and error response would be the actor mismatch error? ISTM these could be combined.

I like the utilities you've made for building error responses. I think of them as simple templates. If I have a lot of response templates for an API or protocol, I might make a file of response_templates. They don't all have to be called "build_X_error", they can be "actor_mismatch_response". The whole response is the response template then, including "valid": False.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

valid: True never carries an error. I was kind of aware about the “valid”: False/True situation and the way I am constructing the errors and I thought of doing some work on them.

I was planning on double checking where I would need to change the responses to make them less redundant. To be honest, I added valid to have a quick way to scan through the codebase (it worked for me) but I was aware that I would need to make some adjustment to them.

In the current codebase we're using the following

if required_scope and not has_scope:
    return {"valid": False, "error_response": build_insufficient_scope_error(...)}
  if not has_scope:
    return {"valid": True}
  
  ...
  
  return {"valid": True}

And that could become:

if required_scope and not has_scope:
    return build_insufficient_scope_error(...)
  if not has_scope:
    return None
  
  ...
  
  return None

Comment thread testbed/core/views/api.py
# must be bound to this actor before its scope-gated discovery surface is exposed (LOLA Section 5).
# A token bound to another actor is rejected with 403 actor_mismatch
# instead of leaking actor <pk>'s migration object / collection URLs.
validation_result = validate_lola_access(request, required_scope=False)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If validate_lola_access is a decorator, why is this not applied as a decorator? It could be a decorator that returns its own result if validate lola access fails. Is it because the method takes a "required_scope"? that could be solved with two decorators, "validate_access_scope_true" and "validate_access_no_scope" or something like that

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense, I agree

Comment thread testbed/core/views/api.py
Returns basic ActivityPub data for unauthenticated requests,
and enhanced LOLA data for authenticated requests with portability scope.
"""
try:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If validate_lola_access is going to be treated as a decorator and declared on the view, then this also needs to be - but this would also make a nice clean "actor_required" decorator.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since you need the actor after applying the decorator, just pass the actor into "actor_detail" from the decorator.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You’re completely right about it. Let me check how I would implement those but since the order is 404 first, then 403 (a nonexisting actor gets 404 regardless of token because the existence check runs before validate_lola_access) should be something like

@api_view(["GET"])
  @authentication_classes([OptionalOAuth2Authentication])
  @activitypub_content
  @actor_required # runs first → 404 if missing, injects `actor`
  @validate_lola_access(required_scope=False) # runs second → 403 on bad binding
  def actor_detail(request, pk, actor):
    auth_context = build_auth_context(request)
    return Response(build_actor_json_ld(actor, auth_context))

@aaronjae22

Copy link
Copy Markdown
Collaborator Author

I iterated over these suggestions and I incorporated them but since I am working on stacked PRs I’ll submit them in another PR next just with these changes.

To be fair, I had to reorganize a couple of things in order to include these changes first but I agree with all of them and I saw them as kind of necessary for upcoming work. They are a great refactor to the codebase and it also helped me to unblock a few more things that could be done/refactor.

@aaronjae22
aaronjae22 merged commit 0efb95a into main Aug 4, 2026
3 checks passed
@aaronjae22
aaronjae22 deleted the aaronaej/validate_lola_access_review branch August 4, 2026 02:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Enforce actor binding consistently across dual-mode endpoints in validate_lola_access

2 participants