Example #1
0
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())
Example #2
0
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
Example #3
0
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
Example #4
0
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)
Example #5
0
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()