コード例 #1
0
ファイル: test_security.py プロジェクト: man-group/adaero
def test_effective_principals_for_user(
    business_unit,
    department,
    have_direct_reports,
    has_direct_reports,
    is_staff,
    is_talent_manager,
    expected,
):
    policy = security.SimpleAuthenticationPolicy()
    request_mock = mock.MagicMock()
    direct_reports = [User(username="******")] if have_direct_reports else []
    User.direct_reports = mock.MagicMock()
    request_mock.user = User(
        username=TEST_USERNAME,
        first_name="Foo",
        last_name="Bar",
        position="Tester",
        manager_username="******",
        department=department,
        employee_id=1,
        business_unit=business_unit,
        location="Planet Earth",
        email="*****@*****.**",
        has_direct_reports=has_direct_reports,
        is_staff=is_staff,
    )
    request_mock.user.direct_reports = direct_reports
    request_mock.registry = mock.MagicMock()
    talent_managers = [TEST_USERNAME] if is_talent_manager else []
    request_mock.registry.settings = {
        constants.TALENT_MANAGER_USERNAMES_KEY: talent_managers,
        constants.BUSINESS_UNIT_KEY: "Alpha",
    }
    assert expected == policy.effective_principals(request_mock)
コード例 #2
0
ファイル: test_feedback.py プロジェクト: man-group/adaero
def test_employee_can_get_blank_feedback_form_to_valid_nominee_while_inside_entry_subperiod(
        app_with_nominees_inside_entry_subperiod):  # noqa: E501
    app = successfully_login(app_with_nominees_inside_entry_subperiod,
                             TEST_EMPLOYEE_USERNAME)

    response = app.get(NOMINATED_USER_FEEDBACK_ENDPOINT)
    form = response.json_body.get("form")
    end_date = form["endDate"]
    # note in entry subperiod for `generate_period_dates` hence 1 day offset
    man_location = TEST_LDAP_FULL_DETAILS[TEST_EMPLOYEE_USERNAME][
        LDAP_LOCATION_ATTR]
    assert end_date == datetimeformat(TEST_UTCNOW + timedelta(days=1),
                                      User(location=man_location))
    items = form["items"]
    assert isinstance(items, list)
    employee = form["employee"]
    assert employee["displayName"] == NOMINATED_DISPLAY_NAME
    assert employee["position"] == NOMINATED_POSITION

    expected_questions = []
    for _, template, caption in QUESTION_IDS_AND_TEMPLATES:
        expected_questions.append([
            template.format(period_name=TEST_PERIOD_NAME,
                            display_name=NOMINATED_DISPLAY_NAME),
            caption,
        ])

    for generated, expected in zip(items, expected_questions):
        assert generated["questionId"]
        assert generated["question"] == expected[0]
        assert generated["answerId"] is None
        assert generated["answer"] == ""
        assert generated["caption"] == expected[1]
コード例 #3
0
ファイル: test_population.py プロジェクト: man-group/adaero
def _generate_user_list():
    ldapsource = ldapauth.build_ldapauth_from_settings(DEFAULT_TEST_SETTINGS)
    users = []
    for rlu in TEST_LDAP_FULL_DETAILS.values():
        u = User.create_from_ldap_details(ldapsource, rlu)
        users.append(u)
    return sorted(users, key=lambda u: (u.last_name, u.first_name))
コード例 #4
0
ファイル: test_population.py プロジェクト: man-group/adaero
def test_population_validation_catches_duplicate_users():
    def _get_ldap_user_by_employee_id_mck(k, id_):
        if k != TEST_UID_KEY:
            pytest.fail('Incorrect key "{}" provided'.format(k))
        details_by_id = {v[TEST_UID_KEY]: v for _, v in TEST_LDAP_FULL_DETAILS.items()}
        return details_by_id.get(id_)

    with mock.patch(
        "adaero.security.ldapauth.LDAPAuth." "get_ldap_user_by_kv",
        side_effect=_get_ldap_user_by_employee_id_mck,
    ):
        ldapsource = ldapauth.build_ldapauth_from_settings(DEFAULT_TEST_SETTINGS)
        input_ = _generate_user_list()
        duplicate_user = User.create_from_ldap_details(
            ldapsource, TEST_LDAP_FULL_DETAILS[NOMINATED_USERNAME]
        )
        duplicate_user.first_name = "Duplicate"
        input_.append(duplicate_user)
        messages = []
        generated, messages = population.remove_duplicate_users(input_, messages)
        assert 1 == len(messages)
        # take into account the header row
        duplicate_row_num = len(TEST_LDAP_FULL_DETAILS.keys()) + 2
        assert (
            population.DEFAULT_DUPLICATE_USER_TMPL.format(
                row_num=duplicate_row_num, first_row_num=USER_TO_REMOVE_ROW_NUM
            )
            == messages[0]
        )
        expected = _generate_user_list()
        for e, g in zip(expected, generated):
            assert e.first_name == e.first_name
コード例 #5
0
ファイル: conftest.py プロジェクト: man-group/adaero
def new_ldap_mocked_app_with_users(dbsession, request):
    settings = DEFAULT_TEST_SETTINGS
    ldapsource = ldapauth.build_ldapauth_from_settings(DEFAULT_TEST_SETTINGS)
    if request.config.getoption("--use-sqlite3"):
        settings["adaero.use_local_sqlite3"] = True

    # need yield_fixture as we need the patch applied over the lifetime of
    # the testapp instance
    with patch(
            "adaero.security.ldapauth.LDAPAuth.auth_user",
            side_effect=auth_user_mock_fn,
            autospec=True,
    ), patch(
            "adaero.security.ldapauth.LDAPAuth."
            "get_ldap_user_by_username",
            side_effect=get_ldap_user_by_username_mock_fn,
            autospec=True,
    ), patch(
            "adaero.security.ldapauth.LDAPAuth."
            "get_ldap_user_by_email",
            side_effect=get_ldap_by_email_mock_fn,
            autospec=True,
    ):

        app = webtest.TestApp(adaero.main({}, **settings))

        dbsession = get_dbsession(app)

        with transaction.manager:
            for user_details in TEST_LDAP_FULL_DETAILS.values():
                if (user_details[tests.integration.constants.TEST_USERNAME_KEY]
                        not in TEST_EMPLOYEES):
                    continue
                user = User.create_from_ldap_details(ldapsource, user_details)
                set_as_staff(user, user_details)
                dbsession.add(user)

            # Add non-staff member e.g. upper management
            non_staff_user = User.create_from_ldap_details(
                ldapsource, TEST_NON_STAFF_USER)
            dbsession.add(non_staff_user)

        freezer = freeze_time(TEST_UTCNOW)
        freezer.start()
        yield app
        freezer.stop()
コード例 #6
0
ファイル: test_population.py プロジェクト: man-group/adaero
def _generate_user_rows():
    ldapsource = ldapauth.build_ldapauth_from_settings(DEFAULT_TEST_SETTINGS)
    user_rows = []
    for rlu in TEST_LDAP_FULL_DETAILS.values():
        u = User.create_from_ldap_details(ldapsource, rlu)
        u.is_staff = True
        user_rows.append(u.to_dict())
    return sorted(user_rows, key=lambda u: (u["last_name"], u["first_name"]))
コード例 #7
0
def stats_session(dbsession):
    ldapsource = ldapauth.build_ldapauth_from_settings(DEFAULT_TEST_SETTINGS)
    with transaction.manager:
        for user_details in TEST_LDAP_FULL_DETAILS.values():
            dbsession.add(
                User.create_from_ldap_details(ldapsource, user_details))
    add_test_data_for_stats(dbsession)
    yield dbsession
    drop_everything(dbsession)
コード例 #8
0
def get_external_invite_status(request):
    """
    Generate list of existing external invites that `request.user` has already
    sent for the current period.

    Parameters
    ----------
    request: `pyramid.request.Request`

    Returns
    -------
    JSON-serialisable dict that contains list of external users invited.
    """
    ldapsource = request.ldapsource
    location = get_config_value(request.registry.settings,
                                constants.HOMEBASE_LOCATION_KEY)
    current_period = Period.get_current_period(request.dbsession)
    if current_period.subperiod(location) in [
            Period.APPROVAL_SUBPERIOD,
            Period.REVIEW_SUBPERIOD,
    ]:
        return interpolate_template(ENTRY_ENDED_TEMPLATE)
    elif current_period.subperiod(location) in [Period.ENROLLMENT_SUBPERIOD]:
        dt = date.datetimeformat(current_period.entry_start_utc, request.user)
        return interpolate_template(PRIOR_ENTRY_TEMPLATE, entry_start=dt)

    with transaction.manager:
        is_nominated = (request.dbsession.query(Nominee).filter(
            Nominee.period == current_period).filter(
                Nominee.username == request.user.username).one_or_none())

    if not is_nominated:
        return interpolate_template(NOT_ENROLLED_TEMPLATE)

    invitee_users = []
    with transaction.manager:
        invites = (request.dbsession.query(ExternalInvite.to_username).filter(
            ExternalInvite.from_username == request.user.username,
            ExternalInvite.period_id == current_period.id,
        ).all())
    for invitee in invites:
        ldap_details = ldapsource.get_ldap_user_by_username(invitee[0])
        invitee_users.append(
            User.create_from_ldap_details(ldapsource, ldap_details))

    invitee_users.sort(key=lambda x: x.first_name)

    payload_users = []
    for user in invitee_users:
        payload_users.append({
            "displayName": user.display_name,
            "businessUnit": user.business_unit,
            "department": user.department,
            "email": user.email,
        })
    return {"canInvite": True, "invitees": payload_users}
コード例 #9
0
def post_external_invite(request):
    """
    If allowed, records that `request.user` sent an external invite to a user
    identified by their email which must be in LDAP, and sends an email to
    that invited used.

    Parameters
    ----------
    request: `pyramid.request.Request`

    Returns
    -------
    JSON serialisable payload stating success
    """
    ldapsource = request.ldapsource
    email = request.json_body["email"]

    location = get_config_value(request.registry.settings,
                                constants.HOMEBASE_LOCATION_KEY)
    current_period = Period.get_current_period(request.dbsession)
    settings = request.registry.settings
    company_name = get_config_value(settings, constants.COMPANY_NAME_KEY,
                                    "company")
    support_email = get_config_value(settings, constants.SUPPORT_EMAIL_KEY,
                                     "your IT support for this tool")
    with transaction.manager:
        if current_period.subperiod(location) != Period.ENTRY_SUBPERIOD:
            raise HTTPBadRequest(explanation="Can only send invite during "
                                 'the "Give feedback" period.')
        ext_user_details = ldapsource.get_ldap_user_by_email(email)
        if not ext_user_details:
            raise HTTPNotFound(explanation="%s is not a valid %s "
                               "email. If you think it is, "
                               "please contact "
                               "%s." % (email, company_name, support_email))

        ext_user = User.create_from_ldap_details(ldapsource, ext_user_details)
        invite = ExternalInvite(
            to_username=ext_user.username,
            from_username=request.user.username,
            period=current_period,
        )
        invites = (request.dbsession.query(ExternalInvite).filter(
            ExternalInvite.to_username == ext_user.username,
            ExternalInvite.from_username == request.user.username,
            ExternalInvite.period_id == current_period.id,
        ).one_or_none())
        if not invites:
            request.dbsession.add(invite)
    mail.send_invite_email(
        request.dbsession,
        request.registry.settings,
        inviter=request.user,
        invitee=ext_user,
    )
    return {"success": True}
コード例 #10
0
ファイル: test_manager.py プロジェクト: man-group/adaero
def test_manager_can_get_initial_summary_for_direct_report(
        ldap_mocked_app_with_users):  # noqa: E501
    app = successfully_login(ldap_mocked_app_with_users, TEST_MANAGER_USERNAME)
    dbsession = get_dbsession(app)
    add_test_data_for_stats(dbsession)
    response_1 = app.get("/api/v1/summarise/%s/" % TEST_EMPLOYEE_USERNAME)

    period_name = response_1.json_body["summary"]["periodName"]
    assert TEST_PERIOD_NAME == period_name

    assert not response_1.json_body["summary"]["readOnly"]

    end_date = response_1.json_body["summary"]["endDate"]

    # note in entry subperiod for `generate_period_dates` hence 1 day offset
    man_location = TEST_LDAP_FULL_DETAILS[TEST_EMPLOYEE_USERNAME][
        LDAP_LOCATION_ATTR]
    assert end_date == datetimeformat(TEST_UTCNOW + timedelta(days=1),
                                      User(location=man_location))

    # can't easily test summaries by direct string comparison, so instead:
    # 1. make sure number of `rawSummary` rows equal number of forms
    #    recevied by the direct report for the CURRENT PERIOD only
    first_raw_summary = response_1.json_body["summary"]["items"][0][
        "rawAnswer"]
    # below assertion only works in test situations as we ensure answers
    # have no new line, but in production, this will not be the case
    assert len(first_raw_summary.split("\n")) == TEST_NUM_FORMS_RECEIVED

    response_2 = app.get("/api/v1/summarise/%s/" % TEST_EMPLOYEE_USERNAME)

    items_1 = sorted(response_1.json_body["summary"]["items"],
                     key=lambda k: k["questionId"])
    items_2 = sorted(response_2.json_body["summary"]["items"],
                     key=lambda k: k["questionId"])

    assert len(items_1) == len(items_2)

    for i, j in zip(items_1, items_2):
        assert i["questionId"] == j["questionId"]
        assert i["question"] == j["question"]
        assert i["rawAnswer"] == j["rawAnswer"]
コード例 #11
0
ファイル: mail.py プロジェクト: man-group/adaero
def check_and_send_email(
    dbsession,
    ldapsource,
    settings,
    template_key=None,
    force=False,
    delay_s=None,
    delay_between_s=None,
):
    """
    Check current conditions and if we haven't sent the relevant email, send
    templated both plain text and HTML content to all relevant email addresses
    using the configured SMTP server.

    Parameters
    ----------
    dbsession:
      sqlalchemy session
    ldapsource:
      used for fetching talent manager email information
    settings:
      configpaste settings
    template_key:
      override relevant email by providing key from
      `adaero.constants.EMAIL_TEMPLATE_MAP`
    force:
      if particular email already sent, send anyway
    delay_s:
      number of seconds to delay before sending emails. If none, look in
      settings
    delay_between_s:
      number of seconds to delay before sending emails. If none, look in
      settings
    """
    log.info("Begin: Sending emails")
    current_period = Period.get_current_period(dbsession)
    location = get_config_value(settings, constants.HOMEBASE_LOCATION_KEY)
    if template_key:
        template_info = constants.EMAIL_TEMPLATE_MAP[template_key]
        log.info("Email template overriden to %s" % template_info["code"])
    else:
        template_info = current_period.current_email_template(location)
        if not template_info:
            log.warning("Attempted to send an email while period is inactive")
            return

    last_sent = current_period.get_email_flag_by_code(template_info["code"])
    if not force and last_sent:
        log.warning("Email code %s already sent at %s so not doing again, "
                    "override with `force=True` kwarg." %
                    (template_info["code"], last_sent))
        return

    audience = template_info["audience"]
    company_name = get_config_value(settings, constants.COMPANY_NAME_KEY, "")
    subject = _build_full_subject(company_name, template_info["summary"])
    if audience == "employee":
        users = get_employee_users(dbsession)
    elif audience == "non-nominated":
        users = get_non_nominated_users(dbsession)
    elif audience == "manager":
        users = get_manager_users(dbsession)
    elif audience == "summarised":
        users = get_summarised_users(dbsession)
    else:
        raise ValueError('Audience value "%s" not in allowed values "%s". '
                         "Please alert the application maintainer." %
                         (audience, ", ".join(constants.AUDIENCE_VALUES)))

    emailing_enabled = _get_send_email_flag(settings)
    app_host = get_root_url(settings)

    # calculate email stats
    users_with_emails = []
    for user in users:
        if not user.email:
            log.warning(
                "Unable to send email for user %s as no email available" %
                user.username)
        else:
            users_with_emails.append(user)

    if delay_s is None:
        delay_s = float(
            get_config_value(settings, constants.EMAIL_START_DELAY_S_KEY,
                             DEFAULT_EMAIL_DELAY_S))
    if delay_between_s is None:
        delay_between_s = float(
            get_config_value(
                settings,
                constants.EMAIL_DELAY_BETWEEN_S_KEY,
                DEFAULT_EMAIL_DELAY_BETWEEN_S,
            ))

    log.info('Sending %s "%s" emails in %s seconds...' %
             (template_info["code"], len(users_with_emails), delay_s))
    time.sleep(delay_s)
    log.info("Sending %s emails now..." % len(users_with_emails))

    env = _build_template_env()
    have_sent_emails = False

    from_email = get_config_value(settings, constants.SUPPORT_EMAIL_KEY)

    s = smtplib.SMTP()
    s.connect()

    for user in users:
        if not user.email:
            continue
        try:
            # because of the modelling of User <-> Manager, attempting to fetch
            # manager directly despite being joinloaded will result in an SELECT
            # to prevent db access by testing against local manager_username
            template = env.get_template(
                os.path.join("email", template_info["template"]))
            rendered_html = template.render(
                user=user,
                period=current_period,
                app_host=app_host,
                company_name=company_name,
            )
            message_root = _generate_message_root(rendered_html, from_email,
                                                  subject)

            if emailing_enabled:
                s.sendmail(from_email, [user.email], message_root.as_string())
                have_sent_emails = True
            log.debug("Email sent to %s" % user.email)
        except Exception as e:
            log.exception(e)
            log.error("Exception occured with sending email to %s, "
                      "skipping over and continuing..." % user.email)
        time.sleep(delay_between_s)

    tm_usernames = settings[constants.TALENT_MANAGER_USERNAMES_KEY]
    if not isinstance(tm_usernames, list):
        talent_managers = json.loads(
            settings[constants.TALENT_MANAGER_USERNAMES_KEY])
    else:
        talent_managers = settings[constants.TALENT_MANAGER_USERNAMES_KEY]

    for tm_username in talent_managers:
        try:
            tm_ldap = ldapsource.get_ldap_user_by_username(tm_username)
            if not tm_ldap:
                log.warning("Unable to find LDAP info for talent manager with "
                            "username {}, unable to send confirmation "
                            "email.".format(tm_username))
                continue
            tm = User.create_from_ldap_details(ldapsource, tm_ldap)

            # send confirmation email
            template = env.get_template(
                os.path.join("email", "tm_confirmation.html.j2"))
            rendered_html = template.render(
                talent_manager=tm,
                subject=subject,
                num_emails=len(users_with_emails),
                datetime_sent_utc=datetime.utcnow(),
                app_host=app_host,
            )
            message_root = _generate_message_root(
                rendered_html,
                from_email,
                _build_full_subject(company_name, "Emails sent"),
            )
            if emailing_enabled and have_sent_emails:
                s.sendmail(from_email, [tm.email], message_root.as_string())
        except Exception as e:
            log.exception(e)
            log.error("Exception occured with sending tm email to %s, "
                      "skipping over and continuing..." % tm_username)

    s.close()

    log.info("Sent %s emails!" % (len(users_with_emails) + 1))
    with transaction.manager:
        current_period.set_email_flag_by_code(template_info["code"])
        dbsession.merge(current_period)
    log.info("End: Sending emails")
コード例 #12
0
def test_employee_able_to_send_feedback_request_within_entry_subperiod(
        ldap_mocked_app_with_users, email, expected):  # noqa: E501
    expected_status_code, expected_msg = expected
    app = ldap_mocked_app_with_users
    ldapsource = ldapauth.build_ldapauth_from_settings(
        app.app.registry.settings)
    dbsession = get_dbsession(app)
    template_id = add_test_template(dbsession)
    add_test_period_with_template(dbsession, Period.ENTRY_SUBPERIOD,
                                  template_id)
    successfully_login(app, TEST_EMPLOYEE_USERNAME)
    csrf_token = app.cookies[ANGULAR_2_XSRF_TOKEN_COOKIE_NAME]

    app.app.registry.settings[app_constants.ENABLE_SEND_EMAIL_KEY] = True

    with patch("smtplib.SMTP") as smtp_mock, patch(
            "socket.gethostname") as gethostname_mock, patch(
                "getpass.getuser") as getuser_mock:
        sendmail_mock = smtp_mock().sendmail
        gethostname_mock.return_value = TEST_PRODUCTION_HOSTNAME
        getuser_mock.return_value = TEST_PRODUCTION_USER
        resp = app.post_json(
            "/api/v1/external-invite",
            {"email": email},
            headers={ANGULAR_2_XSRF_TOKEN_HEADER_NAME: csrf_token},
            expect_errors=expected_status_code != 200,
        )

    if resp.status_code != 200:
        assert expected_msg == resp.json_body["message"]
        return

    # check invite email that is sent
    parser = Parser()
    assert len(sendmail_mock.call_args_list) == 1
    raw_message = sendmail_mock.call_args_list[0][0][2]
    message_root = parser.parsestr(raw_message)
    inviter = User.create_from_ldap_details(
        ldapsource, TEST_LDAP_FULL_DETAILS[TEST_EMPLOYEE_USERNAME])

    subject_str, encoding = decode_header(message_root["Subject"])[0]
    subject_unicode = subject_str.decode(encoding)
    assert subject_unicode.count(inviter.display_name) == 1

    invite_messages = message_root.get_payload()
    assert invite_messages[0]["Reply-To"] == inviter.email

    invite_plain = b64decode(invite_messages[0].get_payload()).decode("utf-8")
    invite_html = b64decode(invite_messages[1].get_payload()).decode("utf-8")

    app_url = "https://%s/feedback/%s" % (
        TEST_PRODUCTION_HOSTNAME,
        TEST_LDAP_FULL_DETAILS[inviter.username][
            tests.integration.constants.TEST_USERNAME_KEY],
    )

    assert invite_plain.count(app_url) > 0
    assert invite_plain.count(inviter.first_name) > 0
    assert invite_html.count(app_url) > 1
    assert invite_html.count(app_url) % 2 == 0

    after_invite_resp = app.post_json(
        "/api/v1/login",
        {
            "username": TEST_COMPANY_COLLEAGUE_USERNAME,
            "password": TEST_PASSWORD
        },
    )

    assert after_invite_resp.status_code == 200
コード例 #13
0
def test_datetimeformat_works(value, man_location, format_, expected):
    user = User(username="******", location=man_location)
    if format_:
        assert expected == adaero.date.datetimeformat(value, user, format_)
    else:
        assert expected == adaero.date.datetimeformat(value, user)