2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-05-01 13:06:45 +00:00

[PUI] SSO improvments (#8527)

* disable PUI SSO login button if not fully set up
see https://github.com/inventree/InvenTree/issues/7972

* refactor provider urls to also support oidc providers

* cleanup
This commit is contained in:
Matthias Mair 2024-11-24 21:25:39 +01:00 committed by GitHub
parent f6124fc4a1
commit 66a769edca
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 145 additions and 110 deletions

View File

@ -6,6 +6,7 @@ from importlib import import_module
from django.conf import settings
from django.urls import NoReverseMatch, include, path, reverse
import allauth.socialaccount.providers.openid_connect.views as oidc_views
from allauth.account.models import EmailAddress
from allauth.socialaccount import providers
from allauth.socialaccount.providers.oauth2.views import OAuth2Adapter, OAuth2LoginView
@ -44,7 +45,7 @@ class GenericOAuth2ApiConnectView(GenericOAuth2ApiLoginView):
return super().dispatch(request, *args, **kwargs)
def handle_oauth2(adapter: OAuth2Adapter):
def handle_oauth2(adapter: OAuth2Adapter, provider=None):
"""Define urls for oauth2 endpoints."""
return [
path(
@ -60,6 +61,22 @@ def handle_oauth2(adapter: OAuth2Adapter):
]
def handle_oidc(provider):
"""Define urls for oidc endpoints."""
return [
path(
'login/',
lambda x: oidc_views.login(x, provider.id),
name=f'{provider.id}_api_login',
),
path(
'connect/',
lambda x: oidc_views.callback(x, provider.id),
name=f'{provider.id}_api_connect',
),
]
legacy = {
'twitter': 'twitter_oauth2',
'bitbucket': 'bitbucket_oauth2',
@ -70,11 +87,15 @@ legacy = {
# Collect urls for all loaded providers
social_auth_urlpatterns = []
def get_provider_urls() -> list:
"""Collect urls for all loaded providers.
provider_urlpatterns = []
Returns:
list: List of urls for all loaded providers.
"""
auth_provider_routes = []
for name, provider in providers.registry.provider_map.items():
for name, provider in providers.registry.provider_map.items():
try:
prov_mod = import_module(provider.get_package() + '.views')
except ImportError:
@ -93,7 +114,10 @@ for name, provider in providers.registry.provider_map.items():
# Get urls
urls = []
if len(adapters) == 1:
urls = handle_oauth2(adapter=adapters[0])
if provider.id == 'openid_connect':
urls = handle_oidc(provider)
else:
urls = handle_oauth2(adapter=adapters[0], provider=provider)
elif provider.id in legacy:
logger.warning(
'`%s` is not supported on platform UI. Use `%s` instead.',
@ -107,10 +131,9 @@ for name, provider in providers.registry.provider_map.items():
provider.id,
)
continue
provider_urlpatterns += [path(f'{provider.id}/', include(urls))]
auth_provider_routes += [path(f'{provider.id}/', include(urls))]
social_auth_urlpatterns += provider_urlpatterns
return auth_provider_routes
class SocialProviderListResponseSerializer(serializers.Serializer):

View File

@ -54,7 +54,7 @@ from .social_auth_urls import (
EmailRemoveView,
EmailVerifyView,
SocialProviderListView,
social_auth_urlpatterns,
get_provider_urls,
)
from .views import (
AboutView,
@ -78,6 +78,71 @@ from .views import (
admin.site.site_header = 'InvenTree Admin'
settings_urls = [
path('i18n/', include('django.conf.urls.i18n')),
path('appearance/', AppearanceSelectView.as_view(), name='settings-appearance'),
# Catch any other urls
path(
'',
SettingsView.as_view(template_name='InvenTree/settings/settings.html'),
name='settings',
),
]
notifications_urls = [
# Catch any other urls
path('', NotificationsView.as_view(), name='notifications')
]
classic_frontendpatterns = [
# Apps
#
path('build/', include(build_urls)),
path('common/', include(common_urls)),
path('company/', include(company_urls)),
path('order/', include(order_urls)),
path('manufacturer-part/', include(manufacturer_part_urls)),
path('part/', include(part_urls)),
path('stock/', include(stock_urls)),
path('supplier-part/', include(supplier_part_urls)),
path('edit-user/', EditUserView.as_view(), name='edit-user'),
path('set-password/', SetPasswordView.as_view(), name='set-password'),
path('index/', IndexView.as_view(), name='index'),
path('notifications/', include(notifications_urls)),
path('search/', SearchView.as_view(), name='search'),
path('settings/', include(settings_urls)),
path('about/', AboutView.as_view(), name='about'),
path('stats/', DatabaseStatsView.as_view(), name='stats'),
# DB user sessions
path(
'accounts/sessions/other/delete/',
view=CustomSessionDeleteOtherView.as_view(),
name='session_delete_other',
),
re_path(
r'^accounts/sessions/(?P<pk>\w+)/delete/$',
view=CustomSessionDeleteView.as_view(),
name='session_delete',
),
# Single Sign On / allauth
# overrides of urlpatterns
path('accounts/email/', CustomEmailView.as_view(), name='account_email'),
path(
'accounts/social/connections/',
CustomConnectionsView.as_view(),
name='socialaccount_connections',
),
re_path(
r'^accounts/password/reset/key/(?P<uidb36>[0-9A-Za-z]+)-(?P<key>.+)/$',
CustomPasswordResetFromKeyView.as_view(),
name='account_reset_password_from_key',
),
# Override login page
path('accounts/login/', CustomLoginView.as_view(), name='account_login'),
path('accounts/', include('allauth_2fa.urls')), # MFA support
path('accounts/', include('allauth.urls')), # included urlpatterns
]
apipatterns = [
# Global search
@ -167,7 +232,7 @@ apipatterns = [
path('', EmailListView.as_view(), name='email-list'),
]),
),
path('social/', include(social_auth_urlpatterns)),
path('social/', include(get_provider_urls())),
path(
'social/', SocialAccountListView.as_view(), name='social_account_list'
),
@ -197,21 +262,6 @@ apipatterns = [
re_path(r'^.*$', NotFoundView.as_view(), name='api-404'),
]
settings_urls = [
path('i18n/', include('django.conf.urls.i18n')),
path('appearance/', AppearanceSelectView.as_view(), name='settings-appearance'),
# Catch any other urls
path(
'',
SettingsView.as_view(template_name='InvenTree/settings/settings.html'),
name='settings',
),
]
notifications_urls = [
# Catch any other urls
path('', NotificationsView.as_view(), name='notifications')
]
# These javascript files are served "dynamically" - i.e. rendered on demand
dynamic_javascript_urls = [
@ -400,54 +450,6 @@ if settings.ENABLE_CLASSIC_FRONTEND:
re_path(r'^js/i18n/', include(translated_javascript_urls)),
]
classic_frontendpatterns = [
# Apps
#
path('build/', include(build_urls)),
path('common/', include(common_urls)),
path('company/', include(company_urls)),
path('order/', include(order_urls)),
path('manufacturer-part/', include(manufacturer_part_urls)),
path('part/', include(part_urls)),
path('stock/', include(stock_urls)),
path('supplier-part/', include(supplier_part_urls)),
path('edit-user/', EditUserView.as_view(), name='edit-user'),
path('set-password/', SetPasswordView.as_view(), name='set-password'),
path('index/', IndexView.as_view(), name='index'),
path('notifications/', include(notifications_urls)),
path('search/', SearchView.as_view(), name='search'),
path('settings/', include(settings_urls)),
path('about/', AboutView.as_view(), name='about'),
path('stats/', DatabaseStatsView.as_view(), name='stats'),
# DB user sessions
path(
'accounts/sessions/other/delete/',
view=CustomSessionDeleteOtherView.as_view(),
name='session_delete_other',
),
re_path(
r'^accounts/sessions/(?P<pk>\w+)/delete/$',
view=CustomSessionDeleteView.as_view(),
name='session_delete',
),
# Single Sign On / allauth
# overrides of urlpatterns
path('accounts/email/', CustomEmailView.as_view(), name='account_email'),
path(
'accounts/social/connections/',
CustomConnectionsView.as_view(),
name='socialaccount_connections',
),
re_path(
r'^accounts/password/reset/key/(?P<uidb36>[0-9A-Za-z]+)-(?P<key>.+)/$',
CustomPasswordResetFromKeyView.as_view(),
name='account_reset_password_from_key',
),
# Override login page
path('accounts/login/', CustomLoginView.as_view(), name='account_login'),
path('accounts/', include('allauth_2fa.urls')), # MFA support
path('accounts/', include('allauth.urls')), # included urlpatterns
]
urlpatterns = []

View File

@ -1,4 +1,4 @@
import { Button } from '@mantine/core';
import { Button, Tooltip } from '@mantine/core';
import {
IconBrandAzure,
IconBrandBitbucket,
@ -14,6 +14,7 @@ import {
IconLogin
} from '@tabler/icons-react';
import { t } from '@lingui/macro';
import { api } from '../../App';
import { ApiEndpoints } from '../../enums/ApiEndpoints';
import { apiUrl } from '../../states/ApiState';
@ -49,14 +50,23 @@ export function SsoButton({ provider }: Readonly<{ provider: Provider }>) {
}
return (
<Tooltip
label={
provider.login
? t`You will be redirected to the provider for further actions.`
: t`This provider is not full set up.`
}
>
<Button
leftSection={getBrandIcon(provider)}
radius='xl'
component='a'
onClick={login}
disabled={!provider.login}
>
{provider.display_name}{' '}
{provider.display_name}
</Button>
</Tooltip>
);
}
function getBrandIcon(provider: Provider) {