Esempio n. 1
0
    async def test_toke_details_from_json(self):
        token_details = await self.ably.auth.request_token()
        token_details_dict = token_details.to_dict()
        token_details_str = json.dumps(token_details_dict)

        assert token_details == TokenDetails.from_json(token_details_dict)
        assert token_details == TokenDetails.from_json(token_details_str)
Esempio n. 2
0
    def test_toke_details_from_json(self):
        token_details = self.ably.auth.request_token()
        token_details_dict = token_details.to_dict()
        token_details_str = json.dumps(token_details_dict)

        assert token_details == TokenDetails.from_json(token_details_dict)
        assert token_details == TokenDetails.from_json(token_details_str)
Esempio n. 3
0
    def __init__(self, ably, options):
        self.__ably = ably
        self.__auth_options = options
        if options.token_details:
            self.__client_id = options.token_details.client_id
        else:
            self.__client_id = options.client_id
        self.__client_id_validated = False

        self.__basic_credentials = None
        self.__auth_params = None
        self.__token_details = None

        must_use_token_auth = options.use_token_auth is True
        must_not_use_token_auth = options.use_token_auth is False
        can_use_basic_auth = options.key_secret is not None and options.client_id is None
        if not must_use_token_auth and can_use_basic_auth:
            # We have the key, no need to authenticate the client
            # default to using basic auth
            log.debug("anonymous, using basic auth")
            self.__auth_mechanism = Auth.Method.BASIC
            basic_key = "%s:%s" % (options.key_name, options.key_secret)
            basic_key = base64.b64encode(basic_key.encode('utf-8'))
            self.__basic_credentials = basic_key.decode('ascii')
            return
        elif must_not_use_token_auth and not can_use_basic_auth:
            raise ValueError(
                'If use_token_auth is False you must provide a key')

        # Using token auth
        self.__auth_mechanism = Auth.Method.TOKEN

        if options.token_details:
            self.__token_details = options.token_details
        elif options.auth_token:
            self.__token_details = TokenDetails(token=options.auth_token)
        else:
            self.__token_details = None

        if options.auth_callback:
            log.debug("using token auth with auth_callback")
        elif options.auth_url:
            log.debug("using token auth with auth_url")
        elif options.key_secret:
            log.debug("using token auth with client-side signing")
        elif options.auth_token:
            log.debug("using token auth with supplied token only")
        elif options.token_details:
            log.debug("using token auth with supplied token_details")
        else:
            raise ValueError(
                "Can't authenticate via token, must provide "
                "auth_callback, auth_url, key, token or a TokenDetail")
Esempio n. 4
0
    def test_toke_details_from_json(self):
        token_details = self.ably.auth.request_token()
        token_details_dict = token_details.to_dict()
        token_details_str = json.dumps(token_details_dict)

        self.assertEqual(
            token_details,
            TokenDetails.from_json(token_details_dict),
        )

        self.assertEqual(
            token_details,
            TokenDetails.from_json(token_details_str),
        )
Esempio n. 5
0
    def __init__(self, ably, options):
        self.__ably = ably
        self.__auth_options = options
        if options.token_details:
            self.__client_id = options.token_details.client_id
        else:
            self.__client_id = options.client_id
        self.__client_id_validated = False

        self.__basic_credentials = None
        self.__auth_params = None
        self.__token_details = None

        must_use_token_auth = options.use_token_auth is True
        must_not_use_token_auth = options.use_token_auth is False
        can_use_basic_auth = options.key_secret is not None and options.client_id is None
        if not must_use_token_auth and can_use_basic_auth:
            # We have the key, no need to authenticate the client
            # default to using basic auth
            log.debug("anonymous, using basic auth")
            self.__auth_mechanism = Auth.Method.BASIC
            basic_key = "%s:%s" % (options.key_name, options.key_secret)
            basic_key = base64.b64encode(basic_key.encode('utf-8'))
            self.__basic_credentials = basic_key.decode('ascii')
            return
        elif must_not_use_token_auth and not can_use_basic_auth:
            raise ValueError('If use_token_auth is False you must provide a key')

        # Using token auth
        self.__auth_mechanism = Auth.Method.TOKEN

        if options.token_details:
            self.__token_details = options.token_details
        elif options.auth_token:
            self.__token_details = TokenDetails(token=options.auth_token)
        else:
            self.__token_details = None

        if options.auth_callback:
            log.debug("using token auth with auth_callback")
        elif options.auth_url:
            log.debug("using token auth with auth_url")
        elif options.key_secret:
            log.debug("using token auth with client-side signing")
        elif options.auth_token:
            log.debug("using token auth with supplied token only")
        elif options.token_details:
            log.debug("using token auth with supplied token_details")
        else:
            raise ValueError("Can't authenticate via token, must provide "
                             "auth_callback, auth_url, key, token or a TokenDetail")
Esempio n. 6
0
    def test_when_not_renewable_with_token_details(self):
        token_details = TokenDetails(token='a_dummy_token')
        self.ably = RestSetup.get_ably_rest(key=None,
                                            token_details=token_details,
                                            use_binary_protocol=False)
        self.ably.channels[self.channel].publish('evt', 'msg')
        assert 1 == self.publish_attempts

        publish = self.ably.channels[self.channel].publish

        match = "The provided token is not renewable and there is no means to generate a new token"
        with pytest.raises(AblyAuthException, match=match):
            publish('evt', 'msg')

        assert 0 == self.token_requests
Esempio n. 7
0
    async def test_when_not_renewable_with_token_details(self):
        token_details = TokenDetails(token='a_dummy_token')
        self.ably = await RestSetup.get_ably_rest(key=None,
                                                  token_details=token_details,
                                                  use_binary_protocol=False)
        await self.ably.channels[self.channel].publish('evt', 'msg')
        assert self.mocked_api["publish_attempt_route"].call_count == 1

        publish = self.ably.channels[self.channel].publish

        match = "The provided token is not renewable and there is no means to generate a new token"
        with pytest.raises(AblyAuthException, match=match):
            await publish('evt', 'msg')

        assert not self.mocked_api["request_token_route"].called
Esempio n. 8
0
    def test_when_not_renewable_with_token_details(self):
        token_details = TokenDetails(token='a_dummy_token')
        self.ably = AblyRest(token_details=token_details,
                             rest_host=test_vars["host"],
                             port=test_vars["port"],
                             tls_port=test_vars["tls_port"],
                             tls=test_vars["tls"],
                             use_binary_protocol=False)
        self.ably.channels[self.channel].publish('evt', 'msg')
        self.assertEquals(1, self.publish_attempts)

        publish = self.ably.channels[self.channel].publish

        self.assertRaisesRegexp(
            AblyAuthException,
            "The provided token is not renewable and there is"
            " no means to generate a new token", publish, 'evt', 'msg')
        self.assertEquals(0, self.token_requests)
Esempio n. 9
0
    def request_token(
            self,
            token_params=None,
            # auth_options
            key_name=None,
            key_secret=None,
            auth_callback=None,
            auth_url=None,
            auth_method=None,
            auth_headers=None,
            auth_params=None,
            query_time=None):
        token_params = token_params or {}
        token_params = dict(self.auth_options.default_token_params,
                            **token_params)
        key_name = key_name or self.auth_options.key_name
        key_secret = key_secret or self.auth_options.key_secret

        log.debug("Auth callback: %s" % auth_callback)
        log.debug("Auth options: %s" % six.text_type(self.auth_options))
        if query_time is None:
            query_time = self.auth_options.query_time
        query_time = bool(query_time)
        auth_callback = auth_callback or self.auth_options.auth_callback
        auth_url = auth_url or self.auth_options.auth_url

        auth_params = auth_params or self.auth_options.auth_params or {}

        auth_method = (auth_method or self.auth_options.auth_method).upper()

        auth_headers = auth_headers or self.auth_options.auth_headers or {}

        log.debug("Token Params: %s" % token_params)
        if auth_callback:
            log.debug("using token auth with authCallback")
            token_request = auth_callback(token_params)
        elif auth_url:
            log.debug("using token auth with authUrl")

            token_request = self.token_request_from_auth_url(
                auth_method, auth_url, token_params, auth_headers, auth_params)
        else:
            token_request = self.create_token_request(token_params,
                                                      key_name=key_name,
                                                      key_secret=key_secret,
                                                      query_time=query_time)
        if isinstance(token_request, TokenDetails):
            return token_request
        elif isinstance(token_request, dict) and 'issued' in token_request:
            return TokenDetails.from_dict(token_request)
        elif isinstance(token_request, dict):
            token_request = TokenRequest(**token_request)
        elif isinstance(token_request, six.text_type):
            return TokenDetails(token=token_request)
        # python2
        elif isinstance(token_request,
                        six.binary_type) and six.binary_type == str:
            return TokenDetails(token=token_request)

        token_path = "/keys/%s/requestToken" % token_request.key_name

        response = self.ably.http.post(token_path,
                                       headers=auth_headers,
                                       native_data=token_request.to_dict(),
                                       skip_auth=True)

        AblyException.raise_for_response(response)
        response_dict = response.to_native()
        log.debug("Token: %s" % str(response_dict.get("token")))
        return TokenDetails.from_dict(response_dict)
Esempio n. 10
0
    def test_auth_token_details(self):
        td = TokenDetails()
        ably = AblyRest(token_details=td)

        assert Auth.Method.TOKEN == ably.auth.auth_mechanism
        assert ably.auth.token_details is td
Esempio n. 11
0
 def callback(token_params):
     assert token_params == called_token_params
     return TokenDetails(token='another_token_string')
Esempio n. 12
0
    def test_auth_token_details(self):
        td = TokenDetails()
        ably = AblyRest(token_details=td)

        self.assertEqual(Auth.Method.TOKEN, ably.auth.auth_mechanism)
        self.assertIs(ably.auth.token_details, td)
Esempio n. 13
0
 def test_with_token_details(self):
     td = TokenDetails()
     ably = AblyRest(token_details=td)
     self.assertIs(ably.options.token_details, td)
Esempio n. 14
0
 def test_with_token_details(self):
     td = TokenDetails()
     ably = AblyRest(token_details=td)
     assert ably.options.token_details is td
Esempio n. 15
0
class Auth(object):
    class Method:
        BASIC = "BASIC"
        TOKEN = "TOKEN"

    def __init__(self, ably, options):
        self.__ably = ably
        self.__auth_options = options
        self.__client_id = options.client_id
        self.__client_id_validated = False

        self.__basic_credentials = None
        self.__auth_params = None
        self.__token_details = None

        must_use_token_auth = options.use_token_auth is True
        must_not_use_token_auth = options.use_token_auth is False
        can_use_basic_auth = options.key_secret is not None and options.client_id is None
        if not must_use_token_auth and can_use_basic_auth:
            # We have the key, no need to authenticate the client
            # default to using basic auth
            log.debug("anonymous, using basic auth")
            self.__auth_mechanism = Auth.Method.BASIC
            basic_key = "%s:%s" % (options.key_name, options.key_secret)
            basic_key = base64.b64encode(basic_key.encode('utf-8'))
            self.__basic_credentials = basic_key.decode('ascii')
            return
        elif must_not_use_token_auth and not can_use_basic_auth:
            raise ValueError(
                'If use_token_auth is False you must provide a key')

        # Using token auth
        self.__auth_mechanism = Auth.Method.TOKEN

        if options.token_details:
            self.__token_details = options.token_details
        elif options.auth_token:
            self.__token_details = TokenDetails(token=options.auth_token)
        else:
            self.__token_details = None

        if options.auth_callback:
            log.debug("using token auth with auth_callback")
        elif options.auth_url:
            log.debug("using token auth with auth_url")
        elif options.key_secret:
            log.debug("using token auth with client-side signing")
        elif options.auth_token:
            log.debug("using token auth with supplied token only")
        elif options.token_details:
            log.debug("using token auth with supplied token_details")
        else:
            raise ValueError(
                "Can't authenticate via token, must provide "
                "auth_callback, auth_url, key, token or a TokenDetail")

    def authorise(self, token_params=None, auth_options=None, force=False):
        self.__auth_mechanism = Auth.Method.TOKEN

        if token_params is None:
            token_params = dict(self.auth_options.default_token_params)
        else:
            token_params = dict(self.auth_options.default_token_params,
                                **token_params)
            self.auth_options.default_token_params = dict(token_params)
            self.auth_options.default_token_params.pop('timestamp', None)

        if auth_options is not None:
            force = auth_options.pop('force', None) or force
            self.auth_options.merge(auth_options)
        auth_options = dict(self.auth_options.auth_options)
        token_params.setdefault('client_id', self.client_id)

        if self.__token_details:
            if not self.__token_details.is_expired(self._timestamp()):
                if not force:
                    log.debug("using cached token; expires = %d",
                              self.__token_details.expires)
                    return self.__token_details
            else:
                # token has expired
                self.__token_details = None

        self.__token_details = self.request_token(token_params, **auth_options)
        self._configure_client_id(self.__token_details.client_id)
        return self.__token_details

    def request_token(
            self,
            token_params=None,
            # auth_options
            key_name=None,
            key_secret=None,
            auth_callback=None,
            auth_url=None,
            auth_method=None,
            auth_headers=None,
            auth_params=None,
            query_time=None):
        token_params = token_params or {}
        token_params = dict(self.auth_options.default_token_params,
                            **token_params)
        key_name = key_name or self.auth_options.key_name
        key_secret = key_secret or self.auth_options.key_secret

        log.debug("Auth callback: %s" % auth_callback)
        log.debug("Auth options: %s" % six.text_type(self.auth_options))
        if query_time is None:
            query_time = self.auth_options.query_time
        query_time = bool(query_time)
        auth_callback = auth_callback or self.auth_options.auth_callback
        auth_url = auth_url or self.auth_options.auth_url

        auth_params = auth_params or self.auth_options.auth_params or {}

        auth_method = (auth_method or self.auth_options.auth_method).upper()

        auth_headers = auth_headers or self.auth_options.auth_headers or {}

        log.debug("Token Params: %s" % token_params)
        if auth_callback:
            log.debug("using token auth with authCallback")
            token_request = auth_callback(token_params)
        elif auth_url:
            log.debug("using token auth with authUrl")

            token_request = self.token_request_from_auth_url(
                auth_method, auth_url, token_params, auth_headers, auth_params)
        else:
            token_request = self.create_token_request(token_params,
                                                      key_name=key_name,
                                                      key_secret=key_secret,
                                                      query_time=query_time)
        if isinstance(token_request, TokenDetails):
            return token_request
        elif isinstance(token_request, dict) and 'issued' in token_request:
            return TokenDetails.from_dict(token_request)
        elif isinstance(token_request, dict):
            token_request = TokenRequest(**token_request)
        elif isinstance(token_request, six.text_type):
            return TokenDetails(token=token_request)
        # python2
        elif isinstance(token_request,
                        six.binary_type) and six.binary_type == str:
            return TokenDetails(token=token_request)

        token_path = "/keys/%s/requestToken" % token_request.key_name

        response = self.ably.http.post(token_path,
                                       headers=auth_headers,
                                       native_data=token_request.to_dict(),
                                       skip_auth=True)

        AblyException.raise_for_response(response)
        response_dict = response.to_native()
        log.debug("Token: %s" % str(response_dict.get("token")))
        return TokenDetails.from_dict(response_dict)

    def create_token_request(self,
                             token_params=None,
                             key_name=None,
                             key_secret=None,
                             query_time=None):
        token_params = token_params or {}
        token_request = {}

        key_name = key_name or self.auth_options.key_name
        key_secret = key_secret or self.auth_options.key_secret
        if not key_name or not key_secret:
            log.debug('key_name or key_secret blank')
            raise AblyException(
                "No key specified: no means to generate a token", 401, 40101)

        token_request['key_name'] = key_name
        if token_params.get('timestamp'):
            token_request['timestamp'] = token_params['timestamp']
        else:
            if query_time is None:
                query_time = self.auth_options.query_time
            if query_time:
                token_request['timestamp'] = self.ably.time()
            else:
                token_request['timestamp'] = self._timestamp()

        token_request['timestamp'] = int(token_request['timestamp'])

        token_request['ttl'] = token_params.get(
            'ttl') or TokenDetails.DEFAULTS['ttl']

        if token_params.get('capability') is None:
            token_request["capability"] = ""
        else:
            token_request['capability'] = six.text_type(
                Capability(token_params['capability']))

        token_request["client_id"] = (token_params.get('client_id')
                                      or self.client_id)

        # Note: There is no expectation that the client
        # specifies the nonce; this is done by the library
        # However, this can be overridden by the client
        # simply for testing purposes
        token_request["nonce"] = token_params.get(
            'nonce') or self._random_nonce()

        token_request = TokenRequest(**token_request)

        if token_params.get('mac') is None:
            # Note: There is no expectation that the client
            # specifies the mac; this is done by the library
            # However, this can be overridden by the client
            # simply for testing purposes.
            token_request.sign_request(key_secret.encode('utf8'))
        else:
            token_request.mac = token_params['mac']

        return token_request

    @property
    def ably(self):
        return self.__ably

    @property
    def auth_mechanism(self):
        return self.__auth_mechanism

    @property
    def auth_options(self):
        return self.__auth_options

    @property
    def auth_params(self):
        return self.__auth_params

    @property
    def basic_credentials(self):
        return self.__basic_credentials

    @property
    def token_credentials(self):
        if self.__token_details:
            token = self.__token_details.token
            token_key = base64.b64encode(token.encode('utf-8'))
            return token_key.decode('ascii')

    @property
    def token_details(self):
        return self.__token_details

    @property
    def client_id(self):
        return self.__client_id

    def _configure_client_id(self, new_client_id):
        # If new client ID from Ably is a wildcard, but preconfigured clientId is set,
        # then keep the existing clientId
        if self.client_id != '*' and new_client_id == '*':
            self.__client_id_validated = True
            return

        # If client_id is defined and not a wildcard, prevent it changing, this is not supported
        if self.client_id is not None and self.client_id != '*' and new_client_id != self.client_id:
            raise IncompatibleClientIdException(
                "Client ID is immutable once configured for a client. "
                "Client ID cannot be changed to '{}'".format(new_client_id),
                400, 40012)

        self.__client_id_validated = True
        self.__client_id = new_client_id

    def can_assume_client_id(self, assumed_client_id):
        if self.__client_id_validated:
            return self.client_id == '*' or self.client_id == assumed_client_id
        elif self.client_id is None or self.client_id == '*':
            return True  # client ID is unknown
        else:
            return self.client_id == assumed_client_id

    def _get_auth_headers(self):
        if self.__auth_mechanism == Auth.Method.BASIC:
            return {
                'Authorization': 'Basic %s' % self.basic_credentials,
            }
        else:
            self.authorise()
            return {
                'Authorization': 'Bearer %s' % self.token_credentials,
            }

    def _timestamp(self):
        """Returns the local time in milliseconds since the unix epoch"""
        return int(time.time() * 1000)

    def _random_nonce(self):
        return uuid.uuid4().hex[:16]

    def token_request_from_auth_url(self, method, url, token_params, headers,
                                    auth_params):
        if method == 'GET':
            body = {}
            params = dict(auth_params, **token_params)
        elif method == 'POST':
            params = {}
            body = dict(auth_params, **token_params)

        from ably.http.http import Response
        response = Response(
            requests.request(method,
                             url,
                             headers=headers,
                             params=params,
                             data=body))

        AblyException.raise_for_response(response)
        try:
            token_request = response.to_native()
        except ValueError:
            token_request = response.text
        return token_request
Esempio n. 16
0
 def callback(token_params):
     self.assertEquals(token_params, called_token_params)
     return TokenDetails(token='another_token_string')
Esempio n. 17
0
    def request_token(self, token_params=None,
                      # auth_options
                      key_name=None, key_secret=None, auth_callback=None,
                      auth_url=None, auth_method=None, auth_headers=None,
                      auth_params=None, query_time=None):
        token_params = token_params or {}
        token_params = dict(self.auth_options.default_token_params,
                            **token_params)
        key_name = key_name or self.auth_options.key_name
        key_secret = key_secret or self.auth_options.key_secret

        log.debug("Auth callback: %s" % auth_callback)
        log.debug("Auth options: %s" % six.text_type(self.auth_options))
        if query_time is None:
            query_time = self.auth_options.query_time
        query_time = bool(query_time)
        auth_callback = auth_callback or self.auth_options.auth_callback
        auth_url = auth_url or self.auth_options.auth_url

        auth_params = auth_params or self.auth_options.auth_params or {}

        auth_method = (auth_method or self.auth_options.auth_method).upper()

        auth_headers = auth_headers or self.auth_options.auth_headers or {}

        log.debug("Token Params: %s" % token_params)
        if auth_callback:
            log.debug("using token auth with authCallback")
            token_request = auth_callback(token_params)
        elif auth_url:
            log.debug("using token auth with authUrl")

            token_request = self.token_request_from_auth_url(
                auth_method, auth_url, token_params, auth_headers, auth_params)
        else:
            token_request = self.create_token_request(
                token_params, key_name=key_name, key_secret=key_secret,
                query_time=query_time)
        if isinstance(token_request, TokenDetails):
            return token_request
        elif isinstance(token_request, dict) and 'issued' in token_request:
            return TokenDetails.from_dict(token_request)
        elif isinstance(token_request, dict):
            token_request = TokenRequest(**token_request)
        elif isinstance(token_request, six.text_type):
            return TokenDetails(token=token_request)
        # python2
        elif isinstance(token_request, six.binary_type) and six.binary_type == str:
            return TokenDetails(token=token_request)

        token_path = "/keys/%s/requestToken" % token_request.key_name

        response = self.ably.http.post(
            token_path,
            headers=auth_headers,
            native_data=token_request.to_dict(),
            skip_auth=True
        )

        AblyException.raise_for_response(response)
        response_dict = response.to_native()
        log.debug("Token: %s" % str(response_dict.get("token")))
        return TokenDetails.from_dict(response_dict)
Esempio n. 18
0
    def request_token(self, key_id=None, key_value=None, query_time=None,
                      auth_token=None, auth_callback=None, auth_url=None,
                      auth_headers=None, auth_params=None, token_params=None):
        key_id = key_id or self.auth_options.key_id
        key_value = key_value or self.auth_options.key_value

        log.debug("Auth callback: %s" % auth_callback)
        log.debug("Auth options: %s" % six.text_type(self.auth_options))
        query_time = bool(query_time)
        auth_token = auth_token or self.auth_options.auth_token
        auth_callback = auth_callback or self.auth_options.auth_callback
        auth_url = auth_url or self.auth_options.auth_url
        auth_headers = auth_headers or {
            "Content-Encoding": "utf-8",
            "Content-Type": "application/json",
        }
        auth_params = auth_params or self.auth_params

        token_params = token_params or {}

        token_params.setdefault("client_id", self.ably.client_id)

        signed_token_request = ""

        log.debug("Token Params: %s" % token_params)
        if auth_callback:
            log.debug("using token auth with authCallback")
            signed_token_request = auth_callback(**token_params)
        elif auth_url:
            log.debug("using token auth with authUrl")
            response = self.ably.http.post(
                auth_url,
                headers=auth_headers,
                body=json.dumps(token_params),
                skip_auth=True
            )

            AblyException.raise_for_response(response)

            signed_token_request = response.text
        elif key_value:
            log.debug("using token auth with client-side signing")
            signed_token_request = self.create_token_request(
                key_id=key_id,
                key_value=key_value,
                query_time=query_time,
                token_params=token_params)
        else:
            log.debug('No auth_callback, auth_url or key_value specified')
            raise AblyException(
                "Auth.request_token() must include valid auth parameters",
                400,
                40000)

        token_path = "/keys/%s/requestToken" % key_id
        response = self.ably.http.post(
            token_path,
            headers=auth_headers,
            body=signed_token_request,
            skip_auth=True
        )

        AblyException.raise_for_response(response)

        access_token = response.json()["access_token"]
        log.debug("Token: %s" % str(access_token))
        return TokenDetails.from_dict(access_token)
Esempio n. 19
0
class Auth(object):

    class Method:
        BASIC = "BASIC"
        TOKEN = "TOKEN"

    def __init__(self, ably, options):
        self.__ably = ably
        self.__auth_options = options
        self.__client_id = options.client_id
        self.__client_id_validated = False

        self.__basic_credentials = None
        self.__auth_params = None
        self.__token_details = None

        must_use_token_auth = options.use_token_auth is True
        must_not_use_token_auth = options.use_token_auth is False
        can_use_basic_auth = options.key_secret is not None and options.client_id is None
        if not must_use_token_auth and can_use_basic_auth:
            # We have the key, no need to authenticate the client
            # default to using basic auth
            log.debug("anonymous, using basic auth")
            self.__auth_mechanism = Auth.Method.BASIC
            basic_key = "%s:%s" % (options.key_name, options.key_secret)
            basic_key = base64.b64encode(basic_key.encode('utf-8'))
            self.__basic_credentials = basic_key.decode('ascii')
            return
        elif must_not_use_token_auth and not can_use_basic_auth:
            raise ValueError('If use_token_auth is False you must provide a key')

        # Using token auth
        self.__auth_mechanism = Auth.Method.TOKEN

        if options.token_details:
            self.__token_details = options.token_details
        elif options.auth_token:
            self.__token_details = TokenDetails(token=options.auth_token)
        else:
            self.__token_details = None

        if options.auth_callback:
            log.debug("using token auth with auth_callback")
        elif options.auth_url:
            log.debug("using token auth with auth_url")
        elif options.key_secret:
            log.debug("using token auth with client-side signing")
        elif options.auth_token:
            log.debug("using token auth with supplied token only")
        elif options.token_details:
            log.debug("using token auth with supplied token_details")
        else:
            raise ValueError("Can't authenticate via token, must provide "
                             "auth_callback, auth_url, key, token or a TokenDetail")

    def authorise(self, token_params=None, auth_options=None, force=False):
        self.__auth_mechanism = Auth.Method.TOKEN

        if token_params is None:
            token_params = dict(self.auth_options.default_token_params)
        else:
            token_params = dict(self.auth_options.default_token_params,
                                **token_params)
            self.auth_options.default_token_params = dict(token_params)
            self.auth_options.default_token_params.pop('timestamp', None)

        if auth_options is not None:
            force = auth_options.pop('force', None) or force
            self.auth_options.merge(auth_options)
        auth_options = dict(self.auth_options.auth_options)
        token_params.setdefault('client_id', self.client_id)

        if self.__token_details:
            if not self.__token_details.is_expired(self._timestamp()):
                if not force:
                    log.debug(
                        "using cached token; expires = %d",
                        self.__token_details.expires
                    )
                    return self.__token_details
            else:
                # token has expired
                self.__token_details = None

        self.__token_details = self.request_token(token_params, **auth_options)
        self._configure_client_id(self.__token_details.client_id)
        return self.__token_details

    def request_token(self, token_params=None,
                      # auth_options
                      key_name=None, key_secret=None, auth_callback=None,
                      auth_url=None, auth_method=None, auth_headers=None,
                      auth_params=None, query_time=None):
        token_params = token_params or {}
        token_params = dict(self.auth_options.default_token_params,
                            **token_params)
        key_name = key_name or self.auth_options.key_name
        key_secret = key_secret or self.auth_options.key_secret

        log.debug("Auth callback: %s" % auth_callback)
        log.debug("Auth options: %s" % six.text_type(self.auth_options))
        if query_time is None:
            query_time = self.auth_options.query_time
        query_time = bool(query_time)
        auth_callback = auth_callback or self.auth_options.auth_callback
        auth_url = auth_url or self.auth_options.auth_url

        auth_params = auth_params or self.auth_options.auth_params or {}

        auth_method = (auth_method or self.auth_options.auth_method).upper()

        auth_headers = auth_headers or self.auth_options.auth_headers or {}

        log.debug("Token Params: %s" % token_params)
        if auth_callback:
            log.debug("using token auth with authCallback")
            token_request = auth_callback(token_params)
        elif auth_url:
            log.debug("using token auth with authUrl")

            token_request = self.token_request_from_auth_url(
                auth_method, auth_url, token_params, auth_headers, auth_params)
        else:
            token_request = self.create_token_request(
                token_params, key_name=key_name, key_secret=key_secret,
                query_time=query_time)
        if isinstance(token_request, TokenDetails):
            return token_request
        elif isinstance(token_request, dict) and 'issued' in token_request:
            return TokenDetails.from_dict(token_request)
        elif isinstance(token_request, dict):
            token_request = TokenRequest(**token_request)
        elif isinstance(token_request, six.text_type):
            return TokenDetails(token=token_request)
        # python2
        elif isinstance(token_request, six.binary_type) and six.binary_type == str:
            return TokenDetails(token=token_request)

        token_path = "/keys/%s/requestToken" % token_request.key_name

        response = self.ably.http.post(
            token_path,
            headers=auth_headers,
            native_data=token_request.to_dict(),
            skip_auth=True
        )

        AblyException.raise_for_response(response)
        response_dict = response.to_native()
        log.debug("Token: %s" % str(response_dict.get("token")))
        return TokenDetails.from_dict(response_dict)

    def create_token_request(self, token_params=None,
                             key_name=None, key_secret=None, query_time=None):
        token_params = token_params or {}
        token_request = {}

        key_name = key_name or self.auth_options.key_name
        key_secret = key_secret or self.auth_options.key_secret
        if not key_name or not key_secret:
            log.debug('key_name or key_secret blank')
            raise AblyException("No key specified: no means to generate a token", 401, 40101)

        token_request['key_name'] = key_name
        if token_params.get('timestamp'):
            token_request['timestamp'] = token_params['timestamp']
        else:
            if query_time is None:
                query_time = self.auth_options.query_time
            if query_time:
                token_request['timestamp'] = self.ably.time()
            else:
                token_request['timestamp'] = self._timestamp()

        token_request['timestamp'] = int(token_request['timestamp'])

        token_request['ttl'] = token_params.get('ttl') or TokenDetails.DEFAULTS['ttl']

        if token_params.get('capability') is None:
            token_request["capability"] = ""
        else:
            token_request['capability'] = six.text_type(
                Capability(token_params['capability'])
            )

        token_request["client_id"] = (
            token_params.get('client_id') or self.client_id)

        # Note: There is no expectation that the client
        # specifies the nonce; this is done by the library
        # However, this can be overridden by the client
        # simply for testing purposes
        token_request["nonce"] = token_params.get('nonce') or self._random_nonce()

        token_request = TokenRequest(**token_request)

        if token_params.get('mac') is None:
            # Note: There is no expectation that the client
            # specifies the mac; this is done by the library
            # However, this can be overridden by the client
            # simply for testing purposes.
            token_request.sign_request(key_secret.encode('utf8'))
        else:
            token_request.mac = token_params['mac']

        return token_request

    @property
    def ably(self):
        return self.__ably

    @property
    def auth_mechanism(self):
        return self.__auth_mechanism

    @property
    def auth_options(self):
        return self.__auth_options

    @property
    def auth_params(self):
        return self.__auth_params

    @property
    def basic_credentials(self):
        return self.__basic_credentials

    @property
    def token_credentials(self):
        if self.__token_details:
            token = self.__token_details.token
            token_key = base64.b64encode(token.encode('utf-8'))
            return token_key.decode('ascii')

    @property
    def token_details(self):
        return self.__token_details

    @property
    def client_id(self):
        return self.__client_id

    def _configure_client_id(self, new_client_id):
        # If new client ID from Ably is a wildcard, but preconfigured clientId is set,
        # then keep the existing clientId
        if self.client_id != '*' and new_client_id == '*':
            self.__client_id_validated = True
            return

        # If client_id is defined and not a wildcard, prevent it changing, this is not supported
        if self.client_id is not None and self.client_id != '*' and new_client_id != self.client_id:
            raise IncompatibleClientIdException(
                "Client ID is immutable once configured for a client. "
                "Client ID cannot be changed to '{}'".format(new_client_id), 400, 40012)

        self.__client_id_validated = True
        self.__client_id = new_client_id

    def can_assume_client_id(self, assumed_client_id):
        if self.__client_id_validated:
            return self.client_id == '*' or self.client_id == assumed_client_id
        elif self.client_id is None or self.client_id == '*':
            return True  # client ID is unknown
        else:
            return self.client_id == assumed_client_id

    def _get_auth_headers(self):
        if self.__auth_mechanism == Auth.Method.BASIC:
            return {
                'Authorization': 'Basic %s' % self.basic_credentials,
            }
        else:
            self.authorise()
            return {
                'Authorization': 'Bearer %s' % self.token_credentials,
            }

    def _timestamp(self):
        """Returns the local time in milliseconds since the unix epoch"""
        return int(time.time() * 1000)

    def _random_nonce(self):
        return uuid.uuid4().hex[:16]

    def token_request_from_auth_url(self, method, url, token_params,
                                    headers, auth_params):
        if method == 'GET':
            body = {}
            params = dict(auth_params, **token_params)
        elif method == 'POST':
            params = {}
            body = dict(auth_params, **token_params)

        from ably.http.http import Response
        response = Response(requests.request(
            method, url, headers=headers, params=params, data=body))

        AblyException.raise_for_response(response)
        try:
            token_request = response.to_native()
        except ValueError:
            token_request = response.text
        return token_request