def auth_request(self, jwt, service, methods, resources): """ Return: bool: authorization response """ if isinstance(resources, string_types): resources = [resources] if isinstance(methods, string_types): methods = [methods] data = { "user": {"token": jwt}, "requests": [ {"resource": resource, "action": {"service": service, "method": method}} for resource in resources for method in methods ], } response = self.post(self._auth_url.rstrip("/") + "/request", json=data) if not response.successful: msg = "request to arborist failed: {}".format(response.error_msg) raise ArboristError(msg, response.code) elif response.code == 200: return bool(response.json["auth"]) else: # arborist could send back a 400 for things like, the user has some policy # that it doesn't recognize, or the request is structured incorrectly; for # these cases we will default to unauthorized msg = "arborist could not process auth request: {}".format( response.error_msg ) self.logger.info(msg) raise ArboristError(msg, response.code)
def update_policy(self, policy_id, policy_json, create_if_not_exist=False): """ Arborist will create policy if not exist and overwrite if exist. """ if "id" in policy_json and policy_json.pop("id") != policy_id: self.logger.warn( "id in policy_json provided but not equal to policy_id, ignoring." ) try: # Arborist 3.x.x url = self._policy_url + urllib.quote(policy_id) response = self.put(url, json=policy_json) except ArboristError as e: if e.code == 405: # For compatibility with Arborist 2.x.x self.logger.info( "This Arborist version has no PUT /policy/{policyID} endpt yet. Falling back on PUT /policy" ) policy_json["id"] = policy_id response = self.put(self._policy_url, json=policy_json) else: raise if response.code == 404 and create_if_not_exist: self.logger.info("Policy `{}` does not exist: Creating".format(policy_id)) policy_json["id"] = policy_id return self.create_policy(policy_json, skip_if_exists=False) if not response.successful: msg = "could not put policy `{}` in arborist: {}".format( policy_id, response.error_msg ) self.logger.error(msg) raise ArboristError(msg, response.code) self.logger.info("put policy {}".format(policy_id)) return response
def delete_resource(self, path): url = self._resource_url + urllib.quote(path) response = self.delete(url) if response.code not in [204, 404]: msg = "could not delete resource `{}` in arborist: {}".format( path, response.error_msg ) raise ArboristError(msg, response.code) return True
def delete_role(self, role_id): response = self.delete(self._role_url + urllib.quote(role_id)) if response.code == 404: # already doesn't exist, this is fine return elif response.code >= 400: msg = "could not delete role in arborist: {}".format(response.error_msg) self.logger.error(msg) raise ArboristError(msg, response.code)
def list_groups(self): response = self.get(self._group_url) if response.code != 200: self.logger.error("could not list groups: {}".format(response.error_msg)) raise ArboristError(response.error_msg, response.code) groups = response.json self.logger.info( "got arborist groups: `{}`".format(json.dumps(groups, indent=2)) ) return groups
def update_client(self, client_id, policies): # retrieve existing client, create one if not found response = self.get("/".join((self._client_url, urllib.quote(client_id)))) if response.code == 404: self.create_client(client_id, policies) return # unpack the result if "error" in response.json: msg = "could not fetch client `{}` in arborist: {}".format( client_id, response.error_msg ) self.logger.error(msg) raise ArboristError(msg, response.code) current_policies = set(response.json["policies"]) policies = set(policies) # find newly granted policies, revoke all if needed url = "/".join((self._client_url, urllib.quote(client_id), "policy")) if current_policies.difference(policies): # if some policies must be removed, revoke all and re-grant later response = self.delete(url) if response.code != 204: msg = "could not revoke policies from client `{}` in arborist: {}".format( client_id, response.error_msg ) self.logger.error(msg) raise ArboristError(msg, response.code) else: # do not add policies that already exist policies.difference_update(current_policies) # grant missing policies for policy in policies: response = self.post(url, json=dict(policy=policy), expect_json=False) if response.code != 204: msg = "could not grant policy `{}` to client `{}` in arborist: {}".format( policy, client_id, response.error_msg ) self.logger.error(msg) raise ArboristError(msg, response.code) self.logger.info("updated policies for client {}".format(client_id))
def update_role(self, role_id, role_json): url = self._role_url + urllib.quote(role_id) response = self.put(url, json=role_json) if not response.successful: msg = "could not update role `{}` in arborist: {}".format( role_id, response.error_msg ) self.logger.error(msg) raise ArboristError(msg, response.code) self.logger.info("updated role {}".format(role_json["name"])) return response
def response(*args, **kwargs): mocked_response = MagicMock(requests.Response) if function_name == "auth_mapping": if not known_user: raise ArboristError("User does not exist in Arborist") mocked_response.items = auth_mapping.items if function_name == "create_resource": mocked_response.get = lambda *args, **kwargs: None return mocked_response
def create_client(self, client_id, policies): response = self.post( self._client_url, json=dict(clientID=client_id, policies=policies or []) ) if "error" in response.json: msg = "could not create client `{}` in arborist: {}".format( client_id, response.error_msg ) self.logger.error(msg) raise ArboristError(msg, response.code) self.logger.info("created client {}".format(client_id)) return response.json
def auth_mapping(self, username): """ For given user, get mapping from the resources that this user can access to the actions on those resources for which they are authorized. Return: dict: response JSON from arborist """ data = {"username": username} response = self.post(self._auth_url.rstrip("/") + "/mapping", json=data) if not response.successful: raise ArboristError(response.error_msg, response.code) return response.json
def list_resources_for_user(self, username): """ Args: username (str) Return: List[str]: list of resource paths which the user has any access to """ url = "{}/{}/resources".format(self._user_url, urllib.quote(username)) response = self.get(url) if response.code != 200: raise ArboristError(response.error_msg, response.code) return response.json["resources"]
def update_resource(self, path, resource_json, create_parents=False): url = self._resource_url + urllib.quote(path) if create_parents: url = url + "?p" response = self.put(url, json=resource_json) if not response.successful: msg = "could not update resource `{}` in arborist: {}".format( path, response.error_msg ) self.logger.error(msg) raise ArboristError(msg, response.code) self.logger.info("updated resource {}".format(resource_json["name"])) return response.json
def create_role(self, role_json): """ Create a new role in arborist (does not affect fence database or otherwise have any interaction with userdatamodel). Used for syncing project permissions from dbgap into arborist roles. Example schema for the role JSON: { "id": "role", "description": "...", "permissions": [ { "id": "permission", "description": "...", "action": { "service": "...", "method": "..." }, "constraints": { "key": "value", } } ] } ("description" fields are optional, as is the "constraints" field in the permission.) Args: role_json (dict): dictionary of information about the role Return: dict: response JSON from arborist Raises: - ArboristError: if the operation failed (couldn't create role) """ response = self.post(self._role_url, json=role_json) if response.code == 409: # already exists; this is ok return None if not response.successful: msg = "could not create role `{}` in arborist: {}".format( role_json["id"], response.error_msg ) self.logger.error(msg) raise ArboristError(msg, response.code) self.logger.info("created role {}".format(role_json["id"])) return response.json
def create_user_if_not_exist(self, username): self.logger.info("making sure user exists: `{}`".format(username)) user_json = {"name": username} response = self.post(self._user_url, json=user_json) if response.code == 409: return None if "error" in response.json: msg = "could not create user `{}` in arborist: {}".format( username, response.error_msg ) self.logger.error(msg) raise ArboristError(msg, response.code) self.logger.info("created user {}".format(username)) return response.json
def put_group(self, name, description="", users=[], policies=[]): """ Arborist will create group if not exist and overwrite if exist. """ data = {"name": name, "users": users, "policies": policies} if description: data["description"] = description response = self.put(self._group_url, json=data) if not response.successful: msg = "could not put group `{}` in arborist: {}".format( name, response.error_msg ) self.logger.error(msg) raise ArboristError(msg, response.code) self.logger.info("put group {}".format(name)) return response.json
def create_policy(self, policy_json, skip_if_exists=True): response = self.post(self._policy_url, json=policy_json) if response.code == 409 and skip_if_exists: # already exists; this is ok, but leave warning self.logger.warning( "policy `{}` already exists in arborist".format(policy_json["id"]) ) return None if not response.successful: msg = "could not create policy `{}` in arborist: {}".format( policy_json["id"], response.error_msg ) self.logger.error(msg) raise ArboristError(msg, response.code) self.logger.info("created policy {}".format(policy_json["id"])) return response.json
def get_resource(self, path): """ Return the information for a resource in arborist. Args: resource_path (str): path for the resource Return: dict: JSON representation of the resource """ url = self._resource_url + urllib.quote(path) response = self.get(url) if response.code == 404: return None if not response.successful: self.logger.error(response.error_msg) raise ArboristError(response.error_msg, response.code) return response.json
def list_resources(self): """ Return the information for a resource in arborist. Args: resource_path (str): path for the resource Return: dict: JSON representation of the resource """ response = self.get(self._resource_url) if response.code != 200: self.logger.error("could not list resources: {}".format(response.error_msg)) raise ArboristError(response.error_msg, response.code) resources = response.json self.logger.info( "got arborist resources: `{}`".format(json.dumps(resources, indent=2)) ) return resources
def __init__(self, response, expect_json=True): self._response = response self.code = response.status_code if not expect_json: return try: self.json = response.json() except ValueError as e: if self.code != 500: raise ArboristError( "got a confusing response from arborist, couldn't parse JSON from" " response but got code {} for this response: {}".format( self.code, _escape_newlines(response.text) ), self.code, ) self.json = {"error": {"message": str(e), "code": 500}}
def create_resource(self, parent_path, resource_json, create_parents=False): """ Create a new resource in arborist (does not affect fence database or otherwise have any interaction with userdatamodel). Used for syncing projects from dbgap into arborist resources. Example schema for resource JSON: { "name": "some_resource", "description": "..." "subresources": [ { "name": "subresource", "description": "..." } ] } Supposing we have some ``"parent_path"``, then the new resource will be created as ``/parent_path/some_resource`` in arborist. ("description" fields are optional, as are subresources, which default to empty.) Args: parent_path (str): the path (like a filepath) to the parent resource above this one; if this one is in the root level, then use "/" resource_json (dict): dictionary of resource information (see the example above) create_parents (bool): if True, then arborist will create parent resources if they do not exist yet. Return: dict: response JSON from arborist Raises: - ArboristError: if the operation failed (couldn't create resource) """ # To add a subresource, all we actually have to do is POST the resource # JSON to its parent in arborist: # # POST /resource/parent # # and now the new resource will exist here: # # /resource/parent/new_resource # path = self._resource_url + urllib.quote(parent_path) if create_parents: path = path + "?p" response = self.post(path, json=resource_json) if response.code == 409: # already exists; this is ok, but leave warning self.logger.warning( "resource `{}` already exists in arborist".format(resource_json["name"]) ) return None if not response.successful: msg = "could not create resource `{}` in arborist: {}".format( path, response.error_msg ) self.logger.error(msg) raise ArboristError(msg, response.code) self.logger.info("created resource {}".format(resource_json["name"])) return response.json