Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 16 additions & 2 deletions testbed/core/templates/oauth_callback.html
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,14 @@ <h3>State:</h3>
<pre class="state">{{ state }}</pre>
<p class="state-note">The state parameter helps prevent CSRF attacks and should be verified by the client.</p>
</div>

{% if activitypub_actor %}
<div class="actor-container">
<h3>Source Actor (LOLA §5.3):</h3>
<pre class="actor">{{ activitypub_actor }}</pre>
<p class="actor-note">The canonical source Actor URL. Per LOLA §5.3 the destination MUST treat this as authoritative for the migration, even if it differs from the source the user originally entered.</p>
</div>
{% endif %}
</div>
{% endif %}

Expand All @@ -42,7 +50,13 @@ <h3>What Just Happened?</h3>
<li>You (the user) approved the request</li>
<li>The authorization server redirected back to this callback URL with an authorization code</li>
</ol>


<p>
For a LOLA portability authorization the source redirect carries three callback parameters —
<code>code</code>, <code>state</code>, and <code>activitypub_actor</code> — and only
<code>activitypub_actor</code> (the canonical source Actor URL) is LOLA-specific.
</p>

<p>
In a real implementation, the next steps would be:
</p>
Expand All @@ -54,7 +68,7 @@ <h3>What Just Happened?</h3>

<div class="callback-actions">
{% if code and not error %}
<a href="{% url 'test-oauth-token' %}?code={{ code }}&state={{ state }}" class="btn btn-primary">
<a href="{% url 'test-oauth-token' %}?code={{ code }}&state={{ state }}{% if activitypub_actor %}&activitypub_actor={{ activitypub_actor|urlencode }}{% endif %}" class="btn btn-primary">
Exchange Code for Token →
</a>
{% endif %}
Expand Down
13 changes: 12 additions & 1 deletion testbed/core/templates/oauth_token_exchange.html
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,18 @@ <h3>State Parameter:</h3>
<pre class="state">{{ state }}</pre>
</div>
{% endif %}


{% if activitypub_actor %}
<div class="actor-container">
<h3>Source Actor:</h3>
<pre class="actor">{{ activitypub_actor }}</pre>
<p>The canonical source Actor URL from the callback — the actor the issued portability token will be bound to.</p>
{% if callback_actor_matches %}
<p class="alert alert-info">✓ Callback actor matches your source actor below.</p>
{% endif %}
</div>
{% endif %}

{% if error %}
<div class="authorization-error">
<h3>Authorization Error:</h3>
Expand Down
18 changes: 17 additions & 1 deletion testbed/core/views/oauth_demo.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
from django.shortcuts import redirect, render
from django.urls import reverse

from ..json_ld_utils import build_actor_id
from ..models import Actor
from ..oauth.utils import (
generate_secure_state,
Expand All @@ -45,13 +46,15 @@ def oauth_callback(request):
error = request.GET.get("error")
error_description = request.GET.get("error_description")
state = request.GET.get("state")
activitypub_actor = request.GET.get("activitypub_actor") # The canonical source Actor URL

# Initialize context with received parameters
context = {
"code": code,
"error": error,
"error_description": error_description,
"state": state,
"activitypub_actor": activitypub_actor,
}

# Validate the state parameter to prevent CSRF attacks
Expand All @@ -69,6 +72,8 @@ def oauth_callback(request):
# Log successful authorization
if code and not error and not context.get("error"):
logger.info("Successfully received authorization code in callback")
if activitypub_actor:
logger.info("Callback included LOLA activitypub_actor: %s", activitypub_actor)

return render(request, "oauth_callback.html", context)

Expand Down Expand Up @@ -148,18 +153,29 @@ def test_token_exchange_view(request):
code = request.GET.get("code")
state = request.GET.get("state")
error = request.GET.get("error")
activitypub_actor = request.GET.get("activitypub_actor")

# Get the user's source actor for LOLA testing
user_actors = Actor.objects.filter(user=request.user)
source_actor = user_actors.filter(role=Actor.ROLE_SOURCE).first()

# build_actor_id is the same builder the source uses for
# activitypub_actor, so an exact match is expected on the testbed.
callback_actor_matches = None
if activitypub_actor and source_actor:
callback_actor_matches = activitypub_actor == build_actor_id(
source_actor.pk, request
)

context = {
"code": code,
"state": state,
"error": error,
"activitypub_actor": activitypub_actor,
"callback_actor_matches": callback_actor_matches,
"token_response": None,
"token_error": None,
"source_actor": source_actor, # Add source actor for LOLA testing
"source_actor": source_actor,
}

# If we have an error or no code, don't attempt token exchange
Expand Down
Loading