Add end-to-end OAuth login and attach tests

This commit is contained in:
Joseph Schorr 2016-12-08 18:35:42 -05:00
parent 36324708db
commit dbdcb802b1
4 changed files with 194 additions and 13 deletions

View file

@ -8,6 +8,7 @@ import base64
from urllib import urlencode
from urlparse import urlparse, urlunparse, parse_qs
from datetime import datetime, timedelta
from httmock import urlmatch, HTTMock
import jwt
@ -22,13 +23,25 @@ from endpoints import keyserver
from endpoints.api import api, api_bp
from endpoints.api.user import Signin
from endpoints.keyserver import jwk_with_kid
from endpoints.csrf import OAUTH_CSRF_TOKEN_NAME
from endpoints.web import web as web_bp
from endpoints.oauthlogin import oauthlogin as oauthlogin_bp
from initdb import setup_database_for_testing, finished_database_for_testing
from test.helpers import assert_action_logged
try:
app.register_blueprint(oauthlogin_bp, url_prefix='/oauth')
except ValueError:
# This blueprint was already registered
pass
try:
app.register_blueprint(web_bp, url_prefix='')
except ValueError:
# This blueprint was already registered
pass
try:
app.register_blueprint(keyserver.key_server, url_prefix='')
except ValueError:
# This blueprint was already registered
@ -69,6 +82,7 @@ class EndpointTestCase(unittest.TestCase):
def setCsrfToken(self, token):
with self.app.session_transaction() as sess:
sess[CSRF_TOKEN_KEY] = token
sess[OAUTH_CSRF_TOKEN_NAME] = 'someoauthtoken'
def getResponse(self, resource_name, expected_code=200, **kwargs):
rv = self.app.get(url_for(resource_name, **kwargs))
@ -108,6 +122,140 @@ class EndpointTestCase(unittest.TestCase):
self.assertEquals(rv.status_code, 200)
class OAuthLoginTestCase(EndpointTestCase):
def invoke_oauth_tests(self, callback_endpoint, attach_endpoint, service_name, service_ident,
new_username):
# Test callback.
created = self.invoke_oauth_test(callback_endpoint, service_name, service_ident, new_username)
# Delete the created user.
model.user.delete_user(created, [])
# Test attach.
self.login('devtable', 'password')
self.invoke_oauth_test(attach_endpoint, service_name, service_ident, 'devtable')
def invoke_oauth_test(self, endpoint_name, service_name, service_ident, username):
# No CSRF.
self.getResponse('oauthlogin.' + endpoint_name, expected_code=403)
# Invalid CSRF.
self.getResponse('oauthlogin.' + endpoint_name, state='somestate', expected_code=403)
# Valid CSRF, invalid code.
self.getResponse('oauthlogin.' + endpoint_name, state='someoauthtoken',
code='invalidcode', expected_code=400)
# Valid CSRF, valid code.
self.getResponse('oauthlogin.' + endpoint_name, state='someoauthtoken',
code='somecode', expected_code=302)
# Ensure the user was added/modified.
found_user = model.user.get_user(username)
self.assertIsNotNone(found_user)
federated_login = model.user.lookup_federated_login(found_user, service_name)
self.assertIsNotNone(federated_login)
self.assertEquals(federated_login.service_ident, service_ident)
return found_user
def test_google_oauth(self):
@urlmatch(netloc=r'accounts.google.com', path='/o/oauth2/token')
def account_handler(_, request):
if request.body.find("code=somecode") > 0:
content = {'access_token': 'someaccesstoken'}
return py_json.dumps(content)
else:
return {'status_code': 400, 'content': '{"message": "Invalid code"}'}
@urlmatch(netloc=r'www.googleapis.com', path='/oauth2/v1/userinfo')
def user_handler(_, __):
content = {
'id': 'someid',
'email': 'someemail@example.com',
'verified_email': True,
}
return py_json.dumps(content)
with HTTMock(account_handler, user_handler):
self.invoke_oauth_tests('google_oauth_callback', 'google_oauth_attach', 'google',
'someid', 'someemail')
def test_github_oauth(self):
@urlmatch(netloc=r'github.com', path='/login/oauth/access_token')
def account_handler(url, _):
if url.query.find("code=somecode") > 0:
content = {'access_token': 'someaccesstoken'}
return py_json.dumps(content)
else:
return {'status_code': 400, 'content': '{"message": "Invalid code"}'}
@urlmatch(netloc=r'github.com', path='/api/v3/user')
def user_handler(_, __):
content = {
'id': 'someid',
'login': 'someusername'
}
return py_json.dumps(content)
@urlmatch(netloc=r'github.com', path='/api/v3/user/emails')
def email_handler(_, __):
content = [{
'email': 'someemail@example.com',
'verified': True,
'primary': True,
}]
return py_json.dumps(content)
with HTTMock(account_handler, email_handler, user_handler):
self.invoke_oauth_tests('github_oauth_callback', 'github_oauth_attach', 'github',
'someid', 'someusername')
def test_dex_oauth(self):
# TODO(jschorr): Add tests for invalid and expired keys.
# Generate a public/private key pair for the OIDC transaction.
private_key = RSA.generate(2048)
jwk = RSAKey(key=private_key.publickey()).serialize()
token = jwt.encode({
'iss': 'https://oidcserver/',
'aud': 'someclientid',
'sub': 'someid',
'exp': int(time.time()) + 60,
'iat': int(time.time()),
'nbf': int(time.time()),
'email': 'someemail@example.com',
'email_verified': True,
}, private_key.exportKey('PEM'), 'RS256')
@urlmatch(netloc=r'oidcserver', path='/.well-known/openid-configuration')
def wellknown_handler(url, _):
return py_json.dumps({
'authorization_endpoint': 'http://oidcserver/auth',
'token_endpoint': 'http://oidcserver/token',
'jwks_uri': 'http://oidcserver/keys',
})
@urlmatch(netloc=r'oidcserver', path='/token')
def account_handler(url, request):
if request.body.find("code=somecode") > 0:
return py_json.dumps({
'access_token': token,
})
else:
return {'status_code': 400, 'content': '{"message": "Invalid code"}'}
@urlmatch(netloc=r'oidcserver', path='/keys')
def keys_handler(_, __):
return py_json.dumps({
"keys": [jwk],
})
with HTTMock(wellknown_handler, account_handler, keys_handler):
self.invoke_oauth_tests('dex_oauth_callback', 'dex_oauth_attach', 'dex',
'someid', 'someemail')
class WebEndpointTestCase(EndpointTestCase):
def test_index(self):
self.getResponse('web.index')

View file

@ -75,3 +75,12 @@ class TestConfig(DefaultConfig):
INSTANCE_SERVICE_KEY_LOCATION = 'test/data/test.pem'
PROMETHEUS_AGGREGATOR_URL = None
FEATURE_GITHUB_LOGIN = True
FEATURE_GOOGLE_LOGIN = True
FEATURE_DEX_LOGIN = True
DEX_LOGIN_CONFIG = {
'CLIENT_ID': 'someclientid',
'OIDC_SERVER': 'https://oidcserver/',
}