Skip to content

Commit 0675f52

Browse files
committed
Prep the tests: Create user_code_generator util
This creates a user friendly but still high entropy user code to be used in the device flow
1 parent 13290f0 commit 0675f52

File tree

2 files changed

+47
-0
lines changed

2 files changed

+47
-0
lines changed

oauth2_provider/utils.py

+43
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import functools
2+
import random
23

34
from django.conf import settings
45
from jwcrypto import jwk
@@ -32,3 +33,45 @@ def get_timezone(time_zone):
3233

3334
return pytz.timezone(time_zone)
3435
return zoneinfo.ZoneInfo(time_zone)
36+
37+
38+
def user_code_generator(user_code_length: int = 8) -> str:
39+
"""
40+
Recommended user code that retains enough entropy but doesn't
41+
ruin the user experience of typing the code in.
42+
43+
the below is based off:
44+
https://datatracker.ietf.org/doc/html/rfc8628#section-5.1
45+
but with added explanation as to where 34.5 bits of entropy is coming from
46+
47+
entropy (in bits) = length of user code * log2(length of set of chars)
48+
e = 8 * log2(20)
49+
e = 34.5
50+
51+
log2(20) is used here to say "you can make 20 yes/no decisions per user code single input character".
52+
53+
_ _ _ _ - _ _ _ _ = 20^8 ~= 2^35.5
54+
*
55+
56+
* you have 20 choices of chars to choose from (20 yes no decisions)
57+
and so on for the other 7 spaces
58+
59+
in english this means an attacker would need to try
60+
2^34.5 unique combinations to exhaust all possibilities.
61+
however with a user code only being valid for 30 seconds
62+
and rate limiting, a brute force attack is extremely unlikely
63+
to work
64+
65+
for our function we'll be using a base 32 character set
66+
"""
67+
68+
# base32 character space
69+
character_space = "0123456789ABCDEFGHIJKLMNOPQRSTUV"
70+
71+
# being explicit with length
72+
user_code = [""] * user_code_length
73+
74+
for i in range(user_code_length):
75+
user_code[i] = random.choice(character_space)
76+
77+
return "".join(user_code)

tests/app/idp/idp/settings.py

+4
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515

1616
import environ
1717

18+
from oauth2_provider.utils import user_code_generator
19+
1820

1921
# Build paths inside the project like this: BASE_DIR / 'subdir'.
2022
BASE_DIR = Path(__file__).resolve().parent.parent
@@ -199,6 +201,8 @@
199201

200202
OAUTH2_PROVIDER = {
201203
"OAUTH2_VALIDATOR_CLASS": "idp.oauth.CustomOAuth2Validator",
204+
"OAUTH_DEVICE_VERIFICATION_URI": "http://127.0.0.1:8000/o/device",
205+
"OAUTH_DEVICE_USER_CODE_GENERATOR": lambda: user_code_generator(),
202206
"OIDC_ENABLED": env("OAUTH2_PROVIDER_OIDC_ENABLED"),
203207
"OIDC_RP_INITIATED_LOGOUT_ENABLED": env("OAUTH2_PROVIDER_OIDC_RP_INITIATED_LOGOUT_ENABLED"),
204208
# this key is just for out test app, you should never store a key like this in a production environment.

0 commit comments

Comments
 (0)