mirror of
https://github.com/jumpserver/jumpserver.git
synced 2025-12-20 19:12:54 +00:00
perf: organize oauth2_provider urls, add .well-known API
This commit is contained in:
14
apps/authentication/backends/oauth2_provider/urls.py
Normal file
14
apps/authentication/backends/oauth2_provider/urls.py
Normal file
@@ -0,0 +1,14 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from django.urls import path
|
||||
|
||||
from oauth2_provider import views as op_views
|
||||
from . import views
|
||||
|
||||
|
||||
urlpatterns = [
|
||||
path("authorize/", op_views.AuthorizationView.as_view(), name="authorize"),
|
||||
path("token/", op_views.TokenView.as_view(), name="token"),
|
||||
path("revoke_token/", op_views.RevokeTokenView.as_view(), name="revoke-token"),
|
||||
path(".well-known/oauth-authorization-server", views.OAuthAuthorizationServerView.as_view(), name="oauth-authorization-server"),
|
||||
]
|
||||
17
apps/authentication/backends/oauth2_provider/utils.py
Normal file
17
apps/authentication/backends/oauth2_provider/utils.py
Normal file
@@ -0,0 +1,17 @@
|
||||
from django.conf import settings
|
||||
from oauth2_provider.models import get_application_model
|
||||
|
||||
def get_or_create_jumpserver_client_application():
|
||||
"""Auto get or create OAuth2 JumpServer Client application."""
|
||||
Application = get_application_model()
|
||||
|
||||
application, created = Application.objects.get_or_create(
|
||||
name='JumpServer Client',
|
||||
defaults={
|
||||
'client_type': Application.CLIENT_PUBLIC,
|
||||
'authorization_grant_type': Application.GRANT_AUTHORIZATION_CODE,
|
||||
'redirect_uris': settings.OAUTH2_PROVIDER_CLIENT_REDIRECT_URI,
|
||||
'skip_authorization': True,
|
||||
}
|
||||
)
|
||||
return application
|
||||
77
apps/authentication/backends/oauth2_provider/views.py
Normal file
77
apps/authentication/backends/oauth2_provider/views.py
Normal file
@@ -0,0 +1,77 @@
|
||||
from django.views.generic import View
|
||||
from django.http import JsonResponse
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.views.decorators.cache import cache_page
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
from django.conf import settings
|
||||
from django.urls import reverse
|
||||
from oauth2_provider.settings import oauth2_settings
|
||||
from typing import List, Dict, Any
|
||||
from .utils import get_or_create_jumpserver_client_application
|
||||
|
||||
|
||||
@method_decorator(csrf_exempt, name='dispatch')
|
||||
@method_decorator(cache_page(60 * 60 * 24), name='dispatch')
|
||||
class OAuthAuthorizationServerView(View):
|
||||
"""
|
||||
OAuth 2.0 Authorization Server Metadata Endpoint
|
||||
RFC 8414: https://datatracker.ietf.org/doc/html/rfc8414
|
||||
|
||||
This endpoint provides machine-readable information about the
|
||||
OAuth 2.0 authorization server's configuration.
|
||||
"""
|
||||
|
||||
def get_base_url(self, request) -> str:
|
||||
scheme = 'https' if request.is_secure() else 'http'
|
||||
host = request.get_host()
|
||||
return f"{scheme}://{host}"
|
||||
|
||||
def get_supported_scopes(self) -> List[str]:
|
||||
scopes_config = oauth2_settings.SCOPES
|
||||
if isinstance(scopes_config, dict):
|
||||
return list(scopes_config.keys())
|
||||
return []
|
||||
|
||||
def get_metadata(self, request) -> Dict[str, Any]:
|
||||
base_url = self.get_base_url(request)
|
||||
application = get_or_create_jumpserver_client_application()
|
||||
metadata = {
|
||||
"issuer": base_url,
|
||||
"client_id": application.client_id if application else "Not found any application.",
|
||||
"authorization_endpoint": base_url + reverse('authentication:oauth2-provider:authorize'),
|
||||
"token_endpoint": base_url + reverse('authentication:oauth2-provider:token'),
|
||||
"revocation_endpoint": base_url + reverse('authentication:oauth2-provider:revoke-token'),
|
||||
|
||||
"response_types_supported": ["code"],
|
||||
"grant_types_supported": ["authorization_code", "refresh_token"],
|
||||
"scopes_supported": self.get_supported_scopes(),
|
||||
|
||||
"token_endpoint_auth_methods_supported": ["none"],
|
||||
"revocation_endpoint_auth_methods_supported": ["none"],
|
||||
"code_challenge_methods_supported": ["S256"],
|
||||
"response_modes_supported": ["query"],
|
||||
}
|
||||
if hasattr(oauth2_settings, 'ACCESS_TOKEN_EXPIRE_SECONDS'):
|
||||
metadata["token_expires_in"] = oauth2_settings.ACCESS_TOKEN_EXPIRE_SECONDS
|
||||
if hasattr(oauth2_settings, 'REFRESH_TOKEN_EXPIRE_SECONDS'):
|
||||
if oauth2_settings.REFRESH_TOKEN_EXPIRE_SECONDS:
|
||||
metadata["refresh_token_expires_in"] = oauth2_settings.REFRESH_TOKEN_EXPIRE_SECONDS
|
||||
return metadata
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
metadata = self.get_metadata(request)
|
||||
response = JsonResponse(metadata)
|
||||
self.add_cors_headers(response)
|
||||
return response
|
||||
|
||||
def options(self, request, *args, **kwargs):
|
||||
response = JsonResponse({})
|
||||
self.add_cors_headers(response)
|
||||
return response
|
||||
|
||||
@staticmethod
|
||||
def add_cors_headers(response):
|
||||
response['Access-Control-Allow-Origin'] = '*'
|
||||
response['Access-Control-Allow-Methods'] = 'GET, OPTIONS'
|
||||
response['Access-Control-Allow-Headers'] = 'Content-Type, Authorization'
|
||||
response['Access-Control-Max-Age'] = '3600'
|
||||
@@ -83,4 +83,6 @@ urlpatterns = [
|
||||
path('oauth2/', include(('authentication.backends.oauth2.urls', 'authentication'), namespace='oauth2')),
|
||||
|
||||
path('captcha/', include('captcha.urls')),
|
||||
|
||||
path('oauth2-provider/', include(('authentication.backends.oauth2_provider.urls', 'authentication'), namespace='oauth2-provider'))
|
||||
]
|
||||
|
||||
@@ -25,20 +25,5 @@ class CommonConfig(AppConfig):
|
||||
django_ready.send(CommonConfig)
|
||||
close_old_connections()
|
||||
|
||||
self._auto_register_jumpserver_client_if_not_exists()
|
||||
|
||||
def _auto_register_jumpserver_client_if_not_exists(self):
|
||||
""" Auto register JumpServer Client application if not exists. """
|
||||
from oauth2_provider.models import get_application_model
|
||||
Application = get_application_model()
|
||||
client_id = settings.OAUTH2_PROVIDER_CLIENT_ID
|
||||
if Application.objects.filter(client_id=client_id).exists():
|
||||
return
|
||||
Application.objects.create(
|
||||
name='JumpServer Client',
|
||||
client_id=client_id,
|
||||
client_type=Application.CLIENT_PUBLIC,
|
||||
authorization_grant_type=Application.GRANT_AUTHORIZATION_CODE,
|
||||
redirect_uris=settings.OAUTH2_PROVIDER_CLIENT_REDIRECT_URI,
|
||||
skip_authorization=True,
|
||||
)
|
||||
from authentication.backends.oauth2_provider import utils
|
||||
utils.get_or_create_jumpserver_client_application()
|
||||
|
||||
@@ -130,6 +130,7 @@ INSTALLED_APPS = [
|
||||
'settings.apps.SettingsConfig',
|
||||
'terminal.apps.TerminalConfig',
|
||||
'audits.apps.AuditsConfig',
|
||||
'oauth2_provider',
|
||||
'authentication.apps.AuthenticationConfig', # authentication
|
||||
'tickets.apps.TicketsConfig',
|
||||
'acls.apps.AclsConfig',
|
||||
@@ -141,7 +142,6 @@ INSTALLED_APPS = [
|
||||
'drf_spectacular',
|
||||
'drf_spectacular_sidecar',
|
||||
'django_cas_ng',
|
||||
'oauth2_provider',
|
||||
'channels',
|
||||
'django_filters',
|
||||
'bootstrap3',
|
||||
|
||||
@@ -228,8 +228,8 @@ JUMPSERVER_UPTIME = int(time.time())
|
||||
OAUTH2_PROVIDER = {
|
||||
'ALLOWED_REDIRECT_URI_SCHEMES': ['https', 'jms'],
|
||||
'PKCE_REQUIRED': True,
|
||||
'OIDC_ENABLED': True,
|
||||
'ACCESS_TOKEN_EXPIRE_SECONDS': CONFIG.OAUTH2_PROVIDER_ACCESS_TOKEN_EXPIRE_SECONDS,
|
||||
'REFRESH_TOKEN_EXPIRE_SECONDS': CONFIG.OAUTH2_PROVIDER_REFRESH_TOKEN_EXPIRE_SECONDS,
|
||||
}
|
||||
OAUTH2_PROVIDER_CLIENT_ID = 'FkkXFf0wPelYPIbvf0VElkZtyrw8TWIcyqakDgni'
|
||||
OAUTH2_PROVIDER_CLIENT_REDIRECT_URI = 'jms://auth/callback'
|
||||
@@ -9,7 +9,6 @@ from django.conf import settings
|
||||
from django.conf.urls.static import static
|
||||
from django.urls import path, include, re_path
|
||||
from django.views.i18n import JavaScriptCatalog
|
||||
from oauth2_provider import views as oauth2_provider_views
|
||||
|
||||
from . import views, api
|
||||
|
||||
@@ -45,13 +44,6 @@ if settings.MCP_ENABLED:
|
||||
path('resources/<str:resource>/<str:pk>/', api.ResourceDetailApi.as_view()),
|
||||
])
|
||||
|
||||
|
||||
oauth2_provider_patterns = [
|
||||
path("authorize/", oauth2_provider_views.AuthorizationView.as_view(), name="authorize"),
|
||||
path("token/", oauth2_provider_views.TokenView.as_view(), name="token"),
|
||||
path("revoke_token/", oauth2_provider_views.RevokeTokenView.as_view(), name="revoke-token"),
|
||||
]
|
||||
|
||||
app_view_patterns = [
|
||||
path('auth/', include('authentication.urls.view_urls'), name='auth'),
|
||||
path('ops/', include('ops.urls.view_urls'), name='ops'),
|
||||
@@ -62,7 +54,6 @@ app_view_patterns = [
|
||||
path('download/', views.ResourceDownload.as_view(), name='download'),
|
||||
path('redirect/confirm/', views.RedirectConfirm.as_view(), name='redirect-confirm'),
|
||||
path('i18n/<str:lang>/', views.I18NView.as_view(), name='i18n-switch'),
|
||||
path('oauth2-provider/', include(oauth2_provider_patterns), name='oauth2-provider')
|
||||
]
|
||||
|
||||
if settings.XPACK_ENABLED:
|
||||
|
||||
Reference in New Issue
Block a user