/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)
|