Beispiel #1
0
    def _get_token(self, auth_info, repo):
        bearer_info = parse_dict_header(self.BEARER_PATTERN.sub('', auth_info, count=1))
        # If repo could not be determined, do not set scope - implies global access
        if repo:
            bearer_info['scope'] = 'repository:{}:{}'.format(repo, ','.join(self.access))
        realm = bearer_info.pop('realm')

        realm_auth = None
        if self.auth_b64:
            realm_auth = HTTPBasicAuthWithB64(self.auth_b64)
        elif self.username and self.password:
            realm_auth = HTTPBasicAuth(self.username, self.password)

        realm_response = requests.get(realm, params=bearer_info, verify=self.verify,
                                      auth=realm_auth)
        realm_response.raise_for_status()
        return realm_response.json()['token']
Beispiel #2
0
    def handle_407(self, r, **kwargs):
        """Handle HTTP 407 only once, otherwise give up

        :param r: current response
        :returns: responses, along with the new response
        """
        if r.status_code == 407 and self.stale_rejects < 2:
            s_auth = r.headers.get("proxy-authenticate")
            if s_auth is None:
                raise IOError(
                    "proxy server violated RFC 7235:"
                    "407 response MUST contain header proxy-authenticate")
            elif not self._pat.match(s_auth):
                return r

            self.chal = utils.parse_dict_header(
                self._pat.sub('', s_auth, count=1))

            # if we present the user/passwd and still get rejected
            # http://tools.ietf.org/html/rfc2617#section-3.2.1
            if ('Proxy-Authorization' in r.request.headers and
                    'stale' in self.chal):
                if self.chal['stale'].lower() == 'true':  # try again
                    self.stale_rejects += 1
                # wrong user/passwd
                elif self.chal['stale'].lower() == 'false':
                    raise IOError("User or password is invalid")

            # Consume content and release the original connection
            # to allow our new request to reuse the same one.
            r.content
            r.close()
            prep = r.request.copy()
            cookies.extract_cookies_to_jar(prep._cookies, r.request, r.raw)
            prep.prepare_cookies(prep._cookies)

            prep.headers['Proxy-Authorization'] = self.build_digest_header(
                prep.method, prep.url)
            _r = r.connection.send(prep, **kwargs)
            _r.history.append(r)
            _r.request = prep

            return _r
        else:  # give up authenticate
            return r
Beispiel #3
0
    def request(self, url: str, *args,
                **kwargs) -> dict:  # noqa: arguments-differ
        """ Providing PIK API standardized auth error response for for API
        views by `WWW-Authenticate` header parsing.

        Example:

            If Provider returns for /api/:

                HTTPError(headers={'WWW-Authenticate': error="oh_error",
                    error_description="Oh Error!"})

            Transforming into:

                AuthenticationFailed({"code": "oh_error",
                    message: "Oh Error!"})

        """

        try:
            return super().request(url, *args, **kwargs)
        except HTTPError as exc:
            if exc.response.status_code != 401:
                raise

            if 'WWW-Authenticate' not in exc.response.headers:
                raise

            try:
                view = resolve(self.strategy.request.path)
            except Resolver404:
                raise exc

            view = getattr(view.func, 'cls')
            if not issubclass(view, APIView):
                raise

            auth = parse_dict_header(exc.response.headers['WWW-Authenticate'])

            if not ('error' in auth and 'error_description' in auth):
                raise

            raise AuthenticationFailed(auth['error_description'].strip('"\''),
                                       auth['error'].strip('"\''))
Beispiel #4
0
    def handle_401(self, r, **kwargs):
        """
        Takes the given response and tries digest-auth, if needed.
        :rtype: requests.Response
        """

        # If response is not 4xx, do not auth
        # See https://github.com/requests/requests/issues/3772
        if not 400 <= r.status_code < 500:
            self._thread_local.num_401_calls = 1
            return r

        if self._thread_local.pos is not None:
            # Rewind the file position indicator of the body to where
            # it was to resend the request.
            r.request.body.seek(self._thread_local.pos)
        s_auth = r.headers.get('www-authenticate', '')

        if 'digest' in s_auth.lower() and self._thread_local.num_401_calls < 2:

            self._thread_local.num_401_calls += 1
            pat = re.compile(r'digest ', flags=re.IGNORECASE)
            self._thread_local.chal = parse_dict_header(
                pat.sub('', s_auth, count=1))

            # Consume content and release the original connection
            # to allow our new request to reuse the same one.
            r.content
            r.close()
            prep = r.request.copy()
            extract_cookies_to_jar(prep._cookies, r.request, r.raw)
            prep.prepare_cookies(prep._cookies)

            prep.headers['Authorization'] = self.build_digest_header(
                prep.method, prep.url)
            _r = r.connection.send(prep, **kwargs)
            _r.history.append(r)
            _r.request = prep

            return _r

        self._thread_local.num_401_calls = 1
        return r
Beispiel #5
0
    def _get_token(self, auth_info, repo):
        bearer_info = parse_dict_header(
            self.BEARER_PATTERN.sub('', auth_info, count=1))
        # If repo could not be determined, do not set scope - implies global access
        if repo:
            bearer_info['scope'] = 'repository:{}:{}'.format(
                repo, ','.join(self.access))
        realm = bearer_info.pop('realm')

        realm_auth = None
        if self.username and self.password:
            realm_auth = HTTPBasicAuth(self.username, self.password)

        realm_response = requests.get(realm,
                                      params=bearer_info,
                                      verify=self.verify,
                                      auth=realm_auth)
        realm_response.raise_for_status()
        return realm_response.json()['token']
Beispiel #6
0
 def test_good(self):
     r = Request()
     root_key = hashlib.sha256("root").hexdigest()
     root_macaroon = Macaroon(key=root_key)
     discharge_key = hashlib.sha256("discharge").hexdigest()
     discharge_caveat_id = '{"secret": "thing"}'
     root_macaroon.add_third_party_caveat(
         "sso.example", discharge_key, discharge_caveat_id)
     unbound_discharge_macaroon = Macaroon(
         location="sso.example", key=discharge_key,
         identifier=discharge_caveat_id)
     MacaroonAuth(
         root_macaroon.serialize(),
         unbound_discharge_macaroon.serialize())(r)
     auth_value = r.headers["Authorization"]
     self.assertThat(auth_value, StartsWith("Macaroon "))
     self.assertThat(
         parse_dict_header(auth_value[len("Macaroon "):]),
         MacaroonsVerify(root_key))
Beispiel #7
0
    def _authenticate(self, response, **kwargs):
        if not 400 <= response.status_code < 500:
            return response

        # we have to handle the authentication, may be token the token expired or it wasn't there at all
        auth_info = response.headers.get('WWW-Authenticate')
        if not auth_info:
            raise exceptions.TrinoAuthError(
                "Error: header WWW-Authenticate not available in the response."
            )

        if not _OAuth2TokenBearer._BEARER_PREFIX.search(auth_info):
            raise exceptions.TrinoAuthError(
                f"Error: header info didn't match {auth_info}")

        auth_info_headers = parse_dict_header(
            _OAuth2TokenBearer._BEARER_PREFIX.sub("", auth_info, count=1))

        auth_server = auth_info_headers.get('x_redirect_server')
        if auth_server is None:
            raise exceptions.TrinoAuthError(
                "Error: header info didn't have x_redirect_server")

        token_server = auth_info_headers.get('x_token_server')
        if token_server is None:
            raise exceptions.TrinoAuthError(
                "Error: header info didn't have x_token_server")

        self._thread_local.token_server = token_server

        # tell app that use this url to proceed with the authentication
        self._redirect_auth_url(auth_server)

        # Consume content and release the original connection
        # to allow our new request to reuse the same one.
        response.content
        response.close()

        self._thread_local.token = self._get_token(token_server, response,
                                                   **kwargs)
        return self._retry_request(response, **kwargs)
Beispiel #8
0
    def handle_407(self, r, **kwargs):
        """Handle HTTP 407 only once, otherwise give up

        :param r: current response
        :returns: responses, along with the new response
        """
        if r.status_code == 407 and self.stale_rejects < 2:
            if "proxy-authenticate" not in r.headers:
                raise IOError(
                    "proxy server violated RFC 7235:"
                    "407 response MUST contain header proxy-authenticate")
            self.chal = utils.parse_dict_header(
                self._pat.sub('', r.headers['proxy-authenticate'], count=1))

            # if we present the user/passwd and still get rejected
            # http://tools.ietf.org/html/rfc2617#section-3.2.1
            if ('Proxy-Authorization' in r.request.headers
                    and 'stale' in self.chal):
                if self.chal['stale'].lower() == 'true':  # try again
                    self.stale_rejects += 1
                # wrong user/passwd
                elif self.chal['stale'].lower() == 'false':
                    raise IOError("User or password is invalid")

            # Consume content and release the original connection
            # to allow our new request to reuse the same one.
            r.content
            r.close()
            prep = r.request.copy()
            cookies.extract_cookies_to_jar(prep._cookies, r.request, r.raw)
            prep.prepare_cookies(prep._cookies)

            prep.headers['Proxy-Authorization'] = self.build_digest_header(
                prep.method, prep.url)
            _r = r.connection.send(prep, **kwargs)
            _r.history.append(r)
            _r.request = prep

            return _r
        else:  # give up authenticate
            return r
Beispiel #9
0
    def handle_421(self, r, **kwargs):
        """
        Given a response, if is 421 parses out the
        Attestation-Challenge header and tries again
        with an attestation. Will try once.
        """

        if self.pos is not None:
            # Rewind the file position indicator of the body to where
            # it was to resend the request.
            r.request.body.seek(self.pos)

        num_421_calls = getattr(self, 'num_421_calls', 1)
        attestation_challenge = r.headers.get('attestation-challenge', '')

        if r.status_code == 421 and num_421_calls < 2:
            setattr(self, 'num_421_calls', num_421_calls + 1)

            pat = re.compile(r'^attestation ', flags=re.IGNORECASE)
            self.chal = parse_dict_header(pat.sub('', attestation_challenge, count=1))

            # Consume content and release the original connection
            # to allow our new request to reuse the same one.
            r.content
            r.raw.release_conn()
            prep = r.request.copy()
            extract_cookies_to_jar(prep._cookies, r.request, r.raw)
            prep.prepare_cookies(prep._cookies)

            prep.headers['Attestation'] = self.build_attestation_header(prep)
            _r = r.connection.send(prep, **kwargs)
            _r.history.append(r)
            _r.request = prep

            return _r

        setattr(self, 'num_421_calls', 1)
        return r
Beispiel #10
0
def test_parse_dict_header(value, expected):
    assert parse_dict_header(value) == expected
Beispiel #11
0
def test_parse_dict_header(value, expected):
    assert parse_dict_header(value) == expected