"""Python steps file for R_013."""importloggingfrombehaveimportrunnerfrombs4importBeautifulSoupfromcommon_stepsimportstep_admin_logged_infromdevices.modelsimportIssuedCredentialModel,RemoteDeviceCredentialDownloadModelfromdevices.tests.conftestimportcreate_mock_modelsfromdjango.testimportClientfromenvironmentimportgiven,then,when# monkey-patched to not ignore exceptions
@when('the admin visits the associated "Download on Device browser" view')
[docs]defstep_when_admin_visits_the_given_view(context:runner.Context)->None:"""Ensures the admin visits the given view. Args: context (runner.Context): Behave context. """response=context.authenticated_client.get(context.download_view_url)ifresponse.status_code!=HTTP_OK:msg='Non-OK response code'raiseAssertionError(msg)context.otp_view_response=response.content
@then('a one-time password is displayed which can be used to download the credential from a remote device')
[docs]defstep_then_an_otp_is_displayed(context:runner.Context)->None:"""Ensures that a one-time password is displayed which can be used to download the credential from a remote device. Args: context (runner.Context): Behave context. """soup=BeautifulSoup(context.otp_view_response,'html.parser')element=soup.find(id='otp-display')ifelementisNone:msg='otp-display not in response'raiseAssertionError(msg)otp=element.text.strip()iflen(otp)<MIN_OTP_LENGTH:msg='OTP string shorter than 9 characters'raiseAssertionError(msg)iflen(otp)>MAX_OTP_LENGTH:msg='OTP string longer than 32 characters'raiseAssertionError(msg)context.otp=otp
@given('a correct one-time password')
[docs]defstep_given_an_otp(context:runner.Context)->None:"""Ensures that a correct one-time password is given. Args: context (runner.Context): Behave context. """try:# This is ugly and a bunch of unnecessary repetition,# but there appears to be no way to make scenarios depend on each other# aka. "Given admin created one time password successfully"step_admin_logged_in(context)step_given_issued_credential_exists(context)step_when_admin_visits_the_given_view(context)step_then_an_otp_is_displayed(context)exceptExceptionase:msg=f'Error in Scenario prerequisites: {e}'raiseAssertionError(msg)fromeifcontext.otpisNone:msg='Correct OTP not in context'raiseAssertionError(msg)
@when('the user visits the "/devices/browser" endpoint and enters the OTP')
[docs]defstep_when_user_visits_endpoint(context:runner.Context)->None:"""Ensures that the user visits the "/devices/browser" endpoint and enters the OTP. Args: context (runner.Context): Behave context. """context.unauthenticated_user_client=Client()response=context.unauthenticated_user_client.get('/devices/browser/')ifresponse.status_code!=HTTP_OK:msg='Non-OK response code, GET login page'raiseAssertionError(msg)if'id="id_otp"'notinresponse.content.decode():msg='Page does not contain OTP input field'raiseAssertionError(msg)response=context.unauthenticated_user_client.post('/devices/browser/',{'otp':context.otp})ifresponse.status_code==HTTP_FOUND:redirect_url=response.urllogger.debug(f'Redirecting to {redirect_url}')# noqa: G004if'?token='inredirect_url:context.download_token=redirect_url.split('?token=')[-1]else:context.download_token=Noneif'credential-download'inredirect_url:context.download_id=int(redirect_url.split('credential-download/')[1].split('/')[0])else:context.download_id=Noneresponse=context.unauthenticated_user_client.get(response.url)ifresponse.status_code!=HTTP_OK:msg=f'Non-OK response code {response.status_code}, POST otp'raiseAssertionError(msg)context.otp_post_view_response=response.content
@then('they will receive a page to select the format for the credential download')
[docs]defstep_then_they_will_receive_page_to_select_the_format(context:runner.Context)->None:"""Ensures that they will receive a page to select the format for the credential download. Args: context (runner.Context): Behave context. """if'value="pem_zip"'notincontext.otp_post_view_response.decode():msg='Page does not contain "Download as ZIP (PEM)" button'raiseAssertionError(msg)
@given('an incorrect one-time password')
[docs]defstep_given_an_incorrect_otp(context:runner.Context)->None:"""Ensures that an incorrect one-time password is given. Args: context (runner.Context): Behave context. """context.otp='very_wrong_otp'
@then('they will receive a warning saying the OTP is incorrect')
[docs]defstep_then_they_will_receive_a_warning(context:runner.Context)->None:"""Ensures that they will receive a warning saying the OTP is incorrect. Args: context (runner.Context): Behave context. """if'The provided password is not valid.'notincontext.otp_post_view_response.decode():msg='Page does not contain OTP error message'raiseAssertionError(msg)
@given('the user is on the credential download page')
[docs]defstep_given_the_user_is_on_the_page(context:runner.Context)->None:"""Ensures that the user is on the credential download page. Args: context (runner.Context): Behave context. """try:# This is ugly and a bunch of unnecessary repetition,# but there appears to be no way to make scenarios depend on each other# aka. "Given admin created one time password successfully"# "And user entered the correct OTP and was forwarded to the format selection page"step_given_an_otp(context)step_when_user_visits_endpoint(context)step_then_they_will_receive_page_to_select_the_format(context)exceptExceptionase:msg=f'Error in Scenario prerequisites: {e}'raiseAssertionError(msg)frome
@given('the download token is not yet expired')
[docs]defstep_given_the_download_is_not_yet_expired(context:runner.Context)->None:"""Ensures that the download token is not yet expired. Args: context (runner.Context): Behave context. """ifcontext.download_tokenisNone:msg='Download token not in context'raiseAssertionError(msg)ifRemoteDeviceCredentialDownloadModel.objects.get(id=context.download_id).check_token('dummy_token'):msg='Dummy token should not be valid'raiseAssertionError(msg)ifnotRemoteDeviceCredentialDownloadModel.objects.get(id=context.download_id).check_token(context.download_token):msg='Actual token from URL should be valid'raiseAssertionError(msg)
@when('the user enters a password to encrypt the credential private key')
[docs]defstep_when_the_user_enters_a_pw_to_encrypt_the_cred_priv_key(context:runner.Context)->None:"""Ensures that the user enters a password to encrypt the credential private key. Args: context (runner.Context): Behave context. """context.test_password='testing321321'# noqa: S105
@when('selects a file format')
[docs]defstep_when_user_selects_a_file(context:runner.Context)->None:"""Ensures that the user selects a file format. Args: context (runner.Context): Behave context. """url=f'/devices/browser/credential-download/{context.download_id}/?token={context.download_token}'post_data={'password':context.test_password,'confirm_password':context.test_password,'file_format':'pem_zip'}download_response=context.unauthenticated_user_client.post(url,post_data)ifdownload_response.status_code!=HTTP_OK:msg='Non-OK response code'raiseAssertionError(msg)context.download_response=download_response
@then('the credential will be downloaded to their browser in the requested format')
[docs]defstep_then_the_cred_will_be_downloaded(context:runner.Context)->None:"""Ensures that the credential will be downloaded to their browser in the requested format. Args: context (runner.Context): Behave context. """if'application/zip'notincontext.download_response['Content-Type']:msg='Downloaded file is not a ZIP file'raiseAssertionError(msg)if'attachment; filename='notincontext.download_response['Content-Disposition']:msg='No filename in response'raiseAssertionError(msg)