This file is indexed.

/usr/lib/python3/dist-packages/maascli/auth.py is in python3-maas-client 2.4.0~beta2-6865-gec43e47e6-0ubuntu1.

This file is owned by root:root, with mode 0o644.

The actual contents of the file can be viewed below.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
# Copyright 2012-2016 Canonical Ltd.  This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).

"""MAAS CLI authentication."""

__all__ = [
    'obtain_credentials',
    ]

from getpass import getpass
import http.client
import sys
from urllib.parse import urljoin

from apiclient.creds import convert_string_to_tuple
from maascli.api import (
    Action,
    http_request,
)
from macaroonbakery import httpbakery


class UnexpectedResponse(Exception):
    """Unexpected API response."""


def try_getpass(prompt):
    """Call `getpass`, ignoring EOF errors."""
    try:
        return getpass(prompt)
    except EOFError:
        return None


def get_apikey_via_macaroon(url):
    """Try to get an API key using a macaroon.

    httpbakery is used to create a new API token. If the MAAS server
    supports macaroons, it will reply that a macaroon discharge is
    required, and bakery will send the user to IDM for authentication,
    and then call the API again with the acquired macaroon.

    If the MAAS server doesn't support macaroons, None is returned.
    """
    url = url.strip('/')
    client = httpbakery.Client()
    resp = client.request(
        'POST', '{}/account/?op=create_authorisation_token'.format(url))
    if resp.status_code != 200:
        # Most likely the MAAS server doesn't support macaroons.
        return None
    result = resp.json()
    return '{consumer_key}:{token_key}:{token_secret}'.format(**result)


def obtain_credentials(url, credentials):
    """Prompt for credentials if possible.

    If the credentials are "-" then read from stdin without interactive
    prompting.
    """
    if credentials == "-":
        credentials = sys.stdin.readline().strip()
    elif credentials is None:
        credentials = get_apikey_via_macaroon(url)
        if credentials is None:
            credentials = try_getpass(
                "API key (leave empty for anonymous access): ")
    # Ensure that the credentials have a valid form.
    if credentials and not credentials.isspace():
        return convert_string_to_tuple(credentials)
    else:
        return None


def check_valid_apikey(url, credentials, insecure=False):
    """Check for valid apikey.

    :param credentials: A 3-tuple of credentials.
    """
    if '/api/1.0' in url:
        check_url = urljoin(url, "nodegroups/")
        uri, body, headers = Action.prepare_payload(
            op="list", method="GET", uri=check_url, data=[])
    else:
        check_url = urljoin(url, "users/")
        uri, body, headers = Action.prepare_payload(
            op="whoami", method="GET", uri=check_url, data=[])

    # Headers are returned as a list, but they must be a dict for
    # the signing machinery.
    headers = dict(headers)

    Action.sign(uri, headers, credentials)

    response, content = http_request(
        uri, method="GET", body=body, headers=headers,
        insecure=insecure)

    status = int(response['status'])
    if status == http.client.UNAUTHORIZED:
        return False
    elif status == http.client.OK:
        return True
    else:
        raise UnexpectedResponse(
            "The MAAS server gave an unexpected response: %s" % status)