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:
parent
f6124fc4a1
commit
66a769edca
@ -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,9 +87,13 @@ 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():
|
||||
try:
|
||||
@ -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):
|
||||
|
@ -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 = []
|
||||
|
||||
|
@ -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) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user