Skip to content
Snippets Groups Projects
Commit 1480fefc authored by Gunnar Schaefer's avatar Gunnar Schaefer
Browse files

finalize refactoring

parent 2977c457
No related branches found
No related tags found
No related merge requests found
...@@ -30,7 +30,7 @@ def update(db, api_uri, site_id, privkey, internims_url): ...@@ -30,7 +30,7 @@ def update(db, api_uri, site_id, privkey, internims_url):
signature = Crypto.Signature.PKCS1_v1_5.new(privkey).sign(h) signature = Crypto.Signature.PKCS1_v1_5.new(privkey).sign(h)
headers = {'Authorization': base64.b64encode(signature)} headers = {'Authorization': base64.b64encode(signature)}
r = requests.post(url=internims_url, data=payload, headers=headers, verify=True) r = requests.post(internims_url, data=payload, headers=headers)
if r.status_code == 200: if r.status_code == 200:
response = (json.loads(r.content)) response = (json.loads(r.content))
# update remotes entries # update remotes entries
...@@ -40,12 +40,12 @@ def update(db, api_uri, site_id, privkey, internims_url): ...@@ -40,12 +40,12 @@ def update(db, api_uri, site_id, privkey, internims_url):
log.debug('updating remotes: ' + ', '.join((r['_id'] for r in response['sites']))) log.debug('updating remotes: ' + ', '.join((r['_id'] for r in response['sites'])))
# delete remotes from users, who no longer have remotes # delete remotes from users, who no longer have remotes
db.users.update({'remotes': {'$exists':True}, 'uid': {'$nin': response['users'].keys()}}, {'$unset': {'remotes': ''}}, multi=True) db.users.update({'remotes': {'$exists':True}, '_id': {'$nin': response['users'].keys()}}, {'$unset': {'remotes': ''}}, multi=True)
# add remotes to users # add remotes to users
log.debug('users w/ remotes: ' + ', '.join(response['users'])) log.debug('users w/ remotes: ' + ', '.join(response['users']))
for uid, remotes in response['users'].iteritems(): for uid, remotes in response['users'].iteritems():
db.users.update({'uid': uid}, {'$set': {'remotes': remotes}}) db.users.update({'_id': uid}, {'$set': {'remotes': remotes}})
else: else:
# r.reason contains generic description for the specific error code # r.reason contains generic description for the specific error code
# need the part of the error response body that contains the detailed explanation # need the part of the error response body that contains the detailed explanation
......
...@@ -33,7 +33,7 @@ class NIMSAPI(nimsapiutil.NIMSRequestHandler): ...@@ -33,7 +33,7 @@ class NIMSAPI(nimsapiutil.NIMSRequestHandler):
"""Return 200 OK.""" """Return 200 OK."""
self.response.set_status(200) self.response.set_status(200)
def get(self): def get(self, *args):
"""Return API documentation""" """Return API documentation"""
resources = """ resources = """
Resource | Description Resource | Description
...@@ -100,6 +100,15 @@ class NIMSAPI(nimsapiutil.NIMSRequestHandler): ...@@ -100,6 +100,15 @@ class NIMSAPI(nimsapiutil.NIMSRequestHandler):
self.response.write('</body>\n') self.response.write('</body>\n')
self.response.write('</html>\n') self.response.write('</html>\n')
def login(self):
"""Return details for the current User."""
log.info(self.uid + ' has logged in')
return self.app.db.users.find_and_modify({'_id': self.uid}, {'$inc': {'logins': 1}}, fields=['firstname', 'lastname', 'superuser'])
def remotes(self):
"""Return the list of all remote sites."""
return [r['_id'] for r in self.app.db.remotes.find()]
def upload(self): def upload(self):
# TODO add security: either authenticated user or machine-to-machine CRAM # TODO add security: either authenticated user or machine-to-machine CRAM
if 'Content-MD5' not in self.request.headers: if 'Content-MD5' not in self.request.headers:
...@@ -129,29 +138,22 @@ class NIMSAPI(nimsapiutil.NIMSRequestHandler): ...@@ -129,29 +138,22 @@ class NIMSAPI(nimsapiutil.NIMSRequestHandler):
paths += _idpaths paths += _idpaths
symlinks += _idsymlinks symlinks += _idsymlinks
def remotes(self):
"""Return the list of all remote sites."""
return list(self.app.db.remotes.find(None, []))
def log(self): def log(self):
"""Return logs.""" """Return logs."""
try: try:
logs = open(app.config['log_path']).readlines() logs = open(app.config['log_path']).readlines()
except IOError as e: except IOError as e:
log.debug(e)
if 'Permission denied' in e: if 'Permission denied' in e:
# specify body format to print details separate from comment
body_template = '${explanation}<br /><br />${detail}<br /><br />${comment}' body_template = '${explanation}<br /><br />${detail}<br /><br />${comment}'
comment = 'To fix permissions, run the following command: chmod o+r ' + logfile comment = 'To fix permissions, run the following command: chmod o+r ' + logfile
self.abort(500, detail=str(e), comment=comment, body_template=body_template) self.abort(500, detail=str(e), comment=comment, body_template=body_template)
else: else:
# file does not exist self.abort(500, e) # file does not exist
self.abort(500, e)
try: try:
n = int(self.request.get('n', 10000)) n = int(self.request.get('n', 10000))
except: except:
self.abort(400, 'n must be an integer') self.abort(400, 'n must be an integer')
return [line for line in reversed(logs) if re.match('[\d\s:-]{17}[\s]+nimsapi:[.]*', line)][:n] return [line.strip() for line in reversed(logs) if re.match('[-:0-9 ]{18} +nimsapi:(?!.*[/a-z]*/log )', line)][:n]
class Users(nimsapiutil.NIMSRequestHandler): class Users(nimsapiutil.NIMSRequestHandler):
...@@ -252,13 +254,6 @@ class User(nimsapiutil.NIMSRequestHandler): ...@@ -252,13 +254,6 @@ class User(nimsapiutil.NIMSRequestHandler):
'required': ['_id'], 'required': ['_id'],
} }
def current(self):
"""Return details for the current User."""
if self.request.method == 'GET':
return self.get(self.uid)
elif self.request.method == 'PUT':
return self.put(self.uid)
def get(self, uid): def get(self, uid):
"""Return User details.""" """Return User details."""
projection = [] projection = []
...@@ -266,14 +261,11 @@ class User(nimsapiutil.NIMSRequestHandler): ...@@ -266,14 +261,11 @@ class User(nimsapiutil.NIMSRequestHandler):
projection += ['remotes'] projection += ['remotes']
if self.request.get('status') in ('1', 'true'): if self.request.get('status') in ('1', 'true'):
projection += ['status'] projection += ['status']
if self.request.get('login') in ('1', 'true'): return self.app.db.users.find_one({'_id': uid}, projection or None)
projection += ['firstname', 'lastname', 'superuser']
self.app.db.users.update({'uid': uid}, {'$inc': {'logins': 1}})
return self.app.db.users.find_one({'uid': uid}, projection or None)
def put(self, uid): def put(self, uid):
"""Update an existing User.""" """Update an existing User."""
user = self.app.db.users.find_one({'uid': uid}) user = self.app.db.users.find_one({'_id': uid})
if not user: if not user:
self.abort(404) self.abort(404)
if uid == self.uid or self.user_is_superuser: # users can only update their own info if uid == self.uid or self.user_is_superuser: # users can only update their own info
...@@ -288,7 +280,7 @@ class User(nimsapiutil.NIMSRequestHandler): ...@@ -288,7 +280,7 @@ class User(nimsapiutil.NIMSRequestHandler):
updates['$set'][k] = False # superuser is tri-state: False indicates granted, but disabled, superuser privileges updates['$set'][k] = False # superuser is tri-state: False indicates granted, but disabled, superuser privileges
elif v.lower() not in ('1', 'true'): elif v.lower() not in ('1', 'true'):
updates['$unset'][k] = '' updates['$unset'][k] = ''
self.app.db.users.update({'uid': uid}, updates) self.app.db.users.update({'_id': uid}, updates)
else: else:
self.abort(403) self.abort(403)
...@@ -396,17 +388,16 @@ class Group(nimsapiutil.NIMSRequestHandler): ...@@ -396,17 +388,16 @@ class Group(nimsapiutil.NIMSRequestHandler):
routes = [ routes = [
webapp2.Route(r'/nimsapi', NIMSAPI),
webapp2_extras.routes.PathPrefixRoute(r'/nimsapi', [ webapp2_extras.routes.PathPrefixRoute(r'/nimsapi', [
webapp2.Route(r'/download', NIMSAPI, handler_method='download', methods=['GET']), webapp2.Route(r'/login', NIMSAPI, handler_method='login', methods=['GET', 'POST']),
webapp2.Route(r'/upload', NIMSAPI, handler_method='upload', methods=['PUT']),
webapp2.Route(r'/remotes', NIMSAPI, handler_method='remotes', methods=['GET']), webapp2.Route(r'/remotes', NIMSAPI, handler_method='remotes', methods=['GET']),
webapp2.Route(r'/upload', NIMSAPI, handler_method='upload', methods=['PUT']),
webapp2.Route(r'/download', NIMSAPI, handler_method='download', methods=['GET']),
webapp2.Route(r'/log', NIMSAPI, handler_method='log', methods=['GET']), webapp2.Route(r'/log', NIMSAPI, handler_method='log', methods=['GET']),
webapp2.Route(r'/users', Users), webapp2.Route(r'/users', Users),
webapp2.Route(r'/users/count', Users, handler_method='count', methods=['GET']), webapp2.Route(r'/users/count', Users, handler_method='count', methods=['GET']),
webapp2.Route(r'/users/listschema', Users, handler_method='schema', methods=['GET']), webapp2.Route(r'/users/listschema', Users, handler_method='schema', methods=['GET']),
webapp2.Route(r'/users/schema', User, handler_method='schema', methods=['GET']), webapp2.Route(r'/users/schema', User, handler_method='schema', methods=['GET']),
webapp2.Route(r'/users/current', User, handler_method='current', methods=['GET', 'PUT']),
webapp2.Route(r'/users/<uid>', User), webapp2.Route(r'/users/<uid>', User),
webapp2.Route(r'/groups', Groups), webapp2.Route(r'/groups', Groups),
webapp2.Route(r'/groups/count', Groups, handler_method='count', methods=['GET']), webapp2.Route(r'/groups/count', Groups, handler_method='count', methods=['GET']),
...@@ -437,16 +428,18 @@ routes = [ ...@@ -437,16 +428,18 @@ routes = [
webapp2.Route(r'/collections/<cid:[0-9a-f]{24}>/sessions', collections_.Sessions), webapp2.Route(r'/collections/<cid:[0-9a-f]{24}>/sessions', collections_.Sessions),
webapp2.Route(r'/collections/<cid:[0-9a-f]{24}>/epochs', collections_.Epochs), webapp2.Route(r'/collections/<cid:[0-9a-f]{24}>/epochs', collections_.Epochs),
]), ]),
webapp2.Route(r'/nimsapi', NIMSAPI),
webapp2.Route(r'/nimsapi/<:.*>', NIMSAPI),
] ]
def dispatcher(router, request, response): def dispatcher(router, request, response):
rv = router.default_dispatcher(request, response) rv = router.default_dispatcher(request, response)
if rv is not None: if rv is not None:
return webapp2.Response(json.dumps(rv, default=bson.json_util.default)) return response.write(json.dumps(rv, default=bson.json_util.default))
app = webapp2.WSGIApplication(routes, debug=True) app = webapp2.WSGIApplication(routes, debug=True)
app.router.set_dispatcher(dispatcher) app.router.set_dispatcher(dispatcher)
app.config = dict(stage_path='', site_id=None, ssl_key=None, insecure=False, log_path='') app.config = dict(stage_path='', site_id='local', ssl_key=None, insecure=False, log_path='')
if __name__ == '__main__': if __name__ == '__main__':
...@@ -482,9 +475,9 @@ if __name__ == '__main__': ...@@ -482,9 +475,9 @@ if __name__ == '__main__':
else: else:
log.warning('private SSL key not specified, internims functionality disabled') log.warning('private SSL key not specified, internims functionality disabled')
app.config['site_id'] = args.site_id or 'local' app.config['site_id'] = args.site_id or app.config['site_id']
app.config['stage_path'] = args.stage_path or config.get('nims', 'stage_path') app.config['stage_path'] = args.stage_path or config.get('nims', 'stage_path')
app.config['log_path'] = args.log_path app.config['log_path'] = args.log_path or app.config['log_path']
app.config['oauth2_id_endpoint'] = args.oauth2_id_endpoint or config.get('oauth2', 'id_endpoint') app.config['oauth2_id_endpoint'] = args.oauth2_id_endpoint or config.get('oauth2', 'id_endpoint')
app.config['insecure'] = config.getboolean('nims', 'insecure') app.config['insecure'] = config.getboolean('nims', 'insecure')
......
...@@ -61,13 +61,13 @@ class NIMSRequestHandler(webapp2.RequestHandler): ...@@ -61,13 +61,13 @@ class NIMSRequestHandler(webapp2.RequestHandler):
self.access_token = self.request.headers.get('Authorization', None) self.access_token = self.request.headers.get('Authorization', None)
# CORS header # CORS header
self.response.headers.add('Access-Control-Allow-Origin', self.request.headers.get('origin', '*')) if 'Origin' in self.request.headers and self.request.headers['Origin'].startswith('https://'):
self.response.headers['Access-Control-Allow-Origin'] = self.request.headers['Origin']
if self.access_token and self.app.config['oauth2_id_endpoint']: 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}) r = requests.get(self.app.config['oauth2_id_endpoint'], headers={'Authorization': 'Bearer ' + self.access_token})
if r.status_code == 200: if r.status_code == 200:
self.uid = json.loads(r.content)['email'] self.uid = json.loads(r.content)['email']
log.debug('oauth user: ' + self.uid)
else: else:
# TODO: add handlers for bad tokens # 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 # inform app of expired token, app will try to get new token, or ask user to log in again
...@@ -79,11 +79,11 @@ class NIMSRequestHandler(webapp2.RequestHandler): ...@@ -79,11 +79,11 @@ class NIMSRequestHandler(webapp2.RequestHandler):
self.user_is_superuser = False self.user_is_superuser = False
if self.uid != '@public': if self.uid != '@public':
user = self.app.db.users.find_one({'_id': self.uid}) user = self.app.db.users.find_one({'_id': self.uid}, ['superuser'])
if user: if user:
self.user_is_superuser = user.get('superuser', None) self.user_is_superuser = user.get('superuser', None)
else: else:
self.abort(403, 'user: ' + self.uid + ' does not exist') self.abort(403, 'user ' + self.uid + ' does not exist')
if self.target_id not in [None, self.app.config['site_id']]: if self.target_id not in [None, self.app.config['site_id']]:
self.rtype = 'to_remote' self.rtype = 'to_remote'
...@@ -95,8 +95,7 @@ class NIMSRequestHandler(webapp2.RequestHandler): ...@@ -95,8 +95,7 @@ class NIMSRequestHandler(webapp2.RequestHandler):
target = self.app.db.remotes.find_one({'_id': self.target_id}, {'_id': False, 'api_uri': True}) target = self.app.db.remotes.find_one({'_id': self.target_id}, {'_id': False, 'api_uri': True})
if not target: if not target:
log.debug('remote host ' + self.target_id + ' is not an authorized remote.') self.abort(402, 'remote host ' + self.target_id + ' is not an authorized remote')
self.abort(403, 'remote host ' + self.target_id + ' is not an authorized remote.')
# adjust headers # adjust headers
self.headers = self.request.headers self.headers = self.request.headers
...@@ -118,7 +117,7 @@ class NIMSRequestHandler(webapp2.RequestHandler): ...@@ -118,7 +117,7 @@ class NIMSRequestHandler(webapp2.RequestHandler):
self.headers['X-Signature'] = base64.b64encode(signature) self.headers['X-Signature'] = base64.b64encode(signature)
# prepare delegated request URI # prepare delegated request URI
self.target_api = target['api_uri'] + self.request.path.split('/nimsapi')[1] self.target_uri = target['api_uri'] + self.request.path.split('/nimsapi')[1]
elif self.request.user_agent.startswith('NIMS Instance'): elif self.request.user_agent.startswith('NIMS Instance'):
self.rtype = 'from_remote' self.rtype = 'from_remote'
...@@ -127,35 +126,41 @@ class NIMSRequestHandler(webapp2.RequestHandler): ...@@ -127,35 +126,41 @@ class NIMSRequestHandler(webapp2.RequestHandler):
self.user_is_superuser = False self.user_is_superuser = False
remote_instance = self.request.user_agent.replace('NIMS Instance', '').strip() remote_instance = self.request.user_agent.replace('NIMS Instance', '').strip()
requester = self.app.db.remotes.find_one({'_id':remote_instance}) requester = self.app.db.remotes.find_one({'_id': remote_instance})
if not requester: if not requester:
log.debug('remote host ' + remote_instance + ' not in auth list. DENIED') self.abort(402, remote_instance + ' is not authorized')
self.abort(403, remote_instance + ' is not authorized')
# assemble msg, hash, and verify recieved signature # assemble msg, hash, and verify received signature
signature = base64.b64decode(self.request.headers.get('X-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') 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'])) verifier = Crypto.Signature.PKCS1_v1_5.new(Crypto.PublicKey.RSA.importKey(requester['pubkey']))
if not verifier.verify(Crypto.Hash.SHA.new(msg), signature): if not verifier.verify(Crypto.Hash.SHA.new(msg), signature):
log.debug('remote message/signature is not authentic') self.abort(402, 'remote message/signature is not authentic')
self.abort(403, 'remote message/signature is not authentic')
else: else:
self.rtype = 'local' 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): def dispatch(self):
"""dispatching and request forwarding""" """dispatching and request forwarding"""
log.info(self.rtype + ' ' + self.uid + ' ' + self.request.method + ' ' + self.request.path + ' ' + str(self.request.params.mixed()))
if self.rtype in ['local', 'from_remote']: if self.rtype in ['local', 'from_remote']:
return super(NIMSRequestHandler, self).dispatch() return super(NIMSRequestHandler, self).dispatch()
else: else:
r = requests.request(method=self.request.method, data=self.request.body, url=self.target_api, params=self.params, headers=self.headers, verify=False) r = requests.request(self.request.method, self.target_uri, params=self.params, data=self.request.body, headers=self.headers, verify=False)
if not r.status_code == 200: if not r.status_code == 200:
self.abort(r.status_code, 'internims p2p err: ' + r.reason) self.abort(r.status_code, 'internims p2p err: ' + r.reason)
self.response.write(r.content) self.response.write(r.content)
def abort(self, code, *args, **kwargs):
log.debug(str(code) + ' ' + '; '.join(args))
if 'Access-Control-Allow-Origin' in self.response.headers:
headers = kwargs.setdefault('headers', {})
headers['Access-Control-Allow-Origin'] = self.response.headers['Access-Control-Allow-Origin']
webapp2.abort(code, *args, **kwargs)
def options(self, *args, **kwargs):
self.response.headers['Access-Control-Allow-Methods'] = 'POST, GET, PUT, DELETE, OPTIONS'
self.response.headers['Access-Control-Allow-Headers'] = 'Authorization'
def schema(self): def schema(self):
return self.json_schema return self.json_schema
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment