Repository: buildbot/buildbot

Issue:
GitHubAuth: 'NoneType' object has no attribute '__getitem__'
Config:
```
        c['www']['auth'] = util.GitHubAuth(os.environ['BUILDMASTER_CLIENT_ID'],
                                           os.environ['BUILDMASTER_CLIENT_SECRET'],
                                           apiVersion=4, getTeamsMembership=True)
```

Traceback when attempting to login using oauth.
```
buildbot_1      | 2018-05-05 10:25:48+0000 [-] /usr/lib/python2.7/site-packages/urllib3/connectionpool.py:858: urllib3.exceptions.InsecureRequestWarning: Unverified HTTPS request is being made. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.io/en/latest/advanced-usage.html#ssl-warnings
buildbot_1      | 2018-05-05 10:25:49+0000 [-] /usr/lib/python2.7/site-packages/urllib3/connectionpool.py:858: urllib3.exceptions.InsecureRequestWarning: Unverified HTTPS request is being made. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.io/en/latest/advanced-usage.html#ssl-warnings
buildbot_1      | 2018-05-05 10:25:49+0000 [-] While rendering resource:
buildbot_1      | 	Traceback (most recent call last):
buildbot_1      | 	  File "/usr/lib/python2.7/site-packages/twisted/internet/defer.py", line 434, in errback
buildbot_1      | 	    self._startRunCallbacks(fail)
buildbot_1      | 	  File "/usr/lib/python2.7/site-packages/twisted/internet/defer.py", line 501, in _startRunCallbacks
buildbot_1      | 	    self._runCallbacks()
buildbot_1      | 	  File "/usr/lib/python2.7/site-packages/twisted/internet/defer.py", line 588, in _runCallbacks
buildbot_1      | 	    current.result = callback(current.result, *args, **kw)
buildbot_1      | 	  File "/usr/lib/python2.7/site-packages/twisted/internet/defer.py", line 1184, in gotResult
buildbot_1      | 	    _inlineCallbacks(r, g, deferred)
buildbot_1      | 	--- <exception caught here> ---
buildbot_1      | 	  File "/usr/lib/python2.7/site-packages/twisted/internet/defer.py", line 1126, in _inlineCallbacks
buildbot_1      | 	    result = result.throwExceptionIntoGenerator(g)
buildbot_1      | 	  File "/usr/lib/python2.7/site-packages/twisted/python/failure.py", line 389, in throwExceptionIntoGenerator
buildbot_1      | 	    return g.throw(self.type, self.value, self.tb)
buildbot_1      | 	  File "/usr/lib/python2.7/site-packages/buildbot/www/oauth2.py", line 63, in renderLogin
buildbot_1      | 	    details = yield self.auth.verifyCode(code)
buildbot_1      | 	  File "/usr/lib/python2.7/site-packages/twisted/python/threadpool.py", line 246, in inContext
buildbot_1      | 	    result = inContext.theWork()
buildbot_1      | 	  File "/usr/lib/python2.7/site-packages/twisted/python/threadpool.py", line 262, in <lambda>
buildbot_1      | 	    inContext.theWork = lambda: context.call(ctx, func, *args, **kw)
buildbot_1      | 	  File "/usr/lib/python2.7/site-packages/twisted/python/context.py", line 118, in callWithContext
buildbot_1      | 	    return self.currentContext().callWithContext(ctx, func, *args, **kw)
buildbot_1      | 	  File "/usr/lib/python2.7/site-packages/twisted/python/context.py", line 81, in callWithContext
buildbot_1      | 	    return func(*args,**kw)
buildbot_1      | 	  File "/usr/lib/python2.7/site-packages/buildbot/www/oauth2.py", line 173, in thd
buildbot_1      | 	    return self.getUserInfoFromOAuthClient(session)
buildbot_1      | 	  File "/usr/lib/python2.7/site-packages/buildbot/www/oauth2.py", line 276, in getUserInfoFromOAuthClient
buildbot_1      | 	    return self.getUserInfoFromOAuthClient_v4(c)
buildbot_1      | 	  File "/usr/lib/python2.7/site-packages/buildbot/www/oauth2.py", line 340, in getUserInfoFromOAuthClient_v4
buildbot_1      | 	    teams.add('%s/%s' % (orgs_name_slug_mapping[org], node['node']['name']))
buildbot_1      | 	exceptions.TypeError: 'NoneType' object has no attribute '__getitem__'
buildbot_1      | 	
```

The OAuth app in github has:
-  Read org and team membership
- Access user email addresses (read-only)



Discussion:
One of the organisations I'm a part of restricts access to third party apps, and in the debug json output the following content is returned:
```
{u'data': {u'org1': {u'teams': {u'edges': [{u'node': None},
                                                        {u'node': None},
                                                        {u'node': None},
                                                        {u'node': None},
                                                        {u'node': None}]}},
           u'org2': {u'teams': {u'edges': [{u'node': None},
                                            {u'node': None},
                                            {u'node': None},
                                            {u'node': None},
                                            {u'node': None},
                                            {u'node': None},
                                            {u'node': None},
                                            {u'node': None},
                                            {u'node': None},
                                            {u'node': None},
                                            {u'node': None},
                                            {u'node': None},
                                            {u'node': None},
                                            {u'node': None},
                                            {u'node': None}]}},
           u'org3': {u'teams': {u'edges': [{u'node': None},
                                                {u'node': None},
                                                {u'node': None},
                                                {u'node': None},
                                                {u'node': None},
                                                {u'node': None},
                                                {u'node': None},
                                                {u'node': None},
                                                {u'node': None},
                                                {u'node': None},
                                                {u'node': None},
                                                {u'node': None},
                                                {u'node': None},
                                                {u'node': None},
                                                {u'node': None},
                                                {u'node': None},
                                                {u'node': None},
                                                {u'node': None},
                                                {u'node': None},
                                                {u'node': None},
                                                {u'node': None}]}}},
 u'errors': [{u'locations': [{u'column': 9, u'line': 6}],
              u'message': u'Although you appear to have the correct authorization credentials,\nthe `org2` organization has enabled OAuth App access restrictions, meaning that data\naccess to third-parties is limited. For more information on these restrictions, including\nhow to whitelist this app, visit\nhttps://help.github.com/articles/restricting-access-to-your-organization-s-data/\n',
              u'path': [u'org2', u'teams', u'edges', 0, u'node'],
              u'type': u'FORBIDDEN'}]}
```
(There are lots of errors, but removed for brevity).
On closer inspection, it looks like the one organization that restricts access is denying access to read from _all_ organizations I'm part of -  _What_.
```
 u'errors': [{u'locations': [{u'column': 9, u'line': 6}],
              u'message': u'Although you appear to have the correct authorization credentials,\nthe `org2` organization has enabled OAuth App access restrictions, meaning that data\naccess to third-parties is limited. For more information on these restrictions, including\nhow to whitelist this app, visit\nhttps://help.github.com/articles/restricting-access-to-your-organization-s-data/\n',
              u'path': [u'org2', u'teams', u'edges', 0, u'node'],
              u'type': u'FORBIDDEN'},
// ...
             {u'locations': [{u'column': 9, u'line': 16}],
              u'message': u'Although you appear to have the correct authorization credentials,\nthe `org2` organization has enabled OAuth App access restrictions, meaning that data\naccess to third-parties is limited. For more information on these restrictions, including\nhow to whitelist this app, visit\nhttps://help.github.com/articles/restricting-access-to-your-organization-s-data/\n',
              u'path': [u'org1', u'teams', u'edges', 0, u'node'],
              u'type': u'FORBIDDEN'},
// ...
             {u'locations': [{u'column': 9, u'line': 26}],
              u'message': u'Although you appear to have the correct authorization credentials,\nthe `org2` organization has enabled OAuth App access restrictions, meaning that data\naccess to third-parties is limited. For more information on these restrictions, including\nhow to whitelist this app, visit\nhttps://help.github.com/articles/restricting-access-to-your-organization-s-data/\n',
              u'path': [u'org3', u'teams', u'edges', 0, u'node'],
              u'type': u'FORBIDDEN'}]}
```
Github seems to have messed up my account here. :-\
Hi,

thanks for investigation!

would you like to send a PR doing the check for None in oauth2.py line 340?

@tardyp - Yes, will do.

Resolving patch:
diff --git a/master/buildbot/www/oauth2.py b/master/buildbot/www/oauth2.py
--- a/master/buildbot/www/oauth2.py
+++ b/master/buildbot/www/oauth2.py
@@ -337,7 +337,8 @@ def getUserInfoFromOAuthClient_v4(self, c):
                         # identical with the inclusion of the organization
                         # since different organizations might share a common
                         # team name
-                        teams.add('%s/%s' % (orgs_name_slug_mapping[org], node['node']['name']))
+                        if node['node'] is not None:
+                            teams.add('%s/%s' % (orgs_name_slug_mapping[org], node['node']['name']))
                 user_info['groups'].extend(sorted(teams))
         if self.debug:
             log.info('{klass} User Details: {user_info}',


Test patch: