Repository: buildbot/buildbot

Issue:
RolesFromEmails doesn't work with GitHub auth (and maybe others)
I've got a setup like this:

```
auth = util.GitHubAuth(CLIENT_ID, CLIENT_SECRET)
authz = util.Authz(
    allowRules = [ util.AnyControlEndpointMatcher(role = "admins") ],
    roleMatchers = [
        util.RolesFromEmails(
            admins = [ "sam@humbug.net" ]
        )
    ]
)
```

`sam@humbug.net` is my primary email address. This doesn't work; I have to use my username, `samizzo`, as the email address in the `RolesFromEmails`.

Looking through the code, I can't see how this has ever worked. The authentication mechanism ends up calling [`UserInfoProviderBase.getUserInfo`](https://github.com/buildbot/buildbot/blob/master/master/buildbot/www/auth.py#L83) which returns the username as the email address in the user info.

I'm not sure what the right fix for this is; I don't know the buildbot code very well. I've switched over to using `RolesFromUsername` which is more convenient anyway.


Discussion:
I think the reason is that Github do not provide email address in the oauth server.
There is no need to know buildbot very well to fix it if you can look at the github API and find a way to get the email address (maybe it evolved since first implementation)
Else, could you please submit a documentation PR to tell that GitHubOauth is only compatible with RolesFromUsername and RolesFromGroups?

GitHub actually does return the correct values these days, and the GitHubAuth [code](https://github.com/buildbot/buildbot/blob/master/master/buildbot/www/oauth2.py#L192) correctly parses it as well. So it seems to me that it should be possible to make it work (maybe originally GitHub didn't return the correct values).

I think the main problem is that `OAuth2LoginResource` [here](https://github.com/buildbot/buildbot/blob/master/master/buildbot/www/oauth2.py#L60) passes in the username to `getUserInfo`, which then gets set as the value for `email` rather than the email address. So I suppose at that point it should be passing in the email instead. But `getUserInfo` seems to expect a username rather than an email string.

I'll look into it and see if it is safe to pass in the email address instead.
Are you sure that userInfoProvider.getUserInfo is actually called?
Normally there is no need for additional userInfoProvider with githubAuth.

userInfoProvider is made for kerberos or similar auth method, which cannot provide additional user infos.

Double checking with the links you provided, it looks like Github auth should actually work with emails
That line was definitely called, from line 60 in oauth2.py. I added debug log calls right after, before the `details` dict is updated, and `details` has the email address, but `infos` has the username as the email address. So it gets overwritten there.

So perhaps the problem is that the `userInfoProvider` is set when it shouldn't be? I agree GitHub auth looks like it should work; all the plumbing is there. I'll try and debug this further.

Resolving patch:
diff --git a/master/buildbot/www/auth.py b/master/buildbot/www/auth.py
--- a/master/buildbot/www/auth.py
+++ b/master/buildbot/www/auth.py
@@ -49,8 +49,6 @@ def getChild(self, path, request):
 class AuthBase(config.ConfiguredMixin):
 
     def __init__(self, userInfoProvider=None):
-        if userInfoProvider is None:
-            userInfoProvider = UserInfoProviderBase()
         self.userInfoProvider = userInfoProvider
 
     def reconfigAuth(self, master, new_config):
@@ -104,6 +102,8 @@ class RemoteUserAuth(AuthBase):
 
     def __init__(self, header=None, headerRegex=None, **kwargs):
         AuthBase.__init__(self, **kwargs)
+        if self.userInfoProvider is None:
+            self.userInfoProvider = UserInfoProviderBase()
         if header is not None:
             self.header = header
         if headerRegex is not None:
@@ -145,6 +145,8 @@ class TwistedICredAuthBase(AuthBase):
 
     def __init__(self, credentialFactories, checkers, **kwargs):
         AuthBase.__init__(self, **kwargs)
+        if self.userInfoProvider is None:
+            self.userInfoProvider = UserInfoProviderBase()
         self.credentialFactories = credentialFactories
         self.checkers = checkers
 


Test patch:
diff --git a/master/buildbot/test/unit/test_www_auth.py b/master/buildbot/test/unit/test_www_auth.py
--- a/master/buildbot/test/unit/test_www_auth.py
+++ b/master/buildbot/test/unit/test_www_auth.py
@@ -75,6 +75,7 @@ def test_getLoginResource(self):
 
     @defer.inlineCallbacks
     def test_updateUserInfo(self):
+        self.auth.userInfoProvider = auth.UserInfoProviderBase()
         self.auth.userInfoProvider.getUserInfo = lambda un: {'info': un}
         self.req.session.user_info = {'username': 'elvira'}
         yield self.auth.updateUserInfo(self.req)