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
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()
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
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
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")
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}", )
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
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)
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()