sources/oauth: split up single large "core" views
This commit is contained in:
parent
2d2b2d08f4
commit
c70310730a
|
@ -1,10 +1,10 @@
|
|||
"""AzureAD OAuth2 Views"""
|
||||
import uuid
|
||||
from typing import Any, Dict
|
||||
from uuid import UUID
|
||||
|
||||
from passbook.sources.oauth.models import OAuthSource, UserOAuthSourceConnection
|
||||
from passbook.sources.oauth.types.manager import MANAGER, RequestKind
|
||||
from passbook.sources.oauth.views.core import OAuthCallback
|
||||
from passbook.sources.oauth.views.callback import OAuthCallback
|
||||
|
||||
|
||||
@MANAGER.source(kind=RequestKind.callback, name="Azure AD")
|
||||
|
@ -12,7 +12,7 @@ class AzureADOAuthCallback(OAuthCallback):
|
|||
"""AzureAD OAuth2 Callback"""
|
||||
|
||||
def get_user_id(self, source: OAuthSource, info: Dict[str, Any]) -> str:
|
||||
return str(uuid.UUID(info.get("objectId")).int)
|
||||
return str(UUID(info.get("objectId")).int)
|
||||
|
||||
def get_user_enroll_context(
|
||||
self,
|
||||
|
|
|
@ -3,7 +3,8 @@ from typing import Any, Dict
|
|||
|
||||
from passbook.sources.oauth.models import OAuthSource, UserOAuthSourceConnection
|
||||
from passbook.sources.oauth.types.manager import MANAGER, RequestKind
|
||||
from passbook.sources.oauth.views.core import OAuthCallback, OAuthRedirect
|
||||
from passbook.sources.oauth.views.callback import OAuthCallback
|
||||
from passbook.sources.oauth.views.redirect import OAuthRedirect
|
||||
|
||||
|
||||
@MANAGER.source(kind=RequestKind.redirect, name="Discord")
|
||||
|
|
|
@ -6,7 +6,8 @@ from facebook import GraphAPI
|
|||
from passbook.sources.oauth.clients import OAuth2Client
|
||||
from passbook.sources.oauth.models import OAuthSource, UserOAuthSourceConnection
|
||||
from passbook.sources.oauth.types.manager import MANAGER, RequestKind
|
||||
from passbook.sources.oauth.views.core import OAuthCallback, OAuthRedirect
|
||||
from passbook.sources.oauth.views.callback import OAuthCallback
|
||||
from passbook.sources.oauth.views.redirect import OAuthRedirect
|
||||
|
||||
|
||||
@MANAGER.source(kind=RequestKind.redirect, name="Facebook")
|
||||
|
|
|
@ -3,7 +3,7 @@ from typing import Any, Dict
|
|||
|
||||
from passbook.sources.oauth.models import OAuthSource, UserOAuthSourceConnection
|
||||
from passbook.sources.oauth.types.manager import MANAGER, RequestKind
|
||||
from passbook.sources.oauth.views.core import OAuthCallback
|
||||
from passbook.sources.oauth.views.callback import OAuthCallback
|
||||
|
||||
|
||||
@MANAGER.source(kind=RequestKind.callback, name="GitHub")
|
||||
|
|
|
@ -3,7 +3,8 @@ from typing import Any, Dict
|
|||
|
||||
from passbook.sources.oauth.models import OAuthSource, UserOAuthSourceConnection
|
||||
from passbook.sources.oauth.types.manager import MANAGER, RequestKind
|
||||
from passbook.sources.oauth.views.core import OAuthCallback, OAuthRedirect
|
||||
from passbook.sources.oauth.views.callback import OAuthCallback
|
||||
from passbook.sources.oauth.views.redirect import OAuthRedirect
|
||||
|
||||
|
||||
@MANAGER.source(kind=RequestKind.redirect, name="Google")
|
||||
|
|
|
@ -6,7 +6,8 @@ from django.utils.text import slugify
|
|||
from structlog import get_logger
|
||||
|
||||
from passbook.sources.oauth.models import OAuthSource
|
||||
from passbook.sources.oauth.views.core import OAuthCallback, OAuthRedirect
|
||||
from passbook.sources.oauth.views.callback import OAuthCallback
|
||||
from passbook.sources.oauth.views.redirect import OAuthRedirect
|
||||
|
||||
LOGGER = get_logger()
|
||||
|
||||
|
|
|
@ -3,7 +3,8 @@ from typing import Any, Dict
|
|||
|
||||
from passbook.sources.oauth.models import OAuthSource, UserOAuthSourceConnection
|
||||
from passbook.sources.oauth.types.manager import MANAGER, RequestKind
|
||||
from passbook.sources.oauth.views.core import OAuthCallback, OAuthRedirect
|
||||
from passbook.sources.oauth.views.callback import OAuthCallback
|
||||
from passbook.sources.oauth.views.redirect import OAuthRedirect
|
||||
|
||||
|
||||
@MANAGER.source(kind=RequestKind.redirect, name="OpenID Connect")
|
||||
|
|
|
@ -6,7 +6,8 @@ from requests.auth import HTTPBasicAuth
|
|||
from passbook.sources.oauth.clients import OAuth2Client
|
||||
from passbook.sources.oauth.models import OAuthSource, UserOAuthSourceConnection
|
||||
from passbook.sources.oauth.types.manager import MANAGER, RequestKind
|
||||
from passbook.sources.oauth.views.core import OAuthCallback, OAuthRedirect
|
||||
from passbook.sources.oauth.views.callback import OAuthCallback
|
||||
from passbook.sources.oauth.views.redirect import OAuthRedirect
|
||||
|
||||
|
||||
@MANAGER.source(kind=RequestKind.redirect, name="reddit")
|
||||
|
|
|
@ -2,9 +2,9 @@
|
|||
from typing import Any, Dict
|
||||
|
||||
from passbook.sources.oauth.models import OAuthSource, UserOAuthSourceConnection
|
||||
from passbook.sources.oauth.views.callback import OAuthCallback
|
||||
|
||||
# from passbook.sources.oauth.types.manager import MANAGER, RequestKind
|
||||
from passbook.sources.oauth.views.core import OAuthCallback
|
||||
|
||||
|
||||
# @MANAGER.source(kind=RequestKind.callback, name="Twitter")
|
||||
|
|
|
@ -1,29 +1,30 @@
|
|||
"""passbook oauth_client urls"""
|
||||
"""passbook OAuth source urls"""
|
||||
|
||||
from django.urls import path
|
||||
|
||||
from passbook.sources.oauth.types.manager import RequestKind
|
||||
from passbook.sources.oauth.views import core, dispatcher, user
|
||||
from passbook.sources.oauth.views.dispatcher import DispatcherView
|
||||
from passbook.sources.oauth.views.user import DisconnectView, UserSettingsView
|
||||
|
||||
urlpatterns = [
|
||||
path(
|
||||
"login/<slug:source_slug>/",
|
||||
dispatcher.DispatcherView.as_view(kind=RequestKind.redirect),
|
||||
DispatcherView.as_view(kind=RequestKind.redirect),
|
||||
name="oauth-client-login",
|
||||
),
|
||||
path(
|
||||
"callback/<slug:source_slug>/",
|
||||
dispatcher.DispatcherView.as_view(kind=RequestKind.callback),
|
||||
DispatcherView.as_view(kind=RequestKind.callback),
|
||||
name="oauth-client-callback",
|
||||
),
|
||||
path(
|
||||
"disconnect/<slug:source_slug>/",
|
||||
core.DisconnectView.as_view(),
|
||||
name="oauth-client-disconnect",
|
||||
),
|
||||
path(
|
||||
"user/<slug:source_slug>/",
|
||||
user.UserSettingsView.as_view(),
|
||||
UserSettingsView.as_view(),
|
||||
name="oauth-client-user",
|
||||
),
|
||||
path(
|
||||
"user/<slug:source_slug>/disconnect/",
|
||||
DisconnectView.as_view(),
|
||||
name="oauth-client-disconnect",
|
||||
),
|
||||
]
|
||||
|
|
19
passbook/sources/oauth/views/base.py
Normal file
19
passbook/sources/oauth/views/base.py
Normal file
|
@ -0,0 +1,19 @@
|
|||
"""OAuth Base views"""
|
||||
from typing import Callable, Optional
|
||||
|
||||
from passbook.sources.oauth.clients import BaseOAuthClient, get_client
|
||||
from passbook.sources.oauth.models import OAuthSource
|
||||
|
||||
|
||||
# pylint: disable=too-few-public-methods
|
||||
class OAuthClientMixin:
|
||||
"Mixin for getting OAuth client for a source."
|
||||
|
||||
client_class: Optional[Callable] = None
|
||||
|
||||
def get_client(self, source: OAuthSource) -> BaseOAuthClient:
|
||||
"Get instance of the OAuth client for this source."
|
||||
if self.client_class is not None:
|
||||
# pylint: disable=not-callable
|
||||
return self.client_class(source)
|
||||
return get_client(source)
|
|
@ -1,11 +1,10 @@
|
|||
"""Core OAauth Views"""
|
||||
"""OAuth Callback Views"""
|
||||
from typing import Any, Callable, Dict, Optional
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.http import Http404, HttpRequest, HttpResponse
|
||||
from django.shortcuts import get_object_or_404, redirect, render
|
||||
from django.shortcuts import redirect
|
||||
from django.urls import reverse
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.views.generic import RedirectView, View
|
||||
|
@ -25,62 +24,13 @@ from passbook.policies.utils import delete_none_keys
|
|||
from passbook.sources.oauth.auth import AuthorizedServiceBackend
|
||||
from passbook.sources.oauth.clients import BaseOAuthClient, get_client
|
||||
from passbook.sources.oauth.models import OAuthSource, UserOAuthSourceConnection
|
||||
from passbook.sources.oauth.views.base import OAuthClientMixin
|
||||
from passbook.stages.password.stage import PLAN_CONTEXT_AUTHENTICATION_BACKEND
|
||||
from passbook.stages.prompt.stage import PLAN_CONTEXT_PROMPT
|
||||
|
||||
LOGGER = get_logger()
|
||||
|
||||
|
||||
# pylint: disable=too-few-public-methods
|
||||
class OAuthClientMixin:
|
||||
"Mixin for getting OAuth client for a source."
|
||||
|
||||
client_class: Optional[Callable] = None
|
||||
|
||||
def get_client(self, source: OAuthSource) -> BaseOAuthClient:
|
||||
"Get instance of the OAuth client for this source."
|
||||
if self.client_class is not None:
|
||||
# pylint: disable=not-callable
|
||||
return self.client_class(source)
|
||||
return get_client(source)
|
||||
|
||||
|
||||
class OAuthRedirect(OAuthClientMixin, RedirectView):
|
||||
"Redirect user to OAuth source to enable access."
|
||||
|
||||
permanent = False
|
||||
params = None
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
def get_additional_parameters(self, source: OAuthSource) -> Dict[str, Any]:
|
||||
"Return additional redirect parameters for this source."
|
||||
return self.params or {}
|
||||
|
||||
def get_callback_url(self, source: OAuthSource) -> str:
|
||||
"Return the callback url for this source."
|
||||
return reverse(
|
||||
"passbook_sources_oauth:oauth-client-callback",
|
||||
kwargs={"source_slug": source.slug},
|
||||
)
|
||||
|
||||
def get_redirect_url(self, **kwargs) -> str:
|
||||
"Build redirect url for a given source."
|
||||
slug = kwargs.get("source_slug", "")
|
||||
try:
|
||||
source = OAuthSource.objects.get(slug=slug)
|
||||
except OAuthSource.DoesNotExist:
|
||||
raise Http404(f"Unknown OAuth source '{slug}'.")
|
||||
else:
|
||||
if not source.enabled:
|
||||
raise Http404(f"source {slug} is not enabled.")
|
||||
client = self.get_client(source)
|
||||
callback = self.get_callback_url(source)
|
||||
params = self.get_additional_parameters(source)
|
||||
return client.get_redirect_url(
|
||||
self.request, callback=callback, parameters=params
|
||||
)
|
||||
|
||||
|
||||
class OAuthCallback(OAuthClientMixin, View):
|
||||
"Base OAuth callback view."
|
||||
|
||||
|
@ -258,46 +208,3 @@ class OAuthCallback(OAuthClientMixin, View):
|
|||
)
|
||||
}
|
||||
return self.handle_login_flow(source.enrollment_flow, **context)
|
||||
|
||||
|
||||
class DisconnectView(LoginRequiredMixin, View):
|
||||
"""Delete connection with source"""
|
||||
|
||||
source = None
|
||||
aas = None
|
||||
|
||||
def dispatch(self, request, source_slug):
|
||||
self.source = get_object_or_404(OAuthSource, slug=source_slug)
|
||||
self.aas = get_object_or_404(
|
||||
UserOAuthSourceConnection, source=self.source, user=request.user
|
||||
)
|
||||
return super().dispatch(request, source_slug)
|
||||
|
||||
def post(self, request, source_slug):
|
||||
"""Delete connection object"""
|
||||
if "confirmdelete" in request.POST:
|
||||
# User confirmed deletion
|
||||
self.aas.delete()
|
||||
messages.success(request, _("Connection successfully deleted"))
|
||||
return redirect(
|
||||
reverse(
|
||||
"passbook_sources_oauth:oauth-client-user",
|
||||
kwargs={"source_slug": self.source.slug},
|
||||
)
|
||||
)
|
||||
return self.get(request, source_slug)
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
def get(self, request, source_slug):
|
||||
"""Show delete form"""
|
||||
return render(
|
||||
request,
|
||||
"generic/delete.html",
|
||||
{
|
||||
"object": self.source,
|
||||
"delete_url": reverse(
|
||||
"passbook_sources_oauth:oauth-client-disconnect",
|
||||
kwargs={"source_slug": self.source.slug},
|
||||
),
|
||||
},
|
||||
)
|
67
passbook/sources/oauth/views/redirect.py
Normal file
67
passbook/sources/oauth/views/redirect.py
Normal file
|
@ -0,0 +1,67 @@
|
|||
"""OAuth Redirect Views"""
|
||||
from typing import Any, Callable, Dict, Optional
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib import messages
|
||||
from django.http import Http404, HttpRequest, HttpResponse
|
||||
from django.shortcuts import redirect
|
||||
from django.urls import reverse
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.views.generic import RedirectView, View
|
||||
from structlog import get_logger
|
||||
|
||||
from passbook.audit.models import Event, EventAction
|
||||
from passbook.core.models import User
|
||||
from passbook.flows.models import Flow
|
||||
from passbook.flows.planner import (
|
||||
PLAN_CONTEXT_PENDING_USER,
|
||||
PLAN_CONTEXT_SSO,
|
||||
FlowPlanner,
|
||||
)
|
||||
from passbook.flows.views import SESSION_KEY_PLAN
|
||||
from passbook.lib.utils.urls import redirect_with_qs
|
||||
from passbook.policies.utils import delete_none_keys
|
||||
from passbook.sources.oauth.auth import AuthorizedServiceBackend
|
||||
from passbook.sources.oauth.clients import BaseOAuthClient, get_client
|
||||
from passbook.sources.oauth.models import OAuthSource, UserOAuthSourceConnection
|
||||
from passbook.sources.oauth.views.base import OAuthClientMixin
|
||||
from passbook.stages.password.stage import PLAN_CONTEXT_AUTHENTICATION_BACKEND
|
||||
from passbook.stages.prompt.stage import PLAN_CONTEXT_PROMPT
|
||||
|
||||
LOGGER = get_logger()
|
||||
|
||||
|
||||
class OAuthRedirect(OAuthClientMixin, RedirectView):
|
||||
"Redirect user to OAuth source to enable access."
|
||||
|
||||
permanent = False
|
||||
params = None
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
def get_additional_parameters(self, source: OAuthSource) -> Dict[str, Any]:
|
||||
"Return additional redirect parameters for this source."
|
||||
return self.params or {}
|
||||
|
||||
def get_callback_url(self, source: OAuthSource) -> str:
|
||||
"Return the callback url for this source."
|
||||
return reverse(
|
||||
"passbook_sources_oauth:oauth-client-callback",
|
||||
kwargs={"source_slug": source.slug},
|
||||
)
|
||||
|
||||
def get_redirect_url(self, **kwargs) -> str:
|
||||
"Build redirect url for a given source."
|
||||
slug = kwargs.get("source_slug", "")
|
||||
try:
|
||||
source = OAuthSource.objects.get(slug=slug)
|
||||
except OAuthSource.DoesNotExist:
|
||||
raise Http404(f"Unknown OAuth source '{slug}'.")
|
||||
else:
|
||||
if not source.enabled:
|
||||
raise Http404(f"source {slug} is not enabled.")
|
||||
client = self.get_client(source)
|
||||
callback = self.get_callback_url(source)
|
||||
params = self.get_additional_parameters(source)
|
||||
return client.get_redirect_url(
|
||||
self.request, callback=callback, parameters=params
|
||||
)
|
|
@ -1,7 +1,13 @@
|
|||
"""passbook oauth_client user views"""
|
||||
from typing import Optional
|
||||
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.views.generic import TemplateView
|
||||
from django.http import HttpRequest, HttpResponse
|
||||
from django.shortcuts import get_object_or_404, redirect, render
|
||||
from django.urls import reverse
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.views.generic import TemplateView, View
|
||||
|
||||
from passbook.sources.oauth.models import OAuthSource, UserOAuthSourceConnection
|
||||
|
||||
|
@ -19,3 +25,46 @@ class UserSettingsView(LoginRequiredMixin, TemplateView):
|
|||
kwargs["source"] = source
|
||||
kwargs["connections"] = connections
|
||||
return super().get_context_data(**kwargs)
|
||||
|
||||
|
||||
class DisconnectView(LoginRequiredMixin, View):
|
||||
"""Delete connection with source"""
|
||||
|
||||
source: Optional[OAuthSource] = None
|
||||
aas: Optional[UserOAuthSourceConnection] = None
|
||||
|
||||
def dispatch(self, request: HttpRequest, source_slug: str) -> HttpResponse:
|
||||
self.source = get_object_or_404(OAuthSource, slug=source_slug)
|
||||
self.aas = get_object_or_404(
|
||||
UserOAuthSourceConnection, source=self.source, user=request.user
|
||||
)
|
||||
return super().dispatch(request, source_slug)
|
||||
|
||||
def post(self, request: HttpRequest, source_slug: str) -> HttpResponse:
|
||||
"""Delete connection object"""
|
||||
if "confirmdelete" in request.POST:
|
||||
# User confirmed deletion
|
||||
self.aas.delete()
|
||||
messages.success(request, _("Connection successfully deleted"))
|
||||
return redirect(
|
||||
reverse(
|
||||
"passbook_sources_oauth:oauth-client-user",
|
||||
kwargs={"source_slug": self.source.slug},
|
||||
)
|
||||
)
|
||||
return self.get(request, source_slug)
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
def get(self, request: HttpRequest, source_slug: str) -> HttpResponse:
|
||||
"""Show delete form"""
|
||||
return render(
|
||||
request,
|
||||
"generic/delete.html",
|
||||
{
|
||||
"object": self.source,
|
||||
"delete_url": reverse(
|
||||
"passbook_sources_oauth:oauth-client-disconnect",
|
||||
kwargs={"source_slug": self.source.slug},
|
||||
),
|
||||
},
|
||||
)
|
||||
|
|
Reference in a new issue