Example #1
0
 def test_stream_note_attachment(self, app, fake_auth):
     with mock_legacy_note_attachment(app):
         fake_auth.login(coe_advisor)
         stream = get_legacy_attachment_stream('9000000000_00002_1.pdf')['stream']
         body = b''
         for chunk in stream:
             body += chunk
         assert body == b'When in the course of human events, it becomes necessarf arf woof woof woof'
Example #2
0
 def test_stream_appointment_attachment(self, app, fake_auth):
     with mock_legacy_appointment_attachment(app):
         fake_auth.login(coe_advisor)
         stream = get_legacy_attachment_stream('9100000000_00010_1.pdf')['stream']
         body = b''
         for chunk in stream:
             body += chunk
         assert body == b'01001000 01100101 01101100 01101100 01101111 00100000 01010111 01101111 01110010 01101100 01100100'
def download_legacy_appointment_attachment(attachment_id):
    stream_data = get_legacy_attachment_stream(attachment_id)
    if not stream_data or not stream_data['stream']:
        return Response('Sorry, attachment not available.', mimetype='text/html', status=404)
    r = Response(stream_data['stream'])
    r.headers['Content-Type'] = 'application/octet-stream'
    encoding_safe_filename = urllib.parse.quote(stream_data['filename'].encode('utf8'))
    r.headers['Content-Disposition'] = f"attachment; filename*=UTF-8''{encoding_safe_filename}"
    return r
Example #4
0
def download_attachment(attachment_id):
    is_legacy = not is_int(attachment_id)
    id_ = attachment_id if is_legacy else int(attachment_id)
    if is_legacy:
        stream_data = get_legacy_attachment_stream(id_)
    else:
        attachment = NoteAttachment.find_by_id(id_)
        note = attachment and attachment.note
        if note and note.is_private and not current_user.can_access_private_notes:
            raise ForbiddenRequestError('Unauthorized')
        stream_data = get_boa_attachment_stream(attachment)

    if not stream_data or not stream_data['stream']:
        return Response('Sorry, attachment not available.',
                        mimetype='text/html',
                        status=404)
    r = Response(stream_data['stream'])
    r.headers['Content-Type'] = 'application/octet-stream'
    encoding_safe_filename = urllib.parse.quote(
        stream_data['filename'].encode('utf8'))
    r.headers[
        'Content-Disposition'] = f"attachment; filename*=UTF-8''{encoding_safe_filename}"
    return r
Example #5
0
 def test_stream_attachment_handles_file_not_in_s3(self, app, fake_auth, caplog):
     with mock_legacy_note_attachment(app):
         fake_auth.login(coe_advisor)
         assert get_legacy_attachment_stream('11667051_00001_1.pdf')['stream'] is None
         assert "the s3 key 'sis-attachment-path/11667051/11667051_00001_1.pdf' does not exist, or is forbidden" in caplog.text
Example #6
0
 def test_stream_attachment_handles_file_not_in_database(self, app, fake_auth, caplog):
     with mock_legacy_note_attachment(app):
         fake_auth.login(coe_advisor)
         assert get_legacy_attachment_stream('11667051_00002_1.pdf') is None
Example #7
0
 def test_stream_attachment_handles_malformed_filename(self, app):
     with mock_legacy_note_attachment(app):
         assert get_legacy_attachment_stream('h0ax.lol') is None
Example #8
0
def get_zip_stream(filename, notes, student):
    app_timezone = pytz.timezone(app.config['TIMEZONE'])

    def iter_csv():
        def csv_line(_list):
            csv_output = io.StringIO()
            csv.writer(csv_output).writerow(_list)
            return csv_output.getvalue().encode('utf-8')
            csv_output.close()

        yield csv_line([
            'date_created',
            'student_sid',
            'student_name',
            'author_uid',
            'author_csid',
            'author_name',
            'subject',
            'topics',
            'attachments',
            'body',
            'is_private',
            'late_change_request_action',
            'late_change_request_status',
            'late_change_request_term',
            'late_change_request_course',
        ])

        supplemental_calnet_advisor_feeds = get_calnet_users_for_csids(
            app,
            list(
                set([
                    note['author']['sid'] for note in notes
                    if note['author']['sid'] and not note['author']['name']
                ])),
        )
        for note in notes:
            calnet_author = supplemental_calnet_advisor_feeds.get(
                note['author']['sid'])
            if calnet_author:
                calnet_author_name =\
                    calnet_author.get('name') or join_if_present(' ', [calnet_author.get('firstName'), calnet_author.get('lastName')])
                calnet_author_uid = calnet_author.get('uid')
            else:
                calnet_author_name = None
                calnet_author_uid = None
            # strptime expects a timestamp without timezone; ancient date-only legacy notes get a bogus time appended.
            timestamp_created = f"{note['createdAt']}T12:00:00" if len(
                note['createdAt']) == 10 else note['createdAt'][:19]
            datetime_created = pytz.utc.localize(
                datetime.strptime(timestamp_created, '%Y-%m-%dT%H:%M:%S'))
            date_local = datetime_created.astimezone(app_timezone).strftime(
                '%Y-%m-%d')
            e_form = note.get('eForm') or {}
            omit_note_body = note.get(
                'isPrivate') and not current_user.can_access_private_notes
            yield csv_line([
                date_local,
                student['sid'],
                join_if_present(' ', [
                    student.get('first_name', ''),
                    student.get('last_name', '')
                ]),
                (note['author']['uid'] or calnet_author_uid),
                note['author']['sid'],
                (note['author']['name'] or calnet_author_name),
                note['subject'],
                '; '.join([t for t in note['topics'] or []]),
                '' if omit_note_body else '; '.join(
                    [a['displayName'] for a in note['attachments'] or []]),
                '' if omit_note_body else note['body'],
                note.get('isPrivate'),
                e_form.get('action'),
                e_form.get('status'),
                term_name_for_sis_id(e_form.get('term')),
                f"{e_form['sectionId']} {e_form['courseName']} - {e_form['courseTitle']} {e_form['section']}"
                if e_form.get('sectionId') else None,
            ])

    z = zipstream.ZipFile(mode='w', compression=zipstream.ZIP_DEFLATED)
    csv_filename = f'{filename}.csv'
    z.write_iter(csv_filename, iter_csv())

    if notes:
        all_attachment_filenames = {csv_filename}
        for note in notes:
            if not note.get(
                    'isPrivate') or current_user.can_access_private_notes:
                for attachment in note['attachments'] or []:
                    is_legacy_attachment = not is_int(attachment['id'])
                    id_ = attachment['id'] if is_legacy_attachment else int(
                        attachment['id'])
                    if is_legacy_attachment:
                        stream_data = get_legacy_attachment_stream(id_)
                    else:
                        attachment = NoteAttachment.find_by_id(id_)
                        stream_data = get_boa_attachment_stream(attachment)
                    if stream_data:
                        attachment_filename = stream_data['filename']
                        basename, extension = path.splitext(
                            attachment_filename)
                        suffix = 1
                        while attachment_filename in all_attachment_filenames:
                            attachment_filename = f'{basename} ({suffix}){extension}'
                            suffix += 1
                        all_attachment_filenames.add(attachment_filename)
                        z.write_iter(attachment_filename,
                                     stream_data['stream'])
    return z