Skip to content

Commit

Permalink
Add prompt=none support (#1268)
Browse files Browse the repository at this point in the history
  • Loading branch information
andyzickler authored and dopry committed Nov 25, 2023
1 parent a4b26b1 commit e2acfbd
Show file tree
Hide file tree
Showing 6 changed files with 94 additions and 2 deletions.
1 change: 1 addition & 0 deletions AUTHORS
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ Allisson Azevedo
Andrea Greco
Andrej Zbín
Andrew Chen Wang
Andrew Zickler
Antoine Laurent
Anvesh Agarwal
Aristóbulo Meneses
Expand Down
4 changes: 2 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
* #1311 Add option to disable client_secret hashing to allow verifying JWTs' signatures.
* #1337 Gracefully handle expired or deleted refresh tokens, in `validate_user`.
* #1350 Support Python 3.12 and Django 5.0
* #1249 Add code_challenge_methods_supported property to auto discovery informations
per [RFC 8414 section 2](https://www.rfc-editor.org/rfc/rfc8414.html#page-7)
* #1249 Add code_challenge_methods_supported property to auto discovery informations, per [RFC 8414 section 2](https://www.rfc-editor.org/rfc/rfc8414.html#page-7)
* #1268 Support prompt=none in authentication request for Silent Authentication

### Fixed
* #1322 Instructions in documentation on how to create a code challenge and code verifier
Expand Down
27 changes: 27 additions & 0 deletions oauth2_provider/views/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,33 @@ def handle_prompt_login(self):
self.get_redirect_field_name(),
)

def handle_no_permission(self):
"""
Generate response for unauthorized users.
If prompt is set to none, then we redirect with an error code
as defined by OIDC 3.1.2.6
Some code copied from OAuthLibMixin.error_response, but that is designed
to operated on OAuth1Error from oauthlib wrapped in a OAuthToolkitError
"""
prompt = self.request.GET.get("prompt")
redirect_uri = self.request.GET.get("redirect_uri")
if prompt == "none" and redirect_uri:
response_parameters = {"error": "login_required"}

# REQUIRED if the Authorization Request included the state parameter.
# Set to the value received from the Client
state = self.request.GET.get("state")
if state:
response_parameters["state"] = state

separator = "&" if "?" in redirect_uri else "?"
redirect_to = redirect_uri + separator + urlencode(response_parameters)
return self.redirect(redirect_to, application=None)
else:
return super().handle_no_permission()

Check warning on line 272 in oauth2_provider/views/base.py

View check run for this annotation

Codecov / codecov/patch

oauth2_provider/views/base.py#L272

Added line #L272 was not covered by tests


@method_decorator(csrf_exempt, name="dispatch")
class TokenView(OAuthLibMixin, View):
Expand Down
36 changes: 36 additions & 0 deletions tests/app/idp/idp/oauth.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
from oauth2_provider.oauth2_validators import OAuth2Validator
from django.contrib.sessions.middleware import SessionMiddleware
from django.contrib.auth.middleware import AuthenticationMiddleware
from django.conf import settings


class CustomOAuth2Validator(OAuth2Validator):
def validate_silent_login(self, request) -> None:
# request is an OAuthLib.common.Request and doesn't have the session
# or user of the django request. We will emulate the session and auth
# middleware here, since that is what the idp is using for auth. You
# may need to modify this if you are using a different session
# middleware or auth backend.

# get_response is required for middlware, it doesn't need to do anything
# the way we're using it, so we just use a lambda that returns None
get_response = lambda: None
session_cookie_name = settings.SESSION_COOKIE_NAME
HTTP_COOKIE = request.headers.get("HTTP_COOKIE")
COOKIES=HTTP_COOKIE.split("; ")
for cookie in COOKIES:
cookie_name, cookie_value = cookie.split("=")
if cookie.startswith(session_cookie_name):
break
session_middleware = SessionMiddleware(get_response)
session = session_middleware.SessionStore(cookie_value)
# add session to request for compatibility with django.contrib.auth
request.session = session

# call the auth middleware to set request.user
auth_middleware = AuthenticationMiddleware(get_response)
auth_middleware.process_request(request)
return request.user.is_authenticated

def validate_silent_authorization(self, request) -> None:
return True
1 change: 1 addition & 0 deletions tests/app/idp/idp/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"

OAUTH2_PROVIDER = {
"OAUTH2_VALIDATOR_CLASS": "idp.oauth.CustomOAuth2Validator",
"OIDC_ENABLED": True,
"OIDC_RP_INITIATED_LOGOUT_ENABLED": True,
# this key is just for out test app, you should never store a key like this in a production environment.
Expand Down
27 changes: 27 additions & 0 deletions tests/test_authorization_code.py
Original file line number Diff line number Diff line change
Expand Up @@ -645,6 +645,33 @@ def test_prompt_login(self):

self.assertNotIn("prompt=login", next)

def test_prompt_none_unauthorized(self):
"""
Test response for redirect when supplied with prompt: none
Should redirect to redirect_uri with an error of login_required
"""
self.oauth2_settings.PKCE_REQUIRED = False

query_data = {
"client_id": self.application.client_id,
"response_type": "code",
"state": "random_state_string",
"scope": "read write",
"redirect_uri": "http://example.org",
"prompt": "none",
}

response = self.client.get(reverse("oauth2_provider:authorize"), data=query_data)

self.assertEqual(response.status_code, 302)

scheme, netloc, path, params, query, fragment = urlparse(response["Location"])
parsed_query = parse_qs(query)

self.assertIn("login_required", parsed_query["error"])
self.assertIn("random_state_string", parsed_query["state"])


class BaseAuthorizationCodeTokenView(BaseTest):
def get_auth(self, scope="read write"):
Expand Down

0 comments on commit e2acfbd

Please sign in to comment.