Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion duo_universal/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,8 @@ def health_check(self):
return res

def create_auth_url(self, username, state, nonce=None, dest_app_name=None,
dest_app_id=None, display_username=None):
dest_app_id=None, display_username=None, max_age=None,
prompt=None):
"""Generate uri to Duo's prompt

Arguments:
Expand All @@ -257,6 +258,12 @@ def create_auth_url(self, username, state, nonce=None, dest_app_name=None,
application; not shown to users
display_username -- (Optional) Username shown in the Duo Mobile "user" field for Push.
Defaults to the Duo username if not provided.
max_age -- (Optional) Maximum number of seconds since the user last
authenticated interactively. A remembered session older than
this forces interactive reauthentication. Zero always forces
it, equivalent to prompt='login'.
prompt -- (Optional) Set to 'login' to force interactive
reauthentication even when the user has a remembered session

Returns:

Expand Down Expand Up @@ -286,6 +293,10 @@ def create_auth_url(self, username, state, nonce=None, dest_app_name=None,
jwt_args['dest_app_id'] = dest_app_id
if display_username is not None:
jwt_args['display_username'] = display_username
if max_age is not None:
jwt_args['max_age'] = max_age
if prompt is not None:
jwt_args['prompt'] = prompt

request_jwt = jwt.encode(jwt_args,
self._signing_key,
Expand Down
73 changes: 73 additions & 0 deletions tests/test_create_auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,79 @@ def test_dest_app_name_without_dest_app_id(self):
self._assert_client_creates_expected_uri(
duo_client, expected_jwt_args, dest_app_name=DEST_APP_NAME)

@patch('time.time', MagicMock(return_value=2))
def test_max_age(self):
"""
Test create_auth_url includes max_age when given
"""
duo_client = client.Client(CLIENT_ID, CLIENT_SECRET, HOST, REDIRECT_URI)

expected_jwt_args = {
'scope': 'openid',
'redirect_uri': REDIRECT_URI,
'client_id': CLIENT_ID,
'iss': CLIENT_ID,
'aud': client.API_HOST_URI_FORMAT.format(HOST),
'exp': 302,
'state': STATE,
'response_type': 'code',
'duo_uname': USERNAME,
'use_duo_code_attribute': True,
'max_age': 3600,
}

self._assert_client_creates_expected_uri(
duo_client, expected_jwt_args, max_age=3600)

@patch('time.time', MagicMock(return_value=2))
def test_max_age_zero(self):
"""
Test create_auth_url includes a max_age of zero, which forces
interactive reauthentication rather than being treated as unset
"""
duo_client = client.Client(CLIENT_ID, CLIENT_SECRET, HOST, REDIRECT_URI)

expected_jwt_args = {
'scope': 'openid',
'redirect_uri': REDIRECT_URI,
'client_id': CLIENT_ID,
'iss': CLIENT_ID,
'aud': client.API_HOST_URI_FORMAT.format(HOST),
'exp': 302,
'state': STATE,
'response_type': 'code',
'duo_uname': USERNAME,
'use_duo_code_attribute': True,
'max_age': 0,
}

self._assert_client_creates_expected_uri(
duo_client, expected_jwt_args, max_age=0)

@patch('time.time', MagicMock(return_value=2))
def test_prompt(self):
"""
Test create_auth_url includes prompt when given
"""
duo_client = client.Client(CLIENT_ID, CLIENT_SECRET, HOST, REDIRECT_URI)

expected_jwt_args = {
'scope': 'openid',
'redirect_uri': REDIRECT_URI,
'client_id': CLIENT_ID,
'iss': CLIENT_ID,
'aud': client.API_HOST_URI_FORMAT.format(HOST),
'exp': 302,
'state': STATE,
'response_type': 'code',
'duo_uname': USERNAME,
'use_duo_code_attribute': True,
'prompt': 'login',
}

self._assert_client_creates_expected_uri(
duo_client, expected_jwt_args, prompt='login')

def _assert_client_creates_expected_uri(self, duo_client, expected_jwt_args, **kwargs):
authorize_endpoint = \
client.OAUTH_V1_AUTHORIZE_ENDPOINT.format(HOST)
Expand Down