Commits

Markus Zapke-Gründemann  committed b066b1e

add tests for find_auth method

  • Participants
  • Parent commits eb6b95a

Comments (0)

Files changed (4)

 ^build/
 ^dist/
 ^mercurial_keyring\.egg-info/
+^tests/htmlcov
+^tests/run-tests.py
+^tests/sitecustomize.py

File mercurial_keyring.py

 library.
 '''
 
-from mercurial import hg, repo, util
+from mercurial import util
 from mercurial.i18n import _
 try:
     from mercurial.url import passwordmgr
 except:
     from mercurial.httprepo import passwordmgr
-from mercurial.httprepo import httprepository
 from mercurial import mail
-from urllib2 import AbstractBasicAuthHandler, AbstractDigestAuthHandler
 
 # mercurial.demandimport incompatibility workaround,
 # would cause gnomekeyring, one of the possible
     ignore.append("gobject._gobject")
 
 import keyring
+import os
+import smtplib
+import socket
+from urllib2 import AbstractBasicAuthHandler, AbstractDigestAuthHandler
 from urlparse import urlparse
-import urllib2
-import smtplib, socket
-import os
 
 KEYRING_SERVICE = "Mercurial"
 
 ############################################################
 
-def monkeypatch_method(cls,fname=None):
+def monkeypatch_method(cls, fname=None):
     def decorator(func):
         local_fname = fname
         if local_fname is None:
 
 ############################################################
 
+
 class HTTPPasswordHandler(object):
     """
     Actual implementation of password handling (user prompting,
         if after_bad_auth:
             _debug(ui, _("Working after bad authentication, cached passwords not used %s") % str(self.last_reply))
 
+        uri = self._parse_uri(authuri)
         # Strip arguments to get actual remote repository url.
-        base_url = self.canonical_url(authuri)
+        base_url = uri.canonical
 
-        # Extracting possible username (or password)
-        # stored directly in repository url
-        user, pwd = urllib2.HTTPPasswordMgrWithDefaultRealm.find_user_password(
-            pwmgr, realm, authuri)
+        # Extracting possible username (or password) stored directly in
+        # repository url
+        user = uri.username
+        pwd = uri.password
         if user and pwd:
             _debug_reply(ui, _("Auth data found in repository URL"),
                          base_url, user, pwd)
-            self.last_reply = dict(realm=realm,authuri=authuri,user=user,req=req)
+            self.last_reply = dict(realm=realm, authuri=authuri, user=user,
+                req=req)
             return user, pwd
 
         # Loading .hg/hgrc [auth] section contents. If prefix is given,
         # it will be used as a key to lookup password in the keyring.
-        auth_user, pwd, prefix_url = self.load_hgrc_auth(ui, base_url, user)
+        if '@' in base_url:
+            # Strip username from URI because it's already passed as an
+            # argument to load_hgrc_auth. This may be wrong for
+            # Mercurial < 1.9.
+            # TODO use util.url which is available in Mercurial >= 1.9
+            parts = base_url.split('@')
+            scheme, http_auth_user = parts[0].split('://')
+            parsed_uri = '%s://%s' % (scheme, parts[1])
+            _debug(ui, _('Stripped username "%s" from URL') % http_auth_user)
+        else:
+            parsed_uri = base_url
+        auth_user, pwd, prefix_url = self.load_hgrc_auth(ui, parsed_uri, user)
         if prefix_url:
             keyring_url = prefix_url
         else:
                 user, pwd = cached_auth
                 _debug_reply(ui, _("Cached auth data found"),
                              base_url, user, pwd)
-                self.last_reply = dict(realm=realm,authuri=authuri,user=user,req=req)
+                self.last_reply = dict(realm=realm, authuri=authuri, user=user,
+                    req=req)
                 return user, pwd
 
         if auth_user:
             if user and (user != auth_user):
+                # TODO this maybe obsolete for Mercurial >= 1.9
                 raise util.Abort(_('mercurial_keyring: username for %s specified both in repository path (%s) and in .hg/hgrc/[auth] (%s). Please, leave only one of those' % (base_url, user, auth_user)))
             user = auth_user
             if pwd:
+                # TODO this maybe obsolete for Mercurial >= 1.9
                 self.pwd_cache[cache_key] = user, pwd
                 _debug_reply(ui, _("Auth data set in .hg/hgrc"),
                              base_url, user, pwd)
-                self.last_reply = dict(realm=realm,authuri=authuri,user=user,req=req)
+                self.last_reply = dict(realm=realm, authuri=authuri, user=user,
+                    req=req)
                 return user, pwd
             else:
                 _debug(ui, _("Username found in .hg/hgrc: %s") % user)
                 self.pwd_cache[cache_key] = user, pwd
                 _debug_reply(ui, _("Keyring password found"),
                              base_url, user, pwd)
-                self.last_reply = dict(realm=realm,authuri=authuri,user=user,req=req)
+                self.last_reply = dict(realm=realm, authuri=authuri, user=user,
+                    req=req)
                 return user, pwd
             else:
                 _debug(ui, _("Password not present in the keyring"))
 
         _debug_reply(ui, _("Manually entered password"),
                      base_url, user, pwd)
-        self.last_reply = dict(realm=realm,authuri=authuri,user=user,req=req)
+        self.last_reply = dict(realm=realm, authuri=authuri, user=user,
+            req=req)
         return user, pwd
 
     def load_hgrc_auth(self, ui, base_url, user):
         shortest_url = scheme + '://' + prefix_host_path
         return shortest_url
 
-    def canonical_url(self, authuri):
-        """
-        Strips query params from url. Used to convert urls like
-        https://repo.machine.com/repos/apps/module?pairs=0000000000000000000000000000000000000000-0000000000000000000000000000000000000000&cmd=between
-        to
-        https://repo.machine.com/repos/apps/module
-        """
-        parsed_url = urlparse(authuri)
-        return "%s://%s%s" % (parsed_url.scheme, parsed_url.netloc,
-                              parsed_url.path)
+    def _parse_uri(self, uri):
+        """Parses the given URI and returns an extended ParseResult object."""
+        # TODO use util.url which is available in Mercurial >= 1.9
+        parsed_uri = urlparse(uri)
+
+        def canonical(self):
+            """Strips query params from URI.
+
+            Use it to convert URIs like
+            https://repo.machine.com/repos/apps/module?pairs=0000000000000000000000000000000000000000-0000000000000000000000000000000000000000&cmd=between
+            to
+            https://repo.machine.com/repos/apps/module
+            """
+            return "%s://%s%s" % (self.scheme, self.netloc, self.path)
+
+        setattr(parsed_uri, 'canonical', canonical(parsed_uri))
+        return parsed_uri
+
 
 ############################################################
 

File tests/test-find_auth.py

+import os
+import sys
+
+from mercurial import ui, util
+from mercurial.keepalive import HTTPResponse
+from mercurial.url import passwordmgr
+
+from mercurial_keyring import HTTPPasswordHandler, PasswordStore
+
+
+class MockUi(ui.ui):
+    def __init__(self, user, password, interactive):
+        self._user = user
+        self._password = password
+        self._interactive = interactive
+        super(MockUi, self).__init__()
+
+    def interactive(self):
+        return self._interactive
+
+    def prompt(self, msg, default='y'):
+        return self._user
+
+    def getpass(self, prompt=None, default=None):
+        return self._password
+
+
+class MockSock(object):
+    fileno = 1
+
+    def makefile(self, mode, bufsize):
+        return ''
+
+
+class TestAuth(object):
+    def __init__(self, user, password=None, interactive=True):
+        self.user = user
+        if password is None:
+            self.password = 'secret'
+        else:
+            self.password = password
+        self.ui = MockUi(self.user, self.password, interactive=interactive)
+        self.pwmgr = passwordmgr(self.ui)
+        self.response = HTTPResponse(MockSock())
+        self.pwhandler = HTTPPasswordHandler()
+
+    @staticmethod
+    def debug(msg):
+        sys.stdout.write('[test] %s' % msg)
+
+    def test_uri(self, uri, realm=None, msg=None, debug=True):
+        if msg is not None:
+            self.debug(msg + '\n')
+        if debug:
+            debug_orig = self.ui.config('ui', 'debug')
+            self.ui.setconfig('ui', 'debug', 'True')
+        self.debug('testing user %s at %s\n' % (self.user, uri))
+        try:
+            user, pwd = self.pwhandler.find_auth(self.pwmgr, realm, uri,
+                self.response)
+            assert user == self.user, 'user did not match'
+            assert pwd == self.password, 'password did not match'
+        except util.Abort, err:
+            self.debug('abort: %s\n' % err)
+        except AssertionError as err:
+            self.debug('warning: %s\n' % err)
+        sys.stdout.write('\n')
+        if debug:
+            self.ui.setconfig('ui', 'debug', debug_orig)
+
+
+def mock_get_http_password(self, uri, username):
+    TestAuth.debug('reading password from keyring for %s\n' % uri)
+    try:
+        password = self.cache[(uri, username)]
+    except KeyError:
+        password = ''
+    return password
+
+
+def mock_set_http_password(self, uri, username, password):
+    self.cache[(uri, username)] = password
+    TestAuth.debug('stored username %s and password %s\n'
+        % (username, password))
+
+
+PasswordStore.get_http_password = mock_get_http_password
+PasswordStore.set_http_password = mock_set_http_password
+
+
+t = TestAuth('alice')
+t.test_uri('https://hg.example.com/repo', msg='ask for the password')
+t.test_uri('https://hg.example.com/repo',
+    msg='ignore password cache because of identical request', debug=False)
+t.test_uri('https://alice@hg.example.com/repo',
+    msg='read username from URI')
+t.test_uri('https://alice:secret@hg.example.com/repo',
+    msg='read username and password from URI')
+t.test_uri('https://hg.example.com/repo', msg='use cached auth data')
+
+# add auth section to .hgrc
+hgrc = open(os.environ["HGRCPATH"], 'w')
+hgrc.write('[auth]\n')
+hgrc.write('myremote.prefix = hg.example.com\n')
+hgrc.write('myremote.username = bob\n')
+hgrc.close()
+
+t = TestAuth('bob')
+t.test_uri('https://hg.example.com/repo', msg='read password from keyring')
+t.test_uri('https://hg.example.com/repo/module?pairs=0000000000000000000000000000000000000000-0000000000000000000000000000000000000000&cmd=between',
+        msg='use cached password and strip off query params')
+
+t = TestAuth('bob')
+t.test_uri('https://bob@hg.example.com/repo',
+    msg='keyring is used because usernames in URI and .hgrc match')
+t.test_uri('https://clara@hg.example.com/repo',
+    msg='store password for clara on keyring')
+
+t = TestAuth('clara')
+t.test_uri('https://clara@hg.example.com/repo',
+    msg='read password for clara on keyring')
+
+# add catch all prefix to .hgrc
+hgrc = open(os.environ["HGRCPATH"], 'a')
+hgrc.write('myremote.prefix = *\n')
+hgrc.close()
+
+t = TestAuth('bob')
+t.test_uri('https://hg.example.com/repo',
+    msg='read password from keyring and use full URI because of catch all prefix')
+
+# add not matching prefix to .hgrc
+hgrc = open(os.environ["HGRCPATH"], 'a')
+hgrc.write('myremote.prefix = code.example.com\n')
+hgrc.close()
+
+t = TestAuth('bob')
+t.test_uri('https://hg.example.com/repo',
+    msg='will prompt for password because prefix does not match')
+
+t = TestAuth('bob', interactive=False)
+t.test_uri('https://hg.example.com/repo',
+    msg='will abort because terminal is not-interactive', debug=False)

File tests/test-find_auth.py.out

+[test] ask for the password
+[test] testing user alice at https://hg.example.com/repo
+[HgKeyring] Keyring URL: https://hg.example.com/repo
+Username not specified in .hg/hgrc. Keyring will not be used.
+http authorization required
+realm: None
+[HgKeyring] Manually entered password. Url: https://hg.example.com/repo, user: alice, passwd: ******
+
+[test] ignore password cache because of identical request
+[test] testing user alice at https://hg.example.com/repo
+Username not specified in .hg/hgrc. Keyring will not be used.
+http authorization required
+realm: None
+
+[test] read username from URI
+[test] testing user alice at https://alice@hg.example.com/repo
+[HgKeyring] Stripped username "alice" from URL
+[HgKeyring] Keyring URL: https://alice@hg.example.com/repo
+[HgKeyring] Looking for password for user alice and url https://alice@hg.example.com/repo
+[test] reading password from keyring for https://alice@hg.example.com/repo
+[HgKeyring] Password not present in the keyring
+http authorization required
+realm: None
+user: alice (fixed in .hg/hgrc)
+[HgKeyring] Saving password for alice to keyring
+[test] stored username alice and password secret
+[HgKeyring] Manually entered password. Url: https://alice@hg.example.com/repo, user: alice, passwd: ******
+
+[test] read username and password from URI
+[test] testing user alice at https://alice:secret@hg.example.com/repo
+[HgKeyring] Auth data found in repository URL. Url: https://alice:secret@hg.example.com/repo, user: alice, passwd: ******
+
+[test] use cached auth data
+[test] testing user alice at https://hg.example.com/repo
+[HgKeyring] Keyring URL: https://hg.example.com/repo
+[HgKeyring] Cached auth data found. Url: https://hg.example.com/repo, user: alice, passwd: ******
+
+[test] read password from keyring
+[test] testing user bob at https://hg.example.com/repo
+[HgKeyring] Keyring URL: https://hg.example.com
+[HgKeyring] Username found in .hg/hgrc: bob
+[HgKeyring] Looking for password for user bob and url https://hg.example.com
+[test] reading password from keyring for https://hg.example.com
+[HgKeyring] Password not present in the keyring
+http authorization required
+realm: None
+user: bob (fixed in .hg/hgrc)
+[HgKeyring] Saving password for bob to keyring
+[test] stored username bob and password secret
+[HgKeyring] Manually entered password. Url: https://hg.example.com/repo, user: bob, passwd: ******
+
+[test] use cached password and strip off query params
+[test] testing user bob at https://hg.example.com/repo/module?pairs=0000000000000000000000000000000000000000-0000000000000000000000000000000000000000&cmd=between
+[HgKeyring] Keyring URL: https://hg.example.com
+[HgKeyring] Cached auth data found. Url: https://hg.example.com/repo/module, user: bob, passwd: ******
+
+[test] keyring is used because usernames in URI and .hgrc match
+[test] testing user bob at https://bob@hg.example.com/repo
+[HgKeyring] Stripped username "bob" from URL
+[HgKeyring] Keyring URL: https://hg.example.com
+[HgKeyring] Username found in .hg/hgrc: bob
+[HgKeyring] Looking for password for user bob and url https://hg.example.com
+[test] reading password from keyring for https://hg.example.com
+[HgKeyring] Keyring password found. Url: https://bob@hg.example.com/repo, user: bob, passwd: ******
+
+[test] store password for clara on keyring
+[test] testing user bob at https://clara@hg.example.com/repo
+[HgKeyring] Stripped username "clara" from URL
+[HgKeyring] Keyring URL: https://clara@hg.example.com/repo
+[HgKeyring] Looking for password for user clara and url https://clara@hg.example.com/repo
+[test] reading password from keyring for https://clara@hg.example.com/repo
+[HgKeyring] Password not present in the keyring
+http authorization required
+realm: None
+user: clara (fixed in .hg/hgrc)
+[HgKeyring] Saving password for clara to keyring
+[test] stored username clara and password secret
+[HgKeyring] Manually entered password. Url: https://clara@hg.example.com/repo, user: clara, passwd: ******
+[test] warning: user did not match
+
+[test] read password for clara on keyring
+[test] testing user clara at https://clara@hg.example.com/repo
+[HgKeyring] Stripped username "clara" from URL
+[HgKeyring] Keyring URL: https://clara@hg.example.com/repo
+[HgKeyring] Looking for password for user clara and url https://clara@hg.example.com/repo
+[test] reading password from keyring for https://clara@hg.example.com/repo
+[HgKeyring] Keyring password found. Url: https://clara@hg.example.com/repo, user: clara, passwd: ******
+
+[test] read password from keyring and use full URI because of catch all prefix
+[test] testing user bob at https://hg.example.com/repo
+[HgKeyring] Keyring URL: https://hg.example.com/repo
+[HgKeyring] Username found in .hg/hgrc: bob
+[HgKeyring] Looking for password for user bob and url https://hg.example.com/repo
+[test] reading password from keyring for https://hg.example.com/repo
+[HgKeyring] Password not present in the keyring
+http authorization required
+realm: None
+user: bob (fixed in .hg/hgrc)
+[HgKeyring] Saving password for bob to keyring
+[test] stored username bob and password secret
+[HgKeyring] Manually entered password. Url: https://hg.example.com/repo, user: bob, passwd: ******
+
+[test] will prompt for password because prefix does not match
+[test] testing user bob at https://hg.example.com/repo
+[HgKeyring] Keyring URL: https://hg.example.com/repo
+Username not specified in .hg/hgrc. Keyring will not be used.
+http authorization required
+realm: None
+[HgKeyring] Manually entered password. Url: https://hg.example.com/repo, user: bob, passwd: ******
+
+[test] will abort because terminal is not-interactive
+[test] testing user bob at https://hg.example.com/repo
+[test] abort: mercurial_keyring: http authorization required but program used in non-interactive mode
+