Merge pull request #82 from minrk/secure_cookie

use secure cookies
This commit is contained in:
Min RK
2014-10-26 20:26:56 -07:00
6 changed files with 42 additions and 23 deletions

View File

@@ -12,9 +12,24 @@ from .base import APIHandler
class AuthorizationsAPIHandler(APIHandler): class TokenAPIHandler(APIHandler):
@token_authenticated @token_authenticated
def get(self, token): def get(self, token):
orm_token = self.db.query(orm.APIToken).filter(orm.APIToken.token == token).first()
if orm_token is None:
raise web.HTTPError(404)
self.write(json.dumps({
'user' : orm_token.user.name,
}))
class CookieAPIHandler(APIHandler):
@token_authenticated
def get(self, cookie_name):
cookie_value = self.request.body
btoken = self.get_secure_cookie(cookie_name, cookie_value)
if not btoken:
raise web.HTTPError(404)
token = btoken.decode('utf8', 'replace')
orm_token = self.db.query(orm.CookieToken).filter(orm.CookieToken.token == token).first() orm_token = self.db.query(orm.CookieToken).filter(orm.CookieToken.token == token).first()
if orm_token is None: if orm_token is None:
raise web.HTTPError(404) raise web.HTTPError(404)
@@ -23,5 +38,6 @@ class AuthorizationsAPIHandler(APIHandler):
})) }))
default_handlers = [ default_handlers = [
(r"/api/authorizations/([^/]+)", AuthorizationsAPIHandler), (r"/api/authorizations/cookie/([^/]+)", CookieAPIHandler),
(r"/api/authorizations/token/([^/]+)", TokenAPIHandler),
] ]

View File

@@ -83,12 +83,15 @@ class BaseHandler(RequestHandler):
def get_current_user_cookie(self): def get_current_user_cookie(self):
"""get_current_user from a cookie token""" """get_current_user from a cookie token"""
token = self.get_cookie(self.hub.server.cookie_name, None) btoken = self.get_secure_cookie(self.hub.server.cookie_name)
if token: if btoken:
token = btoken.decode('utf8', 'replace')
cookie_token = orm.CookieToken.find(self.db, token) cookie_token = orm.CookieToken.find(self.db, token)
if cookie_token: if cookie_token:
return cookie_token.user return cookie_token.user
else: else:
# don't log the token itself
self.log.warn("Invalid cookie token")
# have cookie, but it's not valid. Clear it and start over. # have cookie, but it's not valid. Clear it and start over.
self.clear_cookie(self.hub.server.cookie_name, path=self.hub.server.base_url) self.clear_cookie(self.hub.server.cookie_name, path=self.hub.server.base_url)
@@ -128,7 +131,7 @@ class BaseHandler(RequestHandler):
cookie_token = user.new_cookie_token() cookie_token = user.new_cookie_token()
self.db.add(cookie_token) self.db.add(cookie_token)
self.db.commit() self.db.commit()
self.set_cookie( self.set_secure_cookie(
user.server.cookie_name, user.server.cookie_name,
cookie_token.token, cookie_token.token,
path=user.server.base_url, path=user.server.base_url,
@@ -139,7 +142,7 @@ class BaseHandler(RequestHandler):
cookie_token = user.new_cookie_token() cookie_token = user.new_cookie_token()
self.db.add(cookie_token) self.db.add(cookie_token)
self.db.commit() self.db.commit()
self.set_cookie( self.set_secure_cookie(
self.hub.server.cookie_name, self.hub.server.cookie_name,
cookie_token.token, cookie_token.token,
path=self.hub.server.base_url) path=self.hub.server.base_url)

View File

@@ -299,7 +299,6 @@ class User(Base):
hub = db.query(Hub).first() hub = db.query(Hub).first()
self.server = Server( self.server = Server(
cookie_name='%s-%s' % (hub.server.cookie_name, self.name), cookie_name='%s-%s' % (hub.server.cookie_name, self.name),
cookie_secret=hub.server.cookie_secret,
base_url=url_path_join(base_url, 'user', self.name), base_url=url_path_join(base_url, 'user', self.name),
) )
db.add(self.server) db.add(self.server)

View File

@@ -28,19 +28,20 @@ if V(IPython.__version__) < V('2.2'):
# which authenticate via the central auth server. # which authenticate via the central auth server.
def verify_token(self, token): def verify_token(self, cookie_name, encrypted_cookie):
"""monkeypatch method for token verification""" """monkeypatch method for token verification"""
token_cache = self.settings['token_cache'] cookie_cache = self.settings['cookie_cache']
if token in token_cache: if encrypted_cookie in cookie_cache:
# we've seen this token before, don't ask upstream again # we've seen this token before, don't ask upstream again
return token_cache[token] return cookie_cache[encrypted_cookie]
hub_api_url = self.settings['hub_api_url'] hub_api_url = self.settings['hub_api_url']
hub_api_key = self.settings['hub_api_key'] hub_api_key = self.settings['hub_api_key']
r = requests.get(url_path_join( r = requests.get(url_path_join(
hub_api_url, "authorizations", token, hub_api_url, "authorizations/cookie", cookie_name,
), ),
headers = {'Authorization' : 'token %s' % hub_api_key} headers = {'Authorization' : 'token %s' % hub_api_key},
data=encrypted_cookie,
) )
if r.status_code == 404: if r.status_code == 404:
data = {'user' : ''} data = {'user' : ''}
@@ -49,16 +50,16 @@ def verify_token(self, token):
data = None data = None
else: else:
data = r.json() data = r.json()
token_cache[token] = data cookie_cache[encrypted_cookie] = data
return data return data
def get_current_user(self): def get_current_user(self):
"""alternative get_current_user to query the central server""" """alternative get_current_user to query the central server"""
my_user = self.settings['user'] my_user = self.settings['user']
token = self.get_cookie(self.cookie_name, '') encrypted_cookie = self.get_cookie(self.cookie_name)
if token: if encrypted_cookie:
auth_data = self.verify_token(token) auth_data = self.verify_token(self.cookie_name, encrypted_cookie)
if not auth_data: if not auth_data:
# treat invalid token the same as no token # treat invalid token the same as no token
return None return None
@@ -123,10 +124,9 @@ class SingleUserNotebookApp(NotebookApp):
s = getattr(self, 'tornado_settings', s = getattr(self, 'tornado_settings',
getattr(self, 'webapp_settings') getattr(self, 'webapp_settings')
) )
s['token_cache'] = {} s['cookie_cache'] = {}
s['user'] = self.user s['user'] = self.user
s['hub_api_key'] = env.pop('JPY_API_TOKEN') s['hub_api_key'] = env.pop('JPY_API_TOKEN')
s['cookie_secret'] = env.pop('JPY_COOKIE_SECRET')
s['cookie_name'] = self.cookie_name s['cookie_name'] = self.cookie_name
s['login_url'] = url_path_join(self.hub_prefix, 'login') s['login_url'] = url_path_join(self.hub_prefix, 'login')
s['hub_api_url'] = self.hub_api_url s['hub_api_url'] = self.hub_api_url

View File

@@ -65,7 +65,6 @@ class Spawner(LoggingConfigurable):
for key in self.env_keep: for key in self.env_keep:
if key in os.environ: if key in os.environ:
env[key] = os.environ[key] env[key] = os.environ[key]
env['JPY_COOKIE_SECRET'] = self.user.server.cookie_secret
env['JPY_API_TOKEN'] = self.api_token env['JPY_API_TOKEN'] = self.api_token
return env return env

View File

@@ -48,23 +48,25 @@ def test_auth_api(app):
# make a new cookie token # make a new cookie token
user = db.query(orm.User).first() user = db.query(orm.User).first()
api_token = user.new_api_token()
db.add(api_token)
cookie_token = user.new_cookie_token() cookie_token = user.new_cookie_token()
db.add(cookie_token) db.add(cookie_token)
db.commit() db.commit()
# check success: # check success:
r = api_request(app, 'authorizations', cookie_token.token) r = api_request(app, 'authorizations/token', api_token.token)
assert r.status_code == 200 assert r.status_code == 200
reply = r.json() reply = r.json()
assert reply['user'] == user.name assert reply['user'] == user.name
# check fail # check fail
r = api_request(app, 'authorizations', cookie_token.token, r = api_request(app, 'authorizations/token', api_token.token,
headers={'Authorization': 'no sir'}, headers={'Authorization': 'no sir'},
) )
assert r.status_code == 403 assert r.status_code == 403
r = api_request(app, 'authorizations', cookie_token.token, r = api_request(app, 'authorizations/token', api_token.token,
headers={'Authorization': 'token: %s' % cookie_token.token}, headers={'Authorization': 'token: %s' % cookie_token.token},
) )
assert r.status_code == 403 assert r.status_code == 403