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
INTEGER_ROLES = {
'anon-read': 0,
'read-only': 1,
'read-write': 2,
'admin': 3,
}
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.access_token = self.request.headers.get('Authorization', None)
# CORS header
self.response.headers.add('Access-Control-Allow-Origin', self.request.headers.get('origin', '*'))
if self.access_token and self.app.config['oauth2_id_endpoint']:
r = requests.request(method='GET', url=self.app.config['oauth2_id_endpoint'], headers={'Authorization': 'Bearer ' + self.access_token})
self.uid = json.loads(r.content)['email']
log.debug('oauth user: ' + self.uid)
# TODO: add handlers for bad tokens
# inform app of expired token, app will try to get new token, or ask user to log in again
log.debug('ERR: ' + str(r.status_code) + r.reason + ' bad token')
elif self.app.config['insecure'] and 'X-Requested-With' not in self.request.headers and self.request.get('user', None):
self.uid = self.request.get('user')
else:
self.uid = '@public'
self.user_is_superuser = False
if self.uid != '@public':
user = self.app.db.users.find_one({'_id': self.uid})
if user:
self.user_is_superuser = user.get('superuser', None)
else:
self.abort(403, 'user: ' + self.uid + ' does not exist')
if self.target_id not in [None, self.app.config['site_id']]:
self.rtype = 'to_remote'
if not self.app.config['site_id']:
self.abort(500, 'api site_id is not configured')
if not self.app.config['ssl_key']:
self.abort(500, 'api ssl_key is not configured')
target = self.app.db.remotes.find_one({'_id': self.target_id}, {'_id': False, 'api_uri': True})
if not target:
log.debug('remote host ' + self.target_id + ' is not an authorized remote.')
self.abort(403, 'remote host ' + self.target_id + ' is not an authorized remote.')
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
# adjust headers
self.headers = self.request.headers
self.headers['User-Agent'] = 'NIMS Instance ' + self.app.config['site_id']
self.headers['X-From'] = (self.uid + '#' + self.app.config['site_id']) if self.uid != '@public' else self.uid
self.headers['Content-Length'] = len(self.request.body)
self.headers['Date'] = str(datetime.datetime.now().strftime('%a, %d %b %Y %H:%M:%S')) # Nonce for msg
del self.headers['Host']
if self.headers.get('Authorization'): del self.headers['Authorization']
# adjust params
self.params = self.request.params.mixed()
if self.params.get('user'): del self.params['user']
del self.params['iid']
# assemble msg, hash, and signature
msg = self.request.method + self.request.path + str(self.params) + self.request.body + self.headers.get('Date')
signature = Crypto.Signature.PKCS1_v1_5.new(self.app.config['ssl_key']).sign(Crypto.Hash.SHA.new(msg))
self.headers['X-Signature'] = base64.b64encode(signature)
# prepare delegated request URI
self.target_api = target['api_uri'] + self.request.path.split('/nimsapi')[1]
elif self.request.user_agent.startswith('NIMS Instance'):
self.rtype = 'from_remote'
self.uid = self.request.headers.get('X-From')
self.user_is_superuser = False
remote_instance = self.request.user_agent.replace('NIMS Instance', '').strip()
requester = self.app.db.remotes.find_one({'_id':remote_instance})
if not requester:
log.debug('remote host ' + remote_instance + ' not in auth list. DENIED')
self.abort(403, remote_instance + ' is not authorized')
# assemble msg, hash, and verify recieved signature
signature = base64.b64decode(self.request.headers.get('X-Signature'))
msg = self.request.method + self.request.path + str(self.request.params.mixed()) + self.request.body + self.request.headers.get('Date')
verifier = Crypto.Signature.PKCS1_v1_5.new(Crypto.PublicKey.RSA.importKey(requester['pubkey']))
if not verifier.verify(Crypto.Hash.SHA.new(msg), signature):
log.debug('remote message/signature is not authentic')
self.abort(403, 'remote message/signature is not authentic')
else:
self.rtype = 'local'
# TODO: question: okay to move this logging block into dispatch?
if not self.request.path.endswith('/nimsapi/log'):
log.info(self.rtype + ' ' + self.request.method + ' ' + self.request.path + ' ' + str(self.request.params.mixed()))
def dispatch(self):
"""dispatching and request forwarding"""
return super(NIMSRequestHandler, self).dispatch()
else:
r = requests.request(method=self.request.method, data=self.request.body, url=self.target_api, params=self.params, headers=self.headers, verify=False)
if not r.status_code == 200:
self.abort(r.status_code, 'internims p2p err: ' + r.reason)
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
def get_collection(self, cid, min_role='anon-read'):
collection = self.app.db.collections.find_one({'_id': cid})
if not collection:
self.abort(404)
if not self.user_is_superuser:
for perm in collection['permissions']:
if perm['uid'] == self.uid:
break
else:
self.abort(403, self.uid + ' does not have permission to this Collection')
if INTEGER_ROLES[perm['role']] < INTEGER_ROLES[min_role]:
self.abort(403, self.uid + ' does not have at least ' + min_role + ' on this Collection')
if perm['role'] != 'admin': # if not admin, mask all other permissions
collection['permissions'] = [{'uid': self.uid, 'role': perm['role']}]
return collection
def get_experiment(self, xid, min_role='anon-read'):
experiment = self.app.db.experiments.find_one({'_id': xid})
if not experiment:
self.abort(404)
if not self.user_is_superuser:
for perm in experiment['permissions']:
if perm['uid'] == self.uid:
break
else:
self.abort(403, self.uid + ' does not have permission to this Experiment')
if INTEGER_ROLES[perm['role']] < INTEGER_ROLES[min_role]:
self.abort(403, self.uid + ' does not have at least ' + min_role + ' on this Experiment')
if perm['role'] != 'admin': # if not admin, mask all other permissions
experiment['permissions'] = [{'uid': self.uid, 'role': perm['role']}]
return experiment
def get_session(self, sid, min_role='anon-read'):
#FIXME: implement min_role logic
session = self.app.db.sessions.find_one({'_id': sid})
if not session:
self.abort(404)
experiment = self.app.db.experiments.find_one({'_id': session['experiment']})
if not experiment:
self.abort(500)
if not self.user_is_superuser:
for perm in experiment['permissions']:
if perm['uid'] == self.uid:
break
else:
self.abort(403, 'user does not have permission to this Session')
return session
def get_epoch(self, eid, min_role='anon-read'):
#FIXME: implement min_role logic
epoch = self.app.db.epochs.find_one({'_id': eid})
if not epoch:
self.abort(404)
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)
if not self.user_is_superuser:
for perm in experiment['permissions']:
if perm['uid'] == self.uid:
break
else:
self.abort(403, 'user does not have permission to this Epoch')
return epoch