# Copyright 2020 StrongDM Inc
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

# Code generated by protogen. DO NOT EDIT.

import base64
import collections
import copy
import datetime
import functools
import grpc
import hashlib
import hmac
import random
import re
import time
from . import errors
from . import plumbing
from . import svc

from cryptography.hazmat.primitives.asymmetric import rsa, padding
from cryptography.hazmat.primitives import serialization, hashes

# These defaults are taken from AWS. Customization of these values
# is a future step in the API.
DEFAULT_BASE_RETRY_DELAY = 1  # 1 second
DEFAULT_MAX_RETRY_DELAY = 120  # 120 seconds
DEFAULT_RETRY_FACTOR = 1.6
DEFAULT_RETRY_JITTER = 0.2
API_VERSION = '2025-04-14'
USER_AGENT = 'strongdm-sdk-python/16.3.0'

method_regexp = re.compile(r'\W+')


class _ClientCallDetails(
        collections.namedtuple(
            "_ClientCallDetails",
            ("method", "timeout", "metadata", "credentials")),
        grpc.ClientCallDetails,
):
    """ _ClientCallDetails is used to override some of the attributes of the client_call_details in the interceptors"""
    pass


class _EncryptionInterceptor(grpc.UnaryUnaryClientInterceptor):
    """ _EncryptionInterceptor is used to add transparent encryption/decryption support for managed secrets"""
    def __init__(self, client):
        self.client = client
        self.public_key_cache = {}

    def intercept_unary_unary(self, continuation, client_call_details,
                              request):
        method = method_regexp.sub("_", client_call_details.method.lower())
        callback = getattr(self, method, None)
        if callback is not None:
            return callback(continuation, client_call_details, request)
        return continuation(client_call_details, request)

    @functools.cached_property
    def private_key(self):
        return rsa.generate_private_key(
            public_exponent=65537,
            key_size=4096,
        )

    def _encrypt_secret(self, method, continuation, client_call_details,
                        request):
        secret = request.managed_secret
        if len(secret.value) != 0:
            if secret.secret_engine_id not in self.public_key_cache:
                try:
                    # fetch secret engine details to fill up self.public_key_cache
                    # if it fails the call to create/update will fail as well
                    self.client.secret_engines.get(secret.secret_engine_id)
                except errors.RPCError:
                    pass
            key = self.public_key_cache.get(secret.secret_engine_id)
            if key is not None:
                encrypted = key.encrypt(
                    secret.value,
                    padding.OAEP(mgf=padding.MGF1(algorithm=hashes.SHA256()),
                                 algorithm=hashes.SHA256(),
                                 label=None))
                secret.value = encrypted
            client_call_details = _ClientCallDetails(
                method=client_call_details.method,
                timeout=client_call_details.timeout,
                metadata=self.client.get_metadata(method, request),
                credentials=client_call_details.credentials)
        return continuation(client_call_details, request)

    def _v1_managedsecrets_create(self, continuation, client_call_details,
                                  request):
        return self._encrypt_secret("ManagedSecrets.Create", continuation,
                                    client_call_details, request)

    def _v1_managedsecrets_update(self, continuation, client_call_details,
                                  request):
        return self._encrypt_secret("ManagedSecrets.Update", continuation,
                                    client_call_details, request)

    def _v1_managedsecrets_retrieve(self, continuation, client_call_details,
                                    request):
        if len(request.public_key) != 0:
            return continuation(client_call_details, request)

        privKey = self.private_key
        request.public_key = privKey.public_key().public_bytes(
            serialization.Encoding.PEM,
            serialization.PublicFormat.SubjectPublicKeyInfo)
        client_call_details = _ClientCallDetails(
            method=client_call_details.method,
            timeout=client_call_details.timeout,
            metadata=self.client.get_metadata("ManagedSecrets.Retrieve",
                                              request),
            credentials=client_call_details.credentials)
        resp = continuation(client_call_details, request)
        if resp.code() != grpc.StatusCode.OK:
            return resp
        result = resp.result()
        plaintext = privKey.decrypt(
            result.managed_secret.value,
            padding.OAEP(
                mgf=padding.MGF1(algorithm=hashes.SHA256()),
                algorithm=hashes.SHA256(),
                label=None,
            ))
        result.managed_secret.value = plaintext
        return resp

    def _v1_secretengines_get(self, continuation, client_call_details,
                              request):
        response = continuation(client_call_details, request)
        if response.code() != grpc.StatusCode.OK:
            return response
        result = response.result()
        engine = plumbing.convert_secret_engine_to_porcelain(
            result.secret_engine)
        engineKey = serialization.load_pem_public_key(engine.public_key)
        self.public_key_cache[engine.id] = engineKey
        return response

    def _v1_secretengines_list(self, continuation, client_call_details,
                               request):
        response = continuation(client_call_details, request)
        if response.code() != grpc.StatusCode.OK:
            return response
        result = response.result()
        for plumbing_engine in result.secret_engines:
            engine = plumbing.convert_secret_engine_to_porcelain(
                plumbing_engine)
            engineKey = serialization.load_pem_public_key(engine.public_key)
            self.public_key_cache[engine.id] = engineKey
        return response


class Client:
    '''Client interacts with the strongDM API.'''
    def __init__(self,
                 api_access_key,
                 api_secret,
                 host='app.strongdm.com:443',
                 insecure=False,
                 retry_rate_limit_errors=True,
                 page_limit=0):
        '''
        Create a new Client.

        - api_access_key: the access key to authenticate with strongDM
        - api_secret: the secret key to authenticate with strongDM
        '''
        self.api_access_key = api_access_key.strip()
        self.api_secret = base64.b64decode(api_secret.strip())
        self.base_retry_delay = DEFAULT_BASE_RETRY_DELAY
        self.max_retry_delay = DEFAULT_MAX_RETRY_DELAY
        self.retry_factor = DEFAULT_RETRY_FACTOR
        self.retry_jitter = DEFAULT_RETRY_JITTER
        self.retry_rate_limit_errors = retry_rate_limit_errors
        self.snapshot_datetime = None
        self.page_limit = page_limit

        try:
            if insecure:
                channel = grpc.insecure_channel(host)
            else:
                creds = grpc.ssl_channel_credentials()
                channel = grpc.secure_channel(host, creds)
        except Exception as e:
            raise plumbing.convert_error_to_porcelain(e) from e
        channel = grpc.intercept_channel(channel, _EncryptionInterceptor(self))
        self.channel = channel
        self.access_requests = svc.AccessRequests(channel, self)
        '''
         AccessRequests are requests for access to a resource that may match a Workflow.

        See `strongdm.svc.AccessRequests`.
        '''
        self.access_request_events_history = svc.AccessRequestEventsHistory(
            channel, self)
        '''
         AccessRequestEventsHistory provides records of all changes to the state of an AccessRequest.

        See `strongdm.svc.AccessRequestEventsHistory`.
        '''
        self.access_requests_history = svc.AccessRequestsHistory(channel, self)
        '''
         AccessRequestsHistory provides records of all changes to the state of an AccessRequest.

        See `strongdm.svc.AccessRequestsHistory`.
        '''
        self.account_attachments = svc.AccountAttachments(channel, self)
        '''
         AccountAttachments assign an account to a role.

        See `strongdm.svc.AccountAttachments`.
        '''
        self.account_attachments_history = svc.AccountAttachmentsHistory(
            channel, self)
        '''
         AccountAttachmentsHistory records all changes to the state of an AccountAttachment.

        See `strongdm.svc.AccountAttachmentsHistory`.
        '''
        self.account_grants = svc.AccountGrants(channel, self)
        '''
         AccountGrants assign a resource directly to an account, giving the account the permission to connect to that resource.

        See `strongdm.svc.AccountGrants`.
        '''
        self.account_grants_history = svc.AccountGrantsHistory(channel, self)
        '''
         AccountGrantsHistory records all changes to the state of an AccountGrant.

        See `strongdm.svc.AccountGrantsHistory`.
        '''
        self.account_permissions = svc.AccountPermissions(channel, self)
        '''
         AccountPermissions records the granular permissions accounts have, allowing them to execute
         relevant commands via StrongDM's APIs.

        See `strongdm.svc.AccountPermissions`.
        '''
        self.account_resources = svc.AccountResources(channel, self)
        '''
         AccountResources enumerates the resources to which accounts have access.
         The AccountResources service is read-only.

        See `strongdm.svc.AccountResources`.
        '''
        self.account_resources_history = svc.AccountResourcesHistory(
            channel, self)
        '''
         AccountResourcesHistory records all changes to the state of a AccountResource.

        See `strongdm.svc.AccountResourcesHistory`.
        '''
        self.accounts = svc.Accounts(channel, self)
        '''
         Accounts are users that have access to strongDM. There are two types of accounts:
         1. **Users:** humans who are authenticated through username and password or SSO.
         2. **Service Accounts:** machines that are authenticated using a service token.
         3. **Tokens** are access keys with permissions that can be used for authentication.

        See `strongdm.svc.Accounts`.
        '''
        self.accounts_groups = svc.AccountsGroups(channel, self)
        '''
         An AccountGroup links an account and a group.

        See `strongdm.svc.AccountsGroups`.
        '''
        self.accounts_groups_history = svc.AccountsGroupsHistory(channel, self)
        '''
         AccountsGroupsHistory records all changes to the state of an AccountGroup.

        See `strongdm.svc.AccountsGroupsHistory`.
        '''
        self.accounts_history = svc.AccountsHistory(channel, self)
        '''
         AccountsHistory records all changes to the state of an Account.

        See `strongdm.svc.AccountsHistory`.
        '''
        self.activities = svc.Activities(channel, self)
        '''
         An Activity is a record of an action taken against a strongDM deployment, e.g.
         a user creation, resource deletion, sso configuration change, etc. The Activities
         service is read-only.

        See `strongdm.svc.Activities`.
        '''
        self.approval_workflow_approvers = svc.ApprovalWorkflowApprovers(
            channel, self)
        '''
         ApprovalWorkflowApprovers link approval workflow approvers to an ApprovalWorkflowStep

        See `strongdm.svc.ApprovalWorkflowApprovers`.
        '''
        self.approval_workflow_approvers_history = svc.ApprovalWorkflowApproversHistory(
            channel, self)
        '''
         ApprovalWorkflowApproversHistory records all changes to the state of an ApprovalWorkflowApprover.

        See `strongdm.svc.ApprovalWorkflowApproversHistory`.
        '''
        self.approval_workflow_steps = svc.ApprovalWorkflowSteps(channel, self)
        '''
         ApprovalWorkflowSteps link approval workflow steps to an ApprovalWorkflow

        See `strongdm.svc.ApprovalWorkflowSteps`.
        '''
        self.approval_workflow_steps_history = svc.ApprovalWorkflowStepsHistory(
            channel, self)
        '''
         ApprovalWorkflowStepsHistory records all changes to the state of an ApprovalWorkflowStep.

        See `strongdm.svc.ApprovalWorkflowStepsHistory`.
        '''
        self.approval_workflows = svc.ApprovalWorkflows(channel, self)
        '''
         ApprovalWorkflows are the mechanism by which requests for access can be viewed by authorized
         approvers and be approved or denied.

        See `strongdm.svc.ApprovalWorkflows`.
        '''
        self.approval_workflows_history = svc.ApprovalWorkflowsHistory(
            channel, self)
        '''
         ApprovalWorkflowsHistory records all changes to the state of an ApprovalWorkflow.

        See `strongdm.svc.ApprovalWorkflowsHistory`.
        '''
        self.control_panel = svc.ControlPanel(channel, self)
        '''
         ControlPanel contains all administrative controls.

        See `strongdm.svc.ControlPanel`.
        '''
        self.discovery_connectors = svc.DiscoveryConnectors(channel, self)
        '''
         A Discovery Connector is a configuration object for performing Resource
         Scans in remote systems such as AWS, GCP, Azure, and other systems.

        See `strongdm.svc.DiscoveryConnectors`.
        '''
        self.roles = svc.Roles(channel, self)
        '''
         A Role has a list of access rules which determine which Resources the members
         of the Role have access to. An Account can be a member of multiple Roles via
         AccountAttachments.

        See `strongdm.svc.Roles`.
        '''
        self.groups = svc.Groups(channel, self)
        '''
         A Group is a set of principals.

        See `strongdm.svc.Groups`.
        '''
        self.groups_history = svc.GroupsHistory(channel, self)
        '''
         GroupsHistory records all changes to the state of a Group.

        See `strongdm.svc.GroupsHistory`.
        '''
        self.groups_roles = svc.GroupsRoles(channel, self)
        '''
         A GroupRole is an assignment of a Group to a Role.

        See `strongdm.svc.GroupsRoles`.
        '''
        self.groups_roles_history = svc.GroupsRolesHistory(channel, self)
        '''
         GroupsRolesHistory records all changes to the state of a GroupRole.

        See `strongdm.svc.GroupsRolesHistory`.
        '''
        self.health_checks = svc.HealthChecks(channel, self)
        '''
         HealthChecks lists the last healthcheck between each node and resource.
         Note the unconventional capitalization here is to prevent having a collision with GRPC

        See `strongdm.svc.HealthChecks`.
        '''
        self.identity_aliases = svc.IdentityAliases(channel, self)
        '''
         IdentityAliases assign an alias to an account within an IdentitySet.
         The alias is used as the username when connecting to a identity supported resource.

        See `strongdm.svc.IdentityAliases`.
        '''
        self.identity_aliases_history = svc.IdentityAliasesHistory(
            channel, self)
        '''
         IdentityAliasesHistory records all changes to the state of a IdentityAlias.

        See `strongdm.svc.IdentityAliasesHistory`.
        '''
        self.identity_sets = svc.IdentitySets(channel, self)
        '''
         A IdentitySet is a named grouping of Identity Aliases for Accounts.
         An Account's relationship to a IdentitySet is defined via IdentityAlias objects.

        See `strongdm.svc.IdentitySets`.
        '''
        self.identity_sets_history = svc.IdentitySetsHistory(channel, self)
        '''
         IdentitySetsHistory records all changes to the state of a IdentitySet.

        See `strongdm.svc.IdentitySetsHistory`.
        '''
        self.managed_secrets = svc.ManagedSecrets(channel, self)
        '''
         ManagedSecret is a private vertical for creating, reading, updating,
         deleting, listing and rotating the managed secrets in the secrets engines as
         an authenticated user.

        See `strongdm.svc.ManagedSecrets`.
        '''
        self.nodes = svc.Nodes(channel, self)
        '''
         Nodes make up the StrongDM network, and allow your users to connect securely to your resources.
         There are three types of nodes:
         1. **Relay:** creates connectivity to your datasources, while maintaining the egress-only nature of your firewall
         2. **Gateway:** a relay that also listens for connections from StrongDM clients
         3. **Proxy Cluster:** a cluster of workers that together mediate access from clients to resources

        See `strongdm.svc.Nodes`.
        '''
        self.nodes_history = svc.NodesHistory(channel, self)
        '''
         NodesHistory records all changes to the state of a Node.

        See `strongdm.svc.NodesHistory`.
        '''
        self.organization_history = svc.OrganizationHistory(channel, self)
        '''
         OrganizationHistory records all changes to the state of an Organization.

        See `strongdm.svc.OrganizationHistory`.
        '''
        self.peering_group_nodes = svc.PeeringGroupNodes(channel, self)
        '''
         PeeringGroupNodes provides the building blocks necessary to obtain attach a node to a peering group.

        See `strongdm.svc.PeeringGroupNodes`.
        '''
        self.peering_group_peers = svc.PeeringGroupPeers(channel, self)
        '''
         PeeringGroupPeers provides the building blocks necessary to link two peering groups.

        See `strongdm.svc.PeeringGroupPeers`.
        '''
        self.peering_group_resources = svc.PeeringGroupResources(channel, self)
        '''
         PeeringGroupResources provides the building blocks necessary to obtain attach a resource to a peering group.

        See `strongdm.svc.PeeringGroupResources`.
        '''
        self.peering_groups = svc.PeeringGroups(channel, self)
        '''
         PeeringGroups provides the building blocks necessary to obtain explicit network topology and routing.

        See `strongdm.svc.PeeringGroups`.
        '''
        self.policies = svc.Policies(channel, self)
        '''
         Policies are the collection of one or more statements that enforce fine-grained access
         control for the users of an organization.

        See `strongdm.svc.Policies`.
        '''
        self.policies_history = svc.PoliciesHistory(channel, self)
        '''
         PoliciesHistory records all changes to the state of a Policy.

        See `strongdm.svc.PoliciesHistory`.
        '''
        self.proxy_cluster_keys = svc.ProxyClusterKeys(channel, self)
        '''
         Proxy Cluster Keys are authentication keys for all proxies within a cluster.
         The proxies within a cluster share the same key. One cluster can have
         multiple keys in order to facilitate key rotation.

        See `strongdm.svc.ProxyClusterKeys`.
        '''
        self.queries = svc.Queries(channel, self)
        '''
         A Query is a record of a single client request to a resource, such as a SQL query.
         Long-running SSH, RDP, or Kubernetes interactive sessions also count as queries.
         The Queries service is read-only.

        See `strongdm.svc.Queries`.
        '''
        self.remote_identities = svc.RemoteIdentities(channel, self)
        '''
         RemoteIdentities assign a resource directly to an account, giving the account the permission to connect to that resource.

        See `strongdm.svc.RemoteIdentities`.
        '''
        self.remote_identities_history = svc.RemoteIdentitiesHistory(
            channel, self)
        '''
         RemoteIdentitiesHistory records all changes to the state of a RemoteIdentity.

        See `strongdm.svc.RemoteIdentitiesHistory`.
        '''
        self.remote_identity_groups = svc.RemoteIdentityGroups(channel, self)
        '''
         A RemoteIdentityGroup is a named grouping of Remote Identities for Accounts.
         An Account's relationship to a RemoteIdentityGroup is defined via RemoteIdentity objects.

        See `strongdm.svc.RemoteIdentityGroups`.
        '''
        self.remote_identity_groups_history = svc.RemoteIdentityGroupsHistory(
            channel, self)
        '''
         RemoteIdentityGroupsHistory records all changes to the state of a RemoteIdentityGroup.

        See `strongdm.svc.RemoteIdentityGroupsHistory`.
        '''
        self.replays = svc.Replays(channel, self)
        '''
         A Replay captures the data transferred over a long-running SSH, RDP, or Kubernetes interactive session
         (otherwise referred to as a query). The Replays service is read-only.

        See `strongdm.svc.Replays`.
        '''
        self.resources = svc.Resources(channel, self)
        '''
         Resources are databases, servers, clusters, websites, or clouds that strongDM
         delegates access to.

        See `strongdm.svc.Resources`.
        '''
        self.resources_history = svc.ResourcesHistory(channel, self)
        '''
         ResourcesHistory records all changes to the state of a Resource.

        See `strongdm.svc.ResourcesHistory`.
        '''
        self.role_resources = svc.RoleResources(channel, self)
        '''
         RoleResources enumerates the resources to which roles have access.
         The RoleResources service is read-only.

        See `strongdm.svc.RoleResources`.
        '''
        self.role_resources_history = svc.RoleResourcesHistory(channel, self)
        '''
         RoleResourcesHistory records all changes to the state of a RoleResource.

        See `strongdm.svc.RoleResourcesHistory`.
        '''
        self.roles_history = svc.RolesHistory(channel, self)
        '''
         RolesHistory records all changes to the state of a Role.

        See `strongdm.svc.RolesHistory`.
        '''
        self.secret_stores = svc.SecretStores(channel, self)
        '''
         SecretStores are servers where resource secrets (passwords, keys) are stored.

        See `strongdm.svc.SecretStores`.
        '''
        self.secret_engines = svc.SecretEngines(channel, self)
        '''


        See `strongdm.svc.SecretEngines`.
        '''
        self.secret_store_healths = svc.SecretStoreHealths(channel, self)
        '''
         SecretStoreHealths exposes health states for secret stores.

        See `strongdm.svc.SecretStoreHealths`.
        '''
        self.secret_stores_history = svc.SecretStoresHistory(channel, self)
        '''
         SecretStoresHistory records all changes to the state of a SecretStore.

        See `strongdm.svc.SecretStoresHistory`.
        '''
        self.workflow_approvers = svc.WorkflowApprovers(channel, self)
        '''
         WorkflowApprovers is an account or a role with the ability to approve requests bound to a workflow.

        See `strongdm.svc.WorkflowApprovers`.
        '''
        self.workflow_approvers_history = svc.WorkflowApproversHistory(
            channel, self)
        '''
         WorkflowApproversHistory provides records of all changes to the state of a WorkflowApprover.

        See `strongdm.svc.WorkflowApproversHistory`.
        '''
        self.workflow_roles = svc.WorkflowRoles(channel, self)
        '''
         WorkflowRole links a role to a workflow. The linked roles indicate which roles a user must be a part of
         to request access to a resource via the workflow.

        See `strongdm.svc.WorkflowRoles`.
        '''
        self.workflow_roles_history = svc.WorkflowRolesHistory(channel, self)
        '''
         WorkflowRolesHistory provides records of all changes to the state of a WorkflowRole

        See `strongdm.svc.WorkflowRolesHistory`.
        '''
        self.workflows = svc.Workflows(channel, self)
        '''
         Workflows are the collection of rules that define the resources to which access can be requested,
         the users that can request that access, and the mechanism for approving those requests which can either
         be automatic approval or a set of users authorized to approve the requests.

        See `strongdm.svc.Workflows`.
        '''
        self.workflows_history = svc.WorkflowsHistory(channel, self)
        '''
         WorkflowsHistory provides records of all changes to the state of a Workflow.

        See `strongdm.svc.WorkflowsHistory`.
        '''

    def close(self):
        '''Closes this Client and releases all resources held by it.

        Closing the Client will immediately terminate all RPCs active with the
        Client and it is not valid to invoke new RPCs with the Client.

        This method is idempotent.
        '''
        self.channel.close()

    def get_metadata(self, method_name, req):
        return [
            ('x-sdm-authentication', self.api_access_key),
            ('x-sdm-signature', self.sign(method_name,
                                          req.SerializeToString())),
            ('x-sdm-api-version', API_VERSION),
            ('x-sdm-user-agent', USER_AGENT),
        ]

    def sign(self, method_name, request_bytes):
        def hmac_digest(key, msg_byte_string):
            return hmac.new(key, msg=msg_byte_string,
                            digestmod=hashlib.sha256).digest()

        current_utc_date = datetime.datetime.now(
            datetime.timezone.utc).strftime('%Y-%m-%d')
        signing_key = hmac_digest(self.api_secret, current_utc_date.encode())
        signing_key = hmac_digest(signing_key, b'sdm_api_v1')

        hash = hashlib.sha256()

        hash.update(method_name.encode())
        hash.update(b'\n')
        hash.update(request_bytes)

        return base64.b64encode(hmac_digest(signing_key, hash.digest()))

    def exponentialBackoff(self, retries, deadline=None):
        def applyDeadline(delay, deadline):
            if deadline is None:
                return delay
            remaining = deadline - time.time()
            if remaining < 0:
                return 0
            return min(delay, remaining)

        if retries == 0:
            return applyDeadline(self.base_retry_delay, deadline)

        backoff, max_delay = self.base_retry_delay, self.max_retry_delay
        while backoff < max_delay and retries > 0:
            backoff *= self.retry_factor
            retries -= 1

        if backoff > max_delay:
            backoff = max_delay

        # Randomize backoff delays so that if a cluster of requests start at
        # the same time, they won't operate in lockstep.
        backoff *= 1 + self.retry_jitter * (random.random() * 2 - 1)
        if backoff < 0:
            return 0

        return applyDeadline(backoff, deadline)

    def shouldRetry(self, retries, err, deadline=None):
        # Check if we've passed the deadline
        if deadline is not None and time.time() >= deadline:
            return False

        if not isinstance(err, grpc.RpcError):
            return False

        if self.retry_rate_limit_errors and err.code(
        ) == grpc.StatusCode.RESOURCE_EXHAUSTED:
            return True

        return retries <= 3 and (err.code() == grpc.StatusCode.INTERNAL
                                 or err.code() == grpc.StatusCode.UNAVAILABLE)

    def snapshot_at(self, snapshot_datetime):
        '''
        Constructs a read-only client that will provide historical data from the provided timestamp.

        See `SnapshotClient`.
        '''
        client = copy.copy(self)
        client.snapshot_datetime = snapshot_datetime
        client.access_requests = svc.AccessRequests(client.channel, client)
        client.account_attachments = svc.AccountAttachments(
            client.channel, client)
        client.account_grants = svc.AccountGrants(client.channel, client)
        client.account_permissions = svc.AccountPermissions(
            client.channel, client)
        client.account_resources = svc.AccountResources(client.channel, client)
        client.accounts = svc.Accounts(client.channel, client)
        client.accounts_groups = svc.AccountsGroups(client.channel, client)
        client.approval_workflow_approvers = svc.ApprovalWorkflowApprovers(
            client.channel, client)
        client.approval_workflow_steps = svc.ApprovalWorkflowSteps(
            client.channel, client)
        client.approval_workflows = svc.ApprovalWorkflows(
            client.channel, client)
        client.discovery_connectors = svc.DiscoveryConnectors(
            client.channel, client)
        client.roles = svc.Roles(client.channel, client)
        client.groups = svc.Groups(client.channel, client)
        client.groups_roles = svc.GroupsRoles(client.channel, client)
        client.identity_aliases = svc.IdentityAliases(client.channel, client)
        client.identity_sets = svc.IdentitySets(client.channel, client)
        client.nodes = svc.Nodes(client.channel, client)
        client.policies = svc.Policies(client.channel, client)
        client.proxy_cluster_keys = svc.ProxyClusterKeys(
            client.channel, client)
        client.remote_identities = svc.RemoteIdentities(client.channel, client)
        client.remote_identity_groups = svc.RemoteIdentityGroups(
            client.channel, client)
        client.resources = svc.Resources(client.channel, client)
        client.role_resources = svc.RoleResources(client.channel, client)
        client.secret_stores = svc.SecretStores(client.channel, client)
        client.workflow_approvers = svc.WorkflowApprovers(
            client.channel, client)
        client.workflow_roles = svc.WorkflowRoles(client.channel, client)
        client.workflows = svc.Workflows(client.channel, client)
        return SnapshotClient(client)


class SnapshotClient:
    '''SnapshotClient exposes methods to query historical records at a provided timestamp.'''
    def __init__(self, client):
        self.access_requests = svc.SnapshotAccessRequests(
            client.access_requests)
        '''
         AccessRequests are requests for access to a resource that may match a Workflow.

        See `strongdm.svc.SnapshotAccessRequests`.
        '''
        self.account_attachments = svc.SnapshotAccountAttachments(
            client.account_attachments)
        '''
         AccountAttachments assign an account to a role.

        See `strongdm.svc.SnapshotAccountAttachments`.
        '''
        self.account_grants = svc.SnapshotAccountGrants(client.account_grants)
        '''
         AccountGrants assign a resource directly to an account, giving the account the permission to connect to that resource.

        See `strongdm.svc.SnapshotAccountGrants`.
        '''
        self.account_permissions = svc.SnapshotAccountPermissions(
            client.account_permissions)
        '''
         AccountPermissions records the granular permissions accounts have, allowing them to execute
         relevant commands via StrongDM's APIs.

        See `strongdm.svc.SnapshotAccountPermissions`.
        '''
        self.account_resources = svc.SnapshotAccountResources(
            client.account_resources)
        '''
         AccountResources enumerates the resources to which accounts have access.
         The AccountResources service is read-only.

        See `strongdm.svc.SnapshotAccountResources`.
        '''
        self.accounts = svc.SnapshotAccounts(client.accounts)
        '''
         Accounts are users that have access to strongDM. There are two types of accounts:
         1. **Users:** humans who are authenticated through username and password or SSO.
         2. **Service Accounts:** machines that are authenticated using a service token.
         3. **Tokens** are access keys with permissions that can be used for authentication.

        See `strongdm.svc.SnapshotAccounts`.
        '''
        self.accounts_groups = svc.SnapshotAccountsGroups(
            client.accounts_groups)
        '''
         An AccountGroup links an account and a group.

        See `strongdm.svc.SnapshotAccountsGroups`.
        '''
        self.approval_workflow_approvers = svc.SnapshotApprovalWorkflowApprovers(
            client.approval_workflow_approvers)
        '''
         ApprovalWorkflowApprovers link approval workflow approvers to an ApprovalWorkflowStep

        See `strongdm.svc.SnapshotApprovalWorkflowApprovers`.
        '''
        self.approval_workflow_steps = svc.SnapshotApprovalWorkflowSteps(
            client.approval_workflow_steps)
        '''
         ApprovalWorkflowSteps link approval workflow steps to an ApprovalWorkflow

        See `strongdm.svc.SnapshotApprovalWorkflowSteps`.
        '''
        self.approval_workflows = svc.SnapshotApprovalWorkflows(
            client.approval_workflows)
        '''
         ApprovalWorkflows are the mechanism by which requests for access can be viewed by authorized
         approvers and be approved or denied.

        See `strongdm.svc.SnapshotApprovalWorkflows`.
        '''
        self.discovery_connectors = svc.SnapshotDiscoveryConnectors(
            client.discovery_connectors)
        '''
         A Discovery Connector is a configuration object for performing Resource
         Scans in remote systems such as AWS, GCP, Azure, and other systems.

        See `strongdm.svc.SnapshotDiscoveryConnectors`.
        '''
        self.roles = svc.SnapshotRoles(client.roles)
        '''
         A Role has a list of access rules which determine which Resources the members
         of the Role have access to. An Account can be a member of multiple Roles via
         AccountAttachments.

        See `strongdm.svc.SnapshotRoles`.
        '''
        self.groups = svc.SnapshotGroups(client.groups)
        '''
         A Group is a set of principals.

        See `strongdm.svc.SnapshotGroups`.
        '''
        self.groups_roles = svc.SnapshotGroupsRoles(client.groups_roles)
        '''
         A GroupRole is an assignment of a Group to a Role.

        See `strongdm.svc.SnapshotGroupsRoles`.
        '''
        self.identity_aliases = svc.SnapshotIdentityAliases(
            client.identity_aliases)
        '''
         IdentityAliases assign an alias to an account within an IdentitySet.
         The alias is used as the username when connecting to a identity supported resource.

        See `strongdm.svc.SnapshotIdentityAliases`.
        '''
        self.identity_sets = svc.SnapshotIdentitySets(client.identity_sets)
        '''
         A IdentitySet is a named grouping of Identity Aliases for Accounts.
         An Account's relationship to a IdentitySet is defined via IdentityAlias objects.

        See `strongdm.svc.SnapshotIdentitySets`.
        '''
        self.nodes = svc.SnapshotNodes(client.nodes)
        '''
         Nodes make up the StrongDM network, and allow your users to connect securely to your resources.
         There are three types of nodes:
         1. **Relay:** creates connectivity to your datasources, while maintaining the egress-only nature of your firewall
         2. **Gateway:** a relay that also listens for connections from StrongDM clients
         3. **Proxy Cluster:** a cluster of workers that together mediate access from clients to resources

        See `strongdm.svc.SnapshotNodes`.
        '''
        self.policies = svc.SnapshotPolicies(client.policies)
        '''
         Policies are the collection of one or more statements that enforce fine-grained access
         control for the users of an organization.

        See `strongdm.svc.SnapshotPolicies`.
        '''
        self.proxy_cluster_keys = svc.SnapshotProxyClusterKeys(
            client.proxy_cluster_keys)
        '''
         Proxy Cluster Keys are authentication keys for all proxies within a cluster.
         The proxies within a cluster share the same key. One cluster can have
         multiple keys in order to facilitate key rotation.

        See `strongdm.svc.SnapshotProxyClusterKeys`.
        '''
        self.remote_identities = svc.SnapshotRemoteIdentities(
            client.remote_identities)
        '''
         RemoteIdentities assign a resource directly to an account, giving the account the permission to connect to that resource.

        See `strongdm.svc.SnapshotRemoteIdentities`.
        '''
        self.remote_identity_groups = svc.SnapshotRemoteIdentityGroups(
            client.remote_identity_groups)
        '''
         A RemoteIdentityGroup is a named grouping of Remote Identities for Accounts.
         An Account's relationship to a RemoteIdentityGroup is defined via RemoteIdentity objects.

        See `strongdm.svc.SnapshotRemoteIdentityGroups`.
        '''
        self.resources = svc.SnapshotResources(client.resources)
        '''
         Resources are databases, servers, clusters, websites, or clouds that strongDM
         delegates access to.

        See `strongdm.svc.SnapshotResources`.
        '''
        self.role_resources = svc.SnapshotRoleResources(client.role_resources)
        '''
         RoleResources enumerates the resources to which roles have access.
         The RoleResources service is read-only.

        See `strongdm.svc.SnapshotRoleResources`.
        '''
        self.secret_stores = svc.SnapshotSecretStores(client.secret_stores)
        '''
         SecretStores are servers where resource secrets (passwords, keys) are stored.

        See `strongdm.svc.SnapshotSecretStores`.
        '''
        self.workflow_approvers = svc.SnapshotWorkflowApprovers(
            client.workflow_approvers)
        '''
         WorkflowApprovers is an account or a role with the ability to approve requests bound to a workflow.

        See `strongdm.svc.SnapshotWorkflowApprovers`.
        '''
        self.workflow_roles = svc.SnapshotWorkflowRoles(client.workflow_roles)
        '''
         WorkflowRole links a role to a workflow. The linked roles indicate which roles a user must be a part of
         to request access to a resource via the workflow.

        See `strongdm.svc.SnapshotWorkflowRoles`.
        '''
        self.workflows = svc.SnapshotWorkflows(client.workflows)
        '''
         Workflows are the collection of rules that define the resources to which access can be requested,
         the users that can request that access, and the mechanism for approving those requests which can either
         be automatic approval or a set of users authorized to approve the requests.

        See `strongdm.svc.SnapshotWorkflows`.
        '''
