diff --git a/mercurial/help/config.txt b/mercurial/help/config.txt --- a/mercurial/help/config.txt +++ b/mercurial/help/config.txt @@ -262,7 +262,9 @@ Supported arguments: Optional. Username to authenticate with. If not given, and the remote site requires basic or digest authentication, the user will be prompted for it. Environment variables are expanded in the - username letting you do ``foo.username = $USER``. + username letting you do ``foo.username = $USER``. If the URI + includes a username, only ``[auth]`` entries with a matching + username or without a username will be considered. ``password`` Optional. Password to authenticate with. If not given, and the diff --git a/mercurial/httpconnection.py b/mercurial/httpconnection.py --- a/mercurial/httpconnection.py +++ b/mercurial/httpconnection.py @@ -72,10 +72,19 @@ def readauthforuri(ui, uri): gdict[setting] = val # Find the best match + uri = util.url(uri) + user = uri.user + uri.user = uri.password = None + uri = str(uri) scheme, hostpath = uri.split('://', 1) + bestuser = None bestlen = 0 bestauth = None for group, auth in config.iteritems(): + if user and user != auth.get('username', user): + # If a username was set in the URI, the entry username + # must either match it or be unset + continue prefix = auth.get('prefix') if not prefix: continue @@ -85,9 +94,14 @@ def readauthforuri(ui, uri): else: schemes = (auth.get('schemes') or 'https').split() if (prefix == '*' or hostpath.startswith(prefix)) and \ - len(prefix) > bestlen and scheme in schemes: + (len(prefix) > bestlen or (len(prefix) == bestlen and \ + not bestuser and 'username' in auth)) \ + and scheme in schemes: bestlen = len(prefix) bestauth = group, auth + bestuser = auth.get('username') + if user and not bestuser: + auth['username'] = user return bestauth # Mercurial (at least until we can remove the old codepath) requires diff --git a/mercurial/url.py b/mercurial/url.py --- a/mercurial/url.py +++ b/mercurial/url.py @@ -25,7 +25,7 @@ class passwordmgr(urllib2.HTTPPasswordMg self._writedebug(user, passwd) return (user, passwd) - if not user: + if not user or not passwd: res = httpconnectionmod.readauthforuri(self.ui, authuri) if res: group, auth = res diff --git a/tests/test-hgweb-auth.py b/tests/test-hgweb-auth.py --- a/tests/test-hgweb-auth.py +++ b/tests/test-hgweb-auth.py @@ -1,5 +1,5 @@ from mercurial import demandimport; demandimport.enable() -from mercurial import ui +from mercurial import ui, util from mercurial import url from mercurial.error import Abort @@ -19,13 +19,16 @@ def dumpdict(dict): return '{' + ', '.join(['%s: %s' % (k, dict[k]) for k in sorted(dict.iterkeys())]) + '}' -def test(auth): +def test(auth, urls=None): print 'CFG:', dumpdict(auth) prefixes = set() for k in auth: prefixes.add(k.split('.', 1)[0]) for p in prefixes: - auth.update({p + '.username': p, p + '.password': p}) + for name in ('.username', '.password'): + if (p + name) not in auth: + auth[p + name] = p + auth = dict((k, v) for k, v in auth.iteritems() if v is not None) ui = writeauth(auth) @@ -33,16 +36,26 @@ def test(auth): print 'URI:', uri try: pm = url.passwordmgr(ui) + authinfo = util.url(uri).authinfo()[1] + if authinfo is not None: + pm.add_password(*authinfo) print ' ', pm.find_user_password('test', uri) except Abort, e: print 'abort' - _test('http://example.org/foo') - _test('http://example.org/foo/bar') - _test('http://example.org/bar') - _test('https://example.org/foo') - _test('https://example.org/foo/bar') - _test('https://example.org/bar') + if not urls: + urls = [ + 'http://example.org/foo', + 'http://example.org/foo/bar', + 'http://example.org/bar', + 'https://example.org/foo', + 'https://example.org/foo/bar', + 'https://example.org/bar', + 'https://x@example.org/bar', + 'https://y@example.org/bar', + ] + for u in urls: + _test(u) print '\n*** Test in-uri schemes\n' @@ -62,3 +75,23 @@ test({'x.prefix': 'http://example.org/fo test({'x.prefix': 'http://example.org/foo', 'y.prefix': 'http://example.org/foo/bar'}) test({'x.prefix': '*', 'y.prefix': 'https://example.org/bar'}) + +print '\n*** Test user matching\n' +test({'x.prefix': 'http://example.org/foo', + 'x.username': None, + 'x.password': 'xpassword'}, + urls=['http://y@example.org/foo']) +test({'x.prefix': 'http://example.org/foo', + 'x.username': None, + 'x.password': 'xpassword', + 'y.prefix': 'http://example.org/foo', + 'y.username': 'y', + 'y.password': 'ypassword'}, + urls=['http://y@example.org/foo']) +test({'x.prefix': 'http://example.org/foo/bar', + 'x.username': None, + 'x.password': 'xpassword', + 'y.prefix': 'http://example.org/foo', + 'y.username': 'y', + 'y.password': 'ypassword'}, + urls=['http://y@example.org/foo/bar']) diff --git a/tests/test-hgweb-auth.py.out b/tests/test-hgweb-auth.py.out --- a/tests/test-hgweb-auth.py.out +++ b/tests/test-hgweb-auth.py.out @@ -14,6 +14,10 @@ URI: https://example.org/foo/bar abort URI: https://example.org/bar abort +URI: https://x@example.org/bar + abort +URI: https://y@example.org/bar + abort CFG: {x.prefix: https://example.org} URI: http://example.org/foo abort @@ -27,6 +31,10 @@ URI: https://example.org/foo/bar ('x', 'x') URI: https://example.org/bar ('x', 'x') +URI: https://x@example.org/bar + ('x', 'x') +URI: https://y@example.org/bar + abort CFG: {x.prefix: http://example.org, x.schemes: https} URI: http://example.org/foo ('x', 'x') @@ -40,6 +48,10 @@ URI: https://example.org/foo/bar abort URI: https://example.org/bar abort +URI: https://x@example.org/bar + abort +URI: https://y@example.org/bar + abort CFG: {x.prefix: https://example.org, x.schemes: http} URI: http://example.org/foo abort @@ -53,6 +65,10 @@ URI: https://example.org/foo/bar ('x', 'x') URI: https://example.org/bar ('x', 'x') +URI: https://x@example.org/bar + ('x', 'x') +URI: https://y@example.org/bar + abort *** Test separately configured schemes @@ -69,6 +85,10 @@ URI: https://example.org/foo/bar abort URI: https://example.org/bar abort +URI: https://x@example.org/bar + abort +URI: https://y@example.org/bar + abort CFG: {x.prefix: example.org, x.schemes: https} URI: http://example.org/foo abort @@ -82,6 +102,10 @@ URI: https://example.org/foo/bar ('x', 'x') URI: https://example.org/bar ('x', 'x') +URI: https://x@example.org/bar + ('x', 'x') +URI: https://y@example.org/bar + abort CFG: {x.prefix: example.org, x.schemes: http https} URI: http://example.org/foo ('x', 'x') @@ -95,6 +119,10 @@ URI: https://example.org/foo/bar ('x', 'x') URI: https://example.org/bar ('x', 'x') +URI: https://x@example.org/bar + ('x', 'x') +URI: https://y@example.org/bar + abort *** Test prefix matching @@ -111,6 +139,10 @@ URI: https://example.org/foo/bar abort URI: https://example.org/bar abort +URI: https://x@example.org/bar + abort +URI: https://y@example.org/bar + abort CFG: {x.prefix: http://example.org/foo, y.prefix: http://example.org/foo/bar} URI: http://example.org/foo ('x', 'x') @@ -124,6 +156,10 @@ URI: https://example.org/foo/bar abort URI: https://example.org/bar abort +URI: https://x@example.org/bar + abort +URI: https://y@example.org/bar + abort CFG: {x.prefix: *, y.prefix: https://example.org/bar} URI: http://example.org/foo abort @@ -137,3 +173,19 @@ URI: https://example.org/foo/bar ('x', 'x') URI: https://example.org/bar ('y', 'y') +URI: https://x@example.org/bar + ('x', 'x') +URI: https://y@example.org/bar + ('y', 'y') + +*** Test user matching + +CFG: {x.password: xpassword, x.prefix: http://example.org/foo, x.username: None} +URI: http://y@example.org/foo + ('y', 'xpassword') +CFG: {x.password: xpassword, x.prefix: http://example.org/foo, x.username: None, y.password: ypassword, y.prefix: http://example.org/foo, y.username: y} +URI: http://y@example.org/foo + ('y', 'ypassword') +CFG: {x.password: xpassword, x.prefix: http://example.org/foo/bar, x.username: None, y.password: ypassword, y.prefix: http://example.org/foo, y.username: y} +URI: http://y@example.org/foo/bar + ('y', 'xpassword')