Skip to content

Commit 94d42a8

Browse files
authored
Revert client secret hash #1020 (#1082)
* Revert "Add migration that alters client_secret to ClientSecretField. (#1075)" This reverts commit 58f4f5f. * revert 250120d * bad merge
1 parent e06a9db commit 94d42a8

File tree

9 files changed

+11
-96
lines changed

9 files changed

+11
-96
lines changed

AUTHORS

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,5 @@ Jadiel Teófilo
6464
pySilver
6565
Łukasz Skarżyński
6666
Shaheed Haque
67-
Peter Karman
6867
Vinay Karanam
6968
Eduardo Oliveira

CHANGELOG.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1919
### Added
2020
* #651 Batch expired token deletions in `cleartokens` management command
2121
* Added pt-BR translations.
22-
* #729 Add support for [hashed client_secret values](https://django-oauth-toolkit.readthedocs.io/en/latest/settings.html#client-secret-hasher).
2322

2423
### Fixed
2524
* #1012 Return status for introspecting a nonexistent token from 401 to the correct value of 200 per [RFC 7662](https://datatracker.ietf.org/doc/html/rfc7662#section-2.2).

docs/settings.rst

Lines changed: 10 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -88,13 +88,6 @@ CLIENT_SECRET_GENERATOR_LENGTH
8888
The length of the generated secrets, in characters. If this value is too low,
8989
secrets may become subject to bruteforce guessing.
9090

91-
CLIENT_SECRET_HASHER
92-
~~~~~~~~~~~~~~~~~~~~
93-
If set to one of the Django password hasher algorithm names, client_secret values will be
94-
stored as `hashed Django passwords <https://docs.djangoproject.com/en/stable/topics/auth/passwords/#how-django-stores-passwords>`_.
95-
See the official list in the django.contrib.auth.hashers namespace.
96-
Default is none (stored as plain text).
97-
9891
EXTRA_SERVER_KWARGS
9992
~~~~~~~~~~~~~~~~~~~
10093
A dictionary to be passed to oauthlib's Server class. Three options
@@ -104,19 +97,19 @@ of those three can be a callable) must be passed here directly and classes
10497
must be instantiated (callables should accept request as their only argument).
10598

10699
GRANT_MODEL
107-
~~~~~~~~~~~
100+
~~~~~~~~~~~~~~~~~
108101
The import string of the class (model) representing your grants. Overwrite
109102
this value if you wrote your own implementation (subclass of
110103
``oauth2_provider.models.Grant``).
111104

112105
APPLICATION_ADMIN_CLASS
113-
~~~~~~~~~~~~~~~~~~~~~~~
106+
~~~~~~~~~~~~~~~~~
114107
The import string of the class (model) representing your application admin class.
115108
Overwrite this value if you wrote your own implementation (subclass of
116109
``oauth2_provider.admin.ApplicationAdmin``).
117110

118111
ACCESS_TOKEN_ADMIN_CLASS
119-
~~~~~~~~~~~~~~~~~~~~~~~~
112+
~~~~~~~~~~~~~~~~~
120113
The import string of the class (model) representing your access token admin class.
121114
Overwrite this value if you wrote your own implementation (subclass of
122115
``oauth2_provider.admin.AccessTokenAdmin``).
@@ -128,7 +121,7 @@ Overwrite this value if you wrote your own implementation (subclass of
128121
``oauth2_provider.admin.GrantAdmin``).
129122

130123
REFRESH_TOKEN_ADMIN_CLASS
131-
~~~~~~~~~~~~~~~~~~~~~~~~~
124+
~~~~~~~~~~~~~~~~~
132125
The import string of the class (model) representing your refresh token admin class.
133126
Overwrite this value if you wrote your own implementation (subclass of
134127
``oauth2_provider.admin.RefreshTokenAdmin``).
@@ -161,7 +154,7 @@ If you don't change the validator code and don't run cleartokens all refresh
161154
tokens will last until revoked or the end of time. You should change this.
162155

163156
REFRESH_TOKEN_GRACE_PERIOD_SECONDS
164-
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
157+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
165158
The number of seconds between when a refresh token is first used when it is
166159
expired. The most common case of this for this is native mobile applications
167160
that run into issues of network connectivity during the refresh cycle and are
@@ -185,7 +178,7 @@ See also: validator's rotate_refresh_token method can be overridden to make this
185178
when close to expiration, theoretically).
186179

187180
REFRESH_TOKEN_GENERATOR
188-
~~~~~~~~~~~~~~~~~~~~~~~
181+
~~~~~~~~~~~~~~~~~~~~~~~~~~
189182
See `ACCESS_TOKEN_GENERATOR`. This is the same but for refresh tokens.
190183
Defaults to access token generator if not provided.
191184

@@ -272,7 +265,7 @@ Default: ``""``
272265
The RSA private key used to sign OIDC ID tokens. If not set, OIDC is disabled.
273266

274267
OIDC_RSA_PRIVATE_KEYS_INACTIVE
275-
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
268+
~~~~~~~~~~~~~~~~~~~~
276269
Default: ``[]``
277270

278271
An array of *inactive* RSA private keys. These keys are not used to sign tokens,
@@ -283,7 +276,7 @@ This is useful for providing a smooth transition during key rotation.
283276
should be retained in this inactive list.
284277

285278
OIDC_JWKS_MAX_AGE_SECONDS
286-
~~~~~~~~~~~~~~~~~~~~~~~~~
279+
~~~~~~~~~~~~~~~~~~~~~~
287280
Default: ``3600``
288281

289282
The max-age value for the Cache-Control header on jwks_uri.
@@ -358,9 +351,9 @@ Time of sleep in seconds used by ``cleartokens`` management command between batc
358351

359352

360353
Settings imported from Django project
361-
-------------------------------------
354+
--------------------------
362355

363356
USE_TZ
364-
~~~~~~
357+
~~~~~~~~~~~~~~~~~~~~~~~~~~~
365358

366359
Used to determine whether or not to make token expire dates timezone aware.

oauth2_provider/migrations/0006_alter_application_client_secret.py

Lines changed: 0 additions & 20 deletions
This file was deleted.

oauth2_provider/models.py

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66

77
from django.apps import apps
88
from django.conf import settings
9-
from django.contrib.auth.hashers import make_password
109
from django.core.exceptions import ImproperlyConfigured
1110
from django.db import models, transaction
1211
from django.urls import reverse
@@ -25,19 +24,6 @@
2524
logger = logging.getLogger(__name__)
2625

2726

28-
class ClientSecretField(models.CharField):
29-
def pre_save(self, model_instance, add):
30-
if oauth2_settings.CLIENT_SECRET_HASHER:
31-
plain_secret = getattr(model_instance, self.attname)
32-
if "$" not in plain_secret: # not yet hashed
33-
hashed_secret = make_password(
34-
plain_secret, salt=model_instance.client_id, hasher=oauth2_settings.CLIENT_SECRET_HASHER
35-
)
36-
setattr(model_instance, self.attname, hashed_secret)
37-
return hashed_secret
38-
return super().pre_save(model_instance, add)
39-
40-
4127
class AbstractApplication(models.Model):
4228
"""
4329
An Application instance represents a Client on the Authorization server.
@@ -104,7 +90,7 @@ class AbstractApplication(models.Model):
10490
)
10591
client_type = models.CharField(max_length=32, choices=CLIENT_TYPES)
10692
authorization_grant_type = models.CharField(max_length=32, choices=GRANT_TYPES)
107-
client_secret = ClientSecretField(
93+
client_secret = models.CharField(
10894
max_length=255, blank=True, default=generate_client_secret, db_index=True
10995
)
11096
name = models.CharField(max_length=255, blank=True)

oauth2_provider/oauth2_validators.py

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
import requests
1212
from django.conf import settings
1313
from django.contrib.auth import authenticate, get_user_model
14-
from django.contrib.auth.hashers import check_password
1514
from django.core.exceptions import ObjectDoesNotExist
1615
from django.db import transaction
1716
from django.db.models import Q
@@ -123,17 +122,6 @@ def _authenticate_basic_auth(self, request):
123122
elif request.client.client_id != client_id:
124123
log.debug("Failed basic auth: wrong client id %s" % client_id)
125124
return False
126-
# we use the "$" as a sentinel character to determine
127-
# whether a secret has been hashed like a Django password or not.
128-
# We can do this because the default oauthlib.common.UNICODE_ASCII_CHARACTER_SET
129-
# used by our default generator does not include the "$" character.
130-
# However, if a different character set was used to generate the secret, this sentinel
131-
# might be a false positive.
132-
elif "$" in request.client.client_secret and request.client.client_secret != client_secret:
133-
if not check_password(client_secret, request.client.client_secret):
134-
log.debug("Failed basic auth: wrong hashed client secret %s" % client_secret)
135-
return False
136-
return True
137125
elif request.client.client_secret != client_secret:
138126
log.debug("Failed basic auth: wrong client secret %s" % client_secret)
139127
return False

oauth2_provider/settings.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@
3737
"CLIENT_ID_GENERATOR_CLASS": "oauth2_provider.generators.ClientIdGenerator",
3838
"CLIENT_SECRET_GENERATOR_CLASS": "oauth2_provider.generators.ClientSecretGenerator",
3939
"CLIENT_SECRET_GENERATOR_LENGTH": 128,
40-
"CLIENT_SECRET_HASHER": None,
4140
"ACCESS_TOKEN_GENERATOR": None,
4241
"REFRESH_TOKEN_GENERATOR": None,
4342
"EXTRA_SERVER_KWARGS": {},

tests/presets.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,3 @@
4444
"READ_SCOPE": "read",
4545
"WRITE_SCOPE": "write",
4646
}
47-
48-
# default django auth hasher as of version 3.2
49-
CLIENT_SECRET_HASHER = {"CLIENT_SECRET_HASHER": "pbkdf2_sha256"}

tests/test_client_credential.py

Lines changed: 0 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,6 @@
2424
AccessToken = get_access_token_model()
2525
UserModel = get_user_model()
2626

27-
CLIENT_SECRET = "abcdefghijklmnopqrstuvwxyz1234567890"
28-
2927

3028
# mocking a protected resource view
3129
class ResourceView(ProtectedResourceView):
@@ -46,7 +44,6 @@ def setUp(self):
4644
user=self.dev_user,
4745
client_type=Application.CLIENT_PUBLIC,
4846
authorization_grant_type=Application.GRANT_CLIENT_CREDENTIALS,
49-
client_secret=CLIENT_SECRET,
5047
)
5148

5249
def tearDown(self):
@@ -82,29 +79,6 @@ def test_client_credential_access_allowed(self):
8279
response = view(request)
8380
self.assertEqual(response, "This is a protected resource")
8481

85-
@pytest.mark.oauth2_settings(presets.CLIENT_SECRET_HASHER)
86-
def test_client_credential_with_hashed_client_secret(self):
87-
"""
88-
Verify client_secret is hashed before writing to the db,
89-
and comparison on request uses same hashing algo.
90-
"""
91-
self.assertNotEqual(self.application.client_secret, CLIENT_SECRET)
92-
self.assertIn("$", self.application.client_secret)
93-
self.assertIn(presets.CLIENT_SECRET_HASHER["CLIENT_SECRET_HASHER"], self.application.client_secret)
94-
95-
token_request_data = {
96-
"grant_type": "client_credentials",
97-
}
98-
auth_headers = get_basic_auth_header(self.application.client_id, CLIENT_SECRET)
99-
100-
response = self.client.post(reverse("oauth2_provider:token"), data=token_request_data, **auth_headers)
101-
self.assertEqual(response.status_code, 200)
102-
103-
# secret mismatch should return a 401
104-
auth_headers = get_basic_auth_header(self.application.client_id, "not-the-secret")
105-
response = self.client.post(reverse("oauth2_provider:token"), data=token_request_data, **auth_headers)
106-
self.assertEqual(response.status_code, 401)
107-
10882
def test_client_credential_does_not_issue_refresh_token(self):
10983
token_request_data = {
11084
"grant_type": "client_credentials",

0 commit comments

Comments
 (0)