Obteniendo una URL de un servidor Jenkins protegido con autenticación básica con urllib2

Estoy tratando de obtener una URL de un servidor Jekins. Hasta hace poco, pude usar el patrón descrito en esta página ( CÓMO obtener recursos de Internet usando urllib2 ) para crear un administrador de contraseñas que respondiera correctamente a los desafíos de BasicAuth con el nombre de usuario y la contraseña. Todo estuvo bien hasta que el equipo de Jenkins cambió su modelo de seguridad , y ese código ya no funcionaba.

# DOES NOT WORK! import urllib2 password_mgr = urllib2.HTTPPasswordMgrWithDefaultRealm() top_level_url = "http://localhost:8080" password_mgr.add_password(None, top_level_url, 'sal', 'foobar') handler = urllib2.HTTPBasicAuthHandler(password_mgr) opener = urllib2.build_opener(handler) a_url = 'http://localhost:8080/job/foo/4/api/python' print opener.open(a_url).read() 

Stacktrace:

 Traceback (most recent call last): File "/home/sal/workspace/jenkinsapi/src/examples/password.py", line 11, in  print opener.open(a_url).read() File "/usr/lib/python2.7/urllib2.py", line 410, in open response = meth(req, response) File "/usr/lib/python2.7/urllib2.py", line 523, in http_response 'http', request, response, code, msg, hdrs) File "/usr/lib/python2.7/urllib2.py", line 448, in error return self._call_chain(*args) File "/usr/lib/python2.7/urllib2.py", line 382, in _call_chain result = func(*args) File "/usr/lib/python2.7/urllib2.py", line 531, in http_error_default raise HTTPError(req.get_full_url(), code, msg, hdrs, fp) urllib2.HTTPError: HTTP Error 403: Forbidden [Finished in 0.0s with exit code 1] 

El problema parece ser que Jenkins no devuelve el código 401 esperado, sino un 403 que urllib2 interpreta como un final de conversación. En realidad nunca envía la contraseña. Después de navegar por github, encontramos otra solución para desarrolladores que funciona …

 # WORKS... SORTA def auth_headers(username, password): return 'Basic ' + base64.encodestring('%s:%s' % (username, password))[:-1] auth = auth_headers('sal', 'foobar') top_level_url = "http://localhost:8080" a_url = 'http://localhost:8080/job/foo/4/api/python' req = urllib2.Request(a_url) req.add_header('Authorization', auth) print urllib2.urlopen(req).read() 

Pero eso parece bastante insatisfactorio. No se molesta en comprobar si el dominio es relevante para el nombre de usuario y la contraseña … ¡simplemente me envía los detalles de inicio de sesión!

¿Alguien puede sugerir una manera de hacer que el guión original funcione? Me gustaría usar un administrador de contraseñas de urllib2 de manera que pueda iniciar sesión en Jenkins.

Vea esta información también: https://gist.github.com/dnozay/194d816aa6517dc67ca1

Jenkins no devuelve 401 - retry vuelva a 401 - retry código de error HTTP cuando necesite acceder a una página que necesita autenticación; En cambio, devuelve 403 - forbidden . En la wiki, https://wiki.jenkins-ci.org/display/JENKINS/Authenticating+scripted+clients , muestra que utilizando la herramienta de línea de comandos wget necesita usar wget --auth-no-challenge que es exactamente por ese comportamiento.

Reintentando con autenticación básica cuando obtienes un 403 - forbidden :

digamos que usted definió:

 jenkins_url = "https://jenkins.example.com" username = "johndoe@example.com" api_token = "my-api-token" 

Puede urllib2.HTTPBasicAuthHandler una subclase de urllib2.HTTPBasicAuthHandler para manejar 403 respuestas HTTP.

 import urllib2 class HTTPBasic403AuthHandler(urllib2.HTTPBasicAuthHandler): # retry with basic auth when facing a 403 forbidden def http_error_403(self, req, fp, code, msg, headers): host = req.get_host() realm = None return self.retry_http_basic_auth(host, req, realm) 

Entonces es cuestión de usar ese controlador, por ejemplo, puede instalarlo para que funcione con todas urllib2.urlopen llamadas urllib2.urlopen :

 def install_auth_opener(): '''install the authentication handler. This handles non-standard behavior where the server responds with 403 forbidden, instead of 401 retry. Which means it does not give you the chance to provide your credentials.''' auth_handler = HTTPBasic403AuthHandler() auth_handler.add_password( realm=None, uri=jenkins_url, user=username, passwd=api_token) opener = urllib2.build_opener(auth_handler) # install it for all urllib2.urlopen calls urllib2.install_opener(opener) 

Y aquí hay una prueba simple para ver si funciona bien.

 if __name__ == "__main__": # test install_auth_opener() page = "%s/me/api/python" % jenkins_url try: result = urllib2.urlopen(page) assert result.code == 200 print "ok" except urllib2.HTTPError, err: assert err.code != 401, 'BAD CREDENTIALS!' raise err 

Utilizando la autenticación preventiva.

Hay un buen ejemplo en esta respuesta: https://stackoverflow.com/a/8513913/1733117 . En lugar de volver a intentarlo cuando se 403 forbidden un 403 forbidden , enviaría el encabezado de Authorization cuando la URL coincida.

 class PreemptiveBasicAuthHandler(urllib2.HTTPBasicAuthHandler): '''Preemptive basic auth. Instead of waiting for a 403 to then retry with the credentials, send the credentials if the url is handled by the password manager. Note: please use realm=None when calling add_password.''' def http_request(self, req): url = req.get_full_url() realm = None # this is very similar to the code from retry_http_basic_auth() # but returns a request object. user, pw = self.passwd.find_user_password(realm, url) if pw: raw = "%s:%s" % (user, pw) auth = 'Basic %s' % base64.b64encode(raw).strip() req.add_unredirected_header(self.auth_header, auth) return req https_request = http_request 

En lugar de definir su propio controlador e instalarlo globalmente o usarlo para solicitudes individuales, es mucho más fácil simplemente agregar el encabezado a la solicitud:

 auth_header = 'Basic ' + base64.b64encode('%s:%s' % (USERNAME, API_KEY)).strip() headers = {'Authorization': auth_header} request = urllib2.Request(url, urllib.urlencode(data), headers) result = urllib2.urlopen(request)