Newer
Older
# @author: Gunnar Schaefer, Kevin S. Hahn
import datetime
import requests
import bson.json_util
import Crypto.Hash.SHA
import Crypto.PublicKey.RSA
import Crypto.Signature.PKCS1_v1_5
logging.getLogger('requests').setLevel(logging.WARNING) # silence Requests library logging
class NIMSRequestHandler(webapp2.RequestHandler):
"""fetches pubkey from own self.db.remotes. needs to be aware of OWN site uid"""
json_schema = None
file_schema = {
'title': 'File',
'type': 'object',
'properties': {
'type': {
'title': 'Type',
'type': 'array',
},
'filename': {
'title': 'File Name',
'type': 'string',
},
'ext': {
'title': 'File Name Extension',
'type': 'string',
},
'md5': {
'title': 'MD5',
'type': 'string',
},
'size': {
'title': 'Size',
'type': 'integer',
},
}
}
def __init__(self, request=None, response=None):
self.request.remote_user = self.request.get('user', None) # FIXME: auth system should set REMOTE_USER
self.userid = self.request.remote_user or '@public'
self.user = self.app.db.users.find_one({'oa2_id': self.userid})
self.user_is_superuser = self.user.get('superuser')
self.target_id = self.request.get('iid', None)
if not self.request.path.endswith('/nimsapi/log'):
# TODO: change to log.debug
log.info(self.request.method + ' ' + self.request.path + ' ' + str(self.request.params.mixed()))
def dispatch(self):
"""dispatching and request forwarding"""
# dispatch to local instance
if self.target_id in [None, self.site_id]:
# request originates from remote instance
if self.request.user_agent.startswith('NIMS Instance'):
# is the requester an authorized remote site
requester = self.request.user_agent.replace('NIMS Instance', '').strip()
target = self.app.db.remotes.find_one({'_id':requester})
if not target:
log.debug('remote host ' + requester + ' not in auth list. DENIED')
self.abort(403, requester + ' is not authorized')
log.debug('request from ' + self.request.user_agent + ', interNIMS p2p initiated')
# verify signature
self.signature = base64.b64decode(self.request.headers.get('Authorization'))
payload = self.request.body
key = Crypto.PublicKey.RSA.importKey(target['pubkey'])
h = Crypto.Hash.SHA.new(payload)
verifier = Crypto.Signature.PKCS1_v1_5.new(key)
if verifier.verify(h, self.signature):
# log.debug('message/signature is authentic')
super(NIMSRequestHandler, self).dispatch()
else:
log.warning('message/signature is not authentic')
self.abort(403, 'authentication failed')
# request originates from self
else:
super(NIMSRequestHandler, self).dispatch()
# dispatch to remote instance
elif self.ssl_key is not None and self.site_id is not None:
log.debug(socket.gethostname() + ' dispatching to remote ' + self.target_id)
target = self.app.db.remotes.find_one({'_id': self.target_id}, {'_id':False, 'api_uri':True})
log.debug('remote host ' + self.target_id + ' not in auth list. DENIED')
self.abort(403, self.target_id + 'is not authorized')
# disassemble the incoming request
reqparams = dict(self.request.params)
reqpayload = self.request.body # request payload, almost always empty
reqheaders['User-Agent'] = 'NIMS Instance ' + self.site_id
del reqheaders['Host'] # delete old host destination
# create a signature of the incoming request payload
h = Crypto.Hash.SHA.new(reqpayload)
signature = Crypto.Signature.PKCS1_v1_5.new(self.ssl_key).sign(h)
reqheaders['Authorization'] = base64.b64encode(signature)
# construct outgoing request
target_api = 'https://' + target['api_uri'] + self.request.path.split('/nimsapi')[1]
r = requests.request(method=self.request.method, data=reqpayload, url=target_api, params=reqparams, headers=reqheaders, verify=False)
# return response content
# TODO: headers
elif self.ssl_key is None or self.site_id is None:
log.debug('ssl key or site id undefined, cannot dispatch to remote')
def schema(self, *args, **kwargs):
self.response.write(json.dumps(self.json_schema, default=bson.json_util.default))
def valid_parameters(self):
# FIXME: implement, using self.request.params.mixed()
return True
def user_access_epoch(self, epoch):
if self.user_is_superuser:
return True
session = self.app.db.sessions.find_one({'_id': epoch['session']})
if not session:
self.abort(500)
experiment = self.app.db.experiments.find_one({'_id': session['experiment']})
if not experiment:
self.abort(500)
return (self.userid in experiment['permissions'])