PageRenderTime 28ms CodeModel.GetById 11ms RepoModel.GetById 1ms app.codeStats 0ms

/master/buildbot/test/unit/test_www_oauth.py

https://gitlab.com/murder187ss/buildbot
Python | 299 lines | 224 code | 38 blank | 37 comment | 9 complexity | ac5a69a9195a202bdbc43a249b3479e7 MD5 | raw file
  1. # This file is part of Buildbot. Buildbot is free software: you can
  2. # redistribute it and/or modify it under the terms of the GNU General Public
  3. # License as published by the Free Software Foundation, version 2.
  4. #
  5. # This program is distributed in the hope that it will be useful, but WITHOUT
  6. # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
  7. # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
  8. # details.
  9. #
  10. # You should have received a copy of the GNU General Public License along with
  11. # this program; if not, write to the Free Software Foundation, Inc., 51
  12. # Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  13. #
  14. # Copyright Buildbot Team Members
  15. import os
  16. import webbrowser
  17. import mock
  18. try:
  19. import requests
  20. except ImportError:
  21. requests = None
  22. from twisted.internet import defer
  23. from twisted.internet import reactor
  24. from twisted.internet import threads
  25. from twisted.python import failure
  26. from twisted.trial import unittest
  27. from twisted.web.resource import Resource
  28. from twisted.web.server import Site
  29. from buildbot.test.util import www
  30. from buildbot.util import json
  31. if requests:
  32. from buildbot.www import oauth2
  33. class FakeResponse(object):
  34. def __init__(self, _json):
  35. self.json = lambda: _json
  36. self.content = json.dumps(_json)
  37. def raise_for_status(self):
  38. pass
  39. class OAuth2Auth(www.WwwTestMixin, unittest.TestCase):
  40. def setUp(self):
  41. if requests is None:
  42. raise unittest.SkipTest("Need to install requests to test oauth2")
  43. self.patch(requests, 'request', mock.Mock(spec=requests.request))
  44. self.patch(requests, 'post', mock.Mock(spec=requests.post))
  45. self.patch(requests, 'get', mock.Mock(spec=requests.get))
  46. self.googleAuth = oauth2.GoogleAuth("ggclientID", "clientSECRET")
  47. self.githubAuth = oauth2.GitHubAuth("ghclientID", "clientSECRET")
  48. self.gitlabAuth = oauth2.GitLabAuth("https://gitlab.test/", "glclientID", "clientSECRET")
  49. for auth in [self.googleAuth, self.githubAuth, self.gitlabAuth]:
  50. self._master = master = self.make_master(url='h:/a/b/', auth=auth)
  51. auth.reconfigAuth(master, master.config)
  52. @defer.inlineCallbacks
  53. def test_getGoogleLoginURL(self):
  54. res = yield self.googleAuth.getLoginURL('http://redir')
  55. exp = ("https://accounts.google.com/o/oauth2/auth?scope=https%3A%2F%2F"
  56. "www.googleapis.com%2Fauth%2Fuserinfo.email+https%3A%2F%2Fwww.go"
  57. "ogleapis.com%2Fauth%2Fuserinfo.profile&state=redirect%3Dhttp%253A%252F%252Fredir&redirect_uri=h%3A%2Fa%2Fb"
  58. "%2Fauth%2Flogin&response_type=code&client_id=ggclientID")
  59. self.assertEqual(res, exp)
  60. res = yield self.googleAuth.getLoginURL(None)
  61. exp = ("https://accounts.google.com/o/oauth2/auth?scope=https%3A%2F%2F"
  62. "www.googleapis.com%2Fauth%2Fuserinfo.email+https%3A%2F%2Fwww.go"
  63. "ogleapis.com%2Fauth%2Fuserinfo.profile&redirect_uri=h%3A%2Fa%2Fb"
  64. "%2Fauth%2Flogin&response_type=code&client_id=ggclientID")
  65. self.assertEqual(res, exp)
  66. @defer.inlineCallbacks
  67. def test_getGithubLoginURL(self):
  68. res = yield self.githubAuth.getLoginURL('http://redir')
  69. exp = ("https://github.com/login/oauth/authorize?state=redirect%3Dhttp%253A%252F%252Fredir&redirect_uri="
  70. "h%3A%2Fa%2Fb%2Fauth%2Flogin&response_type=code&client_id=ghclientID")
  71. self.assertEqual(res, exp)
  72. res = yield self.githubAuth.getLoginURL(None)
  73. exp = ("https://github.com/login/oauth/authorize?redirect_uri="
  74. "h%3A%2Fa%2Fb%2Fauth%2Flogin&response_type=code&client_id=ghclientID")
  75. self.assertEqual(res, exp)
  76. @defer.inlineCallbacks
  77. def test_getGitLabLoginURL(self):
  78. res = yield self.gitlabAuth.getLoginURL('http://redir')
  79. exp = ("https://gitlab.test/oauth/authorize"
  80. "?state=redirect%3Dhttp%253A%252F%252Fredir"
  81. "&redirect_uri=h%3A%2Fa%2Fb%2Fauth%2Flogin"
  82. "&response_type=code"
  83. "&client_id=glclientID")
  84. self.assertEqual(res, exp)
  85. res = yield self.gitlabAuth.getLoginURL(None)
  86. exp = ("https://gitlab.test/oauth/authorize"
  87. "?redirect_uri=h%3A%2Fa%2Fb%2Fauth%2Flogin"
  88. "&response_type=code"
  89. "&client_id=glclientID")
  90. self.assertEqual(res, exp)
  91. @defer.inlineCallbacks
  92. def test_GoogleVerifyCode(self):
  93. requests.get.side_effect = []
  94. requests.post.side_effect = [
  95. FakeResponse(dict(access_token="TOK3N"))]
  96. self.googleAuth.get = mock.Mock(side_effect=[dict(
  97. name="foo bar",
  98. email="bar@foo", picture="http://pic")])
  99. res = yield self.googleAuth.verifyCode("code!")
  100. self.assertEqual({'avatar_url': 'http://pic', 'email': 'bar@foo',
  101. 'full_name': 'foo bar', 'username': 'bar'}, res)
  102. @defer.inlineCallbacks
  103. def test_GithubVerifyCode(self):
  104. requests.get.side_effect = []
  105. requests.post.side_effect = [
  106. FakeResponse(dict(access_token="TOK3N"))]
  107. self.githubAuth.get = mock.Mock(side_effect=[
  108. dict( # /user
  109. login="bar",
  110. name="foo bar",
  111. email="bar@foo"),
  112. [dict( # /users/bar/orgs
  113. login="group",)
  114. ]])
  115. res = yield self.githubAuth.verifyCode("code!")
  116. self.assertEqual({'email': 'bar@foo',
  117. 'username': 'bar',
  118. 'groups': ['group'],
  119. 'full_name': 'foo bar'}, res)
  120. @defer.inlineCallbacks
  121. def test_GitlabVerifyCode(self):
  122. requests.get.side_effect = []
  123. requests.post.side_effect = [
  124. FakeResponse(dict(access_token="TOK3N"))]
  125. self.gitlabAuth.get = mock.Mock(side_effect=[
  126. { # /user
  127. "name": "Foo Bar",
  128. "username": "fbar",
  129. "id": 5,
  130. "avatar_url": "https://avatar/fbar.png",
  131. "email": "foo@bar",
  132. "twitter": "fb",
  133. },
  134. [ # /groups
  135. {"id": 10, "name": "Hello", "path": "hello"},
  136. {"id": 20, "name": "Group", "path": "grp"},
  137. ]])
  138. res = yield self.gitlabAuth.verifyCode("code!")
  139. self.assertEqual({"full_name": "Foo Bar",
  140. "username": "fbar",
  141. "email": "foo@bar",
  142. "avatar_url": "https://avatar/fbar.png",
  143. "groups": ["hello", "grp"]}, res)
  144. @defer.inlineCallbacks
  145. def test_loginResource(self):
  146. class fakeAuth(object):
  147. homeUri = "://me"
  148. getLoginURL = mock.Mock(side_effect=lambda x: defer.succeed("://"))
  149. verifyCode = mock.Mock(
  150. side_effect=lambda code: defer.succeed({"username": "bar"}))
  151. userInfoProvider = None
  152. rsrc = self.githubAuth.getLoginResource()
  153. rsrc.auth = fakeAuth()
  154. res = yield self.render_resource(rsrc, '/')
  155. rsrc.auth.getLoginURL.assert_called_once_with(None)
  156. rsrc.auth.verifyCode.assert_not_called()
  157. self.assertEqual(res, {'redirected': '://'})
  158. rsrc.auth.getLoginURL.reset_mock()
  159. rsrc.auth.verifyCode.reset_mock()
  160. res = yield self.render_resource(rsrc, '/?code=code!')
  161. rsrc.auth.getLoginURL.assert_not_called()
  162. rsrc.auth.verifyCode.assert_called_once_with("code!")
  163. self.assertEqual(self.master.session.user_info, {'username': 'bar'})
  164. self.assertEqual(res, {'redirected': '://me'})
  165. def test_getConfig(self):
  166. self.assertEqual(self.githubAuth.getConfigDict(), {'fa_icon': 'fa-github', 'autologin': False,
  167. 'name': 'GitHub', 'oauth2': True})
  168. self.assertEqual(self.googleAuth.getConfigDict(), {'fa_icon': 'fa-google-plus', 'autologin': False,
  169. 'name': 'Google', 'oauth2': True})
  170. self.assertEqual(self.gitlabAuth.getConfigDict(), {'fa_icon': 'fa-git', 'autologin': False,
  171. 'name': 'GitLab', 'oauth2': True})
  172. # unit tests are not very usefull to write new oauth support
  173. # so following is an e2e test, which opens a browser, and do the oauth
  174. # negociation. The browser window close in the end of the test
  175. # in order to use this tests, you need to create Github/Google ClientID (see doc on how to do it)
  176. # point OAUTHCONF environment variable to a file with following params:
  177. # {
  178. # "GitHubAuth": {
  179. # "CLIENTID": "XX
  180. # "CLIENTSECRET": "XX"
  181. # },
  182. # "GoogleAuth": {
  183. # "CLIENTID": "XX",
  184. # "CLIENTSECRET": "XX"
  185. # }
  186. # "GitLabAuth": {
  187. # "INSTANCEURI": "XX",
  188. # "CLIENTID": "XX",
  189. # "CLIENTSECRET": "XX"
  190. # }
  191. # }
  192. class OAuth2AuthGitHubE2E(www.WwwTestMixin, unittest.TestCase):
  193. authClass = "GitHubAuth"
  194. def _instantiateAuth(self, cls, config):
  195. return cls(config["CLIENTID"], config["CLIENTSECRET"])
  196. def setUp(self):
  197. if requests is None:
  198. raise unittest.SkipTest("Need to install requests to test oauth2")
  199. if "OAUTHCONF" not in os.environ:
  200. raise unittest.SkipTest("Need to pass OAUTHCONF path to json file via environ to run this e2e test")
  201. import json
  202. config = json.load(open(os.environ['OAUTHCONF']))[self.authClass]
  203. from buildbot.www import oauth2
  204. self.auth = self._instantiateAuth(getattr(oauth2, self.authClass), config)
  205. # 5000 has to be hardcoded, has oauth clientids are bound to a fully classified web site
  206. master = self.make_master(url='http://localhost:5000/', auth=self.auth)
  207. self.auth.reconfigAuth(master, master.config)
  208. def tearDown(self):
  209. from twisted.internet.tcp import Server
  210. # browsers has the bad habbit on not closing the persistent
  211. # connections, so we need to hack them away to make trial happy
  212. f = failure.Failure(Exception("test end"))
  213. for reader in reactor.getReaders():
  214. if isinstance(reader, Server):
  215. reader.connectionLost(f)
  216. @defer.inlineCallbacks
  217. def test_E2E(self):
  218. d = defer.Deferred()
  219. import twisted
  220. twisted.web.http._logDateTimeUsers = 1
  221. class HomePage(Resource):
  222. isLeaf = True
  223. def render_GET(self, request):
  224. info = request.getSession().user_info
  225. reactor.callLater(0, d.callback, info)
  226. return "<html><script>setTimeout(close,1000)</script><body>WORKED: %s</body></html>" % (info)
  227. class MySite(Site):
  228. def makeSession(self):
  229. uid = self._mkuid()
  230. session = self.sessions[uid] = self.sessionFactory(self, uid)
  231. return session
  232. root = Resource()
  233. root.putChild("", HomePage())
  234. auth = Resource()
  235. root.putChild('auth', auth)
  236. auth.putChild('login', self.auth.getLoginResource())
  237. site = MySite(root)
  238. l = reactor.listenTCP(5000, site)
  239. def thd():
  240. res = requests.get('http://localhost:5000/auth/login')
  241. webbrowser.open(res.content)
  242. threads.deferToThread(thd)
  243. res = yield d
  244. yield l.stopListening()
  245. yield site.stopFactory()
  246. self.assertIn("full_name", res)
  247. self.assertIn("email", res)
  248. self.assertIn("username", res)
  249. class OAuth2AuthGoogleE2E(OAuth2AuthGitHubE2E):
  250. authClass = "GoogleAuth"
  251. class OAuth2AuthGitLabE2E(OAuth2AuthGitHubE2E):
  252. authClass = "GitLabAuth"
  253. def _instantiateAuth(self, cls, config):
  254. return cls(config["INSTANCEURI"], config["CLIENTID"], config["CLIENTSECRET"])