Page Menu
Home
Software Heritage
Search
Configure Global Search
Log In
Files
F7123930
D8420.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
20 KB
Subscribers
None
D8420.diff
View Options
diff --git a/requirements-swh.txt b/requirements-swh.txt
--- a/requirements-swh.txt
+++ b/requirements-swh.txt
@@ -1,4 +1,4 @@
-swh.auth[django] >= 0.5.3
+swh.auth[django] >= 0.6.7
swh.core >= 0.0.95
swh.counters >= 0.5.1
swh.indexer >= 2.0.0
diff --git a/swh/web/add_forge_now/admin_views.py b/swh/web/add_forge_now/admin_views.py
--- a/swh/web/add_forge_now/admin_views.py
+++ b/swh/web/add_forge_now/admin_views.py
@@ -3,7 +3,6 @@
# License: GNU Affero General Public License version 3, or any later version
# See top-level LICENSE file for more information
-from django.conf import settings
from django.contrib.auth.decorators import user_passes_test
from django.shortcuts import render
@@ -11,7 +10,7 @@
from swh.web.auth.utils import is_add_forge_now_moderator
-@user_passes_test(is_add_forge_now_moderator, login_url=settings.LOGIN_URL)
+@user_passes_test(is_add_forge_now_moderator)
def add_forge_now_requests_moderation_dashboard(request):
"""Moderation dashboard to allow listing current requests."""
return render(
@@ -21,7 +20,7 @@
)
-@user_passes_test(is_add_forge_now_moderator, login_url=settings.LOGIN_URL)
+@user_passes_test(is_add_forge_now_moderator)
def add_forge_now_request_dashboard(request, request_id):
"""Moderation dashboard to allow listing current requests."""
return render(
diff --git a/swh/web/add_forge_now/templates/add-forge-creation-form.html b/swh/web/add_forge_now/templates/add-forge-creation-form.html
--- a/swh/web/add_forge_now/templates/add-forge-creation-form.html
+++ b/swh/web/add_forge_now/templates/add-forge-creation-form.html
@@ -13,12 +13,11 @@
<p class="text-primary">
You must be logged in to submit an add forge request. Please
<a id="loginLink" class="link-primary"
- {% if oidc_enabled and 'remote_user' in request.GET %}
- href="{% url 'oidc-login' %}?next={% url 'forge-add-create' %}"
- {% else %}
- href="{% url 'login' %}?next={% url 'forge-add-create' %}"
- {% endif %}>log in</a>
+ href="{% url login_url %}?next={% url 'forge-add-create' %}">
+ log in
+ </a>
</p>
+
{% else %}
<form method="POST" action="{% url 'api-1-add-forge-request-create' %}"
diff --git a/swh/web/add_forge_now/views.py b/swh/web/add_forge_now/views.py
--- a/swh/web/add_forge_now/views.py
+++ b/swh/web/add_forge_now/views.py
@@ -5,7 +5,6 @@
from typing import Any, Dict, List
-from django.conf import settings
from django.contrib.auth.decorators import user_passes_test
from django.core.paginator import Paginator
from django.db.models import Q
@@ -116,11 +115,7 @@
)
-@user_passes_test(
- is_add_forge_now_moderator,
- redirect_field_name="next_path",
- login_url=settings.LOGIN_URL,
-)
+@user_passes_test(is_add_forge_now_moderator)
def create_request_message_source(request: HttpRequest, id: int) -> HttpResponse:
"""View to retrieve the message source for a given request history entry"""
diff --git a/swh/web/api/templates/api.html b/swh/web/api/templates/api.html
--- a/swh/web/api/templates/api.html
+++ b/swh/web/api/templates/api.html
@@ -38,9 +38,11 @@
<li class="list-inline-item">
<a href="#rate-limiting">Rate limiting</a>
</li>
- <li class="list-inline-item">
- <a href="#authentication">Authentication</a>
- </li>
+ {% if oidc_enabled %}
+ <li class="list-inline-item">
+ <a href="#authentication">Authentication</a>
+ </li>
+ {% endif %}
</ul>
<h4 id="endpoint-index">Endpoint index</h4>
@@ -277,28 +279,30 @@
X-RateLimit-Remaining: 119
X-RateLimit-Reset: 1620639052</code></pre>
- <h4 id="authentication">Authentication</h4>
- <p>
- It is possible to perform authenticated requests to the Web API through the use of a bearer token
- sent in HTTP Authorization headers.
- <br/>
- To obtain such a token, an account to the
- <a href="{% url 'oidc-login' %}">Software Heritage Authentication service</a> must be created.
- <br/>
- To generate and manage bearer tokens, a dedicated interface is available on the
- <a href="{% url 'oidc-profile' %}#tokens">user profile page</a> once logged in.
- </p>
- <p>
- The following shows how to perform an authenticated request to the Web API using <code>curl</code>.
- <pre>export TOKEN=eyJhbGciOiJIUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJhMTMxYTQ1My1hM2IyLTQwMTUtO...
-curl -H "Authorization: Bearer ${TOKEN}" {{ site_base_url }}api/...</pre>
- </p>
- <p>
- Authenticated requests can be used to lift rate limiting if the user account has the adequate
- permission.
- If you are in such a need, please <a href="https://www.softwareheritage.org/contact/">contact us</a>
- and we will review your request.
- </p>
+ {% if oidc_enabled %}
+ <h4 id="authentication">Authentication</h4>
+ <p>
+ It is possible to perform authenticated requests to the Web API through the use of a bearer token
+ sent in HTTP Authorization headers.
+ <br/>
+ To obtain such a token, an account to the
+ <a href="{% url 'oidc-login' %}">Software Heritage Authentication service</a> must be created.
+ <br/>
+ To generate and manage bearer tokens, a dedicated interface is available on the
+ <a href="{% url 'oidc-profile' %}#tokens">user profile page</a> once logged in.
+ </p>
+ <p>
+ The following shows how to perform an authenticated request to the Web API using <code>curl</code>.
+ <pre>export TOKEN=eyJhbGciOiJIUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJhMTMxYTQ1My1hM2IyLTQwMTUtO...
+ curl -H "Authorization: Bearer ${TOKEN}" {{ site_base_url }}api/...</pre>
+ </p>
+ <p>
+ Authenticated requests can be used to lift rate limiting if the user account has the adequate
+ permission.
+ If you are in such a need, please <a href="https://www.softwareheritage.org/contact/">contact us</a>
+ and we will review your request.
+ </p>
+ {% endif %}
</div>
<script>
diff --git a/swh/web/auth/templates/logout.html b/swh/web/auth/templates/logout.html
--- a/swh/web/auth/templates/logout.html
+++ b/swh/web/auth/templates/logout.html
@@ -16,26 +16,22 @@
{% endblock %}
{% block content %}
-{% if 'next_path' in request.GET %}
+{% if 'next' in request.GET %}
<p>Your authenticated session expired so you have been automatically logged out.</p>
{% else %}
<p>You have been successfully logged out.</p>
{% endif %}
<p>
-{% if oidc_enabled and 'remote_user' in request.GET %}
-{% if 'next_path' in request.GET %}
-<a href="{% url 'oidc-login' %}?next_path={{ request.GET.next_path }}">
+{% if 'next' in request.GET %}
+<a href="{% url login_url %}?next={{ request.GET.next }}">
{% else %}
-<a href="{% url 'oidc-login' %}">
-{% endif %}
-{% else %}
-<a href="{% url 'login' %}">
+<a href="{% url login_url %}">
{% endif %}
Log in</a> again ?</p>
-{% if 'next_path' in request.GET %}
+{% if 'next' in request.GET %}
<p>
Or go back to
-<a href="{{ request.GET.next_path }}">
+<a href="{{ request.GET.next }}">
previous page</a>.
</p>
{% endif %}
diff --git a/swh/web/auth/urls.py b/swh/web/auth/urls.py
--- a/swh/web/auth/urls.py
+++ b/swh/web/auth/urls.py
@@ -15,38 +15,61 @@
oidc_profile_view,
oidc_revoke_bearer_tokens,
)
+from swh.web.config import get_config
-urlpatterns = auth_urlpatterns + [
- url(
- r"^oidc/generate-bearer-token/$",
- oidc_generate_bearer_token,
- name="oidc-generate-bearer-token",
- ),
- url(
- r"^oidc/generate-bearer-token-complete/$",
- oidc_generate_bearer_token_complete,
- name="oidc-generate-bearer-token-complete",
- ),
- url(
- r"^oidc/list-bearer-token/$",
- oidc_list_bearer_tokens,
- name="oidc-list-bearer-tokens",
- ),
- url(
- r"^oidc/get-bearer-token/$",
- oidc_get_bearer_token,
- name="oidc-get-bearer-token",
- ),
- url(
- r"^oidc/revoke-bearer-tokens/$",
- oidc_revoke_bearer_tokens,
- name="oidc-revoke-bearer-tokens",
- ),
+config = get_config()
+
+oidc_enabled = bool(config["keycloak"]["server_url"])
+
+urlpatterns = []
+
+if not oidc_enabled:
+ urlpatterns = [
+ url(
+ r"^login/$",
+ LoginView.as_view(template_name="login.html"),
+ name="login",
+ )
+ ]
+
+if oidc_enabled or config["e2e_tests_mode"]:
+ urlpatterns += auth_urlpatterns + [
+ url(
+ r"^oidc/generate-bearer-token/$",
+ oidc_generate_bearer_token,
+ name="oidc-generate-bearer-token",
+ ),
+ url(
+ r"^oidc/generate-bearer-token-complete/$",
+ oidc_generate_bearer_token_complete,
+ name="oidc-generate-bearer-token-complete",
+ ),
+ url(
+ r"^oidc/list-bearer-token/$",
+ oidc_list_bearer_tokens,
+ name="oidc-list-bearer-tokens",
+ ),
+ url(
+ r"^oidc/get-bearer-token/$",
+ oidc_get_bearer_token,
+ name="oidc-get-bearer-token",
+ ),
+ url(
+ r"^oidc/revoke-bearer-tokens/$",
+ oidc_revoke_bearer_tokens,
+ name="oidc-revoke-bearer-tokens",
+ ),
+ url(
+ r"^oidc/profile/$",
+ oidc_profile_view,
+ name="oidc-profile",
+ ),
+ ]
+
+urlpatterns.append(
url(
- r"^oidc/profile/$",
- oidc_profile_view,
- name="oidc-profile",
- ),
- url(r"^login/$", LoginView.as_view(template_name="login.html"), name="login"),
- url(r"^logout/$", LogoutView.as_view(template_name="logout.html"), name="logout"),
-]
+ r"^logout/$",
+ LogoutView.as_view(template_name="logout.html"),
+ name="logout",
+ )
+)
diff --git a/swh/web/auth/views.py b/swh/web/auth/views.py
--- a/swh/web/auth/views.py
+++ b/swh/web/auth/views.py
@@ -150,6 +150,6 @@
return HttpResponse(status=401)
-@login_required(login_url="/oidc/login/", redirect_field_name="next_path")
+@login_required(login_url="oidc-login")
def oidc_profile_view(request: HttpRequest) -> HttpResponse:
return render(request, "profile.html")
diff --git a/swh/web/deposit/urls.py b/swh/web/deposit/urls.py
--- a/swh/web/deposit/urls.py
+++ b/swh/web/deposit/urls.py
@@ -7,7 +7,6 @@
import requests
from requests.auth import HTTPBasicAuth
-from django.conf import settings
from django.contrib.auth.decorators import user_passes_test
from django.http import JsonResponse
from django.shortcuts import render
@@ -21,12 +20,12 @@
return user.is_staff or user.has_perm(ADMIN_LIST_DEPOSIT_PERMISSION)
-@user_passes_test(can_list_deposits, login_url=settings.LOGIN_URL)
+@user_passes_test(can_list_deposits)
def admin_deposit(request):
return render(request, "deposit-admin.html")
-@user_passes_test(can_list_deposits, login_url=settings.LOGIN_URL)
+@user_passes_test(can_list_deposits)
def admin_deposit_list(request):
config = get_config()["deposit"]
private_api_url = config["private_api_url"].rstrip("/") + "/"
diff --git a/swh/web/save_code_now/templates/origin-save-help.html b/swh/web/save_code_now/templates/origin-save-help.html
--- a/swh/web/save_code_now/templates/origin-save-help.html
+++ b/swh/web/save_code_now/templates/origin-save-help.html
@@ -47,7 +47,7 @@
Once a save request has been accepted, you can follow its current status in the
<a id="swh-show-origin-save-requests-list" href="#swh-origin-save-requests-list">submitted save requests list</a>.
<br/>
- If you submitted requests while <a href="{% url 'oidc-login' %}">authenticated</a>, you will be able
+ If you submitted requests while <a href="{% url login_url %}">authenticated</a>, you will be able
to only display your own requests.
</p>
</div>
diff --git a/swh/web/settings/common.py b/swh/web/settings/common.py
--- a/swh/web/settings/common.py
+++ b/swh/web/settings/common.py
@@ -80,7 +80,6 @@
"django.middleware.common.CommonMiddleware",
"django.middleware.csrf.CsrfViewMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware",
- "swh.auth.django.middlewares.OIDCSessionExpiredMiddleware",
"django.contrib.messages.middleware.MessageMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware",
"swh.web.utils.middlewares.ThrottlingHeadersMiddleware",
@@ -323,7 +322,26 @@
}
}
-LOGIN_URL = "/login/"
+AUTHENTICATION_BACKENDS = [
+ "django.contrib.auth.backends.ModelBackend",
+]
+
+oidc_enabled = bool(get_config()["keycloak"]["server_url"])
+
+if not oidc_enabled:
+ LOGIN_URL = "login"
+ LOGOUT_URL = "logout"
+else:
+ LOGIN_URL = "oidc-login"
+ LOGOUT_URL = "oidc-logout"
+ AUTHENTICATION_BACKENDS.append(
+ "swh.auth.django.backends.OIDCAuthorizationCodePKCEBackend",
+ )
+ MIDDLEWARE.insert(
+ MIDDLEWARE.index("django.contrib.auth.middleware.AuthenticationMiddleware") + 1,
+ "swh.auth.django.middlewares.OIDCSessionExpiredMiddleware",
+ )
+
LOGIN_REDIRECT_URL = "swh-web-homepage"
SESSION_ENGINE = "django.contrib.sessions.backends.cache"
@@ -337,11 +355,6 @@
CORS_ORIGIN_ALLOW_ALL = True
CORS_URLS_REGEX = r"^/(badge|api)/.*$"
-AUTHENTICATION_BACKENDS = [
- "django.contrib.auth.backends.ModelBackend",
- "swh.auth.django.backends.OIDCAuthorizationCodePKCEBackend",
-]
-
OIDC_SWH_WEB_CLIENT_ID = "swh-web"
SWH_AUTH_SERVER_URL = swh_web_config["keycloak"]["server_url"]
SWH_AUTH_REALM_NAME = swh_web_config["keycloak"]["realm_name"]
diff --git a/swh/web/settings/production.py b/swh/web/settings/production.py
--- a/swh/web/settings/production.py
+++ b/swh/web/settings/production.py
@@ -67,5 +67,6 @@
WEBPACK_LOADER["DEFAULT"]["CACHE"] = not DEBUG
-LOGIN_URL = "/oidc/login/"
-LOGIN_REDIRECT_URL = "/oidc/profile/"
+LOGIN_URL = "oidc-login"
+LOGIN_REDIRECT_URL = "oidc-profile"
+LOGOUT_URL = "oidc-logout"
diff --git a/swh/web/settings/tests.py b/swh/web/settings/tests.py
--- a/swh/web/settings/tests.py
+++ b/swh/web/settings/tests.py
@@ -159,3 +159,6 @@
else:
# Silent DEBUG output when running unit tests
LOGGING["handlers"]["console"]["level"] = "INFO" # type: ignore
+
+LOGIN_URL = "login" if not _pytest else "oidc-login"
+LOGOUT_URL = "logout" if not _pytest else "oidc-logout"
diff --git a/swh/web/tests/auth/test_views.py b/swh/web/tests/auth/test_views.py
--- a/swh/web/tests/auth/test_views.py
+++ b/swh/web/tests/auth/test_views.py
@@ -281,7 +281,7 @@
requesting profile view.
"""
url = reverse("oidc-profile")
- login_url = reverse("oidc-login", query_params={"next_path": url})
+ login_url = reverse("oidc-login", query_params={"next": url})
resp = check_http_get_response(client, url, status_code=302)
assert resp["location"] == login_url
diff --git a/swh/web/tests/conftest.py b/swh/web/tests/conftest.py
--- a/swh/web/tests/conftest.py
+++ b/swh/web/tests/conftest.py
@@ -1226,11 +1226,18 @@
from django.conf import settings
clear_url_caches()
- urlconf = settings.ROOT_URLCONF
- if urlconf in sys.modules:
- reload(sys.modules[urlconf])
- else:
- import_module(urlconf)
+ # force reloading of all URLs as they depend on django settings
+ # and swh-web configuration
+ urlconfs = [settings.ROOT_URLCONF]
+ urlconfs += [f"{app}.urls" for app in settings.SWH_DJANGO_APPS]
+ for urlconf in urlconfs:
+ try:
+ if urlconf in sys.modules:
+ reload(sys.modules[urlconf])
+ else:
+ import_module(urlconf)
+ except ModuleNotFoundError:
+ pass
class SwhSettingsWrapper(SettingsWrapper):
diff --git a/swh/web/tests/deposit/test_views.py b/swh/web/tests/deposit/test_views.py
--- a/swh/web/tests/deposit/test_views.py
+++ b/swh/web/tests/deposit/test_views.py
@@ -7,6 +7,8 @@
import pytest
+from django.conf import settings
+
from swh.web.auth.utils import ADMIN_LIST_DEPOSIT_PERMISSION
from swh.web.config import get_config
from swh.web.tests.helpers import (
@@ -20,7 +22,7 @@
def test_deposit_admin_view_not_available_for_anonymous_user(client):
url = reverse("admin-deposit")
resp = check_html_get_response(client, url, status_code=302)
- assert resp["location"] == reverse("login", query_params={"next": url})
+ assert resp["location"] == reverse(settings.LOGIN_URL, query_params={"next": url})
@pytest.mark.django_db
diff --git a/swh/web/tests/save_code_now/test_origin_save_admin.py b/swh/web/tests/save_code_now/test_origin_save_admin.py
--- a/swh/web/tests/save_code_now/test_origin_save_admin.py
+++ b/swh/web/tests/save_code_now/test_origin_save_admin.py
@@ -7,6 +7,8 @@
import pytest
+from django.conf import settings
+
from swh.web.save_code_now.models import (
SAVE_REQUEST_ACCEPTED,
SAVE_REQUEST_PENDING,
@@ -34,7 +36,7 @@
def check_not_login(client, url):
- login_url = reverse("login", query_params={"next": url})
+ login_url = reverse(settings.LOGIN_URL, query_params={"next": url})
resp = check_http_post_response(client, url, status_code=302)
assert unquote(resp.url) == login_url
diff --git a/swh/web/tests/webapp/test_templates.py b/swh/web/tests/webapp/test_templates.py
--- a/swh/web/tests/webapp/test_templates.py
+++ b/swh/web/tests/webapp/test_templates.py
@@ -9,6 +9,8 @@
from pkg_resources import get_distribution
import pytest
+from django.conf import settings
+
from swh.web.auth.utils import ADMIN_LIST_DEPOSIT_PERMISSION
from swh.web.config import SWH_WEB_SERVER_NAME, SWH_WEB_STAGING_SERVER_NAMES, get_config
from swh.web.tests.django_asserts import assert_contains, assert_not_contains
@@ -56,15 +58,23 @@
assert_contains(resp, reverse("oidc-login"))
-def test_layout_without_oidc_auth_enabled(client, mocker):
+def test_layout_without_oidc_auth_enabled(client, django_settings, mocker):
config = deepcopy(get_config())
config["keycloak"]["server_url"] = ""
- mock_get_config = mocker.patch("swh.web.utils.get_config")
+ mock_get_config = mocker.patch("swh.web.config.get_config")
mock_get_config.return_value = config
+ django_settings.LOGIN_URL = "login"
+ django_settings.LOGOUT_URL = "logout"
+ django_settings.MIDDLEWARE = [
+ mid
+ for mid in django_settings.MIDDLEWARE
+ if mid != "swh.auth.django.middlewares.OIDCSessionExpiredMiddleware"
+ ]
+
url = reverse("swh-web-homepage")
resp = check_http_get_response(client, url, status_code=200)
- assert_contains(resp, reverse("login"))
+ assert_contains(resp, reverse(settings.LOGIN_URL))
def test_layout_swh_web_version_number_display(client):
diff --git a/swh/web/utils/__init__.py b/swh/web/utils/__init__.py
--- a/swh/web/utils/__init__.py
+++ b/swh/web/utils/__init__.py
@@ -319,6 +319,8 @@
"lang": "en",
"sidebar_state": request.COOKIES.get("sidebar-state", "expanded"),
"SWH_DJANGO_APPS": settings.SWH_DJANGO_APPS,
+ "login_url": settings.LOGIN_URL,
+ "logout_url": settings.LOGOUT_URL,
}
diff --git a/swh/web/webapp/templates/layout.html b/swh/web/webapp/templates/layout.html
--- a/swh/web/webapp/templates/layout.html
+++ b/swh/web/webapp/templates/layout.html
@@ -112,27 +112,20 @@
<span id="swh-current-status-description">Operational</span>
<i class="swh-current-status-indicator green"></i>
</a>
- {% url 'logout' as logout_url %}
{% if user.is_authenticated %}
Logged in as
{% if 'OIDC' in user.backend %}
<a id="swh-login" href="{% url 'oidc-profile' %}"><strong>{{ user.username }}</strong></a>,
- <a href= "{% url 'oidc-logout' %}?next_path={% url 'logout' %}?remote_user=1">logout</a>
+ <a href= "{% url 'oidc-logout' %}?next={% url 'logout' %}?remote_user=1">logout</a>
{% else %}
<strong id="swh-login">{{ user.username }}</strong>,
- <a href="{{ logout_url }}">logout</a>
- {% endif %}
- {% elif oidc_enabled %}
- {% if request.path != logout_url %}
- <a id="swh-login" href="{% url 'oidc-login' %}?next_path={{ request.build_absolute_uri }}">login</a>
- {% else %}
- <a id="swh-login" href="{% url 'oidc-login' %}">login</a>
+ <a href="{% url logout_url %}">logout</a>
{% endif %}
{% else %}
- {% if request.path != logout_url %}
- <a id="swh-login" href="{% url 'login' %}?next={{ request.build_absolute_uri }}">login</a>
+ {% if request.resolver_match.url_name != logout_url %}
+ <a id="swh-login" href="{% url login_url %}?next={{ request.build_absolute_uri }}">login</a>
{% else %}
- <a id="swh-login" href="{% url 'login' %}">login</a>
+ <a id="swh-login" href="{% url login_url %}">login</a>
{% endif %}
{% endif %}
</li>
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Dec 20 2024, 6:32 AM (11 w, 4 d ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
3227725
Attached To
D8420: auth: Improve login management and configuration
Event Timeline
Log In to Comment