providers/oauth2: pass scope and other parameters to access policy request context
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> #2641
This commit is contained in:
parent
71c6313c46
commit
4be238018b
|
@ -14,7 +14,7 @@ from authentik.flows.views.executor import SESSION_KEY_APPLICATION_PRE, SESSION_
|
||||||
from authentik.lib.sentry import SentryIgnoredException
|
from authentik.lib.sentry import SentryIgnoredException
|
||||||
from authentik.policies.denied import AccessDeniedResponse
|
from authentik.policies.denied import AccessDeniedResponse
|
||||||
from authentik.policies.engine import PolicyEngine
|
from authentik.policies.engine import PolicyEngine
|
||||||
from authentik.policies.types import PolicyResult
|
from authentik.policies.types import PolicyRequest, PolicyResult
|
||||||
|
|
||||||
LOGGER = get_logger()
|
LOGGER = get_logger()
|
||||||
|
|
||||||
|
@ -103,11 +103,16 @@ class PolicyAccessView(AccessMixin, View):
|
||||||
response.policy_result = result
|
response.policy_result = result
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
def modify_policy_request(self, request: PolicyRequest) -> PolicyRequest:
|
||||||
|
"""optionally modify the policy request"""
|
||||||
|
return request
|
||||||
|
|
||||||
def user_has_access(self, user: Optional[User] = None) -> PolicyResult:
|
def user_has_access(self, user: Optional[User] = None) -> PolicyResult:
|
||||||
"""Check if user has access to application."""
|
"""Check if user has access to application."""
|
||||||
user = user or self.request.user
|
user = user or self.request.user
|
||||||
policy_engine = PolicyEngine(self.application, user or self.request.user, self.request)
|
policy_engine = PolicyEngine(self.application, user or self.request.user, self.request)
|
||||||
policy_engine.use_cache = False
|
policy_engine.use_cache = False
|
||||||
|
policy_engine.request = self.modify_policy_request(policy_engine.request)
|
||||||
policy_engine.build()
|
policy_engine.build()
|
||||||
result = policy_engine.result
|
result = policy_engine.result
|
||||||
LOGGER.debug(
|
LOGGER.debug(
|
||||||
|
|
|
@ -27,6 +27,7 @@ from authentik.flows.views.executor import SESSION_KEY_PLAN
|
||||||
from authentik.lib.utils.time import timedelta_from_string
|
from authentik.lib.utils.time import timedelta_from_string
|
||||||
from authentik.lib.utils.urls import redirect_with_qs
|
from authentik.lib.utils.urls import redirect_with_qs
|
||||||
from authentik.lib.views import bad_request_message
|
from authentik.lib.views import bad_request_message
|
||||||
|
from authentik.policies.types import PolicyRequest
|
||||||
from authentik.policies.views import PolicyAccessView, RequestValidationError
|
from authentik.policies.views import PolicyAccessView, RequestValidationError
|
||||||
from authentik.providers.oauth2.constants import (
|
from authentik.providers.oauth2.constants import (
|
||||||
PROMPT_CONSNET,
|
PROMPT_CONSNET,
|
||||||
|
@ -438,6 +439,16 @@ class AuthorizationFlowInitView(PolicyAccessView):
|
||||||
self.provider = get_object_or_404(OAuth2Provider, client_id=client_id)
|
self.provider = get_object_or_404(OAuth2Provider, client_id=client_id)
|
||||||
self.application = self.provider.application
|
self.application = self.provider.application
|
||||||
|
|
||||||
|
def modify_policy_request(self, request: PolicyRequest) -> PolicyRequest:
|
||||||
|
request.context["oauth_scopes"] = self.params.scope
|
||||||
|
request.context["oauth_grant_type"] = self.params.grant_type
|
||||||
|
request.context["oauth_code_challenge"] = self.params.code_challenge
|
||||||
|
request.context["oauth_code_challenge_method"] = self.params.code_challenge_method
|
||||||
|
request.context["oauth_max_age"] = self.params.max_age
|
||||||
|
request.context["oauth_redirect_uri"] = self.params.redirect_uri
|
||||||
|
request.context["oauth_response_type"] = self.params.response_type
|
||||||
|
return request
|
||||||
|
|
||||||
# pylint: disable=unused-argument
|
# pylint: disable=unused-argument
|
||||||
def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
|
def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
|
||||||
"""Start FlowPLanner, return to flow executor shell"""
|
"""Start FlowPLanner, return to flow executor shell"""
|
||||||
|
|
|
@ -93,6 +93,18 @@ class TokenParams:
|
||||||
code_verifier=request.POST.get("code_verifier"),
|
code_verifier=request.POST.get("code_verifier"),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def __check_policy_access(self, app: Application, request: HttpRequest, **kwargs):
|
||||||
|
engine = PolicyEngine(app, self.user, request)
|
||||||
|
engine.request.context["oauth_scopes"] = self.scope
|
||||||
|
engine.request.context["oauth_grant_type"] = self.grant_type
|
||||||
|
engine.request.context["oauth_code_verifier"] = self.code_verifier
|
||||||
|
engine.request.context.update(kwargs)
|
||||||
|
engine.build()
|
||||||
|
result = engine.result
|
||||||
|
if not result.passing:
|
||||||
|
LOGGER.info("User not authenticated for application", user=self.user, app=app)
|
||||||
|
raise TokenError("invalid_grant")
|
||||||
|
|
||||||
def __post_init__(self, raw_code: str, raw_token: str, request: HttpRequest):
|
def __post_init__(self, raw_code: str, raw_token: str, request: HttpRequest):
|
||||||
if self.grant_type in [GRANT_TYPE_AUTHORIZATION_CODE, GRANT_TYPE_REFRESH_TOKEN]:
|
if self.grant_type in [GRANT_TYPE_AUTHORIZATION_CODE, GRANT_TYPE_REFRESH_TOKEN]:
|
||||||
if (
|
if (
|
||||||
|
@ -233,12 +245,7 @@ class TokenParams:
|
||||||
app = Application.objects.filter(provider=self.provider).first()
|
app = Application.objects.filter(provider=self.provider).first()
|
||||||
if not app or not app.provider:
|
if not app or not app.provider:
|
||||||
raise TokenError("invalid_grant")
|
raise TokenError("invalid_grant")
|
||||||
engine = PolicyEngine(app, self.user, request)
|
self.__check_policy_access(app, request)
|
||||||
engine.build()
|
|
||||||
result = engine.result
|
|
||||||
if not result.passing:
|
|
||||||
LOGGER.info("User not authenticated for application", user=self.user, app=app)
|
|
||||||
raise TokenError("invalid_grant")
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def __post_init_client_credentials_jwt(self, request: HttpRequest):
|
def __post_init_client_credentials_jwt(self, request: HttpRequest):
|
||||||
|
@ -279,13 +286,8 @@ class TokenParams:
|
||||||
LOGGER.info("client_credentials grant for provider without application")
|
LOGGER.info("client_credentials grant for provider without application")
|
||||||
raise TokenError("invalid_grant")
|
raise TokenError("invalid_grant")
|
||||||
|
|
||||||
engine = PolicyEngine(app, self.user, request)
|
self.__check_policy_access(app, request, oauth_jwt=token)
|
||||||
engine.request.context["JWT"] = token
|
|
||||||
engine.build()
|
|
||||||
result = engine.result
|
|
||||||
if not result.passing:
|
|
||||||
LOGGER.info("JWT not authenticated for application", app=app)
|
|
||||||
raise TokenError("invalid_grant")
|
|
||||||
self.user, _ = User.objects.update_or_create(
|
self.user, _ = User.objects.update_or_create(
|
||||||
username=f"{self.provider.name}-{token.get('sub')}",
|
username=f"{self.provider.name}-{token.get('sub')}",
|
||||||
defaults={
|
defaults={
|
||||||
|
|
|
@ -51,5 +51,5 @@ Input JWTs are checked to be signed by any of the selected *Verification certifi
|
||||||
To do additional checks, you can use *[Expression policies](../../policies/expression)*:
|
To do additional checks, you can use *[Expression policies](../../policies/expression)*:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
return request.context["JWT"]["iss"] == "https://my.issuer"
|
return request.context["oauth_jwt"]["iss"] == "https://my.issuer"
|
||||||
```
|
```
|
||||||
|
|
|
@ -43,3 +43,14 @@ Refresh tokens can be used as long-lived tokens to access user data, and further
|
||||||
### `client_credentials`:
|
### `client_credentials`:
|
||||||
|
|
||||||
See [Machine-to-machine authentication](./client_credentials)
|
See [Machine-to-machine authentication](./client_credentials)
|
||||||
|
|
||||||
|
## Scope authorization
|
||||||
|
|
||||||
|
By default, every user that has access to an application can request any of the configured scopes. Starting with authentik 2022.4, you can do additional checks for the scope in an expression policy (bound to the application):
|
||||||
|
|
||||||
|
```python
|
||||||
|
# There are additional fields set in the context, use `ak_logger.debug(request.context)` to see them.
|
||||||
|
if "my-admin-scope" in request.context["oauth_scopes"]:
|
||||||
|
return ak_is_group_member(request.user, name="my-admin-group")
|
||||||
|
return True
|
||||||
|
```
|
||||||
|
|
Reference in New Issue