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('oauth2/', include(('authentication.backends.oauth2.urls', 'authentication'), namespace='oauth2')),
|
||||||
|
|
||||||
path('captcha/', include('captcha.urls')),
|
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)
|
django_ready.send(CommonConfig)
|
||||||
close_old_connections()
|
close_old_connections()
|
||||||
|
|
||||||
self._auto_register_jumpserver_client_if_not_exists()
|
from authentication.backends.oauth2_provider import utils
|
||||||
|
utils.get_or_create_jumpserver_client_application()
|
||||||
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,
|
|
||||||
)
|
|
||||||
|
|||||||
@@ -130,6 +130,7 @@ INSTALLED_APPS = [
|
|||||||
'settings.apps.SettingsConfig',
|
'settings.apps.SettingsConfig',
|
||||||
'terminal.apps.TerminalConfig',
|
'terminal.apps.TerminalConfig',
|
||||||
'audits.apps.AuditsConfig',
|
'audits.apps.AuditsConfig',
|
||||||
|
'oauth2_provider',
|
||||||
'authentication.apps.AuthenticationConfig', # authentication
|
'authentication.apps.AuthenticationConfig', # authentication
|
||||||
'tickets.apps.TicketsConfig',
|
'tickets.apps.TicketsConfig',
|
||||||
'acls.apps.AclsConfig',
|
'acls.apps.AclsConfig',
|
||||||
@@ -141,7 +142,6 @@ INSTALLED_APPS = [
|
|||||||
'drf_spectacular',
|
'drf_spectacular',
|
||||||
'drf_spectacular_sidecar',
|
'drf_spectacular_sidecar',
|
||||||
'django_cas_ng',
|
'django_cas_ng',
|
||||||
'oauth2_provider',
|
|
||||||
'channels',
|
'channels',
|
||||||
'django_filters',
|
'django_filters',
|
||||||
'bootstrap3',
|
'bootstrap3',
|
||||||
|
|||||||
@@ -228,8 +228,8 @@ JUMPSERVER_UPTIME = int(time.time())
|
|||||||
OAUTH2_PROVIDER = {
|
OAUTH2_PROVIDER = {
|
||||||
'ALLOWED_REDIRECT_URI_SCHEMES': ['https', 'jms'],
|
'ALLOWED_REDIRECT_URI_SCHEMES': ['https', 'jms'],
|
||||||
'PKCE_REQUIRED': True,
|
'PKCE_REQUIRED': True,
|
||||||
|
'OIDC_ENABLED': True,
|
||||||
'ACCESS_TOKEN_EXPIRE_SECONDS': CONFIG.OAUTH2_PROVIDER_ACCESS_TOKEN_EXPIRE_SECONDS,
|
'ACCESS_TOKEN_EXPIRE_SECONDS': CONFIG.OAUTH2_PROVIDER_ACCESS_TOKEN_EXPIRE_SECONDS,
|
||||||
'REFRESH_TOKEN_EXPIRE_SECONDS': CONFIG.OAUTH2_PROVIDER_REFRESH_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'
|
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.conf.urls.static import static
|
||||||
from django.urls import path, include, re_path
|
from django.urls import path, include, re_path
|
||||||
from django.views.i18n import JavaScriptCatalog
|
from django.views.i18n import JavaScriptCatalog
|
||||||
from oauth2_provider import views as oauth2_provider_views
|
|
||||||
|
|
||||||
from . import views, api
|
from . import views, api
|
||||||
|
|
||||||
@@ -45,13 +44,6 @@ if settings.MCP_ENABLED:
|
|||||||
path('resources/<str:resource>/<str:pk>/', api.ResourceDetailApi.as_view()),
|
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 = [
|
app_view_patterns = [
|
||||||
path('auth/', include('authentication.urls.view_urls'), name='auth'),
|
path('auth/', include('authentication.urls.view_urls'), name='auth'),
|
||||||
path('ops/', include('ops.urls.view_urls'), name='ops'),
|
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('download/', views.ResourceDownload.as_view(), name='download'),
|
||||||
path('redirect/confirm/', views.RedirectConfirm.as_view(), name='redirect-confirm'),
|
path('redirect/confirm/', views.RedirectConfirm.as_view(), name='redirect-confirm'),
|
||||||
path('i18n/<str:lang>/', views.I18NView.as_view(), name='i18n-switch'),
|
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:
|
if settings.XPACK_ENABLED:
|
||||||
|
|||||||
Reference in New Issue
Block a user