Hi,

GitHub API v3 is intentionally broken (see
http://developer.github.com/v3/auth/):

> The main difference is that the RFC requires unauthenticated requests
> to be answered with 401 Unauthorized responses. In many places, this
> would disclose the existence of user data. Instead, the GitHub API
> responds with 404 Not Found. This may cause problems for HTTP
> libraries that assume a 401 Unauthorized response. The solution is to
> manually craft the Authorization header.

Unfortunately, urllib2.HTTPBasicAuthHandler relies on the
standard-conformant behavior. So a naive programmer (like me) who wants
to program against GitHub API using urllib2 (and foolishly ignores this
comment about the API non-conformance, because he thinks GitHub wouldn't
be that stupid and break all Python applications) writes something like
the attached script, spends couple of hours hitting this issue, until he
tries python-requests (which work) and his (mistaken) conclusion is that
urllib2 is a piece of crap which should never be used again.

I am not sure how widespread is this breaking of RFC, but it seems to me
that quite a lot (e.g., http://stackoverflow.com/a/9698319/164233 which
just en passant expects urllib2 authentication stuff to be useless), and
the question is whether it shouldn't be documented somehow and/or
urllib2.HTTPBasicAuthHandler shouldn't be modified to try add
Authenticate header first.

Any suggestions?

Best,

Matěj

-- 
http://www.ceplovi.cz/matej/, Jabber: mc...@ceplovi.cz
GPG Finger: 89EF 4BC6 288A BF43 1BAB  25C3 E09F EF25 D964 84AC

For a successful technology, reality must take precedence over
public relations, for nature cannot be fooled.
    -- R. P. Feynman's concluding sentence
       in his appendix to the Challenger Report
import urllib.request
import base64
import json
import os
import logging
from configparser import SafeConfigParser
logging.basicConfig(format='%(levelname)s:%(funcName)s:%(message)s',
                    level=logging.DEBUG)


class HTTPBrokenBasicAuthHandler(urllib.request.AbstractHTTPHandler,
                                 urllib.request.AbstractBasicAuthHandler):

    auth_header = 'Authorization'

    def __init__(self, *args, **kwargs):
        urllib.request.AbstractHTTPHandler.__init__(self,
                                                    *args, **kwargs)
        self.set_http_debuglevel(2)

    def do_open(self, http_class, req, **http_conn_args):
        user, password = self.passwd.find_user_password(None, req.full_url)
        b64str = base64.standard_b64encode(
            '{}:{}'.format(user, password)).decode('ascii')
        req.add_header(self.auth_header, 'Basic {}'.format(b64str))
        urllib.request.AbstractHTTPHandler(self,
                                           http_class, req, http_conn_args)

    def http_error_401(self, req, fp, code, msg, headers):
        url = req.full_url
        response = self.http_error_auth_reqed('www-authenticate',
                                              url, req, headers)
        self.reset_retry_count()
        return response

cp = SafeConfigParser()
cp.read(os.path.expanduser('~/.githubrc'))

# That configuration file should look something like
# [github]
# user=mylogin
# password=myveryverysecretpassword

gh_user = cp.get('github', 'user')
gh_passw = cp.get('github', 'password')
repo = 'odt2rst'

pwd_manager = urllib.request.HTTPPasswordMgrWithDefaultRealm()
pwd_manager.add_password(None, uri='https://api.github.com', user=gh_user,
                         passwd=gh_passw)
auth_handler = HTTPBrokenBasicAuthHandler(pwd_manager)

opener = urllib.request.build_opener(auth_handler)
urllib.request.install_opener(opener)

API_URL = "https://api.github.com/repos/{0}/{1}/issues/";
gh_url = API_URL.format(gh_user, repo, 1)

logging.debug("gh_url = {0}".format(gh_url))
req = urllib.request.Request(gh_url)

create_data = {
    'title': 'Testing bug summary',
    'body': '''This is a testing bug. I am writing here this stuff,
            just to have some text for body.''',
    'labels': ['BEbug']
}

handler = urllib.request.urlopen(req,
                                 json.dumps(create_data).encode('utf8'))

print(handler.getcode())
print(handler)

Attachment: signature.asc
Description: OpenPGP digital signature

_______________________________________________
Python-Dev mailing list
Python-Dev@python.org
https://mail.python.org/mailman/listinfo/python-dev
Unsubscribe: 
https://mail.python.org/mailman/options/python-dev/archive%40mail-archive.com

Reply via email to