def _delete_mirror_file_at_time_1(name: str, deletion_time: str) -> None: if current_task.maybe_delay_task( DatetimeWithTimezone.fromisoformat(deletion_time)): return found = p.app.mirror_file_storage.get(name) found.if_just(lambda f: f.delete())
def _get_age_datetime(date: t.Union[DatetimeWithTimezone, str], add_m: bool = False) -> t.Union[float, str]: if isinstance(date, str): date = DatetimeWithTimezone.fromisoformat(date) res = int( round((DatetimeWithTimezone.utcnow() - date).total_seconds() / 60)) if add_m: return f'{res}m' return res
def _delete_file_at_time_1( filename: str, in_mirror_dir: bool, deletion_time: str ) -> None: if current_task.maybe_delay_task( DatetimeWithTimezone.fromisoformat(deletion_time) ): return if in_mirror_dir: root = p.app.config['MIRROR_UPLOAD_DIR'] else: # pragma: no cover # The case outside of the mirror_upload_dir is not yet used root = p.app.config['UPLOAD_DIR'] filename = p.files.safe_join(root, filename) if os.path.isfile(filename): # There is a race condition here (file is removed in this small space), # but we don't care as it is removed in that case try: os.unlink(filename) except FileNotFoundError: # pragma: no cover pass
def test_valid_saml_flows(test_client, describe, logged_in, admin_user, stub_function, app, session, monkeypatch): with describe('setup'), logged_in(admin_user): with open(helpers.test_data('test_saml_xml', 'valid_with_logo.xml')) as f: xml = f.read() with open(helpers.test_data('test_saml_xml', 'valid_response.json')) as f: valid_response = json.load(f) stub_function(m.saml_provider._MetadataParser, 'get_metadata', lambda: xml) prov = helpers.to_db_object( helpers.create_sso_provider(test_client, stub_function, 'Prov', no_stub=True), m.Saml2Provider) token = valid_response.pop('token') request_time = DatetimeWithTimezone.fromisoformat( valid_response.pop('request_time')) auth_n_request = valid_response.pop('AuthNRequest') prov.id = valid_response.pop('prov_id') prov._key_data = valid_response.pop('key').encode('utf8') prov._cert_data = valid_response.pop('cert').encode('utf8') host = valid_response['http_host'] http = 'https' if valid_response['https'] == 'on' else 'http' external_url = furl.furl() external_url.scheme = http external_url.host = host session.commit() stub_function(psef.saml2, '_prepare_flask_request', lambda: valid_response) with test_client as client, freezegun.freeze_time(request_time): with describe('can do login when all is good'): client.get(f'/api/sso/saml2/login/{helpers.get_id(prov)}') with client.session_transaction() as sess: sess['[SAML]_AuthNRequestID'] = auth_n_request sess['[SAML]_TOKEN'] = token with monkeypatch.context() as ctx: ctx.setitem(app.config, 'EXTERNAL_URL', external_url.tostr()) res = client.post(f'/api/sso/saml2/acs/{helpers.get_id(prov)}', data='Not used') assert 300 <= res.status_code < 400 loc = res.headers['Location'] base = furl.furl(external_url.tostr()).add(path='/sso_login/') assert loc.startswith(base.tostr()) assert token == loc.split('/')[-1] db_base_id = flask.session['[SAML]_DB_BLOB_ID'] assert m.BlobStorage.query.get(db_base_id) is not None with describe('cannot use jwt route without token in session'): with client.session_transaction() as sess: assert sess.pop('[SAML]_TOKEN') == token err = client.req('post', f'/api/sso/saml2/jwts/{token}', 409) assert 'Could not find all required data' in err['message'] with describe('cannot use jwt route without correct token in session'): with client.session_transaction() as sess: sess['[SAML]_TOKEN'] = 'incorrect_token' sess['[SAML]_DB_BLOB_ID'] = db_base_id assert m.BlobStorage.query.get(db_base_id) is not None err = client.req('post', f'/api/sso/saml2/jwts/{token}', 400) assert 'Invalid token provided' in err['message'] with describe( 'cannot use jwt route without correct token in url and session' ): wrong_token = str(uuid.uuid4()) with client.session_transaction() as sess: sess['[SAML]_TOKEN'] = wrong_token sess['[SAML]_DB_BLOB_ID'] = db_base_id err = client.req('post', f'/api/sso/saml2/jwts/{wrong_token}', 400) assert 'Invalid token provided' in err['message'] with describe('cannot use jwt route without correct token in url'): with client.session_transaction() as sess: sess['[SAML]_TOKEN'] = token sess['[SAML]_DB_BLOB_ID'] = db_base_id err = client.req('post', f'/api/sso/saml2/jwts/{wrong_token}', 400) assert 'Invalid token provided' in err['message'] with describe('cannot use blob after some time' ), freezegun.freeze_time(request_time + timedelta(minutes=15)): with client.session_transaction() as sess: sess['[SAML]_TOKEN'] = token sess['[SAML]_DB_BLOB_ID'] = db_base_id client.req('post', f'/api/sso/saml2/jwts/{token}', 404) with describe('can use one time if all is right'): with client.session_transaction() as sess: sess['[SAML]_TOKEN'] = token sess['[SAML]_DB_BLOB_ID'] = db_base_id res = client.req('post', f'/api/sso/saml2/jwts/{token}', 200) assert isinstance(res.get('access_token'), str) # Cannot use again with client.session_transaction() as sess: sess['[SAML]_TOKEN'] = token sess['[SAML]_DB_BLOB_ID'] = db_base_id client.req('post', f'/api/sso/saml2/jwts/{token}', 404)
def _send_login_links_to_users_1(assignment_id: int, task_id_hex: str, scheduled_time: str, reset_token_hex: str, mail_idx: int) -> None: task_id = uuid.UUID(hex=task_id_hex) task_result = p.models.TaskResult.query.filter( p.models.TaskResult.id == task_id, p.models.TaskResult.state == p.models.TaskResultState.not_started).with_for_update( of=p.models.TaskResult).one_or_none() eta = DatetimeWithTimezone.fromisoformat(scheduled_time) if task_result is None: logger.error('Could not find task') return def __task() -> p.models.TaskReturnType: reset_token = uuid.UUID(hex=reset_token_hex) assignment = p.models.Assignment.query.filter( p.models.Assignment.id == assignment_id, ).with_for_update( of=p.models.Assignment).one_or_none() if assignment is None or assignment.deadline_expired: logger.error( 'Could not find assignment or deadline expired', assignment_id=assignment_id, assignment=assignment, ) return p.models.TaskResultState.skipped elif reset_token != assignment.send_login_links_token: logger.info('Tokens did not match') return p.models.TaskResultState.skipped login_link_map = { link.user: link for link in p.models.AssignmentLoginLink.query.filter( p.models.AssignmentLoginLink.assignment == assignment) } perm = p.permissions.CoursePermission.can_receive_login_links users = [ user for user, _ in assignment.course.get_all_users_in_course( include_test_students=False, with_permission=perm, ) ] for user in users: if user not in login_link_map: link = p.models.AssignmentLoginLink( user_id=user.id, assignment_id=assignment.id) p.models.db.session.add(link) login_link_map[user] = link # We commit here to release the locks on all tables before we start # emailing. p.models.db.session.commit() with p.mail.mail.connect() as mailer: for user in users: link = login_link_map[user] try: p.mail.send_login_link_mail(mailer, link, mail_idx=mail_idx) # pylint: disable=bare-except except: # pragma: no cover logger.warning( 'Could not send email', receiving_user_id=user.id, exc_info=True, report_to_sentry=True, ) return p.models.TaskResultState.finished if task_result.as_task(__task, eta=eta): p.models.db.session.commit()