Beispiel #1
0
def create_account_external_auth(auth_config, maas_config, bakery_client=None):
    """Make the user login via external auth to create the first admin."""
    if bakery_client is None:
        bakery_client = httpbakery.Client()

    maas_url = maas_config['maas_url'].strip('/')

    failed_msg = ''
    try:
        resp = bakery_client.request(
            'GET', '{}/accounts/discharge-request/'.format(maas_url))
        if resp.status_code != 200:
            failed_msg = 'request failed with code {}'.format(resp.status_code)
    except Exception as e:
        failed_msg = str(e)

    if failed_msg:
        print_msg(
            "An error occurred while waiting for the first user creation: " +
            failed_msg)
        return

    result = resp.json()
    username = result['username']
    if result['is_superuser']:
        print_msg("Administrator user '{}' created".format(username))
    else:
        admin_group = auth_config['external_auth_admin_group']
        message = dedent("""\
            A user with username '{username}' has been created, but it's not
            a superuser. Please log in to MAAS with a user that belongs to
            the '{admin_group}' group to create an administrator user.
            """)
        print_msg(message.format(username=username, admin_group=admin_group))
Beispiel #2
0
 def legacy_interact(self, client, location, visit_url):
     '''Implement LegacyInteractor.legacy_interact by obtaining
     the discharge macaroon using the client's private key
     '''
     agent = self._find_agent(location)
     pk_encoded = self._auth_info.key.public_key.encode().decode('utf-8')
     value = {
         'username': agent.username,
         'public_key': pk_encoded,
     }
     # TODO(rogpeppe) use client passed into interact method.
     client = httpbakery.Client(key=self._auth_info.key)
     client.cookies.set_cookie(
         utils.cookie(
             url=visit_url,
             name='agent-login',
             value=base64.urlsafe_b64encode(
                 json.dumps(value).encode('utf-8')).decode('utf-8'),
         ))
     resp = requests.get(url=visit_url,
                         cookies=client.cookies,
                         auth=client.auth())
     if resp.status_code != 200:
         raise httpbakery.InteractionError(
             'cannot acquire agent macaroon: {}'.format(resp.status_code))
     if not resp.json().get('agent-login', False):
         raise httpbakery.InteractionError('agent login failed')
    def test_cookie_domain_host_not_fqdn(self):
        # See
        # https://github.com/go-macaroon-bakery/py-macaroon-bakery/issues/53

        b = new_bakery('loc', None, None)

        def handler(*args):
            GetHandler(b, None, None, None, None, AGES, *args)
        try:
            httpd = HTTPServer(('', 0), handler)
            thread = threading.Thread(target=httpd.serve_forever)
            thread.start()
            srv_macaroon = b.oven.macaroon(
                version=bakery.LATEST_VERSION, expiry=AGES,
                caveats=None, ops=[TEST_OP])
            self.assertEquals(srv_macaroon.macaroon.location, 'loc')
            client = httpbakery.Client()
            # Note: by using "localhost" instead of the presumably numeric address held
            # in httpd.server_address, we're triggering the no-FQDN logic in the cookie
            # code.
            resp = requests.get(
                url='http://localhost:' + str(httpd.server_address[1]),
                cookies=client.cookies, auth=client.auth())
            resp.raise_for_status()
            self.assertEquals(resp.text, 'done')
        except httpbakery.BakeryException:
            pass  # interacion required exception is expected
        finally:
            httpd.shutdown()

        # the cookie has the .local domain appended
        [cookie] = client.cookies
        self.assertEqual(cookie.name, 'macaroon-test')
        self.assertEqual(cookie.domain, 'localhost.local')
    def test_single_service_first_party(self):
        b = new_bakery('loc', None, None)

        def handler(*args):
            GetHandler(b, None, None, None, None, AGES, *args)

        try:
            httpd = HTTPServer(('', 0), handler)
            thread = threading.Thread(target=httpd.serve_forever)
            thread.start()
            srv_macaroon = b.oven.macaroon(version=bakery.LATEST_VERSION,
                                           expiry=AGES,
                                           caveats=None,
                                           ops=[TEST_OP])
            self.assertEquals(srv_macaroon.macaroon.location, 'loc')
            client = httpbakery.Client()
            client.cookies.set_cookie(
                requests.cookies.create_cookie(
                    'macaroon-test',
                    base64.b64encode(
                        json.dumps([srv_macaroon.to_dict().get('m')
                                    ]).encode('utf-8')).decode('utf-8')))
            resp = requests.get(url='http://' + httpd.server_address[0] + ':' +
                                str(httpd.server_address[1]),
                                cookies=client.cookies,
                                auth=client.auth())
            resp.raise_for_status()
            self.assertEquals(resp.text, 'done')
        finally:
            httpd.shutdown()
Beispiel #5
0
    def __init__(self,
                 url=API_URL,
                 timeout=DEFAULT_TIMEOUT,
                 verify=True,
                 client=None,
                 cookies=None):
        """Initializer.

        @param url The base url to the charmstore API.  Defaults
            to `https://api.jujucharms.com/`.
        @param timeout How long to wait in seconds before timing out a request;
            a value of None means no timeout.
        @param verify Whether to verify the certificate for the charmstore API
            host.
        @param client (httpbakery.Client) holds a context for making http
        requests with macaroons.
        @param cookies (which act as dict) holds cookies to be sent with the
        requests.
        """
        super(CharmStore, self).__init__()
        self.url = url
        self.verify = verify
        self.timeout = timeout
        self.cookies = cookies
        if client is None:
            client = httpbakery.Client()
        self._client = client
Beispiel #6
0
def _get_bakery_client(auth_info=None):
    """Return an httpbakery.Client."""
    interaction_methods = None
    if auth_info is not None:
        interaction_methods = [AgentInteractor(auth_info)]
    else:
        username, password = None, None
        credentials = os.environ.get('MAAS_CANDID_CREDENTIALS')
        if credentials:
            user_pass = credentials.split(':', 1)
            if len(user_pass) == 2:
                username, password = user_pass
        if username and password:

            def login_with_credentials(visit_url):
                """Login with Candid's builtin username/password form.

                This is only intended for use in automated testing.

                """
                resp = requests.get(visit_url)
                assert resp.status_code == 200
                requests.post(resp.url,
                              data={
                                  'username': username,
                                  'password': password
                              })

            interaction_methods = [
                httpbakery.WebBrowserInteractor(open=login_with_credentials)
            ]

    return httpbakery.Client(interaction_methods=interaction_methods)
Beispiel #7
0
def _get_bakery_client(auth_info=None):
    """Return an httpbakery.Client."""
    if auth_info is not None:
        interactor = AgentInteractor(auth_info)
    else:
        interactor = httpbakery.WebBrowserInteractor(
            open=_candid_login(os.environ.get("MAAS_CANDID_CREDENTIALS")))
    return httpbakery.Client(interaction_methods=[interactor])
Beispiel #8
0
 def test_cookie_with_port(self):
     client = httpbakery.Client()
     with HTTMock(first_407_then_200_with_port):
         with HTTMock(discharge_200):
             resp = requests.get('http://example.com:8000/someprotecteurl',
                                 cookies=client.cookies,
                                 auth=client.auth())
     resp.raise_for_status()
     assert 'macaroon-test' in client.cookies.keys()
    def test_discharge_with_interaction_required_error(self):
        class _DischargerLocator(bakery.ThirdPartyLocator):
            def __init__(self):
                self.key = bakery.generate_key()

            def third_party_info(self, loc):
                if loc == 'http://1.2.3.4':
                    return bakery.ThirdPartyInfo(
                        public_key=self.key.public_key,
                        version=bakery.LATEST_VERSION,
                    )

        d = _DischargerLocator()
        b = new_bakery('loc', d, None)

        @urlmatch(path='.*/discharge')
        def discharge(url, request):
            return {
                'status_code': 401,
                'content': {
                    'Code': httpbakery.ERR_INTERACTION_REQUIRED,
                    'Message': 'interaction required',
                    'Info': {
                        'WaitURL': 'http://0.1.2.3/',
                        'VisitURL': 'http://0.1.2.3/',
                    },
                }
            }

        def handler(*args):
            GetHandler(b, 'http://1.2.3.4', None, None, None, AGES, *args)

        try:
            httpd = HTTPServer(('', 0), handler)
            thread = threading.Thread(target=httpd.serve_forever)
            thread.start()

            class MyInteractor(httpbakery.LegacyInteractor):
                def legacy_interact(self, ctx, location, visit_url):
                    raise httpbakery.InteractionError('cannot visit')

                def interact(self, ctx, location, interaction_required_err):
                    pass

                def kind(self):
                    return httpbakery.WEB_BROWSER_INTERACTION_KIND

            client = httpbakery.Client(interaction_methods=[MyInteractor()])

            with HTTMock(discharge):
                with self.assertRaises(httpbakery.InteractionError):
                    requests.get('http://' + httpd.server_address[0] + ':' +
                                 str(httpd.server_address[1]),
                                 cookies=client.cookies,
                                 auth=client.auth())
        finally:
            httpd.shutdown()
Beispiel #10
0
 def test_discharge(self):
     client = httpbakery.Client()
     with HTTMock(first_407_then_200), HTTMock(discharge_200):
             resp = requests.get(ID_PATH,
                                 cookies=client.cookies,
                                 auth=client.auth())
     resp.raise_for_status()
     assert 'macaroon-test' in client.cookies.keys()
     self.assert_cookie_security(client.cookies, 'macaroon-test',
                                 secure=False)
Beispiel #11
0
 def __init__(self, url, auth_info):
     self._url = url.rstrip('/')
     self._auth_info = auth_info
     interaction_methods = None
     if auth_info is not None:
         # if auth info is specified, use the default interaction (most
         # likely the browser-based one)
         interaction_methods = [AgentInteractor(self._auth_info)]
     self._client = httpbakery.Client(
         interaction_methods=interaction_methods)
Beispiel #12
0
 def get_token():
     client = httpbakery.Client()
     resp = client.request(
         'POST',
         '{}/account/?op=create_authorisation_token'.format(url),
         verify=not insecure)
     if resp.status_code != 200:
         raise LoginError('Login failed: {}'.format(resp.text))
     result = resp.json()
     return '{consumer_key}:{token_key}:{token_secret}'.format(**result)
Beispiel #13
0
def authentication(request, payload):
    url = 'https://ubuntu.com/security/releases'

    client = httpbakery.Client(cookies=MozillaCookieJar(".login"))

    if os.path.exists(client.cookies.filename):
        client.cookies.load(ignore_discard=True)

    response = client.request(request, url=url, json=payload)
    client.cookies.save(ignore_discard=True)
    print(response, response.text)
Beispiel #14
0
    def __init__(
            self,
            *,
            user_agent: str = agent.get_user_agent(),
            bakery_client=httpbakery.Client(),
    ) -> None:
        super().__init__(user_agent=user_agent)

        self.bakery_client = bakery_client
        self._conf = CandidConfig()
        self._conf_save = True
Beispiel #15
0
 def test_407_then_no_interaction_methods(self):
     client = httpbakery.Client(interaction_methods=[])
     with HTTMock(first_407_then_200), HTTMock(discharge_401):
         with self.assertRaises(httpbakery.InteractionError) as exc:
             requests.get(
                 ID_PATH,
                 cookies=client.cookies,
                 auth=client.auth(),
             )
     self.assertEqual(str(exc.exception),
                      'cannot start interactive session: interaction '
                      'required but not possible')
Beispiel #16
0
 def bakery_client_for_controller(self, controller_name):
     '''Make a copy of the bakery client with a the appropriate controller's
     cookiejar in it.
     '''
     bakery_client = self.bakery_client
     if bakery_client:
         bakery_client = copy.copy(bakery_client)
     else:
         bakery_client = httpbakery.Client()
     bakery_client.cookies = self.jujudata.cookies_for_controller(
         controller_name)
     return bakery_client
Beispiel #17
0
 def test_407_then_401_on_discharge(self, mock_open):
     client = httpbakery.Client()
     with HTTMock(first_407_then_200), HTTMock(discharge_401), \
             HTTMock(wait_after_401):
         resp = requests.get(
             ID_PATH,
             cookies=client.cookies,
             auth=client.auth(),
         )
         resp.raise_for_status()
     mock_open.assert_called_once_with(u'http://example.com/visit', new=1)
     assert 'macaroon-test' in client.cookies.keys()
    def test_expiry_cookie_is_set(self):
        class _DischargerLocator(bakery.ThirdPartyLocator):
            def __init__(self):
                self.key = bakery.generate_key()

            def third_party_info(self, loc):
                if loc == 'http://1.2.3.4':
                    return bakery.ThirdPartyInfo(
                        public_key=self.key.public_key,
                        version=bakery.LATEST_VERSION,
                    )

        d = _DischargerLocator()
        b = new_bakery('loc', d, None)

        @urlmatch(path='.*/discharge')
        def discharge(url, request):
            qs = parse_qs(request.body)
            content = {q: qs[q][0] for q in qs}
            m = httpbakery.discharge(checkers.AuthContext(), content, d.key, d,
                                     alwaysOK3rd)
            return {
                'status_code': 200,
                'content': {
                    'Macaroon': m.to_dict()
                }
            }

        ages = datetime.datetime.utcnow() + datetime.timedelta(days=1)

        def handler(*args):
            GetHandler(b, 'http://1.2.3.4', None, None, None, ages, *args)
        try:
            httpd = HTTPServer(('', 0), handler)
            thread = threading.Thread(target=httpd.serve_forever)
            thread.start()
            client = httpbakery.Client()
            with HTTMock(discharge):
                resp = requests.get(
                    url='http://' + httpd.server_address[0] + ':' +
                        str(httpd.server_address[1]),
                    cookies=client.cookies,
                    auth=client.auth())
            resp.raise_for_status()
            m = bakery.Macaroon.from_dict(json.loads(
                base64.b64decode(client.cookies.get('macaroon-test')).decode('utf-8'))[0])
            t = checkers.macaroons_expiry_time(
                checkers.Namespace(), [m.macaroon])
            self.assertEquals(ages, t)
            self.assertEquals(resp.text, 'done')
        finally:
            httpd.shutdown()
Beispiel #19
0
def authentication(request, url, payload):
    """
    Authenticate with Macaroons in order to use Webteam API
    """

    client = httpbakery.Client(cookies=MozillaCookieJar(".login"))

    if os.path.exists(client.cookies.filename):
        client.cookies.load(ignore_discard=True)

    response = client.request(request, url=url, json=payload)
    client.cookies.save(ignore_discard=True)
    print(response, response.text)
Beispiel #20
0
    def __init__(self,
                 *,
                 user_agent: str = agent.get_user_agent(),
                 bakery_client=None) -> None:
        super().__init__(user_agent=user_agent)

        if bakery_client is None:
            self.bakery_client = httpbakery.Client(
                interaction_methods=[WebBrowserWaitingInteractor()])
        else:
            self.bakery_client = bakery_client
        self._conf = CandidConfig()
        self._conf_save = True
Beispiel #21
0
def authentication(method, url, payload):
    """
    Authenticate with Macaroons in order to use Webteam API
    """

    client = httpbakery.Client(cookies=MozillaCookieJar(os.path.expanduser("~/.ubuntu.com.login")))

    if os.path.exists(client.cookies.filename):
        client.cookies.load(ignore_discard=True)

    response = client.request(method, url=url, json=payload)
    client.cookies.save(ignore_discard=True)
    return response
Beispiel #22
0
    def __init__(self, url, timeout=DEFAULT_TIMEOUT, client=None):
        """Initializer.

        @param url The url to the Terms Service API.
        @param timeout How long to wait in seconds before timing out a request;
            a value of None means no timeout.
        @param client (httpbakery.Client) holds a context for making http
        requests with macaroons.
        """
        self.url = ensure_trailing_slash(url) + TERMS_VERSION + '/'
        self.timeout = timeout
        if client is None:
            client = httpbakery.Client()
        self._client = client
Beispiel #23
0
 def test_407_then_error_on_wait(self, mock_open):
     client = httpbakery.Client()
     with HTTMock(first_407_then_200), HTTMock(discharge_401),\
             HTTMock(wait_on_error):
         with self.assertRaises(httpbakery.InteractionError) as exc:
             requests.get(
                 ID_PATH,
                 cookies=client.cookies,
                 auth=client.auth(),
             )
     self.assertEqual(
         str(exc.exception), 'cannot start interactive session: cannot get '
         'http://example.com/wait')
     mock_open.assert_called_once_with(u'http://example.com/visit', new=1)
Beispiel #24
0
 def get_token():
     client = httpbakery.Client()
     resp = client.request(
         'POST',
         '{}/account/?op=create_authorisation_token'.format(url),
         verify=not insecure)
     if resp.status_code == HTTPStatus.UNAUTHORIZED:
         # if the auteentication with Candid fails, an exception is raised
         # above so we don't get here
         raise MacaroonLoginNotSupported(
             'Macaroon authentication not supported')
     if resp.status_code != HTTPStatus.OK:
         raise LoginError('Login failed: {}'.format(resp.text))
     result = resp.json()
     return '{consumer_key}:{token_key}:{token_secret}'.format(**result)
    def test_expiry_cookie_set_in_past(self):
        class _DischargerLocator(bakery.ThirdPartyLocator):
            def __init__(self):
                self.key = bakery.generate_key()

            def third_party_info(self, loc):
                if loc == 'http://1.2.3.4':
                    return bakery.ThirdPartyInfo(
                        public_key=self.key.public_key,
                        version=bakery.LATEST_VERSION,
                    )

        d = _DischargerLocator()
        b = new_bakery('loc', d, None)

        @urlmatch(path='.*/discharge')
        def discharge(url, request):
            qs = parse_qs(request.body)
            content = {q: qs[q][0] for q in qs}
            m = httpbakery.discharge(checkers.AuthContext(), content, d.key, d,
                                     alwaysOK3rd)
            return {
                'status_code': 200,
                'content': {
                    'Macaroon': m.to_dict()
                }
            }

        ages = datetime.datetime.utcnow() - datetime.timedelta(days=1)

        def handler(*args):
            GetHandler(b, 'http://1.2.3.4', None, None, None, ages, *args)
        try:
            httpd = HTTPServer(('', 0), handler)
            thread = threading.Thread(target=httpd.serve_forever)
            thread.start()
            client = httpbakery.Client()
            with HTTMock(discharge):
                with self.assertRaises(httpbakery.BakeryException) as ctx:
                    requests.get(
                        url='http://' + httpd.server_address[0] + ':' +
                            str(httpd.server_address[1]),
                        cookies=client.cookies,
                        auth=client.auth())
            self.assertEqual(ctx.exception.args[0],
                             'too many (3) discharge requests')
        finally:
            httpd.shutdown()
async def test_macaroon_auth(event_loop):
    auth_info, username = agent_auth_info()
    # Create a bakery client that can do agent authentication.
    client = httpbakery.Client(
        key=auth_info.key,
        interaction_methods=[agent.AgentInteractor(auth_info)],
    )

    async with base.CleanModel(bakery_client=client) as m:
        async with await m.get_controller() as c:
            await c.grant_model(username, m.info.uuid, 'admin')
        async with Model(
                jujudata=NoAccountsJujuData(m._connector.jujudata),
                bakery_client=client,
        ):
            pass
    def test_too_many_discharge(self):
        class _DischargerLocator(bakery.ThirdPartyLocator):
            def __init__(self):
                self.key = bakery.generate_key()

            def third_party_info(self, loc):
                if loc == 'http://1.2.3.4':
                    return bakery.ThirdPartyInfo(
                        public_key=self.key.public_key,
                        version=bakery.LATEST_VERSION,
                    )

        d = _DischargerLocator()
        b = new_bakery('loc', d, None)

        @urlmatch(path='.*/discharge')
        def discharge(url, request):
            wrong_macaroon = bakery.Macaroon(root_key=b'some key',
                                             id=b'xxx',
                                             location='some other location',
                                             version=bakery.VERSION_0)
            return {
                'status_code': 200,
                'content': {
                    'Macaroon': wrong_macaroon.to_dict()
                }
            }

        def handler(*args):
            GetHandler(b, 'http://1.2.3.4', None, None, None, AGES, *args)

        try:
            httpd = HTTPServer(('', 0), handler)
            thread = threading.Thread(target=httpd.serve_forever)
            thread.start()
            client = httpbakery.Client()
            with HTTMock(discharge):
                with self.assertRaises(httpbakery.BakeryException) as ctx:
                    requests.get(url='http://' + httpd.server_address[0] +
                                 ':' + str(httpd.server_address[1]),
                                 cookies=client.cookies,
                                 auth=client.auth())
            self.assertEqual(ctx.exception.args[0],
                             'too many (3) discharge requests')
        finally:
            httpd.shutdown()
Beispiel #28
0
    async def connect(self, **kwargs):
        """Connect to an arbitrary Juju model.

        kwargs are passed through to Connection.connect()
        """
        kwargs.setdefault('loop', self.loop)
        kwargs.setdefault('max_frame_size', self.max_frame_size)
        kwargs.setdefault('bakery_client', self.bakery_client)
        if 'macaroons' in kwargs:
            if not kwargs['bakery_client']:
                kwargs['bakery_client'] = httpbakery.Client()
            if not kwargs['bakery_client'].cookies:
                kwargs['bakery_client'].cookies = GoCookieJar()
            jar = kwargs['bakery_client'].cookies
            for macaroon in kwargs.pop('macaroons'):
                jar.set_cookie(go_to_py_cookie(macaroon))
        self._connection = await Connection.connect(**kwargs)
Beispiel #29
0
    def test_407_then_unknown_interaction_methods(self):
        class UnknownInteractor(httpbakery.Interactor):
            def kind(self):
                return 'unknown'

        client = httpbakery.Client(interaction_methods=[UnknownInteractor()])
        with HTTMock(first_407_then_200), HTTMock(discharge_401):
            with self.assertRaises(httpbakery.InteractionError) as exc:
                requests.get(
                    ID_PATH,
                    cookies=client.cookies,
                    auth=client.auth(),
                )
        self.assertEqual(
            str(exc.exception),
            'cannot start interactive session: no methods supported; '
            'supported [unknown]; provided [interactive]')
    def test_single_service_third_party(self):
        class _DischargerLocator(bakery.ThirdPartyLocator):
            def __init__(self):
                self.key = bakery.generate_key()

            def third_party_info(self, loc):
                if loc == 'http://1.2.3.4':
                    return bakery.ThirdPartyInfo(
                        public_key=self.key.public_key,
                        version=bakery.LATEST_VERSION,
                    )

        d = _DischargerLocator()
        b = new_bakery('loc', d, None)

        @urlmatch(path='.*/discharge')
        def discharge(url, request):
            qs = parse_qs(request.body)
            content = {q: qs[q][0] for q in qs}
            m = httpbakery.discharge(checkers.AuthContext(), content, d.key, d,
                                     alwaysOK3rd)
            return {
                'status_code': 200,
                'content': {
                    'Macaroon': m.to_dict()
                }
            }

        def handler(*args):
            GetHandler(b, 'http://1.2.3.4', None, None, None, AGES, *args)
        try:
            httpd = HTTPServer(('', 0), handler)
            server_url = 'http://' + httpd.server_address[0] + ':' + str(httpd.server_address[1])
            thread = threading.Thread(target=httpd.serve_forever)
            thread.start()
            client = httpbakery.Client()
            with HTTMock(discharge):
                resp = requests.get(
                    url=server_url,
                    cookies=client.cookies,
                    auth=client.auth())
            resp.raise_for_status()
            self.assertEquals(resp.text, 'done')
        finally:
            httpd.shutdown()