Example #1
0
def request(method,
            url,
            is_success=_default_is_success,
            timeout=None,
            verify=None,
            **kwargs):
    """Sends an HTTP request. If the server responds with a 401, ask the
    user for their credentials, and try request again (up to 3 times).

    :param method: method for the new Request object
    :type method: str
    :param url: URL for the new Request object
    :type url: str
    :param is_success: Defines successful status codes for the request
    :type is_success: Function from int to bool
    :param timeout: request timeout
    :type timeout: int
    :param verify: whether to verify SSL certs or path to cert(s)
    :type verify: bool | str
    :param kwargs: Additional arguments to requests.request
        (see http://docs.python-requests.org/en/latest/api/#requests.request)
    :type kwargs: dict
    :rtype: Response
    """

    toml_config = config.get_config()
    auth_token = config.get_config_val("core.dcos_acs_token", toml_config)
    dcos_url = urlparse(config.get_config_val("core.dcos_url", toml_config))
    parsed_url = urlparse(url)

    # only request with DC/OS Auth if request is to DC/OS cluster
    # request should match scheme + netloc
    scheme_eq = parsed_url.scheme == dcos_url.scheme
    netloc_eq = parsed_url.netloc == dcos_url.netloc
    if auth_token and scheme_eq and netloc_eq:
        auth = DCOSAcsAuth(auth_token)
    else:
        auth = None
    response = _request(method, url, is_success, timeout,
                        auth=auth, verify=verify, **kwargs)

    if is_success(response.status_code):
        return response
    elif response.status_code == 401:
        if auth_token is not None:
            msg = ("Your core.dcos_acs_token is invalid. "
                   "Please run: `dcos auth login`")
            raise DCOSAuthenticationException(msg)
        else:
            raise DCOSAuthenticationException(response)
    elif response.status_code == 422:
        raise DCOSUnprocessableException(response)
    elif response.status_code == 403:
        raise DCOSAuthorizationException(response)
    elif response.status_code == 400:
        raise DCOSBadRequest(response)
    else:
        raise DCOSHTTPException(response)
Example #2
0
def header_challenge_auth(dcos_url):
    """
    Triggers authentication using scheme specified in www-authenticate header.

    Raises exception if authentication fails.

    :param dcos_url: url to cluster
    :type dcos_url: str
    :rtype: None
    """

    # hit protected endpoint which will prompt for auth if cluster has auth
    endpoint = '/pkgpanda/active.buildinfo.full.json'
    url = urllib.parse.urljoin(dcos_url, endpoint)
    response = http._request('HEAD', url)
    auth_scheme = _get_auth_scheme(response)

    for _ in range(3):
        if response.status_code == 401:
            # this header claims the cluster is open DC/OS 1.7, 1.8 or 1.9
            # and supports OIDC implicit auth
            if auth_scheme == "oauthjwt":
                token = _get_dcostoken_by_oidc_implicit_flow(dcos_url)
            # auth_scheme == "acsjwt"
            # this header claims the cluster is enterprise DC/OS 1.7, 1.8 or
            # 1.9 and supports username/pawword auth
            else:
                token = _get_dcostoken_by_dcos_uid_password_auth(dcos_url)

            if token is not None:
                break
        elif response.status_code == 200:
            break
    else:
        raise DCOSAuthenticationException(response)
Example #3
0
def _request_with_auth(response,
                       method,
                       url,
                       is_success=_default_is_success,
                       timeout=None,
                       verify=None,
                       **kwargs):
    """Try request (3 times) with credentials if 401 returned from server

    :param response: requests.response
    :type response: requests.Response
    :param method: method for the new Request object
    :type method: str
    :param url: URL for the new Request object
    :type url: str
    :param is_success: Defines successful status codes for the request
    :type is_success: Function from int to bool
    :param timeout: request timeout
    :type timeout: int
    :param verify: whether to verify SSL certs or path to cert(s)
    :type verify: bool | str
    :param kwargs: Additional arguments to requests.request
        (see http://docs.python-requests.org/en/latest/api/#requests.request)
    :type kwargs: dict
    :rtype: requests.Response
    """

    i = 0
    while i < 3 and response.status_code == 401:
        parsed_url = urlparse(url)
        hostname = parsed_url.hostname
        auth_scheme, realm = get_auth_scheme(response)
        creds = (hostname, auth_scheme, realm)

        with lock:
            if creds not in AUTH_CREDS:
                auth = _get_http_auth(response, parsed_url, auth_scheme)
            else:
                auth = AUTH_CREDS[creds]

        # try request again, with auth
        response = _request(method, url, is_success, timeout, auth,
                            verify, **kwargs)

        # only store credentials if they're valid
        with lock:
            if creds not in AUTH_CREDS and response.status_code == 200:
                AUTH_CREDS[creds] = auth
            # acs invalid token
            elif response.status_code == 401 and auth_scheme == "acsjwt":
                if util.get_config().get("core.dcos_acs_token") is not None:
                    config.unset("core.dcos_acs_token")

        i += 1

    if response.status_code == 401:
        raise DCOSAuthenticationException(response)

    return response
Example #4
0
def request(method,
            url,
            is_success=_default_is_success,
            timeout=DEFAULT_TIMEOUT,
            verify=None,
            toml_config=None,
            **kwargs):
    """Sends an HTTP request. If the server responds with a 401, ask the
    user for their credentials, and try request again (up to 3 times).

    :param method: method for the new Request object
    :type method: str
    :param url: URL for the new Request object
    :type url: str
    :param is_success: Defines successful status codes for the request
    :type is_success: Function from int to bool
    :param timeout: request timeout
    :type timeout: int
    :param verify: whether to verify SSL certs or path to cert(s)
    :type verify: bool | str
    :param toml_config: cluster config to use
    :type toml_config: Toml
    :param kwargs: Additional arguments to requests.request
        (see http://docs.python-requests.org/en/latest/api/#requests.request)
    :type kwargs: dict
    :rtype: Response
    """

    if toml_config is None:
        toml_config = config.get_config()

    auth_token = config.get_config_val("core.dcos_acs_token", toml_config)
    prompt_login = config.get_config_val("core.prompt_login", toml_config)
    dcos_url = urlparse(config.get_config_val("core.dcos_url", toml_config))

    # only request with DC/OS Auth if request is to DC/OS cluster
    if auth_token and _is_request_to_dcos(url):
        auth = DCOSAcsAuth(auth_token)
    else:
        auth = None

    response = _request(method,
                        url,
                        is_success,
                        timeout,
                        auth=auth,
                        verify=verify,
                        toml_config=toml_config,
                        **kwargs)

    if is_success(response.status_code):
        return response
    elif response.status_code == 401:
        if prompt_login:
            # I don't like having imports that aren't at the top level, but
            # this is to resolve a circular import issue between dcos.http and
            # dcos.auth
            from dcos.auth import header_challenge_auth

            header_challenge_auth(dcos_url.geturl())
            # if header_challenge_auth succeeded, then we auth-ed correctly and
            # thus can safely recursively call ourselves and not have to worry
            # about an infinite loop
            return request(method=method,
                           url=url,
                           is_success=is_success,
                           timeout=timeout,
                           verify=verify,
                           **kwargs)
        else:
            if auth_token is not None:
                msg = ("Your core.dcos_acs_token is invalid. "
                       "Please run: `dcos auth login`")
                raise DCOSAuthenticationException(response, msg)
            else:
                raise DCOSAuthenticationException(response)
    elif response.status_code == 422:
        raise DCOSUnprocessableException(response)
    elif response.status_code == 403:
        raise DCOSAuthorizationException(response)
    elif response.status_code == 400:
        raise DCOSBadRequest(response)
    else:
        raise DCOSHTTPException(response)
Example #5
0
def request(method,
            url,
            is_success=_default_is_success,
            timeout=None,
            verify=None,
            **kwargs):
    """Sends an HTTP request. We first send a HEAD request for the
    supplied URL so that we can determine the type of authentication
    required (if any). If authentication is required then we again
    use a HEAD request asking the user for their credentials, and
    try request again (up to 3 times). Once authenticated, we issue
    the request passed in. We are careful to execute the request
    passed in just once given that it may be stateful e.g. any files
    object containing a stream may only be evaluated once.

    :param method: method for the new Request object
    :type method: str
    :param url: URL for the new Request object
    :type url: str
    :param is_success: Defines successful status codes for the request
    :type is_success: Function from int to bool
    :param timeout: request timeout
    :type timeout: int
    :param verify: whether to verify SSL certs or path to cert(s)
    :type verify: bool | str
    :param kwargs: Additional arguments to requests.request
        (see http://docs.python-requests.org/en/latest/api/#requests.request)
    :type kwargs: dict
    :rtype: Response
    """

    if 'headers' not in kwargs:
        kwargs['headers'] = {'Accept': 'application/json'}

    verify = _verify_ssl(verify)

    # Silence 'Unverified HTTPS request' and 'SecurityWarning' for bad certs
    if verify is not None:
        silence_requests_warnings()

    response = _request('head', url, is_success, timeout, verify=verify)
    i = 0
    while i < 3 and response.status_code == 401:
        auth_response = _request_with_auth(response,
                                           'head',
                                           url,
                                           is_success,
                                           timeout,
                                           verify=verify)
        response.status_code = auth_response.status_code
        i += 1

    if response.status_code == 401:
        raise DCOSAuthenticationException(response)

    response = _request_with_auth(response, method, url, is_success, timeout,
                                  verify, **kwargs)

    if is_success(response.status_code):
        return response
    elif response.status_code == 403:
        raise DCOSAuthorizationException(response)
    elif response.status_code == 400:
        raise DCOSBadRequest(response)
    else:
        raise DCOSHTTPException(response)