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):
signature = Crypto.Signature.PKCS1_v1_5.new(privkey).sign(h)
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:
response = (json.loads(r.content))
# update remotes entries
......@@ -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'])))
# 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
log.debug('users w/ remotes: ' + ', '.join(response['users']))
for uid, remotes in response['users'].iteritems():
db.users.update({'uid': uid}, {'$set': {'remotes': remotes}})
db.users.update({'_id': uid}, {'$set': {'remotes': remotes}})
else:
# r.reason contains generic description for the specific error code
# need the part of the error response body that contains the detailed explanation
......
......@@ -33,7 +33,7 @@ class NIMSAPI(nimsapiutil.NIMSRequestHandler):
"""Return 200 OK."""
self.response.set_status(200)
def get(self):
def get(self, *args):
"""Return API documentation"""
resources = """
Resource | Description
......@@ -100,6 +100,15 @@ class NIMSAPI(nimsapiutil.NIMSRequestHandler):
self.response.write('</body>\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):
# TODO add security: either authenticated user or machine-to-machine CRAM
if 'Content-MD5' not in self.request.headers:
......@@ -129,29 +138,22 @@ class NIMSAPI(nimsapiutil.NIMSRequestHandler):
paths += _idpaths
symlinks += _idsymlinks
def remotes(self):
"""Return the list of all remote sites."""
return list(self.app.db.remotes.find(None, []))
def log(self):
"""Return logs."""
try:
logs = open(app.config['log_path']).readlines()
except IOError as e:
log.debug(e)
if 'Permission denied' in e:
# specify body format to print details separate from comment
body_template = '${explanation}<br /><br />${detail}<br /><br />${comment}'
comment = 'To fix permissions, run the following command: chmod o+r ' + logfile
self.abort(500, detail=str(e), comment=comment, body_template=body_template)
else:
# file does not exist
self.abort(500, e)
self.abort(500, e) # file does not exist
try:
n = int(self.request.get('n', 10000))
except:
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):
......@@ -252,13 +254,6 @@ class User(nimsapiutil.NIMSRequestHandler):
'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):
"""Return User details."""
projection = []
......@@ -266,14 +261,11 @@ class User(nimsapiutil.NIMSRequestHandler):
projection += ['remotes']
if self.request.get('status') in ('1', 'true'):
projection += ['status']
if self.request.get('login') in ('1', 'true'):
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)
return self.app.db.users.find_one({'_id': uid}, projection or None)
def put(self, uid):
"""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:
self.abort(404)
if uid == self.uid or self.user_is_superuser: # users can only update their own info
......@@ -288,7 +280,7 @@ class User(nimsapiutil.NIMSRequestHandler):
updates['$set'][k] = False # superuser is tri-state: False indicates granted, but disabled, superuser privileges
elif v.lower() not in ('1', 'true'):
updates['$unset'][k] = ''
self.app.db.users.update({'uid': uid}, updates)
self.app.db.users.update({'_id': uid}, updates)
else:
self.abort(403)
......@@ -396,17 +388,16 @@ class Group(nimsapiutil.NIMSRequestHandler):
routes = [
webapp2.Route(r'/nimsapi', NIMSAPI),
webapp2_extras.routes.PathPrefixRoute(r'/nimsapi', [
webapp2.Route(r'/download', NIMSAPI, handler_method='download', methods=['GET']),
webapp2.Route(r'/upload', NIMSAPI, handler_method='upload', methods=['PUT']),
webapp2.Route(r'/login', NIMSAPI, handler_method='login', methods=['GET', 'POST']),
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'/users', Users),
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/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'/groups', Groups),
webapp2.Route(r'/groups/count', Groups, handler_method='count', methods=['GET']),
......@@ -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}>/epochs', collections_.Epochs),
]),
webapp2.Route(r'/nimsapi', NIMSAPI),
webapp2.Route(r'/nimsapi/<:.*>', NIMSAPI),
]
def dispatcher(router, request, response):
rv = router.default_dispatcher(request, response)
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.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__':
......@@ -482,9 +475,9 @@ if __name__ == '__main__':
else:
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['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['insecure'] = config.getboolean('nims', 'insecure')
......
......@@ -61,13 +61,13 @@ class NIMSRequestHandler(webapp2.RequestHandler):
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 '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']:
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:
self.uid = json.loads(r.content)['email']
log.debug('oauth user: ' + self.uid)
else:
# 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
......@@ -79,11 +79,11 @@ class NIMSRequestHandler(webapp2.RequestHandler):
self.user_is_superuser = False
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:
self.user_is_superuser = user.get('superuser', None)
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']]:
self.rtype = 'to_remote'
......@@ -95,8 +95,7 @@ class NIMSRequestHandler(webapp2.RequestHandler):
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.')
self.abort(402, 'remote host ' + self.target_id + ' is not an authorized remote')
# adjust headers
self.headers = self.request.headers
......@@ -118,7 +117,7 @@ class NIMSRequestHandler(webapp2.RequestHandler):
self.headers['X-Signature'] = base64.b64encode(signature)
# 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'):
self.rtype = 'from_remote'
......@@ -127,35 +126,41 @@ class NIMSRequestHandler(webapp2.RequestHandler):
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})
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')
self.abort(402, 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'))
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')
self.abort(402, '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"""
log.info(self.rtype + ' ' + self.uid + ' ' + self.request.method + ' ' + self.request.path + ' ' + str(self.request.params.mixed()))
if self.rtype in ['local', 'from_remote']:
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)
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:
self.abort(r.status_code, 'internims p2p err: ' + r.reason)
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):
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