Esempio n. 1
0
def test_exception_plugin():
    # type: () -> None
    test_logger = ExceptionLoggerTestPlugin()
    proxy = PluginProxy([test_logger])

    try:
        raise RandomException("some string")
    except RandomException:
        proxy.log_exception(None, None, *sys.exc_info())

    assert test_logger.request is None
    assert test_logger.status is None
    assert test_logger.exc_type == RandomException
    assert str(test_logger.exc_value) == "some string"
    assert test_logger.exc_tb is not None

    # Reinitializing test_logger unconfuses mypy, which otherwise thinks that test_logger.request
    # must still be None.  See mypy/issues/4168.
    test_logger = ExceptionLoggerTestPlugin()
    proxy = PluginProxy([test_logger])

    try:
        raise RandomException("with request")
    except RandomException:
        request = HTTPRequest("GET", "/foo")
        proxy.log_exception(request, 200, *sys.exc_info())

    assert test_logger.request is not None
    assert test_logger.request.uri == "/foo"
    assert test_logger.status == 200
    assert test_logger.exc_type == RandomException
    assert str(test_logger.exc_value) == "with request"
    assert test_logger.exc_tb is not None
Esempio n. 2
0
def test_rejects_ecdsa_keys(get_plugin_proxy, session, users):  # noqa: F811
    get_plugin_proxy.return_value = PluginProxy([SshKeyPolicyPlugin()])

    user = users["*****@*****.**"]

    with pytest.raises(BadPublicKey):
        add_public_key(session, user, SSH_KEY_ECDSA_P256)
Esempio n. 3
0
    def __init__(self, tmpdir):
        # type: (LocalPath) -> None
        self.settings = Settings()
        self.settings.database = db_url(tmpdir)
        self.plugins = PluginProxy([])

        # Reinitialize the global plugin proxy with an empty set of plugins in case a previous test
        # initialized plugins.  This can go away once a plugin proxy is injected into everything
        # that needs it instead of maintained as a global.
        set_global_plugin_proxy(self.plugins)

        self.initialize_database()
        self.session = SessionFactory(self.settings).create_session()
        self.graph = GroupGraph()
        session_factory = SingletonSessionFactory(self.session)
        self.repository_factory = GraphRepositoryFactory(
            self.settings, self.plugins, session_factory, self.graph)
        self.sql_repository_factory = SQLRepositoryFactory(
            self.settings, self.plugins, session_factory)
        self.service_factory = ServiceFactory(self.settings, self.plugins,
                                              self.repository_factory)
        self.usecase_factory = UseCaseFactory(self.settings, self.plugins,
                                              self.service_factory)
        self._transaction_service = self.service_factory.create_transaction_service(
        )
def test_rejects_weak_rsa_keys(get_plugin_proxy, session, users):
    get_plugin_proxy.return_value = PluginProxy([SshKeyPolicyPlugin()])

    user = users["*****@*****.**"]

    with pytest.raises(BadPublicKey):
        add_public_key(session, user, SSH_KEY_RSA_1024)
Esempio n. 5
0
def main(sys_argv=sys.argv):
    # type: (List[str]) -> None
    setup_signal_handlers()

    # The curl HTTP client is required to support proxies.
    AsyncHTTPClient.configure(CurlAsyncHTTPClient)

    # get arguments
    parser = build_arg_parser("Grouper Web Server.")
    args = parser.parse_args(sys_argv[1:])

    try:
        settings = FrontendSettings.global_settings_from_config(args.config)
        setup_logging(args, settings.log_format)
        plugins = PluginProxy.load_plugins(settings, "grouper-fe")
        set_global_plugin_proxy(plugins)
    except PluginsDirectoryDoesNotExist as e:
        logging.fatal("Plugin directory does not exist: {}".format(e))
        sys.exit(1)
    except Exception:
        logging.exception("Uncaught exception in startup")
        sys.exit(1)

    try:
        start_server(args, settings, plugins)
    except Exception:
        plugins.log_exception(None, None, *sys.exc_info())
        logging.exception("Uncaught exception")
    finally:
        logging.info("end")
Esempio n. 6
0
def initialize_plugins(plugin_dirs, plugin_module_paths, service_name):
    # type: (List[str], List[str], str) -> None
    plugins = load_plugins(BasePlugin, plugin_dirs, plugin_module_paths,
                           service_name)

    global _plugin_proxy
    _plugin_proxy = PluginProxy(plugins)
Esempio n. 7
0
def test_group_add_remove_owner(
        make_session,
        get_plugin_proxy,
        session,
        users,
        groups  # noqa: F811
):
    make_session.return_value = session
    get_plugin_proxy.return_value = PluginProxy([GroupOwnershipPolicyPlugin()])

    username = "******"
    groupname = "team-sre"

    # add
    assert (u"User", username) not in groups[groupname].my_members()
    call_main(session, "group", "add_member", "--owner", groupname, username)
    all_members = Group.get(session, name=groupname).my_members()
    assert (u"User", username) in all_members
    _, _, _, role, _, _ = all_members[(u"User", username)]
    assert GROUP_EDGE_ROLES[role] == "owner"

    # remove (fails)
    call_main(session, "group", "remove_member", groupname, username)
    assert (u"User", username) in Group.get(session,
                                            name=groupname).my_members()
Esempio n. 8
0
def session(request, tmpdir):
    # type: (FixtureRequest, LocalPath) -> None
    settings = Settings()
    set_global_settings(settings)

    # Reinitialize plugins in case a previous test configured some.
    set_global_plugin_proxy(PluginProxy([]))

    db_engine = get_db_engine(db_url(tmpdir))

    # Clean up from previous tests if using a persistent database.
    if "MEROU_TEST_DATABASE" in os.environ:
        Model.metadata.drop_all(db_engine)

    # Create the database schema and the corresponding session.
    Model.metadata.create_all(db_engine)
    Session.configure(bind=db_engine)
    session = Session()

    def fin():
        # type: () -> None
        """Explicitly close the session to avoid any dangling transactions."""
        session.close()

    request.addfinalizer(fin)
    return session
Esempio n. 9
0
def main(sys_argv=sys.argv):
    # type: (List[str]) -> None
    setup_signal_handlers()

    # get arguments
    parser = build_arg_parser("Grouper API Server")
    args = parser.parse_args(sys_argv[1:])

    try:
        settings = ApiSettings.global_settings_from_config(args.config)
        setup_logging(args, settings.log_format)
        plugins = PluginProxy.load_plugins(settings, "grouper-api")
        set_global_plugin_proxy(plugins)
    except PluginsDirectoryDoesNotExist as e:
        logging.fatal("Plugin directory does not exist: %s", e)
        sys.exit(1)
    except Exception:
        logging.exception("Uncaught exception in startup")
        sys.exit(1)

    try:
        start_server(args, settings, plugins)
    except Exception:
        plugins.log_exception(None, None, *sys.exc_info())
        logging.exception("Uncaught exception")
    finally:
        logging.info("end")
Esempio n. 10
0
def test_accepts_strong_keys(get_plugin_proxy, session, users):  # noqa: F811
    get_plugin_proxy.return_value = PluginProxy([SshKeyPolicyPlugin()])

    user = users["*****@*****.**"]

    add_public_key(session, user, SSH_KEY_1)
    add_public_key(session, user, SSH_KEY_ED25519)
Esempio n. 11
0
def test_can_add_owner_twice(get_plugin_proxy, session, groups, users):
    get_plugin_proxy.return_value = PluginProxy([GroupOwnershipPolicyPlugin()])

    group = groups["team-infra"]
    owner = users["*****@*****.**"]

    add_member(group, owner, role="owner")
    add_member(group, owner, role="owner")
Esempio n. 12
0
def test_can_disable_member(get_plugin_proxy, session, groups, users):
    get_plugin_proxy.return_value = PluginProxy([GroupOwnershipPolicyPlugin()])

    group = groups["team-infra"]
    member = users["*****@*****.**"]

    add_member(group, member)
    disable_user(session, member)
Esempio n. 13
0
def test_rejected_key(get_plugin_proxy, session, users):
    get_plugin_proxy.return_value = PluginProxy([PublicKeyPlugin()])

    user = users["*****@*****.**"]

    with pytest.raises(BadPublicKey):
        add_public_key(session, user, SSH_KEY_1)

    assert get_public_keys_of_user(session, user.id) == []
Esempio n. 14
0
def api_app(session, standard_graph):
    # type: (Session, GroupGraph) -> GrouperApplication
    settings = ApiSettings()
    set_global_settings(settings)
    session_factory = SingletonSessionFactory(session)
    usecase_factory = create_graph_usecase_factory(
        settings, PluginProxy([]), session_factory, standard_graph
    )
    return create_api_application(standard_graph, settings, usecase_factory)
Esempio n. 15
0
def test_cant_disable_last_owner(get_plugin_proxy, session, groups, users):
    get_plugin_proxy.return_value = PluginProxy([GroupOwnershipPolicyPlugin()])

    group = groups["team-infra"]
    owner = users["*****@*****.**"]

    add_member(group, owner, role="owner")

    with pytest.raises(PluginRejectedDisablingUser):
        disable_user(session, owner)
Esempio n. 16
0
def test_cant_demote_last_owner(get_plugin_proxy, groups, users):
    get_plugin_proxy.return_value = PluginProxy([GroupOwnershipPolicyPlugin()])

    group = groups["team-infra"]
    owner = users["*****@*****.**"]

    add_member(group, owner, role="owner")

    with pytest.raises(PluginRejectedGroupMembershipUpdate):
        group.edit_member(owner, owner, "Unit Testing", role="member")
Esempio n. 17
0
def test_cant_expire_last_owner(get_plugin_proxy, groups, users):
    get_plugin_proxy.return_value = PluginProxy([GroupOwnershipPolicyPlugin()])

    group = groups["team-infra"]
    owner = users["*****@*****.**"]

    expiration = datetime.utcnow() + timedelta(1)

    add_member(group, owner, role="owner")

    with pytest.raises(PluginRejectedGroupMembershipUpdate):
        group.edit_member(owner, owner, "Unit Testing", expiration=expiration)
Esempio n. 18
0
def test_exception_plugin():
    # type: () -> None
    test_logger = ExceptionLoggerTestPlugin()
    proxy = PluginProxy([test_logger])

    try:
        raise RandomException("some string")
    except RandomException:
        proxy.log_exception(None, None, *sys.exc_info())

    assert test_logger.request is None
    assert test_logger.status is None
    assert test_logger.exc_type == RandomException
    assert str(test_logger.exc_value) == "some string"
    assert test_logger.exc_tb is not None

    try:
        raise RandomException("with request")
    except RandomException:
        request = HTTPRequest("GET", "/foo")
        proxy.log_exception(request, 200, *sys.exc_info())

    assert test_logger.request is not None
    assert test_logger.request.uri == "/foo"
    assert test_logger.status == 200
    assert test_logger.exc_type == RandomException
    assert str(test_logger.exc_value) == "with request"
    assert test_logger.exc_tb is not None
Esempio n. 19
0
def test_user_created_plugin(setup: SetupTest):
    """Test calls to the user_created plugin."""
    plugin = UserCreatedPlugin()
    # WARN: Relies on the user_created function being called from the global proxy.
    # Will need to change once everything uses an injected plugin proxy.
    set_global_plugin_proxy(PluginProxy([plugin]))
    with setup.transaction():
        setup.create_user("*****@*****.**")
        assert plugin.calls == 1

        plugin.expected_service_account = True
        setup.create_service_account("*****@*****.**", "owner", "machine set", "desc")
        assert plugin.calls == 2
Esempio n. 20
0
def test_can_always_revoke_members(get_plugin_proxy, groups, users):
    get_plugin_proxy.return_value = PluginProxy([GroupOwnershipPolicyPlugin()])

    group = groups["team-infra"]
    owner = users["*****@*****.**"]
    member = users["*****@*****.**"]

    expiration = datetime.utcnow() + timedelta(1)

    add_member(group, owner, role="owner", expiration=expiration)
    add_member(group, member)

    revoke_member(group, member)
Esempio n. 21
0
    def __init__(self, tmpdir):
        # type: (LocalPath) -> None
        self.settings = Settings()
        self.settings.database = db_url(tmpdir)
        self.plugins = PluginProxy([])
        self.graph = GroupGraph()

        # Reinitialize the global plugin proxy with an empty set of plugins in case a previous test
        # initialized plugins.  This can go away once a plugin proxy is injected into everything
        # that needs it instead of maintained as a global.
        set_global_plugin_proxy(self.plugins)

        self.initialize_database()
        self.open_database()
Esempio n. 22
0
def test_cant_revoke_last_permanent_owner(get_plugin_proxy, groups, users):
    get_plugin_proxy.return_value = PluginProxy([GroupOwnershipPolicyPlugin()])

    group = groups["team-infra"]
    first_owner = users["*****@*****.**"]
    second_owner = users["*****@*****.**"]

    expiration = datetime.utcnow() + timedelta(1)

    add_member(group, first_owner, role="owner", expiration=expiration)
    add_member(group, second_owner, role="owner")

    with pytest.raises(PluginRejectedGroupMembershipUpdate):
        revoke_member(group, second_owner)
def test_group_disable_group_owner(user_make_session, group_make_session, get_plugin_proxy, session,
                                   users, groups):
    group_make_session.return_value = session
    user_make_session.return_value = session
    get_plugin_proxy.return_value = PluginProxy([GroupOwnershipPolicyPlugin()])

    username = '******'
    groupname = 'team-sre'

    # add
    call_main('group', 'add_member', '--owner', groupname, username)
    assert (u'User', username) in Group.get(session, name=groupname).my_members()

    # disable (fails)
    call_main('user', 'disable', username)
    assert (u'User', username) in Group.get(session, name=groupname).my_members()
Esempio n. 24
0
def test_group_disable_group_owner(get_plugin_proxy, session, tmpdir, users,
                                   groups):  # noqa: F811
    get_plugin_proxy.return_value = PluginProxy([GroupOwnershipPolicyPlugin()])

    username = "******"
    groupname = "team-sre"

    # add
    call_main(session, tmpdir, "group", "add_member", "--owner", groupname,
              username)
    assert (u"User", username) in Group.get(session,
                                            name=groupname).my_members()

    # disable (fails)
    call_main(session, tmpdir, "user", "disable", username)
    assert (u"User", username) in Group.get(session,
                                            name=groupname).my_members()
Esempio n. 25
0
def test_user_created_plugin(mocker, session, users, groups):
    """Test calls to the user_created plugin."""
    plugin = UserCreatedPlugin()
    mocker.patch('grouper.models.user.get_plugin_proxy',
                 return_value=PluginProxy([plugin]))

    # Create a regular user.  The service account flag should be false, and the plugin should be
    # called.
    user, created = User.get_or_create(session, username="******")
    assert created == True
    assert plugin.calls == 1

    # Create a role user.  This should cause another plugin call and the service account flag
    # should now be true.
    plugin.expected_service_account = True
    create_role_user(session, user, "*****@*****.**", "description", "canask")
    assert plugin.calls == 2
Esempio n. 26
0
def test_cant_revoke_last_npowner(get_plugin_proxy, session, groups, users):
    get_plugin_proxy.return_value = PluginProxy([GroupOwnershipPolicyPlugin()])

    group = groups["team-infra"]
    first_owner = users["*****@*****.**"]
    second_owner = users["*****@*****.**"]

    add_member(group, first_owner, role="np-owner")
    add_member(group, second_owner, role="np-owner")

    # Revoking the first owner does not raise an exception
    revoke_member(group, first_owner)

    session.commit()

    with pytest.raises(PluginRejectedGroupMembershipUpdate):
        revoke_member(group, second_owner)
Esempio n. 27
0
def start_processor(args, settings, sentry_client):
    # type: (Namespace, BackgroundSettings, SentryProxy) -> None
    log_level = logging.getLevelName(logging.getLogger().level)
    logging.info("begin. log_level={}".format(log_level))

    try:
        plugins = PluginProxy.load_plugins(settings, "grouper-background")
        set_global_plugin_proxy(plugins)
    except PluginsDirectoryDoesNotExist as e:
        logging.fatal("Plugin directory does not exist: {}".format(e))
        sys.exit(1)

    # setup database
    logging.debug("configure database session")
    Session.configure(bind=get_db_engine(settings.database))

    background = BackgroundProcessor(settings, sentry_client)
    background.run()
Esempio n. 28
0
def start_processor(args, settings):
    # type: (Namespace, BackgroundSettings) -> None
    log_level = logging.getLevelName(logging.getLogger().level)
    logging.info("begin. log_level={}".format(log_level))

    try:
        plugins = PluginProxy.load_plugins(settings, "grouper-background")
        set_global_plugin_proxy(plugins)
    except PluginsDirectoryDoesNotExist as e:
        logging.fatal("Plugin directory does not exist: {}".format(e))
        sys.exit(1)

    # setup database
    logging.debug("configure database session")
    Session.configure(bind=get_db_engine(settings.database))

    background = BackgroundProcessor(settings, plugins)
    background.run()
def test_group_add_remove_owner(make_session, get_plugin_proxy, session, users,
                                groups):
    make_session.return_value = session
    get_plugin_proxy.return_value = PluginProxy([GroupOwnershipPolicyPlugin()])

    username = '******'
    groupname = 'team-sre'

    # add
    assert (u'User', username) not in groups[groupname].my_members()
    call_main('group', 'add_member', '--owner', groupname, username)
    all_members = Group.get(session, name=groupname).my_members()
    assert (u'User', username) in all_members
    _, _, _, role, _, _ = all_members[(u'User', username)]
    assert GROUP_EDGE_ROLES[role] == "owner"

    # remove (fails)
    call_main('group', 'remove_member', groupname, username)
    assert (u'User', username) in Group.get(session,
                                            name=groupname).my_members()
Esempio n. 30
0
def start_server(args, settings, sentry_client):
    # type: (Namespace, FrontendSettings, SentryProxy) -> None
    log_level = logging.getLevelName(logging.getLogger().level)
    logging.info("begin. log_level={}".format(log_level))

    assert not (
        settings.debug and settings.num_processes > 1
    ), "debug mode does not support multiple processes"

    try:
        plugins = PluginProxy.load_plugins(settings, "grouper-fe")
        set_global_plugin_proxy(plugins)
    except PluginsDirectoryDoesNotExist as e:
        logging.fatal("Plugin directory does not exist: {}".format(e))
        sys.exit(1)

    # setup database
    logging.debug("configure database session")
    if args.database_url:
        settings.database = args.database_url
    Session.configure(bind=get_db_engine(settings.database))

    application = create_fe_application(settings, args.deployment_name)
    ssl_context = plugins.get_ssl_context()

    if args.listen_stdin:
        logging.info(
            "Starting application server with %d processes on stdin", settings.num_processes
        )
        server = HTTPServer(application, ssl_options=ssl_context)
        if PY2:
            s = socket.fromfd(sys.stdin.fileno(), socket.AF_INET, socket.SOCK_STREAM)
            s.setblocking(False)
            s.listen(5)
        else:
            s = socket.socket(fileno=sys.stdin.fileno())
            s.setblocking(False)
            s.listen()
        server.add_sockets([s])
    else:
        address = args.address or settings.address
        port = args.port or settings.port
        logging.info(
            "Starting application server with %d processes on %s:%d",
            settings.num_processes,
            address,
            port,
        )
        server = HTTPServer(application, ssl_options=ssl_context)
        server.bind(port, address=address)

    # When using multiple processes, the forking happens here
    server.start(settings.num_processes)

    stats.set_defaults()

    # Create the Graph and start the graph update thread post fork to ensure each process gets
    # updated.
    with closing(Session()) as session:
        graph = Graph()
        graph.update_from_db(session)

    refresher = DbRefreshThread(settings, graph, settings.refresh_interval, sentry_client)
    refresher.daemon = True
    refresher.start()

    try:
        IOLoop.current().start()
    except KeyboardInterrupt:
        IOLoop.current().stop()
    finally:
        print("Bye")
Esempio n. 31
0
def main(sys_argv=sys.argv, session=None):
    # type: (List[str], Optional[Session]) -> None
    description_msg = "Grouper Control"
    parser = argparse.ArgumentParser(description=description_msg)

    parser.add_argument(
        "-c", "--config", default=default_settings_path(), help="Path to config file."
    )
    parser.add_argument(
        "-d", "--database-url", type=str, default=None, help="Override database URL in config."
    )
    parser.add_argument(
        "-q", "--quiet", action="count", default=0, help="Decrease logging verbosity."
    )
    parser.add_argument(
        "-v", "--verbose", action="count", default=0, help="Increase logging verbosity."
    )
    parser.add_argument(
        "-V",
        "--version",
        action="version",
        version="%%(prog)s %s" % __version__,
        help="Display version information.",
    )

    subparsers = parser.add_subparsers(dest="command")
    CtlCommandFactory.add_all_parsers(subparsers)

    # Add parsers for legacy commands that have not been refactored.
    for subcommand_module in [group, oneoff, service_account, shell]:
        subcommand_module.add_parser(subparsers)  # type: ignore

    args = parser.parse_args(sys_argv[1:])

    # Construct the CtlSettings object used for all commands, and set it as the global Settings
    # object.  All code in grouper.ctl.* takes the CtlSettings object as an argument if needed, but
    # it may call other legacy code that requires the global Settings object be present.
    settings = CtlSettings.global_settings_from_config(args.config)
    if args.database_url:
        settings.database = args.database_url

    # Construct a session factory, which is passed into all the legacy commands that haven't been
    # converted to usecases yet.
    if session:
        session_factory = SingletonSessionFactory(session)  # type: SessionFactory
    else:
        session_factory = SessionFactory(settings)

    log_level = get_loglevel(args, base=logging.INFO)
    logging.basicConfig(level=log_level, format=settings.log_format)

    if log_level < 0:
        sa_log.setLevel(logging.INFO)

    # Initialize plugins.  The global plugin proxy is used by legacy code.
    try:
        plugins = PluginProxy.load_plugins(settings, "grouper-ctl")
    except PluginsDirectoryDoesNotExist as e:
        logging.fatal("Plugin directory does not exist: {}".format(e))
        sys.exit(1)
    set_global_plugin_proxy(plugins)

    # Set up factories.
    usecase_factory = create_sql_usecase_factory(settings, plugins, session_factory)
    command_factory = CtlCommandFactory(settings, usecase_factory)

    # Old-style subcommands store a func in callable when setting up their arguments.  New-style
    # subcommands are handled via a factory that constructs and calls the correct object.
    if getattr(args, "func", None):
        args.func(args, settings, session_factory)
    else:
        command = command_factory.construct_command(args.command)
        command.run(args)
Esempio n. 32
0
def start_server(args, settings, sentry_client):
    # type: (Namespace, FrontendSettings, SentryProxy) -> None
    log_level = logging.getLevelName(logging.getLogger().level)
    logging.info("begin. log_level={}".format(log_level))

    assert not (settings.debug and settings.num_processes > 1
                ), "debug mode does not support multiple processes"

    try:
        plugins = PluginProxy.load_plugins(settings, "grouper-fe")
        set_global_plugin_proxy(plugins)
    except PluginsDirectoryDoesNotExist as e:
        logging.fatal("Plugin directory does not exist: {}".format(e))
        sys.exit(1)

    # setup database
    logging.debug("configure database session")
    if args.database_url:
        settings.database = args.database_url
    Session.configure(bind=get_db_engine(settings.database))

    application = create_fe_application(settings, args.deployment_name)
    ssl_context = plugins.get_ssl_context()

    if args.listen_stdin:
        logging.info("Starting application server with %d processes on stdin",
                     settings.num_processes)
        server = HTTPServer(application, ssl_options=ssl_context)
        if PY2:
            s = socket.fromfd(sys.stdin.fileno(), socket.AF_INET,
                              socket.SOCK_STREAM)
            s.setblocking(False)
            s.listen(5)
        else:
            s = socket.socket(fileno=sys.stdin.fileno())
            s.setblocking(False)
            s.listen()
        server.add_sockets([s])
    else:
        address = args.address or settings.address
        port = args.port or settings.port
        logging.info(
            "Starting application server with %d processes on %s:%d",
            settings.num_processes,
            address,
            port,
        )
        server = HTTPServer(application, ssl_options=ssl_context)
        server.bind(port, address=address)

    # When using multiple processes, the forking happens here
    server.start(settings.num_processes)

    stats.set_defaults()

    # Create the Graph and start the graph update thread post fork to ensure each process gets
    # updated.
    with closing(Session()) as session:
        graph = Graph()
        graph.update_from_db(session)

    refresher = DbRefreshThread(settings, graph, settings.refresh_interval,
                                sentry_client)
    refresher.daemon = True
    refresher.start()

    try:
        IOLoop.current().start()
    except KeyboardInterrupt:
        IOLoop.current().stop()
    finally:
        print("Bye")
Esempio n. 33
0
def test_machine_set_plugin(
        mocker,
        session,
        standard_graph,
        graph,
        http_client,
        base_url  # noqa: F811
):
    mocker.patch("grouper.service_account.get_plugin_proxy",
                 return_value=PluginProxy([MachineSetPlugin()]))
    admin = "*****@*****.**"

    # Edit the metadata of an existing service account.  This should fail (although return 200)
    # including the appropriate error.
    update = {
        "description": "some service account",
        "machine_set": "not valid"
    }
    fe_url = url(base_url, "/groups/team-sre/service/[email protected]/edit")
    resp = yield http_client.fetch(fe_url,
                                   method="POST",
                                   headers={"X-Grouper-User": admin},
                                   body=urlencode(update))
    assert resp.code == 200
    assert "[email protected] has invalid machine set" in resp.body
    graph.update_from_db(session)
    metadata = graph.user_metadata["*****@*****.**"]
    assert metadata["service_account"]["machine_set"] == "some machines"

    # Use a valid machine set, and then this should go through.
    update["machine_set"] = "is okay"
    resp = yield http_client.fetch(fe_url,
                                   method="POST",
                                   headers={"X-Grouper-User": admin},
                                   body=urlencode(update))
    assert resp.code == 200
    graph.update_from_db(session)
    metadata = graph.user_metadata["*****@*****.**"]
    assert metadata["service_account"]["machine_set"] == "is okay"

    # Try creating a new service account with an invalid machine set.
    data = {
        "name": "*****@*****.**",
        "description": "some other service account",
        "machine_set": "not valid",
    }
    fe_url = url(base_url, "/groups/team-sre/service/create")
    resp = yield http_client.fetch(fe_url,
                                   method="POST",
                                   headers={"X-Grouper-User": admin},
                                   body=urlencode(data))
    assert resp.code == 200
    assert "[email protected] has invalid machine set" in resp.body
    graph.update_from_db(session)
    assert "*****@*****.**" not in graph.users

    # But this should go through with a valid machine set.
    data["machine_set"] = "is okay"
    resp = yield http_client.fetch(fe_url,
                                   method="POST",
                                   headers={"X-Grouper-User": admin},
                                   body=urlencode(data))
    assert resp.code == 200
    graph.update_from_db(session)
    metadata = graph.user_metadata["*****@*****.**"]
    assert metadata["service_account"][
        "description"] == "some other service account"
    assert metadata["service_account"]["machine_set"] == "is okay"