Exemplo n.º 1
0
    def _do_test_basic_auth(self, creds):
        with temporary_dir() as tmpcookiedir:
            cookie_file = os.path.join(tmpcookiedir, 'pants.test.cookies')

            self.context(for_subsystems=[BasicAuth, Cookies],
                         options={
                             BasicAuth.options_scope: {
                                 'providers': {
                                     'foobar': {
                                         'url':
                                         'http://localhost:{}'.format(
                                             self.port)
                                     }
                                 }
                             },
                             Cookies.options_scope: {
                                 'path': cookie_file
                             }
                         })

            basic_auth = BasicAuth.global_instance()
            cookies = Cookies.global_instance()

            self.assertListEqual([], list(cookies.get_cookie_jar()))
            basic_auth.authenticate(provider='foobar',
                                    creds=creds,
                                    cookies=cookies)
            cookies_list = list(cookies.get_cookie_jar())
            self.assertEqual(1, len(cookies_list))
            auth_cookie = cookies_list[0]
            self.assertEqual('test_auth_key', auth_cookie.name)
            self.assertEqual('test_auth_value', auth_cookie.value)
Exemplo n.º 2
0
 def test_basic_auth_with_bad_creds(self):
   self._do_test_basic_auth(creds=BasicAuthCreds('test_user', 'test_password'))
   basic_auth = BasicAuth.global_instance()
   cookies = Cookies.global_instance()
   bad_creds = BasicAuthCreds('test_user', 'bad_password')
   self.assertRaises(BasicAuthException,
     lambda: basic_auth.authenticate(provider='foobar', creds=bad_creds, cookies=cookies))
Exemplo n.º 3
0
 def test_basic_auth_with_bad_creds(self):
     self._do_test_basic_auth(
         creds=BasicAuthCreds('test_user', 'test_password'))
     basic_auth = BasicAuth.global_instance()
     cookies = Cookies.global_instance()
     bad_creds = BasicAuthCreds('test_user', 'bad_password')
     self.assertRaises(
         BasicAuthException, lambda: basic_auth.authenticate(
             provider='foobar', creds=bad_creds, cookies=cookies))
Exemplo n.º 4
0
  def _do_test_basic_auth(self, creds):
    with self._test_options():
      basic_auth = BasicAuth.global_instance()
      cookies = Cookies.global_instance()

      self.assertListEqual([], list(cookies.get_cookie_jar()))
      basic_auth.authenticate(provider='foobar', creds=creds, cookies=cookies)
      cookies_list = list(cookies.get_cookie_jar())
      self.assertEqual(1, len(cookies_list))
      auth_cookie = cookies_list[0]
      self.assertEqual('test_auth_key', auth_cookie.name)
      self.assertEqual('test_auth_value', auth_cookie.value)
Exemplo n.º 5
0
    def post_stats(cls, stats_url, stats, timeout=2, auth_provider=None):
        """POST stats to the given url.

    :return: True if upload was successful, False otherwise.
    """
        def error(msg):
            # Report aleady closed, so just print error.
            print('WARNING: Failed to upload stats to {}. due to {}'.format(
                stats_url, msg),
                  file=sys.stderr)
            return False

        # TODO(benjy): The upload protocol currently requires separate top-level params, with JSON
        # values.  Probably better for there to be one top-level JSON value, namely json.dumps(stats).
        # But this will first require changing the upload receiver at every shop that uses this.
        params = {k: json.dumps(v) for (k, v) in stats.items()}
        cookies = Cookies.global_instance()
        auth_provider = auth_provider or '<provider>'

        # We can't simply let requests handle redirects, as we only allow them for specific codes:
        # 307 and 308 indicate that the redirected request must use the same method, POST in this case.
        # So they indicate a true redirect of the POST itself, and we allow them.
        # The other redirect codes either must, or in practice do, cause the user agent to switch the
        # method to GET. So when they are encountered on a POST, it indicates an auth problem (a
        # redirection to a login page).
        def do_post(url, num_redirects_allowed):
            if num_redirects_allowed < 0:
                return error('too many redirects.')
            r = requests.post(url,
                              data=params,
                              timeout=timeout,
                              cookies=cookies.get_cookie_jar(),
                              allow_redirects=False)
            if r.status_code in {307, 308}:
                return do_post(r.headers['location'],
                               num_redirects_allowed - 1)
            elif r.status_code != 200:
                error('HTTP error code: {}. Reason: {}.'.format(
                    r.status_code, r.reason))
                if 300 <= r.status_code < 400 or r.status_code == 401:
                    print(
                        'Use `path/to/pants login --to={}` to authenticate against the stats '
                        'upload service.'.format(auth_provider),
                        file=sys.stderr)
                return False
            return True

        try:
            return do_post(stats_url, num_redirects_allowed=6)
        except Exception as e:  # Broad catch - we don't want to fail the build over upload errors.
            return error('Error: {}'.format(e))
Exemplo n.º 6
0
    def authenticate(
        self,
        provider: str,
        creds: Optional[BasicAuthCreds] = None,
        cookies: Optional[Cookies] = None,
    ) -> None:
        """Authenticate against the specified provider.

        :param provider: Authorize against this provider.
        :param creds: The creds to use. If unspecified, assumes that creds are set in the netrc file.
        :param cookies: Store the auth cookies in this instance. If unspecified, uses the global instance.
        :raises pants.auth.basic_auth.BasicAuthException: If auth fails due to misconfiguration or
          rejection by the server.
        """
        cookies = cookies or Cookies.global_instance()

        if not provider:
            raise BasicAuthException("No basic auth provider specified.")

        provider_config = self.options.providers.get(provider)
        if not provider_config:
            raise BasicAuthException(
                f"No config found for provider {provider}.")

        url = provider_config.get("url")
        if not url:
            raise BasicAuthException(
                f"No url found in config for provider {provider}.")
        if not self.options.allow_insecure_urls and not url.startswith(
                "https://"):
            raise BasicAuthException(
                f"Auth url for provider {provider} is not secure: {url}.")

        auth = requests.auth.HTTPBasicAuth(creds.username,
                                           creds.password) if creds else None
        response = requests.get(url,
                                auth=auth,
                                headers={"User-Agent": f"pants/v{VERSION}"})

        if response.status_code != requests.codes.ok:
            if response.status_code == requests.codes.unauthorized:
                parsed = www_authenticate.parse(
                    response.headers.get("WWW-Authenticate", ""))
                if "Basic" in parsed:
                    raise Challenged(url, response.status_code,
                                     response.reason, parsed["Basic"]["realm"])
            raise BasicAuthException(url, response.status_code,
                                     response.reason)

        cookies.update(response.cookies)
Exemplo n.º 7
0
    def _do_test_basic_auth(self, creds):
        with self._test_options():
            basic_auth = BasicAuth.global_instance()
            cookies = Cookies.global_instance()

            self.assertListEqual([], list(cookies.get_cookie_jar()))
            basic_auth.authenticate(provider="foobar",
                                    creds=creds,
                                    cookies=cookies)
            cookies_list = list(cookies.get_cookie_jar())
            self.assertEqual(1, len(cookies_list))
            auth_cookie = cookies_list[0]
            self.assertEqual("test_auth_key", auth_cookie.name)
            self.assertEqual("test_auth_value", auth_cookie.value)
Exemplo n.º 8
0
    def authenticate(self, provider, creds=None, cookies=None):
        """Authenticate against the specified provider.

    :param str provider: Authorize against this provider.
    :param pants.auth.basic_auth.BasicAuthCreds creds: The creds to use.
      If unspecified, assumes that creds are set in the netrc file.
    :param pants.auth.cookies.Cookies cookies: Store the auth cookies in this instance.
      If unspecified, uses the global instance.
    :raises pants.auth.basic_auth.BasicAuthException: If auth fails due to misconfiguration or
      rejection by the server.
    """
        cookies = cookies or Cookies.global_instance()

        if not provider:
            raise BasicAuthException('No basic auth provider specified.')

        provider_config = self.get_options().providers.get(provider)
        if not provider_config:
            raise BasicAuthException(
                'No config found for provider {}.'.format(provider))

        url = provider_config.get('url')
        if not url:
            raise BasicAuthException(
                'No url found in config for provider {}.'.format(provider))
        if not self.get_options().allow_insecure_urls and not url.startswith(
                'https://'):
            raise BasicAuthException(
                'Auth url for provider {} is not secure: {}.'.format(
                    provider, url))

        if creds:
            auth = requests.auth.HTTPBasicAuth(creds.username, creds.password)
        else:
            auth = None  # requests will use the netrc creds.
        response = requests.get(url, auth=auth)

        if response.status_code != requests.codes.ok:
            if response.status_code == requests.codes.unauthorized:
                parsed = www_authenticate.parse(
                    response.headers.get('WWW-Authenticate', ''))
                if 'Basic' in parsed:
                    raise Challenged(url, response.status_code,
                                     response.reason, parsed['Basic']['realm'])
            raise BasicAuthException(url, response.status_code,
                                     response.reason)

        cookies.update(response.cookies)
Exemplo n.º 9
0
    def test_cookie_file_permissions(self):
        with temporary_dir() as tmpcookiedir:
            cookie_file = os.path.join(tmpcookiedir, "pants.test.cookies")

            self.context(
                for_subsystems=[Cookies],
                options={Cookies.options_scope: {
                    "path": cookie_file
                }})

            cookies = Cookies.global_instance()
            self.assertFalse(os.path.exists(cookie_file))
            cookies.update([])
            self.assertTrue(os.path.exists(cookie_file))
            file_permissions = os.stat(cookie_file).st_mode
            self.assertEqual(int("0100600", 8), file_permissions)
Exemplo n.º 10
0
  def test_cookie_file_permissions(self):
    with temporary_dir() as tmpcookiedir:
      cookie_file = os.path.join(tmpcookiedir, 'pants.test.cookies')

      self.context(for_subsystems=[Cookies], options={
        Cookies.options_scope: {
          'path': cookie_file
        }
      })

      cookies = Cookies.global_instance()
      self.assertFalse(os.path.exists(cookie_file))
      cookies.update([])
      self.assertTrue(os.path.exists(cookie_file))
      file_permissions = os.stat(cookie_file).st_mode
      self.assertEqual(int('0100600', 8), file_permissions)
Exemplo n.º 11
0
  def post_stats(cls, stats_url, stats, timeout=2, auth_provider=None):
    """POST stats to the given url.

    :return: True if upload was successful, False otherwise.
    """
    def error(msg):
      # Report aleady closed, so just print error.
      print('WARNING: Failed to upload stats to {}. due to {}'.format(stats_url, msg),
            file=sys.stderr)
      return False

    # TODO(benjy): The upload protocol currently requires separate top-level params, with JSON
    # values.  Probably better for there to be one top-level JSON value, namely json.dumps(stats).
    # But this will first require changing the upload receiver at every shop that uses this.
    params = {k: cls._json_dump_options(v) for (k, v) in stats.items()}
    cookies = Cookies.global_instance()
    auth_provider = auth_provider or '<provider>'

    # We can't simply let requests handle redirects, as we only allow them for specific codes:
    # 307 and 308 indicate that the redirected request must use the same method, POST in this case.
    # So they indicate a true redirect of the POST itself, and we allow them.
    # The other redirect codes either must, or in practice do, cause the user agent to switch the
    # method to GET. So when they are encountered on a POST, it indicates an auth problem (a
    # redirection to a login page).
    def do_post(url, num_redirects_allowed):
      if num_redirects_allowed < 0:
        return error('too many redirects.')
      r = requests.post(url, data=params, timeout=timeout,
                        cookies=cookies.get_cookie_jar(), allow_redirects=False)
      if r.status_code in {307, 308}:
        return do_post(r.headers['location'], num_redirects_allowed - 1)
      elif r.status_code != 200:
        error('HTTP error code: {}. Reason: {}.'.format(r.status_code, r.reason))
        if 300 <= r.status_code < 400 or r.status_code == 401:
          print('Use `path/to/pants login --to={}` to authenticate against the stats '
                'upload service.'.format(auth_provider), file=sys.stderr)
        return False
      return True

    try:
      return do_post(stats_url, num_redirects_allowed=6)
    except Exception as e:  # Broad catch - we don't want to fail the build over upload errors.
      return error('Error: {}'.format(e))
Exemplo n.º 12
0
    def authenticate(self, provider, creds=None, cookies=None):
        """Authenticate against the specified provider.

    :param str provider: Authorize against this provider.
    :param pants.auth.basic_auth.BasicAuthCreds creds: The creds to use.
      If unspecified, assumes that creds are set in the netrc file.
    :param pants.auth.cookies.Cookies cookies: Store the auth cookies in this instance.
      If unspecified, uses the global instance.
    :raises pants.auth.basic_auth.BasicAuthException: If auth fails due to misconfiguration or
      rejection by the server.
    """
        cookies = cookies or Cookies.global_instance()

        if not provider:
            raise BasicAuthException('No basic auth provider specified.')

        provider_config = self.get_options().providers.get(provider)
        if not provider_config:
            raise BasicAuthException(
                'No config found for provider {}.'.format(provider))

        url = provider_config.get('url')
        if not url:
            raise BasicAuthException(
                'No url found in config for provider {}.'.format(
                    provider_config))
        # TODO: Require url to be https, except when testing. See
        # https://github.com/pantsbuild/pants/issues/6496.

        if creds:
            auth = requests.auth.HTTPBasicAuth(creds.username, creds.password)
        else:
            auth = None  # requests will use the netrc creds.
        response = requests.get(url, auth=auth)
        if response.status_code != requests.codes.ok:
            raise BasicAuthException(
                'Failed to auth against {}. Status code {}.'.format(
                    response, response.status_code))
        cookies.update(response.cookies)
Exemplo n.º 13
0
  def post_stats(cls, url, stats, timeout=2):
    """POST stats to the given url.

    :return: True if upload was successful, False otherwise.
    """
    def error(msg):
      # Report aleady closed, so just print error.
      print('WARNING: Failed to upload stats to {} due to {}'.format(url, msg),
            file=sys.stderr)
      return False

    # TODO(benjy): The upload protocol currently requires separate top-level params, with JSON
    # values.  Probably better for there to be one top-level JSON value, namely json.dumps(stats).
    # But this will first require changing the upload receiver at every shop that uses this
    # (probably only Foursquare at present).
    params = {k: json.dumps(v) for (k, v) in stats.items()}
    cookies = Cookies.global_instance()
    try:
      r = requests.post(url, data=params, timeout=timeout, cookies=cookies.get_cookie_jar())
      if r.status_code != requests.codes.ok:
        return error("HTTP error code: {}".format(r.status_code))
    except Exception as e:  # Broad catch - we don't want to fail the build over upload errors.
      return error("Error: {}".format(e))
    return True
Exemplo n.º 14
0
 def get_auth_for_provider(self, auth_provider: str) -> Authentication:
     cookies = Cookies.global_instance()
     return Authentication(
         headers={}, request_args={'cookies': cookies.get_cookie_jar()})