forked from superdesk/superdesk-core
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[SDESK-7372] New Auth mechanism (superdesk#2729)
* Move types into separate module * Base auth system * Basic auth rules * Superdesk auth * fix validation: Support ResourceModel inheritence * Expose sync interface in resource service * Allow custom strings in field validators * fix tests: disable auth * remove print statements * fix get_endpoint_for_current_request with no request * impove processing of intrinsic_auth_rule * move more request processing to core * fix cyclic import * change Spawn Test Instance job name * fix response handling tuples * fix cursor types * fix type issue with elastic cursor
- Loading branch information
1 parent
c3f2923
commit 904fe88
Showing
29 changed files
with
1,060 additions
and
509 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
from typing import Any | ||
|
||
from superdesk.core.types import Request | ||
from superdesk.errors import SuperdeskApiError | ||
|
||
|
||
async def login_required_auth_rule(request: Request) -> None: | ||
if request.user is None: | ||
raise SuperdeskApiError.unauthorizedError() | ||
|
||
return None | ||
|
||
|
||
async def endpoint_intrinsic_auth_rule(request: Request) -> Any | None: | ||
methods = ["authorize", f"authorize_{request.method.lower()}"] | ||
for method_name in methods: | ||
intrinsic_auth = getattr(request.endpoint, method_name, None) | ||
|
||
if intrinsic_auth: | ||
response = await intrinsic_auth(request) | ||
if response is not None: | ||
return response | ||
|
||
return None |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
from typing import Any, cast | ||
from datetime import timedelta | ||
|
||
from superdesk.core import get_app_config | ||
from superdesk.core.types import Request | ||
from superdesk import get_resource_service | ||
from superdesk.errors import SuperdeskApiError | ||
from superdesk.resource_fields import LAST_UPDATED, ID_FIELD | ||
from superdesk.utc import utcnow | ||
|
||
from .user_auth import UserAuthProtocol | ||
|
||
|
||
class TokenAuthorization(UserAuthProtocol): | ||
async def authenticate(self, request: Request): | ||
token = request.get_header("Authorization") | ||
new_session = True | ||
if token: | ||
token = token.strip() | ||
if token.lower().startswith(("token", "bearer")): | ||
token = token.split(" ")[1] if " " in token else "" | ||
else: | ||
token = request.storage.session.get("session_token") | ||
new_session = False | ||
|
||
if not token: | ||
await self.stop_session(request) | ||
raise SuperdeskApiError.unauthorizedError() | ||
|
||
# Check provided token is valid | ||
auth_service = get_resource_service("auth") | ||
auth_token = auth_service.find_one(token=token, req=None) | ||
|
||
if not auth_token: | ||
await self.stop_session(request) | ||
raise SuperdeskApiError.unauthorizedError() | ||
|
||
user_service = get_resource_service("users") | ||
user_id = str(auth_token["user"]) | ||
user = user_service.find_one(req=None, _id=user_id) | ||
|
||
if not user: | ||
await self.stop_session(request) | ||
raise SuperdeskApiError.unauthorizedError() | ||
|
||
if new_session: | ||
await self.start_session(request, user, auth_token=auth_token) | ||
else: | ||
await self.continue_session(request, user) | ||
|
||
async def start_session(self, request: Request, user: dict[str, Any], **kwargs) -> None: | ||
auth_token: str | None = kwargs.pop("auth_token", None) | ||
if not auth_token: | ||
await self.stop_session(request) | ||
raise SuperdeskApiError.unauthorizedError() | ||
|
||
request.storage.session.set("session_token", auth_token) | ||
await super().start_session(request, user, **kwargs) | ||
|
||
async def continue_session(self, request: Request, user: dict[str, Any], **kwargs) -> None: | ||
auth_token = request.storage.session.get("session_token") | ||
|
||
if not auth_token: | ||
await self.stop_session(request) | ||
raise SuperdeskApiError.unauthorizedError() | ||
|
||
user_service = get_resource_service("users") | ||
request.storage.request.set("user", user) | ||
request.storage.request.set("role", user_service.get_role(user)) | ||
request.storage.request.set("auth", auth_token) | ||
request.storage.request.set("auth_value", auth_token["user"]) | ||
|
||
if request.method in ("POST", "PUT", "PATCH") or (request.method == "GET" and not request.get_url_arg("auto")): | ||
now = utcnow() | ||
auth_updated = False | ||
session_update_seconds = cast(int, get_app_config("SESSION_UPDATE_SECONDS", 30)) | ||
if auth_token[LAST_UPDATED] + timedelta(seconds=session_update_seconds) < now: | ||
auth_service = get_resource_service("auth") | ||
auth_service.update_session({LAST_UPDATED: now}) | ||
auth_updated = True | ||
if auth_updated or not request.storage.request.get("last_activity_at"): | ||
user_service.system_update(user[ID_FIELD], {"last_activity_at": now, "_updated": now}, user) | ||
|
||
await super().continue_session(request, user, **kwargs) | ||
|
||
def get_current_user(self, request: Request) -> dict[str, Any] | None: | ||
user = request.storage.request.get("user") | ||
return user |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
from typing import Any, cast | ||
|
||
from superdesk.errors import SuperdeskApiError | ||
from superdesk.core.types import Request, AuthRule | ||
|
||
|
||
class UserAuthProtocol: | ||
async def authenticate(self, request: Request) -> Any | None: | ||
raise SuperdeskApiError.unauthorizedError() | ||
|
||
async def authorize(self, request: Request) -> Any | None: | ||
endpoint_rules = request.endpoint.get_auth_rules() | ||
if endpoint_rules is False: | ||
# This is a public facing endpoint | ||
# Meaning Authentication & Authorization is disabled | ||
return None | ||
elif isinstance(endpoint_rules, dict): | ||
endpoint_rules = cast(list[AuthRule], endpoint_rules.get(request.method) or []) | ||
|
||
from .rules import login_required_auth_rule, endpoint_intrinsic_auth_rule | ||
|
||
default_rules: list[AuthRule] = [ | ||
login_required_auth_rule, | ||
endpoint_intrinsic_auth_rule, | ||
] | ||
|
||
for rule in default_rules + (endpoint_rules or []): | ||
response = await rule(request) | ||
if response is not None: | ||
return response | ||
|
||
return None | ||
|
||
async def start_session(self, request: Request, user: Any, **kwargs) -> None: | ||
await self.continue_session(request, user, **kwargs) | ||
|
||
async def continue_session(self, request: Request, user: Any, **kwargs) -> None: | ||
request.user = user | ||
|
||
async def stop_session(self, request: Request) -> None: | ||
pass | ||
|
||
def get_current_user(self, request: Request) -> Any | None: | ||
raise NotImplementedError() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.