Exemplo n.º 1
0
    def test_webhooks(self):
        with contextlib.ExitStack() as stack:
            stack.enter_context(
                mock.patch("cowbird.services.impl.magpie.Magpie",
                           side_effect=utils.MockMagpieService))
            data = {
                "event":
                "created",
                "user_name":
                "test_user",
                "callback_url":
                "http://magpie.domain.ca/tmp/109e1d0d-e27c-4601-9d45-984c9b61ebff"
            }
            resp = utils.test_request(self.app,
                                      "POST",
                                      "/webhooks/users",
                                      json=data)
            utils.check_response_basic_info(resp, 200, expected_method="POST")
            utils.check_response_basic_info(resp)
            magpie = ServiceFactory().get_service("Magpie")
            assert len(magpie.json()["event_users"]) == 1
            assert magpie.json()["event_users"][0] == data["user_name"]

            data["event"] = "deleted"
            data.pop("callback_url")
            resp = utils.test_request(self.app,
                                      "POST",
                                      "/webhooks/users",
                                      json=data)
            utils.check_response_basic_info(resp, 200, expected_method="POST")
            assert len(magpie.json()["event_users"]) == 0

            data = {
                "event": "created",
                "service_name": "string",
                "resource_id": "string",
                "resource_full_name": "thredds/birdhouse/file.nc",
                "name": "read",
                "access": "allow",
                "scope": "recursive",
                "user": "******",
                "group": "string"
            }
            resp = utils.test_request(self.app,
                                      "POST",
                                      "/webhooks/permissions",
                                      json=data)
            utils.check_response_basic_info(resp, 200, expected_method="POST")
            magpie = ServiceFactory().get_service("Magpie")
            assert len(magpie.json()["event_perms"]) == 1
            assert magpie.json(
            )["event_perms"][0] == data["resource_full_name"]

            data["event"] = "deleted"
            resp = utils.test_request(self.app,
                                      "POST",
                                      "/webhooks/permissions",
                                      json=data)
            utils.check_response_basic_info(resp, 200, expected_method="POST")
            assert len(magpie.json()["event_perms"]) == 0
Exemplo n.º 2
0
    def test_evaluate_call_recursive_safeguard(self):
        """
        Validate use case if internal function that handles formatting and generation of a resulting HTTP response
        raises itself an error (because of implementation issue), while it is processing another pre-raised error, that
        it does not end up into an endless recursive call stack of raised errors.
        """
        mock_calls = {"counter": 0}

        def mock_raise(*_, **__):
            # avoid raising forever if the real safeguard fails doing its job
            if mock_calls["counter"] >= 2 * ax.RAISE_RECURSIVE_SAFEGUARD_MAX:
                return TypeError()
            mock_calls["counter"] += 1
            raise TypeError()

        def mock_lambda_call(*_, **__):
            ax.evaluate_call(lambda: int("x"))

        try:
            app = utils.get_test_magpie_app()
            with mock.patch(
                    "magpie.api.exception.generate_response_http_format",
                    side_effect=mock_raise):
                with mock.patch("magpie.api.login.login.get_session",
                                side_effect=mock_lambda_call):
                    # Call request that ends up calling the response formatter via 'evaluate_call' itself raising to
                    # trigger 'mock_raise' recursively within 'raise_http' function.
                    # Since tweens are set up to format all response prior to return, the raised error will itself
                    # call 'raise_http' again each time operation fails, creating recursive raises.
                    # If recursive safeguard does its job, it should end up raising 'HTTPInternalServerError' directly
                    # (without further formatting attempt when reaching the MAX value), stopping the endless loop.
                    utils.test_request(app,
                                       "GET",
                                       "/session",
                                       expect_errors=True)
        except AssertionError:
            # Request called with above 'test_request' should catch the final 'HTTPInternalServerError' that is
            # raised directly instead of usual TestResponse returned. That error is again re-raised as 'AssertionError'
            pass
        except Exception as exc:
            self.fail(
                "unexpected error during request creation should not raise: {}"
                .format(exc))

        # if our counter reached higher than the MAX (i.e.: 2*MAX from mock), the safeguard did not do its job
        # if it did not get called at least more than once, use cases did not really get tested
        utils.check_val_is_in(
            mock_calls["counter"],
            list(range(2, ax.RAISE_RECURSIVE_SAFEGUARD_MAX + 1)))  # noqa
Exemplo n.º 3
0
def test_response_metadata():
    """
    Validate that regardless of response type (success/error) and status-code, metadata details are added.

    note: test only locally to avoid remote server side-effects and because mock cannot be done remotely
    """

    def raise_request(*_, **__):
        raise TypeError()

    app = utils.get_test_magpie_app()
    # all paths below must be publicly accessible
    for code, method, path, kwargs in [
        (200, "GET", "/session", {}),
        # FIXME: sort out 400 vs 422 everywhere (https://github.com/Ouranosinc/Magpie/issues/359)
        # (400, "POST", "/signin", {"body": {}}),  # missing credentials
        (401, "GET", "/services", {}),  # anonymous unauthorized
        (404, "GET", "/random", {}),
        (405, "POST", "/users/{}".format("MAGPIE_LOGGED_USER"), {"body": {}}),
        (406, "GET", "/session", {"headers": {"Accept": "application/pdf"}}),
        # 409: need connection to test conflict, no route available without so (other tests validates them though)
        (422, "POST", "/signin", {"body": {"user_name": "!!!!"}}),  # invalid format
        (500, "GET", "/json", {}),  # see mock
    ]:
        with mock.patch("magpie.api.schemas.generate_api_schema", side_effect=raise_request):
            headers = {"Accept": CONTENT_TYPE_JSON, "Content-Type": CONTENT_TYPE_JSON}
            headers.update(kwargs.get("headers", {}))
            kwargs.pop("headers", None)
            resp = utils.test_request(app, method, path, expect_errors=True, headers=headers, **kwargs)
            # following util check validates all expected request metadata in response body
            utils.check_response_basic_info(resp, expected_code=code, expected_method=method)
Exemplo n.º 4
0
 def mock_request(*args, **kwargs):
     method, url, args = args[0], args[1], args[2:]
     path = url.replace(test_url, "")
     # because CLI utility does multiple login tests, we must force TestApp logout to forget session
     # otherwise, error is raised because of user session mismatch between previous login and new one requested
     if path.startswith("/signin"):
         utils.check_or_try_logout_user(test_app)
     return utils.test_request(test_app, method, path, *args, **kwargs)
Exemplo n.º 5
0
 def test_homepage(self):
     resp = utils.test_request(self.app, "GET", "/")
     body = utils.check_response_basic_info(resp)
     utils.check_val_is_in("name", body)
     utils.check_val_is_in("title", body)
     utils.check_val_is_in("contact", body)
     utils.check_val_is_in("description", body)
     utils.check_val_is_in("documentation", body)
     utils.check_val_is_in("cowbird", body["name"])
Exemplo n.º 6
0
def test_response_metadata():
    """
    Validate that regardless of response type (success/error) and status-code, metadata details are added.

    note: test only locally to avoid remote server side-effects and because mock cannot be done remotely
    """
    class MockService(object):
        def name(self):
            raise TypeError()

    app = utils.get_test_app()
    # all paths below must be publicly accessible
    for i, (code, method, path, kwargs) in enumerate(
        [
            (200, "GET", "", {}),
            (400, "GET", "/services/!!!!", {}),  # invalid format
            # (401, "GET", "/services", {}),  # anonymous unauthorized
            (404, "GET", "/random", {}),
            (405, "POST", "/json", {
                "body": {}
            }),
            (406, "GET", "/api", {
                "headers": {
                    "Accept": "application/pdf"
                }
            }),
            # 409: need connection to test conflict, no route available without so (other tests validates them though)
            # (422, "POST", "/services", {"body": {"name": 1}}),  # invalid field type  # FIXME: route impl required
            (500, "GET", "/services", {}),  # see mock
        ],
            start=1):
        with contextlib.ExitStack() as stack:
            if code == 500:
                stack.enter_context(
                    mock.patch("cowbird.services.impl.magpie.Magpie",
                               side_effect=MockService))
            headers = {
                "Accept": CONTENT_TYPE_JSON,
                "Content-Type": CONTENT_TYPE_JSON
            }
            headers.update(kwargs.get("headers", {}))
            kwargs.pop("headers", None)
            resp = utils.test_request(app,
                                      method,
                                      path,
                                      expect_errors=True,
                                      headers=headers,
                                      **kwargs)
            # following util check validates all expected request metadata in response body
            msg = "\n[Test: #{}, Code: {}]".format(i, code)
            utils.check_response_basic_info(resp,
                                            expected_code=code,
                                            expected_method=method,
                                            extra_message=msg)
Exemplo n.º 7
0
    def test_magpie_prefix_direct_request(self):
        base_url = "http://localhost"
        for url in ["http://localhost", "http://localhost/magpie"]:
            app = utils.get_test_magpie_app({"magpie.url": url})

            path = "/version"
            resp = utils.test_request(app, "GET", path)
            utils.check_response_basic_info(resp)
            utils.check_val_equal(
                resp.request.url, base_url + path,
                "Proxied path should have been auto-resolved [URL: {}].".
                format(url))
Exemplo n.º 8
0
    def test_magpie_prefix_request_with_multiple_route_url(self):
        """
        Test multiple request routing with fixed "MAGPIE_URL" within the API application.

        Signin with invalid credentials will call "/signin" followed by sub-request "/signin_internal" and finally
        "ZigguratSignInBadAuth". Both "/signin" and "ZigguratSignInBadAuth" use "get_multiformat_body".
        """
        from magpie.api.requests import get_value_multiformat_body_checked as real_multiform_post_checked
        base_url = "http://localhost"

        def mock_get_post(real_func, *args, **kwargs):
            if args[1] != "password":
                return real_func(*args, **kwargs)
            request, args = args[0], args[1:]
            utils.check_val_equal(
                request.url, base_url + _paths.pop(0),
                "Proxied path should have been auto-resolved [URL: {}].".
                format(url))
            return real_func(request, *args, **kwargs)

        for url in ["http://localhost", "http://localhost/magpie"]:
            # paths are reduced (pop in mock) each time a post to get the 'password' is called in 'login' module
            # this combination should happen twice, one in signin route and another on the redirected internal login
            _paths = ["/signin", "/signin_internal"]
            app = utils.get_test_magpie_app({"magpie.url": url})

            with mock.patch(
                    "magpie.api.requests.get_value_multiformat_body_checked",
                    side_effect=lambda *_, **__: mock_get_post(
                        real_multiform_post_checked, *_, **__)):
                data = {"user_name": "foo", "password": "******"}
                headers = {
                    "Content-Type": CONTENT_TYPE_JSON,
                    "Accept": CONTENT_TYPE_JSON
                }
                resp = utils.test_request(app,
                                          "POST",
                                          _paths[0],
                                          json=data,
                                          headers=headers,
                                          expect_errors=True)
                if LooseVersion(self.version) < LooseVersion("0.10.0"):
                    # user name doesn't exist
                    utils.check_response_basic_info(resp,
                                                    expected_code=406,
                                                    expected_method="POST")
                else:
                    # invalid username/password credentials
                    utils.check_response_basic_info(resp,
                                                    expected_code=401,
                                                    expected_method="POST")
Exemplo n.º 9
0
 def check_api_resource_permissions(resource_permissions):
     for _r_id, _r_perms in resource_permissions:
         urp_path = "/users/{}/resources/{}/permissions".format(
             self.test_user_name, _r_id)
         urp_resp = utils.test_request(self, "GET", urp_path)
         urp_body = utils.check_response_basic_info(urp_resp)
         ur_perms = [
             perm.json() for perm in _r_perms
             if isinstance(perm, PermissionSet)
         ]
         for perm in ur_perms:
             perm["type"] = PermissionType.DIRECT.value
         permissions = urp_body["permissions"]
         for perm in permissions:
             perm.pop("reason",
                      None)  # >= 3.4, don't care for this test
         utils.check_all_equal(permissions, ur_perms, any_order=True)
Exemplo n.º 10
0
def test_magpie_homepage():
    from magpie.constants import get_constant as real_get_constant  # pylint: disable=W0404,reimported

    def mock_get_constant(*args, **kwargs):
        if args[0] == "MAGPIE_UI_ENABLED":
            return False
        return real_get_constant(*args, **kwargs)

    with mock.patch("magpie.constants.get_constant", side_effect=mock_get_constant), \
            mock.patch("magpie.api.home.get_constant", side_effect=mock_get_constant):
        app = utils.get_test_magpie_app()
        resp = utils.test_request(app, "GET", "/")
        body = utils.check_response_basic_info(resp)
        utils.check_val_is_in("name", body)
        utils.check_val_is_in("title", body)
        utils.check_val_is_in("contact", body)
        utils.check_val_is_in("description", body)
        utils.check_val_is_in("documentation", body)
        utils.check_val_is_in("magpie", body["name"])
Exemplo n.º 11
0
    def test_register_permissions_missing_group_create_new_entries(self):
        utils.TestSetup.create_TestService(
            self,
            override_service_name=self.test_perm_svc_name,
            override_service_type=ServiceAPI.service_type)
        utils.TestSetup.delete_TestGroup(
            self, override_group_name=self.test_perm_grp_name)
        session = get_db_session_from_settings(self.app.app.registry.settings)

        svc_perm = Permission.READ_MATCH
        res1_perm = Permission.READ
        res2_perm = Permission.WRITE
        res1_name = "test-resource"
        res2_name = "sub-test-resource"
        perm_config = {
            "permissions": [
                {
                    "service": self.test_perm_svc_name,
                    "permission": svc_perm.value,
                    "action": "create",
                    "group": self.test_perm_grp_name,
                },
                {
                    "service": self.test_perm_svc_name,
                    "resource": res1_name,
                    "permission": res1_perm.value,
                    "action": "create",
                    "group": self.test_perm_grp_name,
                },
                {
                    "service": self.test_perm_svc_name,
                    "resource": res1_name + "/" + res2_name,
                    "permission": res2_perm.value,
                    "action": "create",
                    "group": self.test_perm_grp_name,
                },
            ]
        }
        utils.check_no_raise(
            lambda: register.magpie_register_permissions_from_config(
                perm_config, db_session=session))

        # check that all service, resources, group are created correctly
        services = utils.TestSetup.get_RegisteredServicesList(self)
        utils.check_val_is_in(self.test_perm_svc_name,
                              [s["service_name"] for s in services])
        groups = utils.TestSetup.get_RegisteredGroupsList(self)
        utils.check_val_is_in(self.test_perm_grp_name, groups)
        resp = utils.test_request(
            self.app, "GET",
            "/services/{}/resources".format(self.test_perm_svc_name))
        body = utils.check_response_basic_info(resp)
        svc_res = body[self.test_perm_svc_name]["resources"]
        svc_res_id = body[self.test_perm_svc_name]["resource_id"]
        utils.check_val_is_in(res1_name,
                              [svc_res[r]["resource_name"] for r in svc_res])
        res1_id = [
            svc_res[r]["resource_id"] for r in svc_res
            if svc_res[r]["resource_name"] == res1_name
        ][0]
        res1_sub = svc_res[str(res1_id)]["children"]
        utils.check_val_is_in(res2_name,
                              [res1_sub[r]["resource_name"] for r in res1_sub])
        res2_id = [
            res1_sub[r]["resource_id"] for r in res1_sub
            if res1_sub[r]["resource_name"] == res2_name
        ][0]

        # check that all permissions are created correctly
        path = "/groups/{}/resources/{}/permissions".format(
            self.test_perm_grp_name, svc_res_id)
        resp = utils.test_request(self.app, "GET", path)
        body = utils.check_response_basic_info(resp)
        utils.check_val_is_in(svc_perm.value, body["permission_names"])
        path = "/groups/{}/resources/{}/permissions".format(
            self.test_perm_grp_name, res1_id)
        resp = utils.test_request(self.app, "GET", path)
        body = utils.check_response_basic_info(resp)
        utils.check_val_is_in(res1_perm.value, body["permission_names"])
        path = "/groups/{}/resources/{}/permissions".format(
            self.test_perm_grp_name, res2_id)
        resp = utils.test_request(self.app, "GET", path)
        body = utils.check_response_basic_info(resp)
        utils.check_val_is_in(res2_perm.value, body["permission_names"])
Exemplo n.º 12
0
    def test_register_permissions_existing_group_without_intermediate_entries(
            self):
        utils.TestSetup.create_TestService(
            self,
            override_service_name=self.test_perm_svc_name,
            override_service_type=ServiceAPI.service_type)
        utils.TestSetup.create_TestGroup(
            self, override_group_name=self.test_perm_grp_name)
        session = get_db_session_from_settings(self.app.app.registry.settings)

        res1_name = "test-resource"
        res2_name = "sub-test-resource"
        res3_name = "sub-sub-test-resource"
        res3_perm = Permission.WRITE_MATCH
        perm_config = {
            "permissions": [
                {
                    "service": self.test_perm_svc_name,  # exists
                    "resource": res1_name + "/" + res2_name + "/" +
                    res3_name,  # none exist, all created for perm
                    "permission": res3_perm.value,  # perm only to lowest child
                    "action": "create",
                    "group": self.test_perm_grp_name,
                },
            ]
        }
        utils.check_no_raise(
            lambda: register.magpie_register_permissions_from_config(
                perm_config, db_session=session))

        # check that all service, resources, group are created correctly
        services = utils.TestSetup.get_RegisteredServicesList(self)
        utils.check_val_is_in(self.test_perm_svc_name,
                              [s["service_name"] for s in services])
        groups = utils.TestSetup.get_RegisteredGroupsList(self)
        utils.check_val_is_in(self.test_perm_grp_name, groups)
        resp = utils.test_request(
            self.app, "GET",
            "/services/{}/resources".format(self.test_perm_svc_name))
        body = utils.check_response_basic_info(resp)
        svc_res = body[self.test_perm_svc_name]["resources"]
        svc_res_id = body[self.test_perm_svc_name]["resource_id"]
        utils.check_val_is_in(res1_name,
                              [svc_res[r]["resource_name"] for r in svc_res])
        res1_id = [
            svc_res[r]["resource_id"] for r in svc_res
            if svc_res[r]["resource_name"] == res1_name
        ][0]
        res1_sub = svc_res[str(res1_id)]["children"]
        utils.check_val_is_in(res2_name,
                              [res1_sub[r]["resource_name"] for r in res1_sub])
        res2_id = [
            res1_sub[r]["resource_id"] for r in res1_sub
            if res1_sub[r]["resource_name"] == res2_name
        ][0]
        res2_sub = res1_sub[str(res2_id)]["children"]
        utils.check_val_is_in(res3_name,
                              [res2_sub[r]["resource_name"] for r in res2_sub])
        res3_id = [
            res2_sub[r]["resource_id"] for r in res2_sub
            if res2_sub[r]["resource_name"] == res3_name
        ][0]

        # check that all permissions are created correctly (only for final item)
        path = "/groups/{}/resources/{}/permissions".format(
            self.test_perm_grp_name, svc_res_id)
        resp = utils.test_request(self.app, "GET", path)
        body = utils.check_response_basic_info(resp)
        utils.check_val_equal(body["permission_names"], [])
        path = "/groups/{}/resources/{}/permissions".format(
            self.test_perm_grp_name, res1_id)
        resp = utils.test_request(self.app, "GET", path)
        body = utils.check_response_basic_info(resp)
        utils.check_val_equal(body["permission_names"], [])
        path = "/groups/{}/resources/{}/permissions".format(
            self.test_perm_grp_name, res2_id)
        resp = utils.test_request(self.app, "GET", path)
        body = utils.check_response_basic_info(resp)
        utils.check_val_equal(body["permission_names"], [])
        path = "/groups/{}/resources/{}/permissions".format(
            self.test_perm_grp_name, res3_id)
        resp = utils.test_request(self.app, "GET", path)
        body = utils.check_response_basic_info(resp)
        utils.check_all_equal(body["permission_names"], [res3_perm.value])
Exemplo n.º 13
0
def run_batch_update_user_command(test_app, expected_users,
                                  create_command_xargs, delete_command_xargs):
    """
    Tests batch user creation and deletion of the CLI utility.

    Because CLI utility employs requests that cannot be mocked if executed through sub-process, we call it directly.
    """
    test_admin_usr = get_constant("MAGPIE_TEST_ADMIN_USERNAME",
                                  raise_not_set=False,
                                  raise_missing=False)
    test_admin_pwd = get_constant("MAGPIE_TEST_ADMIN_PASSWORD",
                                  raise_not_set=False,
                                  raise_missing=False)
    if not test_admin_usr or not test_admin_pwd:
        test_admin_usr = get_constant("MAGPIE_ADMIN_USER")
        test_admin_pwd = get_constant("MAGPIE_ADMIN_PASSWORD")
    test_url = "http://localhost"

    def mock_request(*args, **kwargs):
        method, url, args = args[0], args[1], args[2:]
        path = url.replace(test_url, "")
        # because CLI utility does multiple login tests, we must force TestApp logout to forget session
        # otherwise, error is raised because of user session mismatch between previous login and new one requested
        if path.startswith("/signin"):
            utils.check_or_try_logout_user(test_app)
        return utils.test_request(test_app, method, path, *args, **kwargs)

    def run_command(operation_name, operation_args):
        with tempfile2.TemporaryDirectory() as tmpdir:
            with mock.patch("requests.Session.request",
                            side_effect=mock_request):
                with mock.patch("requests.request", side_effect=mock_request):
                    cmd = [
                        test_url, test_admin_usr, test_admin_pwd, "-o", tmpdir
                    ] + operation_args
                    assert batch_update_users.main(
                        cmd) == 0, "failed execution due to invalid arguments"
                    assert len(
                        os.listdir(tmpdir)
                    ) == 1, "utility should have produced 1 output file"
                    file = os.path.join(tmpdir, os.listdir(tmpdir)[0])
                    utils.check_val_is_in(operation_name, file)
                    assert os.path.isfile(file)
                    with open(file, "r") as fd:
                        file_text = fd.read()
                    assert all([test_user in file_text for test_user in expected_users]), \
                        "all users should have been processed and logged in output result file"

    # cleanup in case of previous failure
    _, test_admin_cookies = utils.check_or_try_login_user(
        test_app, username=test_admin_usr, password=test_admin_pwd)
    for user in expected_users:
        utils.TestSetup.delete_TestUser(
            test_app,
            override_user_name=user,  # noqa
            override_headers={},
            override_cookies=test_admin_cookies)
    utils.check_or_try_logout_user(test_app)

    # test user creation and validate that users were all created
    run_command("create", create_command_xargs)
    utils.check_or_try_logout_user(test_app)
    _, test_admin_cookies = utils.check_or_try_login_user(
        test_app, username=test_admin_usr, password=test_admin_pwd)
    resp = utils.test_request(test_app,
                              "GET",
                              "/users",
                              cookies=test_admin_cookies)
    body = utils.check_response_basic_info(resp)
    for user in expected_users:
        utils.check_val_is_in(user, body["user_names"])

    # test user deletion and validate users are all deleted
    run_command("delete", ["-d"] + delete_command_xargs)
    utils.check_or_try_logout_user(test_app)
    _, test_admin_cookies = utils.check_or_try_login_user(
        test_app, username=test_admin_usr, password=test_admin_pwd)
    resp = utils.test_request(test_app,
                              "GET",
                              "/users",
                              cookies=test_admin_cookies)
    body = utils.check_response_basic_info(resp)
    for user in expected_users:
        utils.check_val_not_in(user, body["user_names"])
Exemplo n.º 14
0
    def test_EditUser_ApplyPermissions(self):
        """
        Verifies that UI button operations are working for the following workflow:
            0. Goto Edit User page.
            1. Change ``service-type`` tab to display services of type :class:`ServiceAPI`.
            2. Set new permissions onto an existing resources and submit them with ``Apply`` button.
            3. Verify the permissions are selected and displayed on page reload.
            4. Remove and modify permission from existing resource and submit.
            5. Validate that changes are reflected.

        Note:
            Only implemented locally with form submission of ``TestApp``.
        """
        utils.warn_version(self,
                           "update permission modifiers with option select",
                           "3.0",
                           skip=True)

        # make sure any sub-resource are all deleted to avoid conflict, then recreate service to add sub-resource
        utils.TestSetup.delete_TestService(self)
        body = utils.TestSetup.create_TestService(self)
        info = utils.TestSetup.get_ResourceInfo(self, override_body=body)
        svc_id, svc_name = info["resource_id"], info["service_name"]
        res_name = "resource1"
        sub_name = "resource2"
        body = utils.TestSetup.create_TestResource(
            self, parent_resource_id=svc_id, override_resource_name=res_name)
        info = utils.TestSetup.get_ResourceInfo(self, override_body=body)
        res_id = info["resource_id"]
        body = utils.TestSetup.create_TestResource(
            self, parent_resource_id=res_id, override_resource_name=sub_name)
        info = utils.TestSetup.get_ResourceInfo(self, override_body=body)
        sub_id = info["resource_id"]
        utils.TestSetup.create_TestGroup(self)
        utils.TestSetup.create_TestUser(self)

        # utilities for later tests
        def to_ui_permission(permission):
            # type: (Union[Str, PermissionSet]) -> Str
            return permission.explicit_permission if isinstance(
                permission, PermissionSet) else ""

        def check_ui_resource_permissions(perm_form, resource_id, permissions):
            select_res_id = "permission_resource_{}".format(resource_id)
            select_options = perm_form.fields[
                select_res_id]  # contains multiple select, one per applicable permission
            utils.check_val_equal(
                len(select_options),
                2,
                msg="Always 2 select combobox expected (read and write).")
            select_values = [select.value for select in select_options]
            if not permissions:
                utils.check_all_equal(
                    ["", ""],
                    select_values,
                    msg="When no permission is selected, values are empty")
                return
            # value must match exactly the *explicit* permission representation
            for r_perm in permissions:
                utils.check_val_is_in(r_perm, select_values)

        def check_api_resource_permissions(resource_permissions):
            for _r_id, _r_perms in resource_permissions:
                urp_path = "/users/{}/resources/{}/permissions".format(
                    self.test_user_name, _r_id)
                urp_resp = utils.test_request(self, "GET", urp_path)
                urp_body = utils.check_response_basic_info(urp_resp)
                ur_perms = [
                    perm.json() for perm in _r_perms
                    if isinstance(perm, PermissionSet)
                ]
                for perm in ur_perms:
                    perm["type"] = PermissionType.DIRECT.value
                permissions = urp_body["permissions"]
                for perm in permissions:
                    perm.pop("reason",
                             None)  # >= 3.4, don't care for this test
                utils.check_all_equal(permissions, ur_perms, any_order=True)

        # 0. goto user edit page (default first service selected)
        path = "/ui/users/{}/default".format(self.test_user_name)
        resp = utils.test_request(self, "GET", path)
        # 1. change to 'api' service-type, validate created test resources are all displayed in resource tree
        resp = resp.click(self.test_service_type
                          )  # tabs are '<a href=...>{service-type}</a>'
        body = utils.check_ui_response_basic_info(resp)
        text = body.replace("\n", "").replace("  ",
                                              "")  # ignore HTML formatting
        tree_keys = re.findall(r"<div class=\"tree-key\">(.*?)</div>", text)
        for r_name in [svc_name, res_name, sub_name]:
            utils.check_val_is_in(
                r_name,
                tree_keys,
                msg="Resource '{}' expected to be listed in tree.".format(
                    r_name))

        # 2. apply new permissions
        # 2.1 validate all initial values of permissions are empty
        res_form_name = "resources_permissions"  # form that wraps the displayed resource tree
        res_form_submit = "edit_permissions"  # id of the Apply 'submit' input of the form
        res_perm_form = resp.forms[res_form_name]
        for r_id in [svc_id, res_id, sub_id]:
            check_ui_resource_permissions(res_perm_form, r_id, [])
        # 2.2 apply new
        # NOTE: because tree-view is created by reversed order of permissions, we must provide them as [WRITE, READ]
        svc_perm1 = PermissionSet(Permission.READ, Access.ALLOW,
                                  Scope.RECURSIVE)
        svc_perms = ["", svc_perm1]
        res_perm1 = PermissionSet(Permission.READ, Access.DENY,
                                  Scope.RECURSIVE)
        res_perm2 = PermissionSet(Permission.WRITE, Access.ALLOW,
                                  Scope.RECURSIVE)
        res_perms = [res_perm2, res_perm1]
        sub_perm1 = PermissionSet(Permission.READ, Access.ALLOW, Scope.MATCH)
        sub_perms = ["", sub_perm1]
        data = {
            # set value for following correspond to 'select' option that was chosen
            "permission_resource_{}".format(svc_id):
            [to_ui_permission(perm) for perm in svc_perms],
            "permission_resource_{}".format(res_id):
            [to_ui_permission(perm) for perm in res_perms],
            "permission_resource_{}".format(sub_id):
            [to_ui_permission(perm) for perm in sub_perms],
        }
        resp = utils.TestSetup.check_FormSubmit(self,
                                                previous_response=resp,
                                                form_match=res_perm_form,
                                                form_submit=res_form_submit,
                                                form_data=data)

        # 3. validate result
        # 3.1 validate displayed UI permissions
        res_perm_form = resp.forms[res_form_name]
        check_ui_resource_permissions(
            res_perm_form, svc_id,
            [to_ui_permission(perm) for perm in svc_perms])
        check_ui_resource_permissions(
            res_perm_form, res_id,
            [to_ui_permission(perm) for perm in res_perms])
        check_ui_resource_permissions(
            res_perm_form, sub_id,
            [to_ui_permission(perm) for perm in sub_perms])
        # 3.2 validate applied using API (to make sure that changes are not only reflected in UI)
        check_api_resource_permissions([(svc_id, svc_perms),
                                        (res_id, res_perms),
                                        (sub_id, sub_perms)])

        # 4. remove permission: this is equivalent to re-apply the ones we want to keep without the one to remove
        #    modify permission: this is detected by combo of (res-id, perm-name) with different (access, scope)
        svc_perms_mod = ["",
                         ""]  # remove the previous (READ, ALLOW, RECURSIVE)
        res_perm1_mod = PermissionSet(Permission.READ, Access.ALLOW,
                                      Scope.RECURSIVE)  # DENY -> ALLOW
        res_perms_mod = [res_perm2,
                         res_perm1_mod]  # second WRITE permission is unchanged
        sub_perms_mod = sub_perms  # all unchanged for this resource
        data = {
            "permission_resource_{}".format(svc_id):
            [to_ui_permission(perm) for perm in svc_perms_mod],
            "permission_resource_{}".format(res_id):
            [to_ui_permission(perm) for perm in res_perms_mod],
            "permission_resource_{}".format(sub_id):
            [to_ui_permission(perm) for perm in sub_perms_mod],
        }
        resp = utils.TestSetup.check_FormSubmit(self,
                                                previous_response=resp,
                                                form_match=res_perm_form,
                                                form_submit=res_form_submit,
                                                form_data=data)

        # 5. validate applied permissions modifications
        res_perm_form = resp.forms[res_form_name]
        check_ui_resource_permissions(
            res_perm_form, svc_id,
            [to_ui_permission(perm) for perm in svc_perms_mod])
        check_ui_resource_permissions(
            res_perm_form, res_id,
            [to_ui_permission(perm) for perm in res_perms_mod])
        check_ui_resource_permissions(
            res_perm_form, sub_id,
            [to_ui_permission(perm) for perm in sub_perms_mod])
        check_api_resource_permissions([(svc_id, svc_perms_mod),
                                        (res_id, res_perms_mod),
                                        (sub_id, sub_perms_mod)])
Exemplo n.º 15
0
    def test_register_permissions_multiple_resource_type_possible(self):
        """
        Verify that service that allows multiple resource-types children retrieves ``type`` field for their creation.
        """
        svc_type = ServiceTHREDDS.service_type
        svc_name = "unittest-service-thredds-register-children-resources"
        utils.TestSetup.create_TestService(self,
                                           override_service_name=svc_name,
                                           override_service_type=svc_type)
        utils.TestSetup.create_TestGroup(
            self, override_group_name=self.test_perm_grp_name)
        session = get_db_session_from_settings(self.app.app.registry.settings)

        res1_name = "test-resource"
        res2_name = "sub-test-resource"
        res3_name = "sub-sub-test-resource"
        res3_perm = "write-deny-recursive"
        res3_path = res1_name + "/" + res2_name + "/" + res3_name  # none exist, all created for final permission
        perm_config = {
            "permissions": [
                {
                    "service": svc_name,  # exists
                    "resource": res3_path,
                    "permission": res3_perm,  # perm only on lowest child
                    "type": Directory.
                    resource_type_name,  # without this, fails because cannot infer Directory/File
                    "action": "create",
                    "group": self.test_perm_grp_name,
                },
            ]
        }
        utils.check_no_raise(
            lambda: register.magpie_register_permissions_from_config(
                perm_config, db_session=session))

        # check that all service, resources, group are created correctly
        services = utils.TestSetup.get_RegisteredServicesList(self)
        utils.check_val_is_in(svc_name, [s["service_name"] for s in services])
        groups = utils.TestSetup.get_RegisteredGroupsList(self)
        utils.check_val_is_in(self.test_perm_grp_name, groups)
        resp = utils.test_request(self.app, "GET",
                                  "/services/{}/resources".format(svc_name))
        body = utils.check_response_basic_info(resp)
        svc_res = body[svc_name]["resources"]  # type: JSON
        utils.check_val_is_in(res1_name,
                              [svc_res[r]["resource_name"] for r in svc_res])
        res1_id = [
            svc_res[r]["resource_id"] for r in svc_res
            if svc_res[r]["resource_name"] == res1_name
        ][0]
        res1_sub = svc_res[str(res1_id)]["children"]  # type: JSON
        utils.check_val_is_in(res2_name,
                              [res1_sub[r]["resource_name"] for r in res1_sub])
        res2_id = [
            res1_sub[r]["resource_id"] for r in res1_sub
            if res1_sub[r]["resource_name"] == res2_name
        ][0]
        res2_sub = res1_sub[str(res2_id)]["children"]  # type: JSON
        utils.check_val_is_in(res3_name,
                              [res2_sub[r]["resource_name"] for r in res2_sub])
        res3_id = [
            res2_sub[r]["resource_id"] for r in res2_sub
            if res2_sub[r]["resource_name"] == res3_name
        ][0]

        path = "/groups/{}/resources/{}/permissions".format(
            self.test_perm_grp_name, res3_id)
        resp = utils.test_request(self.app, "GET", path)
        body = utils.check_response_basic_info(resp)
        utils.check_val_is_in(res3_perm, body["permission_names"])

        # validate that without the 'type', similar resource would not be created due to missing information
        res_missing_type_name = "missing-type"
        res_missing_type_path = res3_path.replace("/" + res3_name,
                                                  "/" + res_missing_type_name)
        missing_type_perm_config = copy.deepcopy(perm_config)  # type: JSON
        missing_type_perm_config["permissions"][0].pop("type")
        missing_type_perm_config["permissions"][0][
            "resource"] = res_missing_type_path
        utils.check_no_raise(
            lambda: register.magpie_register_permissions_from_config(
                missing_type_perm_config, db_session=session))
        resp = utils.test_request(self.app, "GET",
                                  "/resources/{}".format(res2_id))
        body = utils.check_response_basic_info(resp)
        res_children = body["resource"]["children"]
        res_found = [
            res for _, res in res_children.items()
            if res["resource_name"] == res_missing_type_name
        ]
        utils.check_val_equal(res_found, [])