def test_calls_send_email_to_multiple_addresses(email_app, mandrill):
    with email_app.app_context():

        mandrill.messages.send.return_value = [
            {'_id': '123', 'email': '123'}]

        send_email(
            ["email_address1", "email_address2"],
            "body",
            "api_key",
            "subject",
            "from_email",
            "from_name",
            ["password-resets"]

        )

        assert mandrill.messages.send.call_args[1]['message']['to'] == [
            {'email': "email_address1", 'type': 'to'},
            {'email': "email_address2", 'type': 'to'},
        ]

        assert mandrill.messages.send.call_args[1]['message']['recipient_metadata'] == [
            {'rcpt': "email_address1"},
            {'rcpt': "email_address2"},
        ]
def notify_users(email_api_key, brief):
    logger.info("Notifying users about brief ID: {brief_id} - '{brief_title}'",
                extra={'brief_title': brief['title'], 'brief_id': brief['id']})
    for user in brief['users']:
        try:
            email_body = render_html('email_templates/requirements_closed.html', data={
                'brief_id': brief['id'],
                'brief_title': brief['title'],
                'lot_slug': brief['lotSlug'],
                'framework_slug': brief['frameworkSlug']
            })
            send_email(
                [user['emailAddress'] for user in brief['users'] if user['active']],
                email_body,
                email_api_key,
                u'Next steps for your ‘{}’ requirements'.format(brief['title']),
                '*****@*****.**',
                'Digital Marketplace Admin',
                ['requirements-closed'],
                metadata={'brief_id': brief['id']},
                logger=logger
            )

            return True
        except MandrillException as e:
            logger.error(
                "Email failed to send for brief_id: {brief_id}",
                extra={'error': e, 'brief_id': brief['id']}
            )

            return False
示例#3
0
def send_new_opportunity_email_to_sellers(brief_json, brief_url):
    to_email_addresses = []
    if brief_json.get('sellerEmail'):
        to_email_addresses.append(brief_json['sellerEmail'])
    if brief_json.get('sellerEmailList'):
        to_email_addresses += brief_json['sellerEmailList']

    if to_email_addresses:
        email_body = render_template(
            'emails/seller_new_opportunity.html',
            brief=brief_json,
            brief_url=brief_url
        )

        for to_email_address in to_email_addresses:  # Send emails individually rather than sending to a list of emails

            try:
                send_email(
                    to_email_address,
                    email_body,
                    current_app.config['SELLER_NEW_OPPORTUNITY_EMAIL_SUBJECT'],
                    current_app.config['DM_GENERIC_NOREPLY_EMAIL'],
                    current_app.config['DM_GENERIC_SUPPORT_NAME'],
                )
            except EmailError as e:
                rollbar.report_exc_info()
                current_app.logger.error(
                    'seller new opportunity email failed to send. '
                    'error {error}',
                    extra={
                        'error': six.text_type(e), })
                abort(503, response='Failed to send seller new opportunity email.')
示例#4
0
def send_buyer_account_activation_email(name, email_address, token):
    token = generate_buyer_creation_token(name=name,
                                          email_address=email_address)
    url = url_for('main.create_buyer_account', token=token, _external=True)
    email_body = render_template('emails/create_buyer_user_email.html',
                                 url=url)
    try:
        send_email(
            email_address,
            email_body,
            current_app.config['CREATE_USER_SUBJECT'],
            current_app.config['RESET_PASSWORD_EMAIL_FROM'],
            current_app.config['RESET_PASSWORD_EMAIL_NAME'],
        )
        session['email_sent_to'] = email_address
    except EmailError as e:
        rollbar.report_exc_info()
        current_app.logger.error(
            'buyercreate.fail: Create user email failed to send. '
            'error {error} email_hash {email_hash}',
            extra={
                'error': six.text_type(e),
                'email_hash': hash_email(email_address)
            })
        abort(503, response='Failed to send user creation email.')
示例#5
0
def send_thank_you_email_to_responders(brief, brief_response, brief_response_url):
    ess = brief.get('essentialRequirements', [])
    nth = brief.get('niceToHaveRequirements', [])

    ess = zip(ess, brief_response.get('essentialRequirements', []))
    nth = zip(nth, brief_response.get('niceToHaveRequirements', []))

    to_email_address = brief_response['respondToEmailAddress']

    email_body = render_template(
        'emails/brief_response_submitted.html',
        brief=brief,
        essential_requirements=ess,
        nice_to_have_requirements=nth,
        brief_response=brief_response,
        brief_response_url=brief_response_url,
        frontend_address=current_app.config['FRONTEND_ADDRESS']
    )

    try:
        send_email(
            to_email_address,
            email_body,
            'We\'ve received your application',
            current_app.config['DM_GENERIC_NOREPLY_EMAIL'],
            current_app.config['DM_GENERIC_SUPPORT_NAME'],
        )
    except EmailError as e:
        rollbar.report_exc_info()
        current_app.logger.error(
            'seller response received email failed to send. '
            'error {error}',
            extra={
                'error': six.text_type(e), })
        abort(503, response='Failed to send seller response received email.')
def send_new_opportunity_email_to_sellers(brief_json, brief_url):
    to_email_addresses = []
    if brief_json.get('sellerEmail'):
        to_email_addresses.append(brief_json['sellerEmail'])
    if brief_json.get('sellerEmailList'):
        to_email_addresses += brief_json['sellerEmailList']

    if to_email_addresses:
        email_body = render_template(
            'emails/seller_new_opportunity.html',
            brief=brief_json,
            brief_url=brief_url
        )

        for to_email_address in to_email_addresses:  # Send emails individually rather than sending to a list of emails

            try:
                send_email(
                    to_email_address,
                    email_body,
                    current_app.config['SELLER_NEW_OPPORTUNITY_EMAIL_SUBJECT'],
                    current_app.config['DM_GENERIC_NOREPLY_EMAIL'],
                    current_app.config['DM_GENERIC_SUPPORT_NAME'],
                )
            except EmailError as e:
                rollbar.report_exc_info()
                current_app.logger.error(
                    'seller new opportunity email failed to send. '
                    'error {error}',
                    extra={
                        'error': six.text_type(e), })
                abort(503, response='Failed to send seller new opportunity email.')
示例#7
0
def test_calls_send_email_with_correct_params(email_app, email_client):
    with email_app.app_context():
        send_email(
            "email_address",
            "body",
            "subject",
            "from_email",
            "from_name",
        )

    email_client.send_email.assert_called_once_with(
        ReplyToAddresses=['from_email'],
        Message={
            'Body': {
                'Html': {
                    'Charset': 'UTF-8',
                    'Data': 'body'
                }
            },
            'Subject': {
                'Charset': 'UTF-8',
                'Data': 'subject'
            }
        },
        Destination={
            'ToAddresses': ['email_address'],
            'BccAddresses': [TEST_ARCHIVE_ADDRESS]
        },
        Source='from_name <from_email>',
        ReturnPath=TEST_RETURN_ADDRESS)
def send_brief_clarification_question(data_api_client, brief, clarification_question):
    # Email the question to brief owners
    email_body = render_template(
        "emails/brief_clarification_question.html",
        brief_id=brief['id'],
        brief_name=brief['title'],
        publish_by_date=brief['clarificationQuestionsPublishedBy'],
        framework_slug=brief['frameworkSlug'],
        lot_slug=brief['lotSlug'],
        message=clarification_question,
    )
    try:
        send_email(
            to_email_addresses=get_brief_user_emails(brief),
            email_body=email_body,
            api_key=current_app.config['DM_MANDRILL_API_KEY'],
            subject=u"You’ve received a new supplier question about ‘{}’".format(brief['title']),
            from_email=current_app.config['CLARIFICATION_EMAIL_FROM'],
            from_name="{} Supplier".format(brief['frameworkName']),
            tags=["brief-clarification-question"]
        )
    except MandrillException as e:
        current_app.logger.error(
            "Brief question email failed to send. error={error} supplier_id={supplier_id} brief_id={brief_id}",
            extra={'error': six.text_type(e), 'supplier_id': current_user.supplier_id, 'brief_id': brief['id']}
        )

        abort(503, "Clarification question email failed to send")

    data_api_client.create_audit_event(
        audit_type=AuditTypes.send_clarification_question,
        user=current_user.email_address,
        object_type="briefs",
        object_id=brief['id'],
        data={"question": clarification_question, "briefId": brief['id']})

    # Send the supplier a copy of the question
    supplier_email_body = render_template(
        "emails/brief_clarification_question_confirmation.html",
        brief_id=brief['id'],
        brief_name=brief['title'],
        framework_slug=brief['frameworkSlug'],
        message=clarification_question
    )
    try:
        send_email(
            to_email_addresses=[current_user.email_address],
            email_body=supplier_email_body,
            api_key=current_app.config['DM_MANDRILL_API_KEY'],
            subject=u"Your question about ‘{}’".format(brief['title']),
            from_email=current_app.config['CLARIFICATION_EMAIL_FROM'],
            from_name=current_app.config['CLARIFICATION_EMAIL_NAME'],
            tags=["brief-clarification-question-confirmation"]
        )
    except MandrillException as e:
        current_app.logger.error(
            "Brief question supplier email failed to send. error={error} supplier_id={supplier_id} brief_id={brief_id}",
            extra={'error': six.text_type(e), 'supplier_id': current_user.supplier_id, 'brief_id': brief['id']}
        )
def submit_buyer_invite_request():
    form = auth_forms.BuyerInviteRequestForm(request.form)

    if not form.validate():
        return render_template_with_csrf('auth/buyer-invite-request.html',
                                         status_code=400,
                                         form=form)

    token = generate_buyer_creation_token(**form.data)
    invite_url = url_for('.send_buyer_invite', token=token, _external=True)
    email_body = render_template(
        'emails/buyer_account_invite_request_email.html',
        form=form,
        invite_url=invite_url)
    try:
        send_email(
            current_app.config['BUYER_INVITE_REQUEST_ADMIN_EMAIL'],
            email_body,
            current_app.config['BUYER_INVITE_REQUEST_SUBJECT'],
            current_app.config['BUYER_INVITE_REQUEST_EMAIL_FROM'],
            current_app.config['BUYER_INVITE_REQUEST_EMAIL_NAME'],
        )
    except EmailError as e:
        rollbar.report_exc_info()
        current_app.logger.error(
            'buyerinvite.fail: Buyer account invite request email failed to send. '
            'error {error} invite email_hash {email_hash}',
            extra={
                'error': six.text_type(e),
                'email_hash': hash_email(form.email_address.data)
            })
        abort(503,
              response='Failed to send buyer account invite request email.')

    email_body = render_template(
        'emails/buyer_account_invite_manager_confirmation.html', form=form)
    try:
        send_email(
            form.manager_email.data,
            email_body,
            current_app.config['BUYER_INVITE_MANAGER_CONFIRMATION_SUBJECT'],
            current_app.config['DM_GENERIC_NOREPLY_EMAIL'],
            current_app.config['BUYER_INVITE_MANAGER_CONFIRMATION_NAME'],
        )
    except EmailError as e:
        rollbar.report_exc_info()
        current_app.logger.error(
            'buyerinvite.fail: Buyer account invite request manager email failed to send. '
            'error {error} invite email_hash {email_hash} manager email_hash {manager_email_hash}',
            extra={
                'error': six.text_type(e),
                'email_hash': hash_email(form.email_address.data),
                'manager_email_hash': hash_email(form.manager_email.data)
            })
        abort(503,
              response=
              'Failed to send buyer account invite request manager email.')

    return render_template('auth/buyer-invite-request-complete.html')
示例#10
0
def send_invite_user():
    form = EmailAddressForm(request.form)
    if form.validate():
        email_address = form.email_address.data
        token = generate_supplier_invitation_token(
            name='',
            supplier_code=current_user.supplier_code,
            supplier_name=current_user.supplier_name,
            email_address=email_address
        )
        url = url_for('main.create_user', token=token, _external=True)
        email_body = render_template(
            'emails/invite_user_email.html',
            url=url,
            user=current_user.name,
            supplier=current_user.supplier_name,
            generic_contact_email=current_app.config['GENERIC_CONTACT_EMAIL'])

        try:
            send_email(
                email_address,
                email_body,
                'Invitation to join {} as a team member'.format(
                    current_user
                    .supplier_name
                    .encode("ascii", "ignore")
                    .decode('ascii')
                ),
                current_app.config['INVITE_EMAIL_FROM'],
                current_app.config['INVITE_EMAIL_NAME']
            )
        except EmailError as e:
            rollbar.report_exc_info()
            current_app.logger.error(
                'Invitation email failed to send. '
                'error {error} supplier_code {supplier_code} email_hash {email_hash}',
                extra={'error': six.text_type(e),
                       'supplier_code': current_user.supplier_code,
                       'email_hash': hash_email(current_user.email_address)})
            abort(503, 'Failed to send user invite reset')

        data_api_client.create_audit_event(
            audit_type=AuditTypes.invite_user,
            user=current_user.email_address,
            object_type='suppliers',
            object_id=current_user.supplier_code,
            data={'invitedEmail': email_address},
        )
        form.email_address.data = None
        return render_template_with_csrf(
            'auth/submit_email_address.html',
            form=form,
            invited_user=email_address)
    else:
        return render_template_with_csrf(
            'auth/submit_email_address.html',
            status_code=400,
            form=form)
示例#11
0
文件: helpers.py 项目: das2011/orams
def notify_team(subject, body, more_info_url=None):
    """
    Generic routine for making simple notifications to the Marketplace team.

    Notification messages should be very simple so that they're compatible with a variety of backends.
    """
    # ensure strings can be encoded as ascii only
    body = body.encode("ascii", "ignore").decode('ascii')
    subject = subject.encode("ascii", "ignore").decode('ascii')

    if current_app.config.get('DM_TEAM_SLACK_WEBHOOK', None):
        slack_body = slack_escape(body)
        if more_info_url:
            slack_body += '\n' + more_info_url
        data = {
            'attachments': [{
                'title':
                subject,
                'text':
                slack_body,
                'fallback':
                '{} - {} {}'.format(subject, body, more_info_url),
            }],
            'username':
            '******',
        }
        response = requests.post(current_app.config['DM_TEAM_SLACK_WEBHOOK'],
                                 json=data)
        if response.status_code != 200:
            msg = 'Failed to send notification to Slack channel: {} - {}'.format(
                response.status_code, response.text)
            current_app.logger.error(msg)

    if current_app.config.get('DM_TEAM_EMAIL', None):
        email_body = render_template_string(
            '<p>{{ body }}</p>{% if more_info_url %}<a href="{{ more_info_url }}">More info</a>{% endif %}',
            body=body,
            more_info_url=more_info_url)
        try:
            send_email(
                current_app.config['DM_TEAM_EMAIL'],
                email_body,
                subject,
                current_app.config['DM_GENERIC_NOREPLY_EMAIL'],
                current_app.config['DM_GENERIC_ADMIN_NAME'],
            )
        except EmailError as error:
            try:
                msg = error.message
            except AttributeError:
                msg = str(error)
            rollbar.report_exc_info()
            current_app.logger.error(
                'Failed to send notification email: {}'.format(msg))
示例#12
0
def invite_user_to_application(application_id):
    json_payload = request.get_json(True)
    email_address = json_payload.get('email')
    name = json_payload.get('name')

    application = data_api_client.get_application(application_id)
    if not application:
        return abort(404)

    user_json = data_api_client.get_user(email_address=email_address)

    if user_json:
        return abort(400)

    token_data = {'id': application_id, 'name': name, 'email_address': email_address}
    token = generate_token(token_data, current_app.config['SECRET_KEY'], current_app.config['INVITE_EMAIL_SALT'])
    try:
        token = token.decode()
    except AttributeError:
        pass

    url = '{}://{}/{}/{}'.format(
        current_app.config['DM_HTTP_PROTO'],
        current_app.config['DM_MAIN_SERVER_NAME'],
        current_app.config['CREATE_APPLICANT_PATH'],
        format(token)
    )

    email_body = render_template(
        'emails/invite_user_application_email.html',
        url=url,
        supplier=application['application']['name'],
        name=name,
    )

    try:
        send_email(
            email_address,
            email_body,
            current_app.config['INVITE_EMAIL_SUBJECT'],
            current_app.config['INVITE_EMAIL_FROM'],
            current_app.config['INVITE_EMAIL_NAME'],
        )
    except EmailError as e:
        current_app.logger.error(
            'Invitation email failed to send error {} to {} supplier {} supplier code {} '.format(
                str(e),
                email_address,
                current_user.supplier_name,
                current_user.supplier_code)
        )
        abort(503, "Failed to send user invite reset")

    return jsonify(success=True), 200
def send_invite_user():
    form = EmailAddressForm()

    if form.validate_on_submit():
        token = generate_token(
            {
                "supplier_id": current_user.supplier_id,
                "supplier_name": current_user.supplier_name,
                "email_address": form.email_address.data
            },
            current_app.config['SHARED_EMAIL_KEY'],
            current_app.config['INVITE_EMAIL_SALT']
        )
        url = url_for('main.create_user', encoded_token=token, _external=True)
        email_body = render_template(
            "emails/invite_user_email.html",
            url=url,
            user=current_user.name,
            supplier=current_user.supplier_name)

        try:
            send_email(
                form.email_address.data,
                email_body,
                current_app.config['DM_MANDRILL_API_KEY'],
                current_app.config['INVITE_EMAIL_SUBJECT'],
                current_app.config['INVITE_EMAIL_FROM'],
                current_app.config['INVITE_EMAIL_NAME'],
                ["user-invite"]
            )
        except MandrillException as e:
            current_app.logger.error(
                "Invitation email failed to send. "
                "error {error} supplier_id {supplier_id} email_hash {email_hash}",
                extra={'error': six.text_type(e),
                       'supplier_id': current_user.supplier_id,
                       'email_hash': hash_email(current_user.email_address)})
            abort(503, "Failed to send user invite reset")

        data_api_client.create_audit_event(
            audit_type=AuditTypes.invite_user,
            user=current_user.email_address,
            object_type='suppliers',
            object_id=current_user.supplier_id,
            data={'invitedEmail': form.email_address.data},
        )

        flash('user_invited', 'success')
        return redirect(url_for('.list_users'))
    else:
        return render_template(
            "auth/submit_email_address.html",
            form=form), 400
def send_invite_user():
    form = EmailAddressForm()

    if form.validate_on_submit():
        token = generate_token(
            {
                "supplier_id": current_user.supplier_id,
                "supplier_name": current_user.supplier_name,
                "email_address": form.email_address.data
            },
            current_app.config['SHARED_EMAIL_KEY'],
            current_app.config['INVITE_EMAIL_SALT']
        )
        url = url_for('main.create_user', encoded_token=token, _external=True)
        email_body = render_template(
            "emails/invite_user_email.html",
            url=url,
            user=current_user.name,
            supplier=current_user.supplier_name)

        try:
            send_email(
                form.email_address.data,
                email_body,
                current_app.config['DM_MANDRILL_API_KEY'],
                current_app.config['INVITE_EMAIL_SUBJECT'],
                current_app.config['INVITE_EMAIL_FROM'],
                current_app.config['INVITE_EMAIL_NAME'],
                ["user-invite"]
            )
        except MandrillException as e:
            current_app.logger.error(
                "Invitation email failed to send. "
                "error {error} supplier_id {supplier_id} email_hash {email_hash}",
                extra={'error': six.text_type(e),
                       'supplier_id': current_user.supplier_id,
                       'email_hash': hash_email(current_user.email_address)})
            abort(503, "Failed to send user invite reset")

        data_api_client.create_audit_event(
            audit_type=AuditTypes.invite_user,
            user=current_user.email_address,
            object_type='suppliers',
            object_id=current_user.supplier_id,
            data={'invitedEmail': form.email_address.data},
        )

        flash('user_invited', 'success')
        return redirect(url_for('.list_users'))
    else:
        return render_template(
            "auth/submit_email_address.html",
            form=form), 400
def authorise_application(id):
    application = data_api_client.get_application(id)
    if not can_user_view_application(application):
        abort(403, 'Not authorised to access application')
    if is_application_submitted(application):
        return redirect(url_for('.submit_application', id=id))

    application = application['application']
    url = url_for('main.render_application',
                  id=id,
                  step='submit',
                  _external=True)
    user_json = data_api_client.get_user(email_address=application['email'])
    template = 'emails/create_authorise_email_has_account.html'

    if not user_json:
        token_data = {
            'id': id,
            'name': application['representative'],
            'email_address': application['email']
        }
        token = generate_application_invitation_token(token_data)
        url = url_for('main.render_create_application',
                      token=token,
                      _external=True)
        template = 'emails/create_authorise_email_no_account.html'

    email_body = render_template(
        template,
        url=url,
        name=application['representative'],
        business_name=application['name'],
    )

    try:
        send_email(application['email'], email_body,
                   current_app.config['AUTHREP_EMAIL_SUBJECT'],
                   current_app.config['INVITE_EMAIL_FROM'],
                   current_app.config['INVITE_EMAIL_NAME'])
    except EmailError as e:
        rollbar.report_exc_info()
        current_app.logger.error(
            'Authorisation email failed to send. '
            'error {error}',
            extra={'error': six.text_type(e)})
        abort(503, 'Failed to send user invite reset')

    return render_template('suppliers/authorisation_submitted.html',
                           name=application['representative'],
                           email_address=application['email'],
                           subject=current_app.config['AUTHREP_EMAIL_SUBJECT'])
def test_calls_send_email_to_multiple_addresses(email_app, email_client):
    with email_app.app_context():
        send_email(
            ["email_address1", "email_address2"],
            "body",
            "subject",
            "from_email",
            "from_name",
        )

        assert email_client.send_email.call_args[1]['Destination']['ToAddresses'] == [
            "email_address1",
            "email_address2",
        ]
def test_should_throw_exception_if_email_client_fails(email_app, email_client):
    with email_app.app_context():
        email_client.send_email.side_effect = ClientError(
            {'Error': {'Message': "this is an error"}}, ""
        )

        with pytest.raises(EmailError):
            send_email(
                "email_address",
                "body",
                "subject",
                "from_email",
                "from_name",
            )
def invite_user_to_application(application_id):
    json_payload = request.get_json(True)
    email_address = json_payload.get('email')
    name = json_payload.get('name')

    application = data_api_client.get_application(application_id)
    if not application:
        return abort(404)

    user_json = data_api_client.get_user(email_address=email_address)

    if user_json:
        return abort(400)

    token_data = {'id': application_id, 'name': name, 'email_address': email_address}
    token = generate_token(token_data, current_app.config['SECRET_KEY'], current_app.config['INVITE_EMAIL_SALT'])

    url = '{}://{}/{}/{}'.format(
        current_app.config['DM_HTTP_PROTO'],
        current_app.config['DM_MAIN_SERVER_NAME'],
        current_app.config['CREATE_APPLICANT_PATH'],
        format(token)
    )

    email_body = render_template(
        'emails/invite_user_application_email.html',
        url=url,
        supplier=application['application']['name'],
        name=name,
    )

    try:
        send_email(
            email_address,
            email_body,
            current_app.config['INVITE_EMAIL_SUBJECT'],
            current_app.config['INVITE_EMAIL_FROM'],
            current_app.config['INVITE_EMAIL_NAME'],
        )
    except EmailError as e:
        current_app.logger.error(
            'Invitation email failed to send error {} to {} supplier {} supplier code {} '.format(
                str(e),
                email_address,
                current_user.supplier_name,
                current_user.supplier_code)
        )
        abort(503, "Failed to send user invite reset")

    return jsonify(success=True), 200
def notify_team(subject, body, more_info_url=None):
    """
    Generic routine for making simple notifications to the Marketplace team.

    Notification messages should be very simple so that they're compatible with a variety of backends.
    """
    # ensure strings can be encoded as ascii only
    body = body.encode("ascii", "ignore").decode('ascii')
    subject = subject.encode("ascii", "ignore").decode('ascii')

    if current_app.config.get('DM_TEAM_SLACK_WEBHOOK', None):
        slack_body = slack_escape(body)
        if more_info_url:
            slack_body += '\n' + more_info_url
        data = {
            'attachments': [{
                'title': subject,
                'text': slack_body,
                'fallback': '{} - {} {}'.format(subject, body, more_info_url),
            }],
            'username': '******',
        }
        response = requests.post(
            current_app.config['DM_TEAM_SLACK_WEBHOOK'],
            json=data
        )
        if response.status_code != 200:
            msg = 'Failed to send notification to Slack channel: {} - {}'.format(response.status_code, response.text)
            current_app.logger.error(msg)

    if current_app.config.get('DM_TEAM_EMAIL', None):
        email_body = render_template_string(
            '<p>{{ body }}</p>{% if more_info_url %}<a href="{{ more_info_url }}">More info</a>{% endif %}',
            body=body, more_info_url=more_info_url
        )
        try:
            send_email(
                current_app.config['DM_TEAM_EMAIL'],
                email_body,
                subject,
                current_app.config['DM_GENERIC_NOREPLY_EMAIL'],
                current_app.config['DM_GENERIC_ADMIN_NAME'],
            )
        except EmailError as e:
            try:
                msg = e.message
            except AttributeError:
                msg = str(e)
            rollbar.report_exc_info()
            current_app.logger.error('Failed to send notification email: {}'.format(msg))
def send_invite_user():
    form = EmailAddressForm(request.form)
    if form.validate():
        token = generate_supplier_invitation_token(
            name='',
            supplier_code=current_user.supplier_code,
            supplier_name=current_user.supplier_name,
            email_address=form.email_address.data
        )
        url = url_for('main.create_user', token=token, _external=True)
        email_body = render_template(
            'emails/invite_user_email.html',
            url=url,
            user=current_user.name,
            supplier=current_user.supplier_name)

        try:
            send_email(
                form.email_address.data,
                email_body,
                current_app.config['INVITE_EMAIL_SUBJECT'],
                current_app.config['INVITE_EMAIL_FROM'],
                current_app.config['INVITE_EMAIL_NAME']
            )
        except EmailError as e:
            rollbar.report_exc_info()
            current_app.logger.error(
                'Invitation email failed to send. '
                'error {error} supplier_code {supplier_code} email_hash {email_hash}',
                extra={'error': six.text_type(e),
                       'supplier_code': current_user.supplier_code,
                       'email_hash': hash_email(current_user.email_address)})
            abort(503, 'Failed to send user invite reset')

        data_api_client.create_audit_event(
            audit_type=AuditTypes.invite_user,
            user=current_user.email_address,
            object_type='suppliers',
            object_id=current_user.supplier_code,
            data={'invitedEmail': form.email_address.data},
        )

        flash('user_invited', 'success')
        return redirect(url_for('.list_users'))
    else:
        return render_template_with_csrf(
            'auth/submit_email_address.html',
            status_code=400,
            form=form)
def authorise_application(id):
    application = data_api_client.get_application(id)
    if not can_user_view_application(application):
        abort(403, 'Not authorised to access application')
    if is_application_submitted(application):
        return redirect(url_for('.submit_application', id=id))

    application = application['application']
    url = url_for('main.render_application', id=id, step='submit', _external=True)
    user_json = data_api_client.get_user(email_address=application['email'])
    template = 'emails/create_authorise_email_has_account.html'

    if not user_json:
        token_data = {'id': id, 'name': application['representative'], 'email_address': application['email']}
        token = generate_application_invitation_token(token_data)
        url = url_for('main.render_create_application', token=token, _external=True)
        template = 'emails/create_authorise_email_no_account.html'

    email_body = render_template(
        template,
        url=url,
        name=application['representative'],
        business_name=application['name'],
    )

    try:
        send_email(
            application['email'],
            email_body,
            current_app.config['AUTHREP_EMAIL_SUBJECT'],
            current_app.config['INVITE_EMAIL_FROM'],
            current_app.config['INVITE_EMAIL_NAME']
        )
    except EmailError as e:
        rollbar.report_exc_info()
        current_app.logger.error(
            'Authorisation email failed to send. '
            'error {error}',
            extra={'error': six.text_type(e)}
        )
        abort(503, 'Failed to send user invite reset')

    return render_template('suppliers/authorisation_submitted.html',
                           name=application['representative'],
                           email_address=application['email'],
                           subject=current_app.config['AUTHREP_EMAIL_SUBJECT'])
def test_calls_send_email_missing_config(email_app_missing_config, email_client):
    with email_app_missing_config.app_context():
        send_email(
            "email_address",
            "body",
            "subject",
            "from_email",
            "from_name",
        )

    email_client.send_email.assert_called_once_with(
        ReplyToAddresses=['from_email'],
        Message={'Body': {'Html': {'Charset': 'UTF-8', 'Data': b'body'}},
                 'Subject': {'Charset': 'UTF-8', 'Data': b'subject'}},
        Destination={'ToAddresses': ['email_address'], 'BccAddresses': [TEST_ARCHIVE_ADDRESS]},
        Source=u'from_name <from_email>',
        ReturnPath='from_email'
    )
def test_should_throw_exception_if_mandrill_fails(email_app, mandrill):
    with email_app.app_context():

        mandrill.messages.send.side_effect = Error("this is an error")

        try:
            send_email(
                "email_address",
                "body",
                "api_key",
                "subject",
                "from_email",
                "from_name",
                ["password-resets"]

            )
        except MandrillException as e:
            assert str(e) == "this is an error"
def test_calls_send_email_with_alternative_reply_to(email_app, email_client):
    with email_app.app_context():
        send_email(
            "email_address",
            "body",
            "subject",
            "from_email",
            "from_name",
            reply_to="reply_address"
        )

    email_client.send_email.assert_called_once_with(
        ReplyToAddresses=['reply_address'],
        Message={'Body': {'Html': {'Charset': 'UTF-8', 'Data': b'body'}},
                 'Subject': {'Charset': 'UTF-8', 'Data': b'subject'}},
        Destination={'ToAddresses': ['email_address'], 'BccAddresses': [TEST_ARCHIVE_ADDRESS]},
        Source=u'from_name <from_email>',
        ReturnPath=TEST_RETURN_ADDRESS
    )
示例#25
0
def send_buyer_onboarding_email(name, email_address):
    email_body = render_template('emails/buyer_onboarding.html', name=name)
    try:
        send_email(
            email_address,
            email_body,
            'Welcome to the Digital Marketplace',
            current_app.config['RESET_PASSWORD_EMAIL_FROM'],
            current_app.config['RESET_PASSWORD_EMAIL_NAME'],
        )
        session['email_sent_to'] = email_address
    except EmailError as e:
        rollbar.report_exc_info()
        current_app.logger.error(
            'buyeronboarding.fail: Buyer onboarding email failed to send. '
            'error {error} email_hash {email_hash}',
            extra={
                'error': six.text_type(e),
                'email_hash': hash_email(email_address)})
        abort(503, response='Failed to send buyer onboarding email.')
def send_buyer_onboarding_email(name, email_address):
    email_body = render_template('emails/buyer_onboarding.html', name=name)
    try:
        send_email(
            email_address,
            email_body,
            'Welcome to the Digital Marketplace',
            current_app.config['RESET_PASSWORD_EMAIL_FROM'],
            current_app.config['RESET_PASSWORD_EMAIL_NAME'],
        )
        session['email_sent_to'] = email_address
    except EmailError as e:
        rollbar.report_exc_info()
        current_app.logger.error(
            'buyeronboarding.fail: Buyer onboarding email failed to send. '
            'error {error} email_hash {email_hash}',
            extra={
                'error': six.text_type(e),
                'email_hash': hash_email(email_address)})
        abort(503, response='Failed to send buyer onboarding email.')
def send_supplier_invite(name, email_address, supplier_code, supplier_name):
    """Send invite email to new supplier from Marketplace admin and record in API's log.

    Raises EmailError if failed to send, or HTTPError if logging failed.
    """
    token = generate_supplier_invitation_token(name, email_address,
                                               supplier_code, supplier_name)
    activation_url = url_for('main.create_user',
                             token=token,
                             _external=True,
                             _scheme=current_app.config['DM_HTTP_PROTO'])
    subject = current_app.config['NEW_SUPPLIER_INVITE_SUBJECT']
    email_body = render_template('emails/new_supplier_invite_email.html',
                                 subject=subject,
                                 name=name,
                                 activation_url=activation_url)
    send_email(email_address, email_body, subject,
               current_app.config['DM_GENERIC_NOREPLY_EMAIL'],
               current_app.config['DM_GENERIC_ADMIN_NAME'])
    data_api_client.record_supplier_invite(supplier_code=supplier_code,
                                           email_address=email_address)
def send_seller_signup_email():
    user = from_response(request)
    if user.get('user_type') != 'seller':
        return abort(400)

    fields = ['name', 'email_address']
    errors = validate_form_data(user, fields)
    if errors:
        return start_seller_signup(user, errors)

    duplicate = data_api_client.req.users().checkduplicates().post(data={"email_address": user['email_address']})
    if duplicate.get('duplicate'):
        return render_template('auth/seller-signup-email-sent.html', email_address=user['email_address'])

    token = generate_application_invitation_token(user)
    url = url_for('main.render_create_application', token=token, _external=True)
    email_body = render_template(
        'emails/create_seller_user_email.html',
        url=url,
        user=user,
    )

    try:
        send_email(
            user['email_address'],
            email_body,
            current_app.config['INVITE_EMAIL_SUBJECT'],
            current_app.config['INVITE_EMAIL_FROM'],
            current_app.config['INVITE_EMAIL_NAME']
        )
    except EmailError as e:
        rollbar.report_exc_info()
        current_app.logger.error(
            'Invitation email failed to send. '
            'error {error}',
            extra={'error': six.text_type(e)}
        )
        abort(503, 'Failed to send user invite reset')

    return render_template('auth/seller-signup-email-sent.html', email_address=user['email_address'])
def send_buyer_account_activation_email(name, email_address, token):
    token = generate_buyer_creation_token(name=name, email_address=email_address)
    url = url_for('main.create_buyer_account', token=token, _external=True)
    email_body = render_template('emails/create_buyer_user_email.html', url=url)
    try:
        send_email(
            email_address,
            email_body,
            current_app.config['CREATE_USER_SUBJECT'],
            current_app.config['RESET_PASSWORD_EMAIL_FROM'],
            current_app.config['RESET_PASSWORD_EMAIL_NAME'],
        )
        session['email_sent_to'] = email_address
    except EmailError as e:
        rollbar.report_exc_info()
        current_app.logger.error(
            'buyercreate.fail: Create user email failed to send. '
            'error {error} email_hash {email_hash}',
            extra={
                'error': six.text_type(e),
                'email_hash': hash_email(email_address)})
        abort(503, response='Failed to send user creation email.')
def test_calls_send_email_with_correct_params(email_app, mandrill):
    with email_app.app_context():

        mandrill.messages.send.return_value = [
            {'_id': '123', 'email': '123'}]

        expected_call = {
            'html': "body",
            'subject': "subject",
            'from_email': "from_email",
            'from_name': "from_name",
            'to': [{
                'email': "email_address",
                'type': 'to'
            }],
            'important': False,
            'track_opens': False,
            'track_clicks': False,
            'auto_text': True,
            'tags': ['password-resets'],
            'headers': {'Reply-To': "from_email"},  # noqa
            'preserve_recipients': False,
            'recipient_metadata': [{
                'rcpt': "email_address"
            }]
        }

        send_email(
            "email_address",
            "body",
            "api_key",
            "subject",
            "from_email",
            "from_name",
            ["password-resets"]

        )

        mandrill.messages.send.assert_called_once_with(message=expected_call, async=True)
def send_brief_clarification_question(data_api_client, brief,
                                      clarification_question):
    # Email the question to brief owners
    subject = u"You received a new question for ‘{}’".format(brief['title'])
    email_body = render_template(
        "emails/brief_clarification_question.html",
        brief_id=brief['id'],
        brief_name=brief['title'],
        publish_by_date=brief['clarificationQuestionsPublishedBy'],
        framework_slug=brief['frameworkSlug'],
        lot_slug=brief['lotSlug'],
        message=clarification_question,
        frontend_address=current_app.config['FRONTEND_ADDRESS'],
        supplier_name=current_user.supplier_name)
    try:
        send_email(to_email_addresses=get_brief_user_emails(brief),
                   email_body=email_body,
                   subject=subject,
                   from_email=current_app.config['CLARIFICATION_EMAIL_FROM'],
                   from_name="{} Supplier".format(brief['frameworkName']))
    except EmailError as e:
        current_app.logger.error(
            "Brief question email failed to send. error={error} supplier_code={supplier_code} brief_id={brief_id}",
            extra={
                'error': six.text_type(e),
                'supplier_code': current_user.supplier_code,
                'brief_id': brief['id']
            })

        abort(503, "Clarification question email failed to send")

    data_api_client.create_audit_event(
        audit_type=AuditTypes.send_clarification_question,
        user=current_user.email_address,
        object_type="briefs",
        object_id=brief['id'],
        data={
            "question": clarification_question,
            "briefId": brief['id']
        })

    # Send the supplier a copy of the question
    supplier_email_body = render_template(
        "emails/brief_clarification_question_confirmation.html",
        brief_id=brief['id'],
        brief_name=brief['title'],
        message=clarification_question,
        supplier_name=current_user.name,
        frontend_address=current_app.config['FRONTEND_ADDRESS'],
        brief_organisation=brief['organisation'])
    try:
        send_email(
            to_email_addresses=[current_user.email_address],
            email_body=supplier_email_body,
            subject=u"You submitted a question for {} ({}) successfully".
            format(brief['title'], brief['id']),
            from_email=current_app.config['CLARIFICATION_EMAIL_FROM'],
            from_name=current_app.config['CLARIFICATION_EMAIL_NAME'])
    except EmailError as e:
        current_app.logger.error(
            'Brief question supplier email failed to send. error={error} supplier_code={supplier_code} brief_id={brief_id}',  # noqa
            extra={
                'error': six.text_type(e),
                'supplier_code': current_user.supplier_code,
                'brief_id': brief['id']
            })
示例#32
0
def framework_dashboard(framework_slug):
    framework = get_framework(data_api_client, framework_slug)

    if request.method == 'POST':
        register_interest_in_framework(data_api_client, framework_slug)
        supplier_users = data_api_client.find_users(
            supplier_id=current_user.supplier_id)

        try:
            email_body = render_template(
                'emails/{}_application_started.html'.format(framework_slug))
            send_email([
                user['emailAddress']
                for user in supplier_users['users'] if user['active']
            ], email_body, current_app.config['DM_MANDRILL_API_KEY'],
                       'You have started your {} application'.format(
                           framework['name']),
                       current_app.config['CLARIFICATION_EMAIL_FROM'],
                       current_app.config['CLARIFICATION_EMAIL_NAME'],
                       ['{}-application-started'.format(framework_slug)])
        except MandrillException as e:
            current_app.logger.error(
                "Application started email failed to send: {error}, supplier_id: {supplier_id}",
                extra={
                    'error': six.text_type(e),
                    'supplier_id': current_user.supplier_id
                })

    drafts, complete_drafts = get_drafts(data_api_client, framework_slug)

    supplier_framework_info = get_supplier_framework_info(
        data_api_client, framework_slug)
    declaration_status = get_declaration_status_from_info(
        supplier_framework_info)
    supplier_is_on_framework = get_supplier_on_framework_from_info(
        supplier_framework_info)

    # Do not show a framework dashboard for earlier G-Cloud iterations
    if declaration_status == 'unstarted' and framework['status'] == 'live':
        abort(404)

    key_list = s3.S3(current_app.config['DM_COMMUNICATIONS_BUCKET']).list(
        framework_slug, load_timestamps=True)
    key_list.reverse()

    first_page = content_loader.get_manifest(
        framework_slug, 'declaration').get_next_editable_section_id()

    # filenames
    supplier_pack_filename = '{}-supplier-pack.zip'.format(framework_slug)
    result_letter_filename = RESULT_LETTER_FILENAME
    countersigned_agreement_file = None
    if countersigned_framework_agreement_exists_in_bucket(
            framework_slug, current_app.config['DM_AGREEMENTS_BUCKET']):
        countersigned_agreement_file = COUNTERSIGNED_AGREEMENT_FILENAME

    return render_template(
        "frameworks/dashboard.html",
        application_made=supplier_is_on_framework
        or (len(complete_drafts) > 0 and declaration_status == 'complete'),
        completed_lots=[
            {
                'name':
                lot['name'],
                'complete_count':
                count_drafts_by_lot(complete_drafts, lot['slug']),
                'one_service_limit':
                lot['oneServiceLimit'],
                'unit':
                'lab' if framework['slug']
                == 'digital-outcomes-and-specialists' else 'service',
                'unit_plural':
                'labs' if framework['slug']
                == 'digital-outcomes-and-specialists' else 'service'
                # TODO: ^ make this dynamic, eg, lab, service, unit
            } for lot in framework['lots']
            if count_drafts_by_lot(complete_drafts, lot['slug'])
        ],
        counts={
            "draft": len(drafts),
            "complete": len(complete_drafts)
        },
        dates=content_loader.get_message(framework_slug, 'dates'),
        declaration_status=declaration_status,
        first_page_of_declaration=first_page,
        framework=framework,
        last_modified={
            'supplier_pack':
            get_last_modified_from_first_matching_file(
                key_list, framework_slug,
                "communications/{}".format(supplier_pack_filename)),
            'supplier_updates':
            get_last_modified_from_first_matching_file(
                key_list, framework_slug, "communications/updates/")
        },
        supplier_is_on_framework=supplier_is_on_framework,
        supplier_pack_filename=supplier_pack_filename,
        result_letter_filename=result_letter_filename,
        countersigned_agreement_file=countersigned_agreement_file), 200
def submit_company_summary():

    required_fields = [
        "email_address",
        "phone_number",
        "contact_name",
        "duns_number",
        "company_name",
        "account_email_address"
    ]

    missing_fields = [field for field in required_fields if field not in session]

    if not missing_fields:
        supplier = {
            "name": session["company_name"],
            "dunsNumber": str(session["duns_number"]),
            "contacts": [{
                "email": session["email_address"],
                "phoneNumber": session["phone_number"],
                "contactName": session["contact_name"]
            }]
        }

        if session.get("companies_house_number", None):
            supplier["companiesHouseNumber"] = session.get("companies_house_number")

        account_email_address = session.get("account_email_address", None)

        supplier = data_api_client.create_supplier(supplier)['supplier']
        session.clear()
        session['email_company_name'] = supplier['name']
        session['email_supplier_code'] = supplier['id']

        token = generate_supplier_invitation_token(
            name='',
            email_address=account_email_address,
            supplier_code=session['email_supplier_code'],
            supplier_name=session['email_company_name']
        )

        url = url_for('main.create_user', token=token, _external=True)

        email_body = render_template(
            "emails/create_user_email.html",
            company_name=session['email_company_name'],
            url=url
        )
        try:
            send_email(
                account_email_address,
                email_body,
                current_app.config['CREATE_USER_SUBJECT'],
                current_app.config['RESET_PASSWORD_EMAIL_FROM'],
                current_app.config['RESET_PASSWORD_EMAIL_NAME'],
                ["user-creation"]
            )
            session['email_sent_to'] = account_email_address
        except EmailError as e:
            rollbar.report_exc_info()
            current_app.logger.error(
                "suppliercreate.fail: Create user email failed to send. "
                "error {error} supplier_code {supplier_code} email_hash {email_hash}",
                extra={
                    'error': six.text_type(e),
                    'supplier_code': session['email_supplier_code'],
                    'email_hash': hash_email(account_email_address)})
            abort(503, "Failed to send user creation email")

        data_api_client.create_audit_event(
            audit_type=AuditTypes.invite_user,
            object_type='suppliers',
            object_id=session['email_supplier_code'],
            data={'invitedEmail': account_email_address})

        return redirect(url_for('.create_your_account_complete'), 302)
    else:
        return render_template_with_csrf(
            "suppliers/company_summary.html",
            status_code=400,
            missing_fields=missing_fields
        )
def contract_review(framework_slug):
    framework = get_framework(data_api_client, framework_slug)
    supplier_framework = return_supplier_framework_info_if_on_framework_or_abort(data_api_client, framework_slug)
    agreements_bucket = s3.S3(current_app.config['DM_AGREEMENTS_BUCKET'])
    signature_page = get_most_recently_uploaded_agreement_file_or_none(agreements_bucket, framework_slug)

    # if supplier_framework doesn't have a name or a role or the agreement file, then 404
    if not (
        supplier_framework['agreementDetails'] and
        supplier_framework['agreementDetails'].get('signerName') and
        supplier_framework['agreementDetails'].get('signerRole') and
        signature_page
    ):
        abort(404)

    form = ContractReviewForm(request.form)
    form_errors = None

    if request.method == 'POST':
        if form.validate():
            data_api_client.register_framework_agreement_returned(
                current_user.supplier_code, framework_slug, current_user.email_address, current_user.id
            )

            email_recipients = [supplier_framework['declaration']['primaryContactEmail']]
            if supplier_framework['declaration']['primaryContactEmail'].lower() != current_user.email_address.lower():
                email_recipients.append(current_user.email_address)

            try:
                email_body = render_template(
                    'emails/framework_agreement_with_framework_version_returned.html',
                    framework_name=framework['name'],
                    framework_slug=framework['slug'],
                    framework_live_date=content_loader.get_message(framework_slug, 'dates')['framework_live_date'],  # noqa
                )

                send_email(
                    email_recipients,
                    email_body,
                    'Your {} signature page has been received'.format(framework['name']),
                    current_app.config["DM_GENERIC_NOREPLY_EMAIL"],
                    current_app.config["FRAMEWORK_AGREEMENT_RETURNED_NAME"],
                    ['{}-framework-agreement'.format(framework_slug)],
                )
            except EmailError as e:
                rollbar.report_exc_info()
                current_app.logger.error(
                    "Framework agreement email failed to send. "
                    "error {error} supplier_code {supplier_code} email_hash {email_hash}",
                    extra={'error': six.text_type(e),
                           'supplier_code': current_user.supplier_code,
                           'email_hash': hash_email(current_user.email_address)})
                abort(503, "Framework agreement email failed to send")

            session.pop('signature_page', None)

            flash(
                'Your framework agreement has been returned to the Crown Commercial Service to be countersigned.',
                'success'
            )

            return redirect(url_for(".framework_dashboard", framework_slug=framework_slug))

        else:
            form_errors = [
                {'question': form['authorisation'].label.text, 'input_name': 'authorisation'}
            ]

    form.authorisation.description = u"I have the authority to return this agreement on behalf of {}.".format(
        supplier_framework['declaration']['nameOfOrganisation']
    )

    status_code = 400 if form_errors else 200
    return render_template_with_csrf(
        "frameworks/contract_review.html",
        status_code=status_code,
        form=form,
        form_errors=form_errors,
        framework=framework,
        signature_page=signature_page,
        supplier_framework=supplier_framework,
    )
def framework_dashboard(framework_slug):
    framework = get_framework(data_api_client, framework_slug)
    if request.method == 'POST':
        register_interest_in_framework(data_api_client, framework_slug)
        supplier_users = data_api_client.find_users(supplier_code=current_user.supplier_code)

        try:
            email_body = render_template('emails/{}_application_started.html'.format(framework_slug))
            send_email(
                [user['emailAddress'] for user in supplier_users['users'] if user['active']],
                email_body,
                'You have started your {} application'.format(framework['name']),
                current_app.config['CLARIFICATION_EMAIL_FROM'],
                current_app.config['CLARIFICATION_EMAIL_NAME'],
                ['{}-application-started'.format(framework_slug)]
            )
        except EmailError as e:
            rollbar.report_exc_info()
            current_app.logger.error(
                "Application started email failed to send: {error}, supplier_code: {supplier_code}",
                extra={'error': six.text_type(e), 'supplier_code': current_user.supplier_code}
            )

    drafts, complete_drafts = get_drafts(data_api_client, framework_slug)

    supplier_framework_info = get_supplier_framework_info(data_api_client, framework_slug)
    declaration_status = get_declaration_status_from_info(supplier_framework_info)
    supplier_is_on_framework = get_supplier_on_framework_from_info(supplier_framework_info)

    # Do not show a framework dashboard for earlier G-Cloud iterations
    if declaration_status == 'unstarted' and framework['status'] == 'live':
        abort(404)

    key_list = s3.S3(current_app.config['DM_COMMUNICATIONS_BUCKET']).list(framework_slug, load_timestamps=True)
    key_list.reverse()

    first_page = content_loader.get_manifest(
        framework_slug, 'declaration'
    ).get_next_editable_section_id()

    # filenames
    supplier_pack_filename = '{}-supplier-pack.zip'.format(framework_slug)
    result_letter_filename = RESULT_LETTER_FILENAME
    countersigned_agreement_file = None
    if countersigned_framework_agreement_exists_in_bucket(framework_slug, current_app.config['DM_AGREEMENTS_BUCKET']):
        countersigned_agreement_file = COUNTERSIGNED_AGREEMENT_FILENAME

    application_made = supplier_is_on_framework or (len(complete_drafts) > 0 and declaration_status == 'complete')
    lots_with_completed_drafts = [lot for lot in framework['lots'] if count_drafts_by_lot(complete_drafts, lot['slug'])]

    last_modified = {
        'supplier_pack': get_last_modified_from_first_matching_file(
            key_list, framework_slug, "communications/{}".format(supplier_pack_filename)
        ),
        'supplier_updates': get_last_modified_from_first_matching_file(
            key_list, framework_slug, "communications/updates/"
        )
    }

    # if supplier has returned agreement for framework with framework_agreement_version, show contract_submitted page
    if supplier_is_on_framework and framework['frameworkAgreementVersion'] and supplier_framework_info['agreementReturned']:  # noqa
        agreements_bucket = s3.S3(current_app.config['DM_AGREEMENTS_BUCKET'])
        signature_page = get_most_recently_uploaded_agreement_file_or_none(agreements_bucket, framework_slug)

        return render_template(
            "frameworks/contract_submitted.html",
            framework=framework,
            framework_live_date=content_loader.get_message(framework_slug, 'dates')['framework_live_date'],
            document_name='{}.{}'.format(SIGNED_AGREEMENT_PREFIX, signature_page['ext']),
            supplier_framework=supplier_framework_info,
            supplier_pack_filename=supplier_pack_filename,
            last_modified=last_modified
        ), 200

    return render_template(
        "frameworks/dashboard.html",
        application_made=application_made,
        completed_lots=tuple(
            dict(lot, complete_count=count_drafts_by_lot(complete_drafts, lot['slug']))
            for lot in lots_with_completed_drafts
        ),
        counts={
            "draft": len(drafts),
            "complete": len(complete_drafts)
        },
        dates=content_loader.get_message(framework_slug, 'dates'),
        declaration_status=declaration_status,
        first_page_of_declaration=first_page,
        framework=framework,
        last_modified=last_modified,
        supplier_is_on_framework=supplier_is_on_framework,
        supplier_pack_filename=supplier_pack_filename,
        result_letter_filename=result_letter_filename,
        countersigned_agreement_file=countersigned_agreement_file
    ), 200
示例#36
0
def view_contract_variation(framework_slug, variation_slug):
    framework = get_framework(data_api_client,
                              framework_slug,
                              allowed_statuses=['live'])
    supplier_framework = return_supplier_framework_info_if_on_framework_or_abort(
        data_api_client, framework_slug)
    variation_details = framework.get('variations', {}).get(variation_slug)

    # 404 if framework doesn't have contract variation
    if not variation_details:
        abort(404)

    # 404 if agreement hasn't been returned yet
    if not supplier_framework['agreementReturned']:
        abort(404)

    agreed_details = supplier_framework['agreedVariations'].get(
        variation_slug, {})
    variation_content_name = 'contract_variation_{}'.format(variation_slug)
    content_loader.load_messages(framework_slug, [variation_content_name])
    form = AcceptAgreementVariationForm()
    form_errors = None

    # Do not call API or send email if already agreed to
    if request.method == 'POST' and not agreed_details.get("agreedAt"):
        if form.validate_on_submit():
            # Set variation as agreed to in database
            data_api_client.agree_framework_variation(
                current_user.supplier_id, framework_slug, variation_slug,
                current_user.id, current_user.email_address)

            # Send email confirming accepted
            try:
                email_body = render_template(
                    'emails/{}_variation_{}_agreed.html'.format(
                        framework_slug, variation_slug))
                send_email(
                    returned_agreement_email_recipients(supplier_framework),
                    email_body, current_app.config['DM_MANDRILL_API_KEY'],
                    '{}: you have accepted the proposed contract variation'.
                    format(framework['name']),
                    current_app.config['CLARIFICATION_EMAIL_FROM'],
                    current_app.config['CLARIFICATION_EMAIL_NAME'],
                    ['{}-variation-accepted'.format(framework_slug)])
            except MandrillException as e:
                current_app.logger.error(
                    "Variation agreed email failed to send: {error}, supplier_id: {supplier_id}",
                    extra={
                        'error': six.text_type(e),
                        'supplier_id': current_user.supplier_id
                    })
            flash('variation_accepted')
            return redirect(
                url_for(".view_contract_variation",
                        framework_slug=framework_slug,
                        variation_slug=variation_slug))
        else:
            form_errors = [{
                'question': form['accept_changes'].label.text,
                'input_name': 'accept_changes'
            }]

    supplier_name = supplier_framework['declaration']['nameOfOrganisation']
    variation_content = content_loader.get_message(
        framework_slug,
        variation_content_name).filter({'supplier_name': supplier_name})

    return render_template(
        "frameworks/contract_variation.html",
        form=form,
        form_errors=form_errors,
        framework=framework,
        supplier_framework=supplier_framework,
        variation_details=variation_details,
        variation=variation_content,
        agreed_details=agreed_details,
        supplier_name=supplier_name,
    ), 400 if form_errors else 200
示例#37
0
def upload_framework_agreement(framework_slug):
    """
    This is the route used to upload agreements for pre-G-Cloud 8 frameworks
    """
    framework = get_framework(data_api_client,
                              framework_slug,
                              allowed_statuses=['standstill', 'live'])
    # if there's a frameworkAgreementVersion key it means we're on G-Cloud 8 or higher and shouldn't be using this route
    if framework.get('frameworkAgreementVersion'):
        abort(404)

    supplier_framework = return_supplier_framework_info_if_on_framework_or_abort(
        data_api_client, framework_slug)

    upload_error = None
    if not file_is_less_than_5mb(request.files['agreement']):
        upload_error = "Document must be less than 5MB"
    elif file_is_empty(request.files['agreement']):
        upload_error = "Document must not be empty"

    if upload_error is not None:
        return render_template(
            "frameworks/agreement.html",
            framework=framework,
            supplier_framework=supplier_framework,
            upload_error=upload_error,
            agreement_filename=AGREEMENT_FILENAME,
        ), 400

    agreements_bucket = s3.S3(current_app.config['DM_AGREEMENTS_BUCKET'])
    extension = get_extension(request.files['agreement'].filename)

    path = generate_timestamped_document_upload_path(
        framework_slug, current_user.supplier_id, 'agreements',
        '{}{}'.format(SIGNED_AGREEMENT_PREFIX, extension))
    agreements_bucket.save(
        path,
        request.files['agreement'],
        acl='private',
        download_filename='{}-{}-{}{}'.format(
            sanitise_supplier_name(current_user.supplier_name),
            current_user.supplier_id, SIGNED_AGREEMENT_PREFIX, extension))

    agreement_id = data_api_client.create_framework_agreement(
        current_user.supplier_id, framework_slug,
        current_user.email_address)['agreement']['id']
    data_api_client.update_framework_agreement(agreement_id,
                                               {"signedAgreementPath": path},
                                               current_user.email_address)
    data_api_client.sign_framework_agreement(
        agreement_id, current_user.email_address,
        {"uploaderUserId": current_user.id})

    try:
        email_body = render_template(
            'emails/framework_agreement_uploaded.html',
            framework_name=framework['name'],
            supplier_name=current_user.supplier_name,
            supplier_id=current_user.supplier_id,
            user_name=current_user.name)
        send_email(
            current_app.config['DM_FRAMEWORK_AGREEMENTS_EMAIL'],
            email_body,
            current_app.config['DM_MANDRILL_API_KEY'],
            '{} framework agreement'.format(framework['name']),
            current_app.config["DM_GENERIC_NOREPLY_EMAIL"],
            '{} Supplier'.format(framework['name']),
            ['{}-framework-agreement'.format(framework_slug)],
            reply_to=current_user.email_address,
        )
    except MandrillException as e:
        current_app.logger.error(
            "Framework agreement email failed to send. "
            "error {error} supplier_id {supplier_id} email_hash {email_hash}",
            extra={
                'error': six.text_type(e),
                'supplier_id': current_user.supplier_id,
                'email_hash': hash_email(current_user.email_address)
            })
        abort(503, "Framework agreement email failed to send")

    return redirect(
        url_for('.framework_agreement', framework_slug=framework_slug))
示例#38
0
def framework_dashboard(framework_slug):
    framework = get_framework(data_api_client, framework_slug)
    if request.method == 'POST':
        register_interest_in_framework(data_api_client, framework_slug)
        supplier_users = data_api_client.find_users(
            supplier_id=current_user.supplier_id)

        try:
            email_body = render_template(
                'emails/{}_application_started.html'.format(framework_slug))
            send_email([
                user['emailAddress']
                for user in supplier_users['users'] if user['active']
            ], email_body, current_app.config['DM_MANDRILL_API_KEY'],
                       'You started a {} application'.format(
                           framework['name']),
                       current_app.config['CLARIFICATION_EMAIL_FROM'],
                       current_app.config['CLARIFICATION_EMAIL_NAME'],
                       ['{}-application-started'.format(framework_slug)])
        except MandrillException as e:
            current_app.logger.error(
                "Application started email failed to send: {error}, supplier_id: {supplier_id}",
                extra={
                    'error': six.text_type(e),
                    'supplier_id': current_user.supplier_id
                })

    drafts, complete_drafts = get_drafts(data_api_client, framework_slug)

    supplier_framework_info = get_supplier_framework_info(
        data_api_client, framework_slug)
    declaration_status = get_declaration_status_from_info(
        supplier_framework_info)
    supplier_is_on_framework = get_supplier_on_framework_from_info(
        supplier_framework_info)

    # Do not show a framework dashboard for earlier G-Cloud iterations
    if declaration_status == 'unstarted' and framework['status'] == 'live':
        abort(404)

    application_made = supplier_is_on_framework or (
        len(complete_drafts) > 0 and declaration_status == 'complete')
    lots_with_completed_drafts = [
        lot for lot in framework['lots']
        if count_drafts_by_lot(complete_drafts, lot['slug'])
    ]

    first_page = content_loader.get_manifest(
        framework_slug, 'declaration').get_next_editable_section_id()
    framework_dates = content_loader.get_message(framework_slug, 'dates')
    framework_urls = content_loader.get_message(framework_slug, 'urls')

    # filenames
    result_letter_filename = RESULT_LETTER_FILENAME

    countersigned_agreement_file = None
    if supplier_framework_info and supplier_framework_info['countersignedPath']:
        countersigned_agreement_file = degenerate_document_path_and_return_doc_name(
            supplier_framework_info['countersignedPath'])

    signed_agreement_document_name = None
    if supplier_is_on_framework and supplier_framework_info[
            'agreementReturned']:
        signed_agreement_document_name = degenerate_document_path_and_return_doc_name(
            supplier_framework_info['agreementPath'])

    key_list = s3.S3(current_app.config['DM_COMMUNICATIONS_BUCKET']).list(
        framework_slug, load_timestamps=True)
    key_list.reverse()

    base_communications_files = {
        "invitation": {
            "path": "communications/",
            "filename": "{}-invitation.pdf".format(framework_slug),
        },
        "proposed_agreement": {
            "path":
            "communications/",
            "filename":
            "{}-proposed-framework-agreement.pdf".format(framework_slug),
        },
        "final_agreement": {
            "path": "communications/",
            "filename":
            "{}-final-framework-agreement.pdf".format(framework_slug),
        },
        "proposed_call_off": {
            "path": "communications/",
            "filename": "{}-proposed-call-off.pdf".format(framework_slug),
        },
        "final_call_off": {
            "path": "communications/",
            "filename": "{}-final-call-off.pdf".format(framework_slug),
        },
        "reporting_template": {
            "path": "communications/",
            "filename": "{}-reporting-template.xls".format(framework_slug),
        },
        "supplier_updates": {
            "path": "communications/updates/",
        },
    }
    # now we annotate these with last_modified information which also tells us whether the file exists
    communications_files = {
        label: dict(
            d,
            last_modified=get_last_modified_from_first_matching_file(
                key_list,
                framework_slug,
                d["path"] + d.get("filename", ""),
            ),
        )
        for label, d in six.iteritems(base_communications_files)
    }

    return render_template(
        "frameworks/dashboard.html",
        application_made=application_made,
        communications_files=communications_files,
        completed_lots=tuple(
            dict(lot,
                 complete_count=count_drafts_by_lot(complete_drafts,
                                                    lot['slug']))
            for lot in lots_with_completed_drafts),
        countersigned_agreement_file=countersigned_agreement_file,
        counts={
            "draft": len(drafts),
            "complete": len(complete_drafts)
        },
        declaration_status=declaration_status,
        signed_agreement_document_name=signed_agreement_document_name,
        first_page_of_declaration=first_page,
        framework=framework,
        framework_dates=framework_dates,
        framework_urls=framework_urls,
        result_letter_filename=result_letter_filename,
        supplier_framework=supplier_framework_info,
        supplier_is_on_framework=supplier_is_on_framework,
    ), 200
示例#39
0
def invite_user(supplier_id):
    invite_form = EmailAddressForm()

    try:
        suppliers = data_api_client.get_supplier(supplier_id)
        users = data_api_client.find_users(supplier_id)
    except HTTPError as e:
        current_app.logger.error(str(e), supplier_id)
        if e.status_code != 404:
            raise
        else:
            abort(404, "Supplier not found")

    if invite_form.validate_on_submit():
        token = generate_token(
            {
                "supplier_id": supplier_id,
                "supplier_name": suppliers['suppliers']['name'],
                "email_address": invite_form.email_address.data
            },
            current_app.config['SHARED_EMAIL_KEY'],
            current_app.config['INVITE_EMAIL_SALT']
        )

        url = "{}{}/{}".format(
            request.url_root,
            current_app.config['CREATE_USER_PATH'],
            format(token)
        )

        email_body = render_template(
            "emails/invite_user_email.html",
            url=url,
            supplier=suppliers['suppliers']['name'])

        try:
            send_email(
                invite_form.email_address.data,
                email_body,
                current_app.config['DM_MANDRILL_API_KEY'],
                current_app.config['INVITE_EMAIL_SUBJECT'],
                current_app.config['INVITE_EMAIL_FROM'],
                current_app.config['INVITE_EMAIL_NAME'],
                ["user-invite"]
            )
        except MandrillException as e:
            current_app.logger.error(
                "Invitation email failed to send error {} to {} supplier {} supplier id {} ".format(
                    str(e),
                    invite_form.email_address.data,
                    current_user.supplier_name,
                    current_user.supplier_id)
            )
            abort(503, "Failed to send user invite reset")

        data_api_client.create_audit_event(
            audit_type=AuditTypes.invite_user,
            user=current_user.email_address,
            object_type='suppliers',
            object_id=supplier_id,
            data={'invitedEmail': invite_form.email_address.data})

        flash('user_invited', 'success')
        return redirect(url_for('.find_supplier_users', supplier_id=supplier_id))
    else:
        return render_template(
            "view_supplier_users.html",
            invite_form=invite_form,
            move_user_form=MoveUserForm(),
            users=users["users"],
            supplier=suppliers["suppliers"]
        ), 400
def send_brief_clarification_question(data_api_client, brief,
                                      clarification_question):
    # Email the question to brief owners
    email_body = render_template(
        "emails/brief_clarification_question.html",
        brief_id=brief['id'],
        brief_name=brief['title'],
        publish_by_date=brief['clarificationQuestionsPublishedBy'],
        framework_slug=brief['frameworkSlug'],
        lot_slug=brief['lotSlug'],
        message=clarification_question,
    )
    try:
        send_email(
            to_email_addresses=get_brief_user_emails(brief),
            email_body=email_body,
            api_key=current_app.config['DM_MANDRILL_API_KEY'],
            subject=u"You’ve received a new supplier question about ‘{}’".
            format(brief['title']),
            from_email=current_app.config['CLARIFICATION_EMAIL_FROM'],
            from_name="{} Supplier".format(brief['frameworkName']),
            tags=["brief-clarification-question"])
    except MandrillException as e:
        current_app.logger.error(
            "Brief question email failed to send. error={error} supplier_id={supplier_id} brief_id={brief_id}",
            extra={
                'error': six.text_type(e),
                'supplier_id': current_user.supplier_id,
                'brief_id': brief['id']
            })

        abort(503, "Clarification question email failed to send")

    data_api_client.create_audit_event(
        audit_type=AuditTypes.send_clarification_question,
        user=current_user.email_address,
        object_type="briefs",
        object_id=brief['id'],
        data={
            "question": clarification_question,
            "briefId": brief['id']
        })

    # Send the supplier a copy of the question
    supplier_email_body = render_template(
        "emails/brief_clarification_question_confirmation.html",
        brief_id=brief['id'],
        brief_name=brief['title'],
        framework_slug=brief['frameworkSlug'],
        message=clarification_question)
    try:
        send_email(to_email_addresses=[current_user.email_address],
                   email_body=supplier_email_body,
                   api_key=current_app.config['DM_MANDRILL_API_KEY'],
                   subject=u"Your question about ‘{}’".format(brief['title']),
                   from_email=current_app.config['CLARIFICATION_EMAIL_FROM'],
                   from_name=current_app.config['CLARIFICATION_EMAIL_NAME'],
                   tags=["brief-clarification-question-confirmation"])
    except MandrillException as e:
        current_app.logger.error(
            "Brief question supplier email failed to send. error={error} supplier_id={supplier_id} brief_id={brief_id}",
            extra={
                'error': six.text_type(e),
                'supplier_id': current_user.supplier_id,
                'brief_id': brief['id']
            })
def submit_company_summary():

    required_fields = [
        "email_address", "phone_number", "contact_name", "duns_number",
        "company_name", "account_email_address"
    ]

    missing_fields = [
        field for field in required_fields if field not in session
    ]

    if not missing_fields:
        supplier = {
            "name":
            session["company_name"],
            "dunsNumber":
            str(session["duns_number"]),
            "contactInformation": [{
                "email": session["email_address"],
                "phoneNumber": session["phone_number"],
                "contactName": session["contact_name"]
            }]
        }

        if session.get("companies_house_number", None):
            supplier["companiesHouseNumber"] = session.get(
                "companies_house_number")

        account_email_address = session.get("account_email_address", None)

        supplier = data_api_client.create_supplier(supplier)
        session.clear()
        session['email_company_name'] = supplier['suppliers']['name']
        session['email_supplier_id'] = supplier['suppliers']['id']

        token = generate_token(
            {
                "email_address": account_email_address,
                "supplier_id": session['email_supplier_id'],
                "supplier_name": session['email_company_name']
            }, current_app.config['SHARED_EMAIL_KEY'],
            current_app.config['INVITE_EMAIL_SALT'])

        url = url_for('main.create_user', encoded_token=token, _external=True)

        email_body = render_template(
            "emails/create_user_email.html",
            company_name=session['email_company_name'],
            url=url)
        try:
            send_email(account_email_address, email_body,
                       current_app.config['DM_MANDRILL_API_KEY'],
                       current_app.config['CREATE_USER_SUBJECT'],
                       current_app.config['RESET_PASSWORD_EMAIL_FROM'],
                       current_app.config['RESET_PASSWORD_EMAIL_NAME'],
                       ["user-creation"])
            session['email_sent_to'] = account_email_address
        except MandrillException as e:
            current_app.logger.error(
                "suppliercreate.fail: Create user email failed to send. "
                "error {error} supplier_id {supplier_id} email_hash {email_hash}",
                extra={
                    'error': six.text_type(e),
                    'supplier_id': session['email_supplier_id'],
                    'email_hash': hash_email(account_email_address)
                })
            abort(503, "Failed to send user creation email")

        data_api_client.create_audit_event(
            audit_type=AuditTypes.invite_user,
            object_type='suppliers',
            object_id=session['email_supplier_id'],
            data={'invitedEmail': account_email_address})

        return redirect(url_for('.create_your_account_complete'), 302)
    else:
        return render_template("suppliers/company_summary.html",
                               missing_fields=missing_fields), 400
示例#42
0
def framework_updates_email_clarification_question(framework_slug):
    framework = get_framework(data_api_client, framework_slug)

    # Stripped input should not empty
    clarification_question = request.form.get(CLARIFICATION_QUESTION_NAME,
                                              '').strip()

    if not clarification_question:
        return framework_updates(framework_slug, "Question cannot be empty")
    elif len(clarification_question) > 5000:
        return framework_updates(
            framework_slug,
            error_message="Question cannot be longer than 5000 characters",
            default_textbox_value=clarification_question)

    # Submit email to Zendesk so the question can be answered
    # Fail if this email does not send
    if framework['clarificationQuestionsOpen']:
        subject = "{} clarification question".format(framework['name'])
        to_address = current_app.config['DM_CLARIFICATION_QUESTION_EMAIL']
        from_address = "suppliers+{}@digitalmarketplace.service.gov.uk".format(
            framework['slug'])
        email_body = render_template("emails/clarification_question.html",
                                     supplier_name=current_user.supplier_name,
                                     user_name=current_user.name,
                                     message=clarification_question)
        tags = ["clarification-question"]
    else:
        subject = "{} application question".format(framework['name'])
        to_address = current_app.config['DM_FOLLOW_UP_EMAIL_TO']
        from_address = current_user.email_address
        email_body = render_template("emails/follow_up_question.html",
                                     supplier_name=current_user.supplier_name,
                                     user_name=current_user.name,
                                     framework_name=framework['name'],
                                     message=clarification_question)
        tags = ["application-question"]
    try:
        send_email(
            to_address,
            email_body,
            current_app.config['DM_MANDRILL_API_KEY'],
            subject,
            current_app.config["DM_GENERIC_NOREPLY_EMAIL"],
            "{} Supplier".format(framework['name']),
            tags,
            reply_to=from_address,
        )
    except MandrillException as e:
        current_app.logger.error(
            "{framework} clarification question email failed to send. "
            "error {error} supplier_id {supplier_id} email_hash {email_hash}",
            extra={
                'error': six.text_type(e),
                'framework': framework['slug'],
                'supplier_id': current_user.supplier_id,
                'email_hash': hash_email(current_user.email_address)
            })
        abort(503, "Clarification question email failed to send")

    if framework['clarificationQuestionsOpen']:
        # Send confirmation email to the user who submitted the question
        # No need to fail if this email does not send
        subject = current_app.config['CLARIFICATION_EMAIL_SUBJECT']
        tags = ["clarification-question-confirm"]
        audit_type = AuditTypes.send_clarification_question
        email_body = render_template(
            "emails/clarification_question_submitted.html",
            user_name=current_user.name,
            framework_name=framework['name'],
            message=clarification_question)
        try:
            send_email(current_user.email_address, email_body,
                       current_app.config['DM_MANDRILL_API_KEY'], subject,
                       current_app.config['CLARIFICATION_EMAIL_FROM'],
                       current_app.config['CLARIFICATION_EMAIL_NAME'], tags)
        except MandrillException as e:
            current_app.logger.error(
                "{framework} clarification question confirmation email failed to send. "
                "error {error} supplier_id {supplier_id} email_hash {email_hash}",
                extra={
                    'error': six.text_type(e),
                    'framework': framework['slug'],
                    'supplier_id': current_user.supplier_id,
                    'email_hash': hash_email(current_user.email_address)
                })
    else:
        # Do not send confirmation email to the user who submitted the question
        # Zendesk will handle this instead
        audit_type = AuditTypes.send_application_question

    data_api_client.create_audit_event(audit_type=audit_type,
                                       user=current_user.email_address,
                                       object_type="suppliers",
                                       object_id=current_user.supplier_id,
                                       data={
                                           "question": clarification_question,
                                           'framework': framework['slug']
                                       })

    flash('message_sent', 'success')
    return framework_updates(framework['slug'])
def send_reset_password_email():
    form = auth_forms.EmailAddressForm(request.form)
    if form.validate():
        email_address = form.email_address.data
        user_json = data_api_client.get_user(email_address=email_address)

        if user_json is not None:

            user = User.from_json(user_json)

            token = generate_token(
                {
                    "user": user.id,
                    "email": user.email_address
                }, current_app.config['SECRET_KEY'],
                current_app.config['RESET_PASSWORD_SALT'])

            url = url_for('main.reset_password', token=token, _external=True)

            email_body = render_template("emails/reset_password_email.html",
                                         url=url,
                                         locked=user.locked)

            try:
                send_email(
                    user.email_address,
                    email_body,
                    current_app.config['RESET_PASSWORD_EMAIL_SUBJECT'],
                    current_app.config['RESET_PASSWORD_EMAIL_FROM'],
                    current_app.config['RESET_PASSWORD_EMAIL_NAME'],
                )
            except EmailError as e:
                rollbar.report_exc_info()
                current_app.logger.error(
                    "Password reset email failed to send. "
                    "error {error} email_hash {email_hash}",
                    extra={
                        'error': six.text_type(e),
                        'email_hash': hash_email(user.email_address)
                    })
                abort(503, response="Failed to send password reset.")

            current_app.logger.info(
                "login.reset-email.sent: Sending password reset email for "
                "supplier_code {supplier_code} email_hash {email_hash}",
                extra={
                    'supplier_code': user.supplier_code,
                    'email_hash': hash_email(user.email_address)
                })
        else:
            current_app.logger.info(
                "login.reset-email.invalid-email: "
                "Password reset request for invalid supplier email {email_hash}",
                extra={'email_hash': hash_email(email_address)})

        flash('email_sent')
        return redirect(url_for('.request_password_reset'))
    else:
        return render_template_with_csrf("auth/request-password-reset.html",
                                         status_code=400,
                                         form=form)
示例#44
0
def contract_review(framework_slug, agreement_id):
    framework = get_framework(data_api_client,
                              framework_slug,
                              allowed_statuses=['standstill', 'live'])
    # if there's no frameworkAgreementVersion key it means we're pre-G-Cloud 8 and shouldn't be using this route
    if not framework.get('frameworkAgreementVersion'):
        abort(404)
    supplier_framework = return_supplier_framework_info_if_on_framework_or_abort(
        data_api_client, framework_slug)
    agreement = data_api_client.get_framework_agreement(
        agreement_id)['agreement']
    check_agreement_is_related_to_supplier_framework_or_abort(
        agreement, supplier_framework)

    # if framework agreement doesn't have a name or a role or the agreement file, then 404
    if not (agreement.get('signedAgreementDetails')
            and agreement['signedAgreementDetails'].get('signerName')
            and agreement['signedAgreementDetails'].get('signerRole')
            and agreement.get('signedAgreementPath')):
        abort(404)

    agreements_bucket = s3.S3(current_app.config['DM_AGREEMENTS_BUCKET'])
    signature_page = agreements_bucket.get_key(
        agreement['signedAgreementPath'])

    form = ContractReviewForm()
    form_errors = None

    if request.method == 'POST':
        if form.validate_on_submit():
            data_api_client.sign_framework_agreement(
                agreement_id, current_user.email_address,
                {'uploaderUserId': current_user.id})

            try:
                email_body = render_template(
                    'emails/framework_agreement_with_framework_version_returned.html',
                    framework_name=framework['name'],
                    framework_slug=framework['slug'],
                    framework_live_date=content_loader.get_message(
                        framework_slug,
                        'dates')['framework_live_date'],  # noqa
                )

                send_email(
                    returned_agreement_email_recipients(supplier_framework),
                    email_body,
                    current_app.config['DM_MANDRILL_API_KEY'],
                    'Your {} signature page has been received'.format(
                        framework['name']),
                    current_app.config["DM_GENERIC_NOREPLY_EMAIL"],
                    current_app.config["FRAMEWORK_AGREEMENT_RETURNED_NAME"],
                    ['{}-framework-agreement'.format(framework_slug)],
                )
            except MandrillException as e:
                current_app.logger.error(
                    "Framework agreement email failed to send. "
                    "error {error} supplier_id {supplier_id} email_hash {email_hash}",
                    extra={
                        'error': six.text_type(e),
                        'supplier_id': current_user.supplier_id,
                        'email_hash': hash_email(current_user.email_address)
                    })
                abort(503, "Framework agreement email failed to send")

            session.pop('signature_page', None)

            flash(
                'Your framework agreement has been returned to the Crown Commercial Service to be countersigned.',
                'success')

            if feature.is_active('CONTRACT_VARIATION'):
                # Redirect to contract variation if it has not been signed
                if (framework.get('variations')
                        and not supplier_framework['agreedVariations']):
                    variation_slug = list(framework['variations'].keys())[0]
                    return redirect(
                        url_for('.view_contract_variation',
                                framework_slug=framework_slug,
                                variation_slug=variation_slug))

            return redirect(
                url_for(".framework_dashboard", framework_slug=framework_slug))

        else:
            form_errors = [{
                'question': form['authorisation'].label.text,
                'input_name': 'authorisation'
            }]

    form.authorisation.description = u"I have the authority to return this agreement on behalf of {}.".format(
        supplier_framework['declaration']['nameOfOrganisation'])

    return render_template(
        "frameworks/contract_review.html",
        agreement=agreement,
        form=form,
        form_errors=form_errors,
        framework=framework,
        signature_page=signature_page,
        supplier_framework=supplier_framework,
    ), 400 if form_errors else 200
def framework_dashboard(framework_slug):
    framework = get_framework(data_api_client, framework_slug)
    if request.method == 'POST':
        register_interest_in_framework(data_api_client, framework_slug)
        supplier_users = data_api_client.find_users(
            supplier_code=current_user.supplier_code)

        try:
            email_body = render_template(
                'emails/{}_application_started.html'.format(framework_slug))
            send_email([
                user['emailAddress']
                for user in supplier_users['users'] if user['active']
            ], email_body, 'You have started your {} application'.format(
                framework['name']),
                       current_app.config['CLARIFICATION_EMAIL_FROM'],
                       current_app.config['CLARIFICATION_EMAIL_NAME'],
                       ['{}-application-started'.format(framework_slug)])
        except EmailError as e:
            rollbar.report_exc_info()
            current_app.logger.error(
                "Application started email failed to send: {error}, supplier_code: {supplier_code}",
                extra={
                    'error': six.text_type(e),
                    'supplier_code': current_user.supplier_code
                })

    drafts, complete_drafts = get_drafts(data_api_client, framework_slug)

    supplier_framework_info = get_supplier_framework_info(
        data_api_client, framework_slug)
    declaration_status = get_declaration_status_from_info(
        supplier_framework_info)
    supplier_is_on_framework = get_supplier_on_framework_from_info(
        supplier_framework_info)

    # Do not show a framework dashboard for earlier G-Cloud iterations
    if declaration_status == 'unstarted' and framework['status'] == 'live':
        abort(404)

    key_list = s3.S3(current_app.config['DM_COMMUNICATIONS_BUCKET']).list(
        framework_slug, load_timestamps=True)
    key_list.reverse()

    first_page = content_loader.get_manifest(
        framework_slug, 'declaration').get_next_editable_section_id()

    # filenames
    supplier_pack_filename = '{}-supplier-pack.zip'.format(framework_slug)
    result_letter_filename = RESULT_LETTER_FILENAME
    countersigned_agreement_file = None
    if countersigned_framework_agreement_exists_in_bucket(
            framework_slug, current_app.config['DM_AGREEMENTS_BUCKET']):
        countersigned_agreement_file = COUNTERSIGNED_AGREEMENT_FILENAME

    application_made = supplier_is_on_framework or (
        len(complete_drafts) > 0 and declaration_status == 'complete')
    lots_with_completed_drafts = [
        lot for lot in framework['lots']
        if count_drafts_by_lot(complete_drafts, lot['slug'])
    ]

    last_modified = {
        'supplier_pack':
        get_last_modified_from_first_matching_file(
            key_list, framework_slug,
            "communications/{}".format(supplier_pack_filename)),
        'supplier_updates':
        get_last_modified_from_first_matching_file(key_list, framework_slug,
                                                   "communications/updates/")
    }

    # if supplier has returned agreement for framework with framework_agreement_version, show contract_submitted page
    if supplier_is_on_framework and framework[
            'frameworkAgreementVersion'] and supplier_framework_info[
                'agreementReturned']:  # noqa
        agreements_bucket = s3.S3(current_app.config['DM_AGREEMENTS_BUCKET'])
        signature_page = get_most_recently_uploaded_agreement_file_or_none(
            agreements_bucket, framework_slug)

        return render_template("frameworks/contract_submitted.html",
                               framework=framework,
                               framework_live_date=content_loader.get_message(
                                   framework_slug,
                                   'dates')['framework_live_date'],
                               document_name='{}.{}'.format(
                                   SIGNED_AGREEMENT_PREFIX,
                                   signature_page['ext']),
                               supplier_framework=supplier_framework_info,
                               supplier_pack_filename=supplier_pack_filename,
                               last_modified=last_modified), 200

    return render_template(
        "frameworks/dashboard.html",
        application_made=application_made,
        completed_lots=tuple(
            dict(lot,
                 complete_count=count_drafts_by_lot(complete_drafts,
                                                    lot['slug']))
            for lot in lots_with_completed_drafts),
        counts={
            "draft": len(drafts),
            "complete": len(complete_drafts)
        },
        dates=content_loader.get_message(framework_slug, 'dates'),
        declaration_status=declaration_status,
        first_page_of_declaration=first_page,
        framework=framework,
        last_modified=last_modified,
        supplier_is_on_framework=supplier_is_on_framework,
        supplier_pack_filename=supplier_pack_filename,
        result_letter_filename=result_letter_filename,
        countersigned_agreement_file=countersigned_agreement_file), 200
def framework_updates_email_clarification_question(framework_slug):
    framework = get_framework(data_api_client, framework_slug)

    # Stripped input should not empty
    clarification_question = request.form.get(CLARIFICATION_QUESTION_NAME, '').strip()

    if not clarification_question:
        return framework_updates(framework_slug, "Question cannot be empty")
    elif len(clarification_question) > 5000:
        return framework_updates(
            framework_slug,
            error_message="Question cannot be longer than 5000 characters",
            default_textbox_value=clarification_question
        )

    # Submit email to Zendesk so the question can be answered
    # Fail if this email does not send
    if framework['clarificationQuestionsOpen']:
        subject = "{} clarification question".format(framework['name'])
        to_address = current_app.config['DM_CLARIFICATION_QUESTION_EMAIL']
        # FIXME: we have login_required, so should we use the supplier's email address instead?
        # Decide once we're actually using this feature.
        from_address = 'marketplace+{}@digital.gov.au'.format(framework['slug'])
        email_body = render_template(
            "emails/clarification_question.html",
            supplier_name=current_user.supplier_name,
            user_name=current_user.name,
            message=clarification_question
        )
        tags = ["clarification-question"]
    else:
        subject = "{} application question".format(framework['name'])
        to_address = current_app.config['DM_FOLLOW_UP_EMAIL_TO']
        from_address = current_user.email_address
        email_body = render_template(
            "emails/follow_up_question.html",
            supplier_name=current_user.supplier_name,
            user_name=current_user.name,
            framework_name=framework['name'],
            message=clarification_question
        )
        tags = ["application-question"]
    try:
        send_email(
            to_address,
            email_body,
            subject,
            current_app.config["DM_GENERIC_NOREPLY_EMAIL"],
            "{} Supplier".format(framework['name']),
            tags,
            reply_to=from_address,
        )
    except EmailError as e:
        rollbar.report_exc_info()
        current_app.logger.error(
            "{framework} clarification question email failed to send. "
            "error {error} supplier_code {supplier_code} email_hash {email_hash}",
            extra={'error': six.text_type(e),
                   'framework': framework['slug'],
                   'supplier_code': current_user.supplier_code,
                   'email_hash': hash_email(current_user.email_address)})
        abort(503, "Clarification question email failed to send")

    if framework['clarificationQuestionsOpen']:
        # Send confirmation email to the user who submitted the question
        # No need to fail if this email does not send
        subject = current_app.config['CLARIFICATION_EMAIL_SUBJECT']
        tags = ["clarification-question-confirm"]
        audit_type = AuditTypes.send_clarification_question
        email_body = render_template(
            "emails/clarification_question_submitted.html",
            user_name=current_user.name,
            framework_name=framework['name'],
            message=clarification_question
        )
        try:
            send_email(
                current_user.email_address,
                email_body,
                subject,
                current_app.config['CLARIFICATION_EMAIL_FROM'],
                current_app.config['CLARIFICATION_EMAIL_NAME'],
                tags
            )
        except EmailError as e:
            rollbar.report_exc_info()
            current_app.logger.error(
                "{framework} clarification question confirmation email failed to send. "
                "error {error} supplier_code {supplier_code} email_hash {email_hash}",
                extra={'error': six.text_type(e),
                       'framework': framework['slug'],
                       'supplier_code': current_user.supplier_code,
                       'email_hash': hash_email(current_user.email_address)})
    else:
        # Do not send confirmation email to the user who submitted the question
        # Zendesk will handle this instead
        audit_type = AuditTypes.send_application_question

    data_api_client.create_audit_event(
        audit_type=audit_type,
        user=current_user.email_address,
        object_type="suppliers",
        object_id=current_user.supplier_code,
        data={"question": clarification_question, 'framework': framework['slug']})

    flash('message_sent', 'success')
    return framework_updates(framework['slug'])
def upload_framework_agreement(framework_slug):
    framework = get_framework(data_api_client,
                              framework_slug,
                              allowed_statuses=['standstill', 'live'])
    supplier_framework = return_supplier_framework_info_if_on_framework_or_abort(
        data_api_client, framework_slug)

    upload_error = None
    if not file_is_less_than_5mb(request.files['agreement']):
        upload_error = "Document must be less than 5MB"
    elif file_is_empty(request.files['agreement']):
        upload_error = "Document must not be empty"

    if upload_error is not None:
        return render_template_with_csrf("frameworks/agreement.html",
                                         status_code=400,
                                         framework=framework,
                                         supplier_framework=supplier_framework,
                                         upload_error=upload_error,
                                         agreement_filename=AGREEMENT_FILENAME)

    agreements_bucket = s3.S3(current_app.config['DM_AGREEMENTS_BUCKET'])
    extension = get_extension(request.files['agreement'].filename)

    path = get_agreement_document_path(
        framework_slug, current_user.supplier_code,
        '{}{}'.format(SIGNED_AGREEMENT_PREFIX, extension))
    agreements_bucket.save(
        path,
        request.files['agreement'],
        acl='private',
        download_filename='{}-{}-{}{}'.format(
            sanitise_supplier_name(current_user.supplier_name),
            current_user.supplier_code, SIGNED_AGREEMENT_PREFIX, extension))

    data_api_client.register_framework_agreement_returned(
        current_user.supplier_code, framework_slug, current_user.email_address)

    try:
        email_body = render_template(
            'emails/framework_agreement_uploaded.html',
            framework_name=framework['name'],
            supplier_name=current_user.supplier_name,
            supplier_code=current_user.supplier_code,
            user_name=current_user.name)
        send_email(
            current_app.config['DM_FRAMEWORK_AGREEMENTS_EMAIL'],
            email_body,
            '{} framework agreement'.format(framework['name']),
            current_app.config["DM_GENERIC_NOREPLY_EMAIL"],
            '{} Supplier'.format(framework['name']),
            ['{}-framework-agreement'.format(framework_slug)],
            reply_to=current_user.email_address,
        )
    except EmailError as e:
        rollbar.report_exc_info()
        current_app.logger.error(
            "Framework agreement email failed to send. "
            "error {error} supplier_code {supplier_code} email_hash {email_hash}",
            extra={
                'error': six.text_type(e),
                'supplier_code': current_user.supplier_code,
                'email_hash': hash_email(current_user.email_address)
            })
        abort(503, "Framework agreement email failed to send")

    return redirect(
        url_for('.framework_agreement', framework_slug=framework_slug))
def upload_framework_agreement(framework_slug):
    framework = get_framework(data_api_client, framework_slug, allowed_statuses=['standstill', 'live'])
    supplier_framework = return_supplier_framework_info_if_on_framework_or_abort(data_api_client, framework_slug)

    upload_error = None
    if not file_is_less_than_5mb(request.files['agreement']):
        upload_error = "Document must be less than 5MB"
    elif file_is_empty(request.files['agreement']):
        upload_error = "Document must not be empty"

    if upload_error is not None:
        return render_template_with_csrf(
            "frameworks/agreement.html",
            status_code=400,
            framework=framework,
            supplier_framework=supplier_framework,
            upload_error=upload_error,
            agreement_filename=AGREEMENT_FILENAME
        )

    agreements_bucket = s3.S3(current_app.config['DM_AGREEMENTS_BUCKET'])
    extension = get_extension(request.files['agreement'].filename)

    path = get_agreement_document_path(
        framework_slug,
        current_user.supplier_code,
        '{}{}'.format(SIGNED_AGREEMENT_PREFIX, extension)
    )
    agreements_bucket.save(
        path,
        request.files['agreement'],
        acl='private',
        download_filename='{}-{}-{}{}'.format(
            sanitise_supplier_name(current_user.supplier_name),
            current_user.supplier_code,
            SIGNED_AGREEMENT_PREFIX,
            extension
        )
    )

    data_api_client.register_framework_agreement_returned(
        current_user.supplier_code, framework_slug, current_user.email_address)

    try:
        email_body = render_template(
            'emails/framework_agreement_uploaded.html',
            framework_name=framework['name'],
            supplier_name=current_user.supplier_name,
            supplier_code=current_user.supplier_code,
            user_name=current_user.name
        )
        send_email(
            current_app.config['DM_FRAMEWORK_AGREEMENTS_EMAIL'],
            email_body,
            '{} framework agreement'.format(framework['name']),
            current_app.config["DM_GENERIC_NOREPLY_EMAIL"],
            '{} Supplier'.format(framework['name']),
            ['{}-framework-agreement'.format(framework_slug)],
            reply_to=current_user.email_address,
        )
    except EmailError as e:
        rollbar.report_exc_info()
        current_app.logger.error(
            "Framework agreement email failed to send. "
            "error {error} supplier_code {supplier_code} email_hash {email_hash}",
            extra={'error': six.text_type(e),
                   'supplier_code': current_user.supplier_code,
                   'email_hash': hash_email(current_user.email_address)})
        abort(503, "Framework agreement email failed to send")

    return redirect(url_for('.framework_agreement', framework_slug=framework_slug))
def contract_review(framework_slug):
    framework = get_framework(data_api_client, framework_slug)
    supplier_framework = return_supplier_framework_info_if_on_framework_or_abort(
        data_api_client, framework_slug)
    agreements_bucket = s3.S3(current_app.config['DM_AGREEMENTS_BUCKET'])
    signature_page = get_most_recently_uploaded_agreement_file_or_none(
        agreements_bucket, framework_slug)

    # if supplier_framework doesn't have a name or a role or the agreement file, then 404
    if not (supplier_framework['agreementDetails']
            and supplier_framework['agreementDetails'].get('signerName')
            and supplier_framework['agreementDetails'].get('signerRole')
            and signature_page):
        abort(404)

    form = ContractReviewForm(request.form)
    form_errors = None

    if request.method == 'POST':
        if form.validate():
            data_api_client.register_framework_agreement_returned(
                current_user.supplier_code, framework_slug,
                current_user.email_address, current_user.id)

            email_recipients = [
                supplier_framework['declaration']['primaryContactEmail']
            ]
            if supplier_framework['declaration']['primaryContactEmail'].lower(
            ) != current_user.email_address.lower():
                email_recipients.append(current_user.email_address)

            try:
                email_body = render_template(
                    'emails/framework_agreement_with_framework_version_returned.html',
                    framework_name=framework['name'],
                    framework_slug=framework['slug'],
                    framework_live_date=content_loader.get_message(
                        framework_slug,
                        'dates')['framework_live_date'],  # noqa
                )

                send_email(
                    email_recipients,
                    email_body,
                    'Your {} signature page has been received'.format(
                        framework['name']),
                    current_app.config["DM_GENERIC_NOREPLY_EMAIL"],
                    current_app.config["FRAMEWORK_AGREEMENT_RETURNED_NAME"],
                    ['{}-framework-agreement'.format(framework_slug)],
                )
            except EmailError as e:
                rollbar.report_exc_info()
                current_app.logger.error(
                    "Framework agreement email failed to send. "
                    "error {error} supplier_code {supplier_code} email_hash {email_hash}",
                    extra={
                        'error': six.text_type(e),
                        'supplier_code': current_user.supplier_code,
                        'email_hash': hash_email(current_user.email_address)
                    })
                abort(503, "Framework agreement email failed to send")

            session.pop('signature_page', None)

            flash(
                'Your framework agreement has been returned to the Crown Commercial Service to be countersigned.',
                'success')

            return redirect(
                url_for(".framework_dashboard", framework_slug=framework_slug))

        else:
            form_errors = [{
                'question': form['authorisation'].label.text,
                'input_name': 'authorisation'
            }]

    form.authorisation.description = u"I have the authority to return this agreement on behalf of {}.".format(
        supplier_framework['declaration']['nameOfOrganisation'])

    status_code = 400 if form_errors else 200
    return render_template_with_csrf(
        "frameworks/contract_review.html",
        status_code=status_code,
        form=form,
        form_errors=form_errors,
        framework=framework,
        signature_page=signature_page,
        supplier_framework=supplier_framework,
    )
def invite_user(supplier_code):
    invite_form = InviteForm(request.form)

    try:
        supplier = data_api_client.get_supplier(supplier_code)['supplier']
        users = data_api_client.find_users(supplier_code)
    except HTTPError as e:
        current_app.logger.error(str(e), supplier_code)
        if e.status_code != 404:
            raise
        else:
            abort(404, "Supplier not found")

    if invite_form.validate():
        email_address = invite_form.email_address.data
        user_name = invite_form.name.data
        data = {
            'name': user_name,
            'emailAddress': email_address,
            'supplierCode': supplier_code,
            'supplierName': supplier['name'],
        }
        token = generate_token(data, current_app.config['SECRET_KEY'],
                               current_app.config['INVITE_EMAIL_SALT'])

        url = '{}://{}/{}/{}'.format(current_app.config['DM_HTTP_PROTO'],
                                     current_app.config['DM_MAIN_SERVER_NAME'],
                                     current_app.config['CREATE_USER_PATH'],
                                     format(token))

        email_body = render_template(
            'emails/invite_user_email.html',
            url=url,
            supplier=supplier['name'],
            name=user_name,
        )

        try:
            send_email(
                email_address,
                email_body,
                current_app.config['INVITE_EMAIL_SUBJECT'],
                current_app.config['INVITE_EMAIL_FROM'],
                current_app.config['INVITE_EMAIL_NAME'],
            )
        except EmailError as e:
            current_app.logger.error(
                'Invitation email failed to send error {} to {} supplier {} supplier code {} '
                .format(str(e), email_address, current_user.supplier_name,
                        current_user.supplier_code))
            abort(503, "Failed to send user invite reset")

        try:
            data_api_client.record_supplier_invite(supplier_code=supplier_code,
                                                   email_address=email_address)
        except APIError as e:
            current_app.logger.warning(
                'Could not record supplier invite for {}: {}'.format(
                    e.message, email_address))

        flash('user_invited', 'success')
        return redirect(
            url_for('.find_supplier_users', supplier_code=supplier_code))
    else:
        return render_template_with_csrf("view_supplier_users.html",
                                         status_code=400,
                                         invite_form=invite_form,
                                         move_user_form=MoveUserForm(),
                                         users=users["users"],
                                         supplier=supplier)
def invite_user(supplier_code):
    invite_form = InviteForm(request.form)

    try:
        supplier = data_api_client.get_supplier(supplier_code)['supplier']
        users = data_api_client.find_users(supplier_code)
    except HTTPError as e:
        current_app.logger.error(str(e), supplier_code)
        if e.status_code != 404:
            raise
        else:
            abort(404, "Supplier not found")

    if invite_form.validate():
        email_address = invite_form.email_address.data
        user_name = invite_form.name.data
        data = {
            'name': user_name,
            'emailAddress': email_address,
            'supplierCode': supplier_code,
            'supplierName': supplier['name'],
        }
        token = generate_token(data, current_app.config['SECRET_KEY'], current_app.config['INVITE_EMAIL_SALT'])

        url = '{}://{}/{}/{}'.format(
            current_app.config['DM_HTTP_PROTO'],
            current_app.config['DM_MAIN_SERVER_NAME'],
            current_app.config['CREATE_USER_PATH'],
            format(token)
        )

        email_body = render_template(
            'emails/invite_user_email.html',
            url=url,
            supplier=supplier['name'],
            name=user_name,
        )

        try:
            send_email(
                email_address,
                email_body,
                current_app.config['INVITE_EMAIL_SUBJECT'],
                current_app.config['INVITE_EMAIL_FROM'],
                current_app.config['INVITE_EMAIL_NAME'],
            )
        except EmailError as e:
            current_app.logger.error(
                'Invitation email failed to send error {} to {} supplier {} supplier code {} '.format(
                    str(e),
                    email_address,
                    current_user.supplier_name,
                    current_user.supplier_code)
            )
            abort(503, "Failed to send user invite reset")

        try:
            data_api_client.record_supplier_invite(
                supplier_code=supplier_code,
                email_address=email_address
            )
        except APIError as e:
            current_app.logger.warning('Could not record supplier invite for {}: {}'.format(e.message, email_address))

        flash('user_invited', 'success')
        return redirect(url_for('.find_supplier_users', supplier_code=supplier_code))
    else:
        return render_template_with_csrf(
            "view_supplier_users.html",
            status_code=400,
            invite_form=invite_form,
            move_user_form=MoveUserForm(),
            users=users["users"],
            supplier=supplier
        )