1515import json
1616import os
1717import random
18+ import re
1819import shutil
1920import signal
2021import subprocess
4142
4243ENV = 'test-trac-github'
4344CONF = '%s/conf/trac.ini' % ENV
45+ HTDIGEST = '%s/passwd' % ENV
4446URL = 'http://localhost:8765/%s' % ENV
4547SECRET = 'test-secret'
4648HEADERS = {'Content-Type' : 'application/json' , 'X-GitHub-Event' : 'push' }
@@ -148,6 +150,8 @@ def createTracEnvironment(cls, **kwargs):
148150 conf .set ('github' , 'access_token' , kwargs ['access_token' ])
149151 if 'webhook_secret' in kwargs :
150152 conf .set ('github' , 'webhook_secret' , kwargs ['webhook_secret' ])
153+ if 'username_prefix' in kwargs :
154+ conf .set ('github' , 'username_prefix' , kwargs ['username_prefix' ])
151155
152156 if SHOW_LOG :
153157 # The [logging] section already exists in the default trac.ini file.
@@ -177,6 +181,10 @@ def createTracEnvironment(cls, **kwargs):
177181 with open (CONF , 'wb' ) as fp :
178182 conf .write (fp )
179183
184+ with open (HTDIGEST , 'w' ) as fp :
185+ # user: user, pass: pass, realm: realm
186+ fp .write ("user:realm:8493fbc53ba582fb4c044c456bdc40eb\n " )
187+
180188 run_resync = kwargs ['resync' ] if 'resync' in kwargs else True
181189 if run_resync :
182190 # Allow skipping resync for perfomance reasons if not required
@@ -200,7 +208,7 @@ def startTracd(cls, **kwargs):
200208 if SHOW_LOG :
201209 kwargs ['stdout' ] = sys .stdout
202210 kwargs ['stderr' ] = sys .stderr
203- cls .tracd = subprocess .Popen (tracd + ['--port' , '8765' , ENV ], ** kwargs )
211+ cls .tracd = subprocess .Popen (tracd + ['--port' , '8765' , '--auth=*,%s,realm' % HTDIGEST , ENV ], ** kwargs )
204212
205213 while True :
206214 try :
@@ -538,14 +546,42 @@ def testLoginWithUnconfiguredClientId(self):
538546 "An unconfigured GitHubLogin module should redirect and print "
539547 "a warning on login attempts." )
540548
549+ def attemptHttpAuth (self , testenv , ** kwargs ):
550+ """
551+ Helper method that attempts to log in using HTTP authentication in the
552+ given testenv; returns a tuple where the first item is the error
553+ message in the notification box on the trac page loaded after the login
554+ attempt (or an empty string on success) and the second item is the
555+ username as seen by trac.
556+ """
557+ with TracContext (self , env = testenv , resync = False , ** kwargs ):
558+ session = requests .Session ()
559+
560+ # This logs into trac using HTTP authentication
561+ # This adds a oauth_state parameter in the Trac session.
562+ response = session .get (URL + '/login' , auth = requests .auth .HTTPDigestAuth ('user' , 'pass' ))
563+ self .assertNotEqual (response .status_code , 403 )
564+
565+ response = session .get (URL + '/newticket' ) # this should trigger IPermissionGroupProvider
566+ self .assertEqual (response .status_code , 200 )
567+ tree = html .fromstring (response .content )
568+ warning = '' .join (tree .xpath ('//div[@id="warning"]/text()' )).strip ()
569+ user = ''
570+ match = re .match (r"logged in as (.*)" ,
571+ ', ' .join (tree .xpath ('//div[@id="metanav"]/ul/li[@class="first"]/text()' )))
572+ if match :
573+ user = match .group (1 )
574+ return (warning , user )
575+
541576 def attemptValidOauth (self , testenv , callback , ** kwargs ):
542577 """
543578 Helper method that runs a valid OAuth2 attempt in the given testenv
544579 with the given callback; returns a tuple where the first item is the
545580 error message in the notification box on the trac page loaded after the
546- login attempt (or an empty string on success) and the second item is
581+ login attempt (or an empty string on success), the second item is
547582 a list of email addresses found in email fields of the preferences
548- after login.
583+ after login and the third item is the username of the user that was
584+ logged in as seen by trac..
549585 """
550586 ctxt_kwargs = {}
551587 other_kwargs = {}
@@ -579,8 +615,14 @@ def attemptValidOauth(self, testenv, callback, **kwargs):
579615 response = session .get (URL + '/prefs' )
580616 self .assertEqual (response .status_code , 200 )
581617 tree = html .fromstring (response .content )
582- return ('' .join (tree .xpath ('//div[@id="warning"]/text()' )).strip (),
583- tree .xpath ('//input[@id="email"]/@value' ))
618+ warning = '' .join (tree .xpath ('//div[@id="warning"]/text()' )).strip ()
619+ email = tree .xpath ('//input[@id="email"]/@value' )
620+ user = ''
621+ match = re .match (r"logged in as (.*)" ,
622+ ', ' .join (tree .xpath ('//div[@id="metanav"]/ul/li[@class="first"]/text()' )))
623+ if match :
624+ user = match .group (1 )
625+ return (warning , email , user )
584626 finally :
585627 # disable callback again
586628 updateMockData (self .mockdata , postcallback = "" )
@@ -590,7 +632,7 @@ def testOauthBackendUnavailable(self):
590632 Test that an OAuth backend that resets the connection does not crash
591633 the login
592634 """
593- errmsg , emails = self .attemptValidOauth (self .trac_env_broken , "" )
635+ errmsg , emails , _ = self .attemptValidOauth (self .trac_env_broken , "" )
594636 self .assertIn (
595637 "Invalid request. Please try to login again." ,
596638 errmsg ,
@@ -600,7 +642,7 @@ def testOauthBackendFails(self):
600642 """Test that an OAuth backend that fails does not crash the login"""
601643 def cb (path , args ):
602644 return 403 , {}
603- errmsg , emails = self .attemptValidOauth (self .trac_env , cb )
645+ errmsg , emails , _ = self .attemptValidOauth (self .trac_env , cb )
604646 self .assertIn (
605647 "Invalid request. Please try to login again." ,
606648 errmsg ,
@@ -621,7 +663,7 @@ def testOauthValidButUnavailAPI(self):
621663 Test that accessing an unavailable GitHub API with what seems to be
622664 a valid OAuth2 token does not crash the login
623665 """
624- errmsg , emails = self .attemptValidOauth (self .trac_env_broken_api , self .oauthCallbackSuccess )
666+ errmsg , emails , _ = self .attemptValidOauth (self .trac_env_broken_api , self .oauthCallbackSuccess )
625667 self .assertIn (
626668 "An error occurred while communicating with the GitHub API" ,
627669 errmsg ,
@@ -632,9 +674,9 @@ def testOauthValidButBrokenAPI(self):
632674 Test that accessing an broken GitHub API with what seems to be a valid
633675 OAuth2 token does not crash the login
634676 """
635- errmsg , emails = self .attemptValidOauth (self .trac_env_broken_api ,
636- self .oauthCallbackSuccess ,
637- retcode = 403 )
677+ errmsg , emails , _ = self .attemptValidOauth (self .trac_env_broken_api ,
678+ self .oauthCallbackSuccess ,
679+ retcode = 403 )
638680 self .assertIn (
639681 "An error occurred while communicating with the GitHub API" ,
640682 errmsg ,
@@ -656,7 +698,7 @@ def testOauthValidEmailAPIInvalid(self):
656698 }
657699 }
658700
659- errmsg , emails = self .attemptValidOauth (
701+ errmsg , emails , _ = self .attemptValidOauth (
660702 self .trac_env , self .oauthCallbackSuccess , retcode = 200 ,
661703 answers = answers , request_email = True )
662704 self .assertIn (
@@ -666,14 +708,24 @@ def testOauthValidEmailAPIInvalid(self):
666708
667709 def getEmail (self , answers , ** kwargs ):
668710 """Get and return the email address the system has chosen for the given config and answers"""
669- errmsg , emails = self .attemptValidOauth (
711+ errmsg , emails , _ = self .attemptValidOauth (
670712 self .trac_env , self .oauthCallbackSuccess , retcode = 200 ,
671713 answers = answers , ** kwargs )
672714 self .assertEqual (len (errmsg ), 0 ,
673715 "Successful login should not print an error." )
674716
675717 return emails
676718
719+ def getUser (self , answers , ** kwargs ):
720+ """Get and return the user name the system has chosen for the given config and answers"""
721+ errmsg , _ , user = self .attemptValidOauth (
722+ self .trac_env , self .oauthCallbackSuccess , retcode = 200 ,
723+ answers = answers , ** kwargs )
724+ self .assertEqual (len (errmsg ), 0 ,
725+ "Successful login should not print an error." )
726+
727+ return user
728+
677729 def testOauthValid (self ):
678730 """Test that a login with a valid OAuth2 token succeeds"""
679731 answers = {
@@ -686,6 +738,31 @@ def testOauthValid(self):
686738
687739 email = self .getEmail (answers )
688740 self .
assertEqual (
email , [
'[email protected] ' ])
741+ user = self .getUser (answers )
742+ self .assertEqual (user , 'trololol' )
743+
744+ def testUsernamePrefix (self ):
745+ """Test that setting a prefix for GitHub usernames works"""
746+ answers = {
747+ '/user' : {
748+ 'user' : 'trololol' ,
749+ 750+ 'login' : 'trololol'
751+ }
752+ }
753+
754+ user = self .getUser (answers , username_prefix = 'github-' )
755+ self .assertEqual (user , 'github-trololol' )
756+
757+ errmsg , user = self .attemptHttpAuth (self .trac_env ,
758+ username_prefix = 'github-' ,
759+ organization = 'org' ,
760+ username = 'github-bot-user' ,
761+ access_token = 'accesstoken' )
762+ self .assertEqual (len (errmsg ), 0 ,
763+ "HTTP authentication should still work." )
764+ self .assertEqual (user , "user" ,
765+ "Non-GitHub-authentication should yield unprefixed usernames" )
689766
690767 def testOauthEmailIgnoresUnverified (self ):
691768 """
@@ -1163,6 +1240,7 @@ class TracContext(object):
11631240 'username' ,
11641241 'access_token' ,
11651242 'webhook_secret' ,
1243+ 'username_prefix' ,
11661244 'resync' )
11671245 """ List of all valid attributes to be passed to createTracEnvironment() """
11681246
@@ -1195,6 +1273,8 @@ def __init__(self, testobj, env=None, **kwargs):
11951273 group syncing. Defaults to unset.
11961274 :param webhook_secret: Secret used to validate WebHook API calls if
11971275 present. Defaults to unset.
1276+ :param username_prefix: Prefix for GitHub usernames to allow
1277+ co-existance of non-GitHub with GitHub accounts.
11981278 :param resync: `False` to skip running `trac admin repository resync`
11991279 during environment setup for speed reasons. Defaults to
12001280 `True`.
0 commit comments