# Copyright 2016 Splunk, Inc. # SPDX-FileCopyrightText: 2020 2020 # # SPDX-License-Identifier: Apache-2.0 """ This module contains interfaces that support CRUD operations on ACL. """ import json from . import splunk_rest_client as rest_client from splunklib import binding from .utils import retry __all__ = ["ACLException", "ACLManager"] class ACLException(Exception): pass class ACLManager: """ACL manager. :param session_key: Splunk access token. :type session_key: ``string`` :param app: App name of namespace. :type app: ``string`` :param owner: (optional) Owner of namespace, default is `nobody`. :type owner: ``string`` :param scheme: (optional) The access scheme, default is None. :type scheme: ``string`` :param host: (optional) The host name, default is None. :type host: ``string`` :param port: (optional) The port number, default is None. :type port: ``integer`` :param context: Other configurations for Splunk rest client. :type context: ``dict`` Usage:: >>> import solnlib.acl as sacl >>> saclm = sacl.ACLManager(session_key, 'Splunk_TA_test') >>> saclm.get('data/transforms/extractions') >>> saclm.update('data/transforms/extractions/_acl', perms_read=['*'], perms_write=['*']) """ def __init__( self, session_key, app, owner="nobody", scheme=None, host=None, port=None, **context ): self._rest_client = rest_client.SplunkRestClient( session_key, app, owner=owner, scheme=scheme, host=host, port=port, **context ) @retry(exceptions=[binding.HTTPError]) def get(self, path): """Get ACL of /servicesNS/{`owner`}/{`app`}/{`path`}. :param path: Path of ACL relative to /servicesNS/{`owner`}/{`app`} :type path: ``string`` :returns: A dict contains ACL. :rtype: ``dict`` :raises ACLException: If `path` is invalid. Usage:: >>> aclm = acl.ACLManager(session_key, 'Splunk_TA_test') >>> perms = aclm.get('data/transforms/extractions/_acl') """ try: content = self._rest_client.get(path, output_mode="json").body.read() except binding.HTTPError as e: if e.status != 404: raise raise ACLException("Invalid endpoint: %s.", path) return json.loads(content)["entry"][0]["acl"] @retry(exceptions=[binding.HTTPError]) def update(self, path, owner=None, perms_read=None, perms_write=None): """Update ACL of /servicesNS/{`owner`}/{`app`}/{`path`}. If the ACL is per-entity (ends in /acl), owner can be reassigned. If the acl is endpoint-level (ends in _acl), owner will be ignored. The 'sharing' setting is always retrieved from the current. :param path: Path of ACL relative to /servicesNS/{owner}/{app}. MUST end with /acl or /_acl indicating whether the permission is applied at the per-entity level or endpoint level respectively. :type path: ``string`` :param owner: (optional) New owner of ACL, default is `nobody`. :type owner: ``string`` :param perms_read: (optional) List of roles (['*'] for all roles). If unspecified we will POST with current (if available) perms.read, default is None. :type perms_read: ``list`` :param perms_write: (optional) List of roles (['*'] for all roles). If unspecified we will POST with current (if available) perms.write, default is None. :type perms_write: ``list`` :returns: A dict contains ACL after update. :rtype: ``dict`` :raises ACLException: If `path` is invalid. Usage:: >>> aclm = acl.ACLManager(session_key, 'Splunk_TA_test') >>> perms = aclm.update('data/transforms/extractions/_acl', perms_read=['admin'], perms_write=['admin']) """ if not path.endswith("/acl") and not path.endswith("/_acl"): raise ACLException( "Invalid endpoint: %s, must end with /acl or /_acl." % path ) curr_acl = self.get(path) postargs = {} if perms_read: postargs["perms.read"] = ",".join(perms_read) else: curr_read = curr_acl["perms"].get("read", []) if curr_read: postargs["perms.read"] = ",".join(curr_read) if perms_write: postargs["perms.write"] = ",".join(perms_write) else: curr_write = curr_acl["perms"].get("write", []) if curr_write: postargs["perms.write"] = ",".join(curr_write) if path.endswith("/acl"): # Allow ownership to be reset only at entity level. postargs["owner"] = owner or curr_acl["owner"] postargs["sharing"] = curr_acl["sharing"] try: content = self._rest_client.post( path, body=binding._encode(**postargs), output_mode="json" ).body.read() except binding.HTTPError as e: if e.status != 404: raise raise ACLException("Invalid endpoint: %s.", path) return json.loads(content)["entry"][0]["acl"]