def to_stage_response(request: HttpRequest, source: HttpResponse) -> HttpResponse: """Convert normal HttpResponse into JSON Response""" if isinstance(source, HttpResponseRedirect) or source.status_code == 302: redirect_url = source["Location"] # Redirects to the same URL usually indicate an Error within a form if request.get_full_path() == redirect_url: return source LOGGER.debug( "converting to redirect challenge", to=str(redirect_url), current=request.path, ) return HttpChallengeResponse( RedirectChallenge({ "type": ChallengeTypes.REDIRECT, "to": str(redirect_url), })) if isinstance(source, TemplateResponse): return HttpChallengeResponse( ShellChallenge({ "type": ChallengeTypes.SHELL, "body": source.render().content.decode("utf-8"), })) # Check for actual HttpResponse (without isinstance as we don't want to check inheritance) if source.__class__ == HttpResponse: return HttpChallengeResponse( ShellChallenge({ "type": ChallengeTypes.SHELL, "body": source.content.decode("utf-8"), })) return source
def login_challenge(self, source: OAuthSource, request: HttpRequest) -> Challenge: """Allow types to return custom challenges""" return RedirectChallenge( instance={ "type": ChallengeTypes.REDIRECT.value, "to": reverse( "authentik_sources_oauth:oauth-client-login", kwargs={"source_slug": source.slug}, ), } )
def ui_login_button(self, request: HttpRequest) -> UILoginButton: return UILoginButton( challenge=RedirectChallenge( instance={ "type": ChallengeTypes.REDIRECT.value, "to": reverse( "authentik_sources_saml:login", kwargs={"source_slug": self.slug}, ), }), name=self.name, )
def ui_login_button(self) -> UILoginButton: return UILoginButton( challenge=RedirectChallenge( instance={ "type": ChallengeTypes.REDIRECT.value, "to": reverse( "authentik_sources_oauth:oauth-client-login", kwargs={"source_slug": self.slug}, ), } ), icon_url=static(f"authentik/sources/{self.provider_type}.svg"), name=self.name, )
class PlexSourceViewSet(ModelViewSet): """Plex source Viewset""" queryset = PlexSource.objects.all() serializer_class = PlexSourceSerializer lookup_field = "slug" @permission_required(None) @swagger_auto_schema( request_body=PlexTokenRedeemSerializer(), responses={ 200: RedirectChallenge(), 400: "Token not found", 403: "Access denied", }, manual_parameters=[ openapi.Parameter( name="slug", in_=openapi.IN_QUERY, type=openapi.TYPE_STRING, ) ], ) @action( methods=["POST"], detail=False, pagination_class=None, filter_backends=[], permission_classes=[AllowAny], ) def redeem_token(self, request: Request) -> Response: """Redeem a plex token, check it's access to resources against what's allowed for the source, and redirect to an authentication/enrollment flow.""" source: PlexSource = get_object_or_404(PlexSource, slug=request.query_params.get( "slug", "")) plex_token = request.data.get("plex_token", None) if not plex_token: raise ValidationError("No plex token given") auth_api = PlexAuth(source, plex_token) user_info, identifier = auth_api.get_user_info() # Check friendship first, then check server overlay friends_allowed = False owner_id = None if source.allow_friends: owner_api = PlexAuth(source, source.plex_token) owner_id = owner_api.get_user_info owner_friends = owner_api.get_friends() for friend in owner_friends: if int(friend.get("id", "0")) == int(identifier): friends_allowed = True LOGGER.info( "allowing user for plex because of friend", user=user_info["username"], ) servers_allowed = auth_api.check_server_overlap() owner_allowed = owner_id == identifier if any([friends_allowed, servers_allowed, owner_allowed]): sfm = PlexSourceFlowManager( source=source, request=request, identifier=str(identifier), enroll_info=user_info, ) return to_stage_response(request, sfm.get_flow(plex_token=plex_token)) LOGGER.warning( "Denying plex auth because no server overlay and no friends and not owner", user=user_info["username"], ) raise PermissionDenied("Access denied.")
from authentik.flows.stage import PLAN_CONTEXT_PENDING_USER_IDENTIFIER, ChallengeStageView from authentik.flows.views.executor import SESSION_KEY_APPLICATION_PRE from authentik.sources.oauth.types.apple import AppleLoginChallenge from authentik.sources.plex.models import PlexAuthenticationChallenge from authentik.stages.identification.models import IdentificationStage from authentik.stages.identification.signals import identification_failed from authentik.stages.password.stage import authenticate LOGGER = get_logger() @extend_schema_field( PolymorphicProxySerializer( component_name="LoginChallengeTypes", serializers={ RedirectChallenge().fields["component"].default: RedirectChallenge, PlexAuthenticationChallenge().fields["component"].default: PlexAuthenticationChallenge, AppleLoginChallenge().fields["component"].default: AppleLoginChallenge, }, resource_type_field_name="component", )) class ChallengeDictWrapper(DictField): """Wrapper around DictField that annotates itself as challenge proxy""" class LoginSourceSerializer(PassiveSerializer): """Serializer for Login buttons of sources""" name = CharField()
class PlexSourceViewSet(UsedByMixin, ModelViewSet): """Plex source Viewset""" queryset = PlexSource.objects.all() serializer_class = PlexSourceSerializer lookup_field = "slug" filterset_fields = [ "name", "slug", "enabled", "authentication_flow", "enrollment_flow", "policy_engine_mode", "user_matching_mode", "client_id", "allow_friends", ] ordering = ["name"] @permission_required(None) @extend_schema( request=PlexTokenRedeemSerializer(), responses={ 200: RedirectChallenge(), 400: OpenApiResponse(description="Token not found"), 403: OpenApiResponse(description="Access denied"), }, parameters=[ OpenApiParameter( name="slug", location=OpenApiParameter.QUERY, type=OpenApiTypes.STR, ) ], ) @action( methods=["POST"], detail=False, pagination_class=None, filter_backends=[], permission_classes=[AllowAny], ) def redeem_token(self, request: Request) -> Response: """Redeem a plex token, check it's access to resources against what's allowed for the source, and redirect to an authentication/enrollment flow.""" source: PlexSource = get_object_or_404(PlexSource, slug=request.query_params.get( "slug", "")) plex_token = request.data.get("plex_token", None) if not plex_token: raise ValidationError("No plex token given") auth_api = PlexAuth(source, plex_token) user_info, identifier = auth_api.get_user_info() # Check friendship first, then check server overlay friends_allowed = False if source.allow_friends: owner_api = PlexAuth(source, source.plex_token) friends_allowed = owner_api.check_friends_overlap(identifier) servers_allowed = auth_api.check_server_overlap() if any([friends_allowed, servers_allowed]): sfm = PlexSourceFlowManager( source=source, request=request, identifier=str(identifier), enroll_info=user_info, ) return to_stage_response(request, sfm.get_flow(plex_token=plex_token)) LOGGER.warning( "Denying plex auth because no server overlay and no friends and not owner", user=user_info["username"], ) raise PermissionDenied("Access denied.") @extend_schema( request=PlexTokenRedeemSerializer(), responses={ 204: OpenApiResponse(), 400: OpenApiResponse(description="Token not found"), 403: OpenApiResponse(description="Access denied"), }, parameters=[ OpenApiParameter( name="slug", location=OpenApiParameter.QUERY, type=OpenApiTypes.STR, ) ], ) @action( methods=["POST"], detail=False, pagination_class=None, filter_backends=[], permission_classes=[IsAuthenticated], ) def redeem_token_authenticated(self, request: Request) -> Response: """Redeem a plex token for an authenticated user, creating a connection""" source: PlexSource = get_object_or_404(PlexSource, slug=request.query_params.get( "slug", "")) plex_token = request.data.get("plex_token", None) if not plex_token: raise ValidationError("No plex token given") auth_api = PlexAuth(source, plex_token) user_info, identifier = auth_api.get_user_info() # Check friendship first, then check server overlay friends_allowed = False if source.allow_friends: owner_api = PlexAuth(source, source.plex_token) friends_allowed = owner_api.check_friends_overlap(identifier) servers_allowed = auth_api.check_server_overlap() if any([friends_allowed, servers_allowed]): PlexSourceConnection.objects.create( plex_token=plex_token, user=request.user, identifier=identifier, source=source, ) return Response(status=204) LOGGER.warning( "Denying plex connection because no server overlay and no friends and not owner", user=user_info["username"], friends_allowed=friends_allowed, servers_allowed=servers_allowed, ) raise PermissionDenied("Access denied.")