Log in to Globus services as a confidential client
    (a client with its own login information).

    Arguments:
        credentials (str or dict): A string filename, string JSON, or dictionary
                with credential and config information.
                By default, uses the ``DEFAULT_CRED_FILENAME`` and token_dir.
                Contains ``client_id``, ``client_secret``, and ``services`` as defined below.
        client_id (str): The ID of the client.
        client_secret (str): The client's secret for authentication.
        services (list of str): Services to authenticate with.
        make_clients (bool): If ``True``, will make and return appropriate clients
                with generated tokens.
                If ``False``, will only return authorizers.
                **Default**: ``True``.
        token_dir (str): The path to the directory to save tokens in and look for
                credentials by default.
                **Default**: ``DEFAULT_CRED_PATH``.

    Returns:
        dict: The clients and authorizers requested, indexed by service name.

def confidential_login(credentials=None, client_id=None, client_secret=None, services=None,
                       make_clients=True, token_dir=DEFAULT_CRED_PATH):
    """Log in to Globus services as a confidential client
    (a client with its own login information).

    Arguments:
        credentials (str or dict): A string filename, string JSON, or dictionary
                with credential and config information.
                By default, uses the ``DEFAULT_CRED_FILENAME`` and token_dir.
                Contains ``client_id``, ``client_secret``, and ``services`` as defined below.
        client_id (str): The ID of the client.
        client_secret (str): The client's secret for authentication.
        services (list of str): Services to authenticate with.
        make_clients (bool): If ``True``, will make and return appropriate clients
                with generated tokens.
                If ``False``, will only return authorizers.
                **Default**: ``True``.
        token_dir (str): The path to the directory to save tokens in and look for
                credentials by default.
                **Default**: ``DEFAULT_CRED_PATH``.

    Returns:
        dict: The clients and authorizers requested, indexed by service name.
    """
    DEFAULT_CRED_FILENAME = "confidential_globus_login.json"
    # Read credentials if supplied
    if credentials:
        if type(credentials) is str:
            try:
                with open(credentials) as cred_file:
                    creds = json.load(cred_file)
            except IOError:
                try:
                    creds = json.loads(credentials)
                except ValueError:
                    raise ValueError("Credentials unreadable or missing")
        elif type(credentials) is dict:
            creds = credentials
        else:
            try:
                with open(os.path.join(os.getcwd(), DEFAULT_CRED_FILENAME)) as cred_file:
                    creds = json.load(cred_file)
            except IOError:
                try:
                    with open(os.path.join(token_dir, DEFAULT_CRED_FILENAME)) as cred_file:
                        creds = json.load(cred_file)
                except IOError:
                    raise ValueError("Credentials/configuration must be passed as a "
                                     "filename string, JSON string, or dictionary, or provided "
                                     "in '{}' or '{}'.".format(DEFAULT_CRED_FILENAME,
                                                               token_dir))
        client_id = creds.get("client_id")
        client_secret = creds.get("client_secret")
        services = creds.get("services", services)
    if not client_id or not client_secret:
        raise ValueError("A client_id and client_secret are required.")
    if not services:
        services = []
    elif isinstance(services, str):
        services = [services]

    conf_client = globus_sdk.ConfidentialAppAuthClient(client_id, client_secret)
    servs = []
    for serv in services:
        serv = serv.lower().strip()
        if type(serv) is str:
            servs += serv.split(" ")
        else:
            servs += list(serv)
    # Translate services into scopes as possible
    scopes = [KNOWN_SCOPES.get(sc, sc) for sc in servs]

    # Make authorizers with every returned token
    all_authorizers = {}
    for scope in scopes:
        # TODO: Allow non-CC authorizers?
        try:
            all_authorizers[scope] = globus_sdk.ClientCredentialsAuthorizer(conf_client, scope)
        except Exception as e:
            print("Error: Cannot create authorizer for scope '{}' ({})".format(scope, str(e)))
    returnables = {}
    # Populate clients and named services
    # Only translate back services - if user provides scope directly, don't translate back
    # ex. transfer => urn:transfer.globus.org:all => transfer,
    #     but urn:transfer.globus.org:all !=> transfer
    for service in servs:
        token_key = KNOWN_SCOPES.get(service)
        # If the .by_resource_server key (token key) for the service was returned
        if token_key in all_authorizers.keys():
            # If there is an applicable client (all clients have known token key)
            # Pop from all_authorizers to remove from final return value
            if make_clients and KNOWN_CLIENTS.get(service):
                try:
                    returnables[service] = KNOWN_CLIENTS[service](
                                                authorizer=all_authorizers.pop(token_key),
                                                http_timeout=STD_TIMEOUT)
                except globus_sdk.GlobusAPIError as e:
                    print("Error: Unable to create {} client: {}".format(service, e.message))
            # If no applicable client, just translate the key
            else:
                returnables[service] = all_authorizers.pop(token_key)
    # Add authorizers not associated with service to returnables
    returnables.update(all_authorizers)

    return returnables