summaryrefslogtreecommitdiffstats
path: root/contrib/python/oauthlib/tests/oauth2/rfc8628
diff options
context:
space:
mode:
authorrobot-piglet <[email protected]>2025-09-05 12:45:40 +0300
committerrobot-piglet <[email protected]>2025-09-05 13:32:17 +0300
commit22568739dbd9bf62b94abb305c082a53b21e5aef (patch)
tree7833f8119b1292867b256356e25ee8593dccac75 /contrib/python/oauthlib/tests/oauth2/rfc8628
parent2db41830cd5fce47e0bac5493c1e1472908cc4c5 (diff)
Intermediate changes
commit_hash:39273c986cf85d42ec97d1388aaaf324a02a04bc
Diffstat (limited to 'contrib/python/oauthlib/tests/oauth2/rfc8628')
-rw-r--r--contrib/python/oauthlib/tests/oauth2/rfc8628/endpoints/__init__.py0
-rw-r--r--contrib/python/oauthlib/tests/oauth2/rfc8628/endpoints/test_device_application_server.py26
-rw-r--r--contrib/python/oauthlib/tests/oauth2/rfc8628/endpoints/test_error_responses.py95
-rw-r--r--contrib/python/oauthlib/tests/oauth2/rfc8628/grant_types/__init__.py0
-rw-r--r--contrib/python/oauthlib/tests/oauth2/rfc8628/grant_types/test_device_code.py172
-rw-r--r--contrib/python/oauthlib/tests/oauth2/rfc8628/test_server.py113
6 files changed, 406 insertions, 0 deletions
diff --git a/contrib/python/oauthlib/tests/oauth2/rfc8628/endpoints/__init__.py b/contrib/python/oauthlib/tests/oauth2/rfc8628/endpoints/__init__.py
new file mode 100644
index 00000000000..e69de29bb2d
--- /dev/null
+++ b/contrib/python/oauthlib/tests/oauth2/rfc8628/endpoints/__init__.py
diff --git a/contrib/python/oauthlib/tests/oauth2/rfc8628/endpoints/test_device_application_server.py b/contrib/python/oauthlib/tests/oauth2/rfc8628/endpoints/test_device_application_server.py
new file mode 100644
index 00000000000..f0436754a5e
--- /dev/null
+++ b/contrib/python/oauthlib/tests/oauth2/rfc8628/endpoints/test_device_application_server.py
@@ -0,0 +1,26 @@
+import json
+from unittest import TestCase, mock
+
+from oauthlib.common import Request, urlencode
+from oauthlib.oauth2.rfc6749 import errors
+from oauthlib.oauth2.rfc8628.endpoints.pre_configured import DeviceApplicationServer
+from oauthlib.oauth2.rfc8628.request_validator import RequestValidator
+
+
+def test_server_set_up_device_endpoint_instance_attributes_correctly():
+ """
+ Simple test that just instantiates DeviceApplicationServer
+ and asserts the important attributes are present
+ """
+ validator = mock.MagicMock(spec=RequestValidator)
+ validator.get_default_redirect_uri.return_value = None
+ validator.get_code_challenge.return_value = None
+
+ verification_uri = "test.com/device"
+ verification_uri_complete = "test.com/device?user_code=123"
+ device = DeviceApplicationServer(validator, verification_uri=verification_uri, verification_uri_complete=verification_uri_complete)
+ device_vars = vars(device)
+ assert device_vars["_verification_uri_complete"] == "test.com/device?user_code=123"
+ assert device_vars["_verification_uri"] == "test.com/device"
+ assert device_vars["_expires_in"] == 1800
+ assert device_vars["_interval"] == 5
diff --git a/contrib/python/oauthlib/tests/oauth2/rfc8628/endpoints/test_error_responses.py b/contrib/python/oauthlib/tests/oauth2/rfc8628/endpoints/test_error_responses.py
new file mode 100644
index 00000000000..c799cc81af2
--- /dev/null
+++ b/contrib/python/oauthlib/tests/oauth2/rfc8628/endpoints/test_error_responses.py
@@ -0,0 +1,95 @@
+import json
+from unittest import TestCase, mock
+
+from oauthlib.common import Request, urlencode
+from oauthlib.oauth2.rfc6749 import errors
+from oauthlib.oauth2.rfc8628.endpoints.pre_configured import DeviceApplicationServer
+from oauthlib.oauth2.rfc8628.request_validator import RequestValidator
+
+
+class ErrorResponseTest(TestCase):
+ def set_client(self, request):
+ request.client = mock.MagicMock()
+ request.client.client_id = "mocked"
+ return True
+
+ def build_request(self, uri="https://example.com/device_authorize", client_id="foo"):
+ body = ""
+ if client_id:
+ body = f"client_id={client_id}"
+ return Request(
+ uri,
+ http_method="POST",
+ body=body,
+ headers={"Content-Type": "application/x-www-form-urlencoded"},
+ )
+
+ def assert_request_raises(self, error, request):
+ """Test that the request fails similarly on the validation and response endpoint."""
+ self.assertRaises(
+ error,
+ self.device.validate_device_authorization_request,
+ request,
+ )
+ self.assertRaises(
+ error,
+ self.device.create_device_authorization_response,
+ uri=request.uri,
+ http_method=request.http_method,
+ body=request.body,
+ headers=request.headers,
+ )
+
+ def setUp(self):
+ self.validator = mock.MagicMock(spec=RequestValidator)
+ self.validator.get_default_redirect_uri.return_value = None
+ self.validator.get_code_challenge.return_value = None
+ self.device = DeviceApplicationServer(self.validator, "https://example.com/verify")
+
+ def test_missing_client_id(self):
+ # Device code grant
+ request = self.build_request(client_id=None)
+ self.assert_request_raises(errors.MissingClientIdError, request)
+
+ def test_empty_client_id(self):
+ # Device code grant
+ self.assertRaises(
+ errors.MissingClientIdError,
+ self.device.create_device_authorization_response,
+ "https://i.l/",
+ "POST",
+ "client_id=",
+ {"Content-Type": "application/x-www-form-urlencoded"},
+ )
+
+ def test_invalid_client_id(self):
+ request = self.build_request(client_id="foo")
+ # Device code grant
+ self.validator.validate_client_id.return_value = False
+ self.assert_request_raises(errors.InvalidClientIdError, request)
+
+ def test_duplicate_client_id(self):
+ request = self.build_request()
+ request.body = "client_id=foo&client_id=bar"
+ # Device code grant
+ self.validator.validate_client_id.return_value = False
+ self.assert_request_raises(errors.InvalidRequestFatalError, request)
+
+ def test_unauthenticated_confidential_client(self):
+ self.validator.client_authentication_required.return_value = True
+ self.validator.authenticate_client.return_value = False
+ request = self.build_request()
+ self.assert_request_raises(errors.InvalidClientError, request)
+
+ def test_unauthenticated_public_client(self):
+ self.validator.client_authentication_required.return_value = False
+ self.validator.authenticate_client_id.return_value = False
+ request = self.build_request()
+ self.assert_request_raises(errors.InvalidClientError, request)
+
+ def test_duplicate_scope_parameter(self):
+ request = self.build_request()
+ request.body = "client_id=foo&scope=foo&scope=bar"
+ # Device code grant
+ self.validator.validate_client_id.return_value = False
+ self.assert_request_raises(errors.InvalidRequestFatalError, request)
diff --git a/contrib/python/oauthlib/tests/oauth2/rfc8628/grant_types/__init__.py b/contrib/python/oauthlib/tests/oauth2/rfc8628/grant_types/__init__.py
new file mode 100644
index 00000000000..e69de29bb2d
--- /dev/null
+++ b/contrib/python/oauthlib/tests/oauth2/rfc8628/grant_types/__init__.py
diff --git a/contrib/python/oauthlib/tests/oauth2/rfc8628/grant_types/test_device_code.py b/contrib/python/oauthlib/tests/oauth2/rfc8628/grant_types/test_device_code.py
new file mode 100644
index 00000000000..da0592f7426
--- /dev/null
+++ b/contrib/python/oauthlib/tests/oauth2/rfc8628/grant_types/test_device_code.py
@@ -0,0 +1,172 @@
+import json
+from unittest import mock
+import pytest
+
+from oauthlib import common
+
+from oauthlib.oauth2.rfc8628.grant_types import DeviceCodeGrant
+from oauthlib.oauth2.rfc6749.tokens import BearerToken
+
+def create_request(body: str = "") -> common.Request:
+ request = common.Request("http://a.b/path", body=body or None)
+ request.scopes = ("hello", "world")
+ request.expires_in = 1800
+ request.client = "batman"
+ request.client_id = "abcdef"
+ request.code = "1234"
+ request.response_type = "code"
+ request.grant_type = "urn:ietf:params:oauth:grant-type:device_code"
+ request.redirect_uri = "https://a.b/"
+ return request
+
+
+def create_device_code_grant(mock_validator: mock.MagicMock) -> DeviceCodeGrant:
+ return DeviceCodeGrant(request_validator=mock_validator)
+
+
+def test_custom_auth_validators_unsupported():
+ custom_validator = mock.Mock()
+ validator = mock.MagicMock()
+
+ expected = (
+ "DeviceCodeGrant does not "
+ "support authorization validators. Use token validators instead."
+ )
+ with pytest.raises(ValueError, match=expected):
+ DeviceCodeGrant(validator, pre_auth=[custom_validator])
+
+ with pytest.raises(ValueError, match=expected):
+ DeviceCodeGrant(validator, post_auth=[custom_validator])
+
+ expected = "'tuple' object has no attribute 'append'"
+ auth = DeviceCodeGrant(validator)
+ with pytest.raises(AttributeError, match=expected):
+ auth.custom_validators.pre_auth.append(custom_validator)
+
+
+def test_custom_pre_and_post_token_validators():
+ client = mock.MagicMock()
+
+ validator = mock.MagicMock()
+ pre_token_validator = mock.Mock()
+ post_token_validator = mock.Mock()
+
+ request: common.Request = create_request()
+ request.client = client
+
+ auth = DeviceCodeGrant(validator)
+
+ auth.custom_validators.pre_token.append(pre_token_validator)
+ auth.custom_validators.post_token.append(post_token_validator)
+
+ bearer = BearerToken(validator)
+ auth.create_token_response(request, bearer)
+
+ pre_token_validator.assert_called()
+ post_token_validator.assert_called()
+
+
+def test_create_token_response():
+ validator = mock.MagicMock()
+ request: common.Request = create_request()
+ request.client = mock.Mock()
+
+ auth = DeviceCodeGrant(validator)
+
+ bearer = BearerToken(validator)
+
+ headers, body, status_code = auth.create_token_response(request, bearer)
+ token = json.loads(body)
+
+ assert headers == {
+ "Content-Type": "application/json",
+ "Cache-Control": "no-store",
+ "Pragma": "no-cache",
+ }
+
+ # when a custom token generator callable isn't used
+ # the random generator is used as default for the access token
+ assert token == {
+ "access_token": mock.ANY,
+ "expires_in": 3600,
+ "token_type": "Bearer",
+ "scope": "hello world",
+ "refresh_token": mock.ANY,
+ }
+
+ assert status_code == 200
+
+ validator.save_token.assert_called_once()
+
+
+def test_invalid_client_error():
+ validator = mock.MagicMock()
+ request: common.Request = create_request()
+ request.client = mock.Mock()
+
+ auth = DeviceCodeGrant(validator)
+ bearer = BearerToken(validator)
+
+ validator.authenticate_client.return_value = False
+
+ headers, body, status_code = auth.create_token_response(request, bearer)
+ body = json.loads(body)
+
+ assert headers == {
+ "Content-Type": "application/json",
+ "Cache-Control": "no-store",
+ "Pragma": "no-cache",
+ "WWW-Authenticate": 'Bearer error="invalid_client"',
+ }
+ assert body == {"error": "invalid_client"}
+ assert status_code == 401
+
+ validator.save_token.assert_not_called()
+
+
+def test_invalid_grant_type_error():
+ validator = mock.MagicMock()
+ request: common.Request = create_request()
+ request.client = mock.Mock()
+
+ request.grant_type = "not_device_code"
+
+ auth = DeviceCodeGrant(validator)
+ bearer = BearerToken(validator)
+
+ headers, body, status_code = auth.create_token_response(request, bearer)
+ body = json.loads(body)
+
+ assert headers == {
+ "Content-Type": "application/json",
+ "Cache-Control": "no-store",
+ "Pragma": "no-cache",
+ }
+ assert body == {"error": "unsupported_grant_type"}
+ assert status_code == 400
+
+ validator.save_token.assert_not_called()
+
+
+def test_duplicate_params_error():
+ validator = mock.MagicMock()
+ request: common.Request = create_request(
+ "client_id=123&scope=openid&scope=openid"
+ )
+ request.client = mock.Mock()
+
+ auth = DeviceCodeGrant(validator)
+ bearer = BearerToken(validator)
+
+ headers, body, status_code = auth.create_token_response(request, bearer)
+ body = json.loads(body)
+
+ assert headers == {
+ "Content-Type": "application/json",
+ "Cache-Control": "no-store",
+ "Pragma": "no-cache",
+ }
+ assert body == {"error": "invalid_request", "error_description": "Duplicate scope parameter."}
+ assert status_code == 400
+
+ validator.save_token.assert_not_called()
diff --git a/contrib/python/oauthlib/tests/oauth2/rfc8628/test_server.py b/contrib/python/oauthlib/tests/oauth2/rfc8628/test_server.py
new file mode 100644
index 00000000000..52025032849
--- /dev/null
+++ b/contrib/python/oauthlib/tests/oauth2/rfc8628/test_server.py
@@ -0,0 +1,113 @@
+import json
+from unittest import mock
+
+from oauthlib.oauth2.rfc8628.endpoints import DeviceAuthorizationEndpoint
+from oauthlib.oauth2.rfc8628.request_validator import RequestValidator
+
+from tests.unittest import TestCase
+
+
+class DeviceAuthorizationEndpointTest(TestCase):
+ def _configure_endpoint(
+ self, interval=None, verification_uri_complete=None, user_code_generator=None
+ ):
+ self.endpoint = DeviceAuthorizationEndpoint(
+ request_validator=mock.MagicMock(spec=RequestValidator),
+ verification_uri=self.verification_uri,
+ interval=interval,
+ verification_uri_complete=verification_uri_complete,
+ user_code_generator=user_code_generator,
+ )
+
+ def setUp(self):
+ self.request_validator = mock.MagicMock(spec=RequestValidator)
+ self.verification_uri = "http://i.b/l/verify"
+ self.uri = "http://i.b/l"
+ self.http_method = "POST"
+ self.body = "client_id=abc"
+ self.headers = {"Content-Type": "application/x-www-form-urlencoded"}
+ self._configure_endpoint()
+
+ def response_payload(self):
+ return self.uri, self.http_method, self.body, self.headers
+
+ @mock.patch("oauthlib.oauth2.rfc8628.endpoints.device_authorization.generate_token")
+ def test_device_authorization_grant(self, generate_token):
+ generate_token.side_effect = ["abc", "def"]
+ _, body, status_code = self.endpoint.create_device_authorization_response(
+ *self.response_payload()
+ )
+ expected_payload = {
+ "verification_uri": "http://i.b/l/verify",
+ "user_code": "abc",
+ "device_code": "def",
+ "expires_in": 1800,
+ }
+ self.assertEqual(200, status_code)
+ self.assertEqual(body, expected_payload)
+
+ @mock.patch(
+ "oauthlib.oauth2.rfc8628.endpoints.device_authorization.generate_token",
+ lambda: "abc",
+ )
+ def test_device_authorization_grant_interval(self):
+ self._configure_endpoint(interval=5)
+ _, body, _ = self.endpoint.create_device_authorization_response(*self.response_payload())
+ self.assertEqual(5, body["interval"])
+
+ @mock.patch(
+ "oauthlib.oauth2.rfc8628.endpoints.device_authorization.generate_token",
+ lambda: "abc",
+ )
+ def test_device_authorization_grant_interval_with_zero(self):
+ self._configure_endpoint(interval=0)
+ _, body, _ = self.endpoint.create_device_authorization_response(*self.response_payload())
+ self.assertEqual(0, body["interval"])
+
+ @mock.patch(
+ "oauthlib.oauth2.rfc8628.endpoints.device_authorization.generate_token",
+ lambda: "abc",
+ )
+ def test_device_authorization_grant_verify_url_complete_string(self):
+ self._configure_endpoint(verification_uri_complete="http://i.l/v?user_code={user_code}")
+ _, body, _ = self.endpoint.create_device_authorization_response(*self.response_payload())
+ self.assertEqual(
+ "http://i.l/v?user_code=abc",
+ body["verification_uri_complete"],
+ )
+
+ @mock.patch(
+ "oauthlib.oauth2.rfc8628.endpoints.device_authorization.generate_token",
+ lambda: "abc",
+ )
+ def test_device_authorization_grant_verify_url_complete_callable(self):
+ self._configure_endpoint(verification_uri_complete=lambda u: f"http://i.l/v?user_code={u}")
+ _, body, _ = self.endpoint.create_device_authorization_response(*self.response_payload())
+ self.assertEqual(
+ "http://i.l/v?user_code=abc",
+ body["verification_uri_complete"],
+ )
+
+ @mock.patch(
+ "oauthlib.oauth2.rfc8628.endpoints.device_authorization.generate_token",
+ lambda: "abc",
+ )
+ def test_device_authorization_grant_user_gode_generator(self):
+ def user_code():
+ """
+ A friendly user code the device can display and the user
+ can type in. It's up to the device how
+ this code should be displayed. e.g 123-456
+ """
+ return "123456"
+
+ self._configure_endpoint(
+ verification_uri_complete=lambda u: f"http://i.l/v?user_code={u}",
+ user_code_generator=user_code,
+ )
+
+ _, body, _ = self.endpoint.create_device_authorization_response(*self.response_payload())
+ self.assertEqual(
+ "http://i.l/v?user_code=123456",
+ body["verification_uri_complete"],
+ )