예제 #1
0
def _get_outpost_override_ip(request: HttpRequest) -> Optional[str]:
    """Get the actual remote IP when set by an outpost. Only
    allowed when the request is authenticated, by a user with USER_ATTRIBUTE_CAN_OVERRIDE_IP set
    to outpost"""
    from authentik.core.models import USER_ATTRIBUTE_CAN_OVERRIDE_IP, Token, TokenIntents

    if OUTPOST_REMOTE_IP_HEADER not in request.META or OUTPOST_TOKEN_HEADER not in request.META:
        return None
    fake_ip = request.META[OUTPOST_REMOTE_IP_HEADER]
    tokens = Token.filter_not_expired(
        key=request.META.get(OUTPOST_TOKEN_HEADER),
        intent=TokenIntents.INTENT_API)
    if not tokens.exists():
        LOGGER.warning("Attempted remote-ip override without token",
                       fake_ip=fake_ip)
        return None
    user = tokens.first().user
    if not user.group_attributes().get(USER_ATTRIBUTE_CAN_OVERRIDE_IP, False):
        LOGGER.warning(
            "Remote-IP override: user doesn't have permission",
            user=user,
            fake_ip=fake_ip,
        )
        return None
    # Update sentry scope to include correct IP
    user = Hub.current.scope._user
    if not user:
        user = {}
    user["ip_address"] = fake_ip
    Hub.current.scope.set_user(user)
    return fake_ip
예제 #2
0
def token_from_header(raw_header: bytes) -> Optional[Token]:
    """raw_header in the Format of `Bearer dGVzdDp0ZXN0`"""
    auth_credentials = raw_header.decode()
    if auth_credentials == "":
        return None
    auth_type, auth_credentials = auth_credentials.split()
    if auth_type.lower() not in ["basic", "bearer"]:
        LOGGER.debug("Unsupported authentication type, denying", type=auth_type.lower())
        raise AuthenticationFailed("Unsupported authentication type")
    password = auth_credentials
    if auth_type.lower() == "basic":
        try:
            auth_credentials = b64decode(auth_credentials.encode()).decode()
        except (UnicodeDecodeError, Error):
            raise AuthenticationFailed("Malformed header")
        # Accept credentials with username and without
        if ":" in auth_credentials:
            _, password = auth_credentials.split(":")
        else:
            password = auth_credentials
    if password == "":  # nosec
        raise AuthenticationFailed("Malformed header")
    tokens = Token.filter_not_expired(key=password, intent=TokenIntents.INTENT_API)
    if not tokens.exists():
        raise AuthenticationFailed("Token invalid/expired")
    return tokens.first()
예제 #3
0
 def token(self) -> Token:
     """Get/create token for auto-generated user"""
     managed = f"goauthentik.io/outpost/{self.token_identifier}"
     tokens = Token.filter_not_expired(
         identifier=self.token_identifier,
         intent=TokenIntents.INTENT_API,
         managed=managed,
     )
     if tokens.exists():
         return tokens.first()
     try:
         return Token.objects.create(
             user=self.user,
             identifier=self.token_identifier,
             intent=TokenIntents.INTENT_API,
             description=
             f"Autogenerated by authentik for Outpost {self.name}",
             expiring=False,
             managed=managed,
         )
     except IntegrityError:
         # Integrity error happens mostly when managed is re-used
         Token.objects.filter(managed=managed).delete()
         Token.objects.filter(identifier=self.token_identifier).delete()
         return self.token
예제 #4
0
def bearer_auth(raw_header: bytes) -> Optional[User]:
    """raw_header in the Format of `Bearer dGVzdDp0ZXN0`"""
    auth_credentials = raw_header.decode()
    if auth_credentials == "" or " " not in auth_credentials:
        return None
    auth_type, _, auth_credentials = auth_credentials.partition(" ")
    if auth_type.lower() not in ["basic", "bearer"]:
        LOGGER.debug("Unsupported authentication type, denying",
                     type=auth_type.lower())
        raise AuthenticationFailed("Unsupported authentication type")
    password = auth_credentials
    if auth_type.lower() == "basic":
        try:
            auth_credentials = b64decode(auth_credentials.encode()).decode()
        except (UnicodeDecodeError, Error):
            raise AuthenticationFailed("Malformed header")
        # Accept credentials with username and without
        if ":" in auth_credentials:
            _, _, password = auth_credentials.partition(":")
        else:
            password = auth_credentials
    if password == "":  # nosec
        raise AuthenticationFailed("Malformed header")
    tokens = Token.filter_not_expired(key=password,
                                      intent=TokenIntents.INTENT_API)
    if not tokens.exists():
        user = token_secret_key(password)
        if not user:
            raise AuthenticationFailed("Token invalid/expired")
        return user
    if hasattr(LOCAL, "authentik"):
        LOCAL.authentik[KEY_AUTH_VIA] = "api_token"
    return tokens.first().user
예제 #5
0
 def get(self, request: HttpRequest, key: str) -> HttpResponse:
     """Check if token exists, log user in and delete token."""
     tokens = Token.filter_not_expired(key=key,
                                       intent=TokenIntents.INTENT_RECOVERY)
     if not tokens.exists():
         raise Http404
     token = tokens.first()
     login(request, token.user, backend=BACKEND_INBUILT)
     token.delete()
     messages.warning(request, _("Used recovery-link to authenticate."))
     return redirect("authentik_core:if-user")
예제 #6
0
 def token(self) -> Token:
     """Get/create token for auto-generated user"""
     token = Token.filter_not_expired(user=self.user,
                                      intent=TokenIntents.INTENT_API)
     if token.exists():
         return token.first()
     return Token.objects.create(
         user=self.user,
         identifier=self.token_identifier,
         intent=TokenIntents.INTENT_API,
         description=f"Autogenerated by authentik for Outpost {self.name}",
         expiring=False,
         managed=f"goauthentik.io/outpost/{self.token_identifier}",
     )
예제 #7
0
 def authenticate(
     self, request: HttpRequest, username: Optional[str], password: Optional[str], **kwargs: Any
 ) -> Optional[User]:
     try:
         user = User._default_manager.get_by_natural_key(username)
     except User.DoesNotExist:
         # Run the default password hasher once to reduce the timing
         # difference between an existing and a nonexistent user (#20760).
         User().set_password(password)
         return None
     tokens = Token.filter_not_expired(
         user=user, key=password, intent=TokenIntents.INTENT_APP_PASSWORD
     )
     if not tokens.exists():
         return None
     token = tokens.first()
     self.set_method("token", request, token=token)
     return token.user
예제 #8
0
class TokenViewSet(ModelViewSet):
    """Token Viewset"""

    lookup_field = "identifier"
    queryset = Token.filter_not_expired()
    serializer_class = TokenSerializer
    search_fields = [
        "identifier",
        "intent",
        "user__username",
        "description",
    ]
    filterset_fields = [
        "identifier",
        "intent",
        "user__username",
        "description",
    ]
    ordering = ["expires"]

    def perform_create(self, serializer: TokenSerializer):
        serializer.save(user=self.request.user, intent=TokenIntents.INTENT_API)

    @permission_required("authentik_core.view_token_key")
    @swagger_auto_schema(
        responses={
            200: TokenViewSerializer(many=False),
            404: "Token not found or expired",
        }
    )
    @action(detail=True, pagination_class=None, filter_backends=[])
    # pylint: disable=unused-argument
    def view_key(self, request: Request, identifier: str) -> Response:
        """Return token key and log access"""
        token: Token = self.get_object()
        if token.is_expired:
            raise Http404
        Event.new(EventAction.SECRET_VIEW, secret=token).from_http(  # noqa # nosec
            request
        )
        return Response(TokenViewSerializer({"key": token.key}).data)
예제 #9
0
def token_from_header(raw_header: bytes) -> Optional[Token]:
    """raw_header in the Format of `Basic dGVzdDp0ZXN0`"""
    auth_credentials = raw_header.decode()
    if auth_credentials == "":
        return None
    # Legacy, accept basic auth thats fully encoded (2021.3 outposts)
    if " " not in auth_credentials:
        try:
            plain = b64decode(auth_credentials.encode()).decode()
            auth_type, body = plain.split()
            auth_credentials = f"{auth_type} {b64encode(body.encode()).decode()}"
        except (UnicodeDecodeError, Error):
            return None
    auth_type, auth_credentials = auth_credentials.split()
    if auth_type.lower() not in ["basic", "bearer"]:
        LOGGER.debug("Unsupported authentication type, denying",
                     type=auth_type.lower())
        return None
    password = auth_credentials
    if auth_type.lower() == "basic":
        try:
            auth_credentials = b64decode(auth_credentials.encode()).decode()
        except (UnicodeDecodeError, Error):
            return None
        # Accept credentials with username and without
        if ":" in auth_credentials:
            _, password = auth_credentials.split(":")
        else:
            password = auth_credentials
    if password == "":  # nosec
        return None
    tokens = Token.filter_not_expired(key=password,
                                      intent=TokenIntents.INTENT_API)
    if not tokens.exists():
        LOGGER.debug("Token not found")
        return None
    return tokens.first()