Coverage for runmacs/processor/authserver/authserver.py : 53%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
# -*- coding: utf-8 -*- authserver ========== Authentication server for ther macsServer.
The main purpose of the authentication server is to issue empty HTTP responses with status codes set according to the access restictions for the requesting user. Furthermore, it serves the login page and performs access logs after processing the request. """
r"^oc[\.0-9]+$", r"^curl/[\.0-9]+$", ])
""" MACSserver authentication server. """ (r"/ping", PingHandler), (r"/auth/static/(.*)", tornado.web.StaticFileHandler, {'path': os.path.join(os.path.dirname(__file__), "static")}), (r"/auth/login/?", AuthLoginHandler), (r"/auth/logout/?", AuthLogoutHandler), (r"/auth/userinfo/?", AuthUserInfoHandler), (r"/auth/profile/?", AuthUserProfileHandler), (r"/auth/profile/userinfo\.json", AuthUserInfoHandler, {'fmt': 'json'}), (r"/auth/profile/newKey", AuthUserProfileNewKeyHandler), (r"/auth/profile/changepass", AuthUserProfileChangePassHandler), (r"/auth/profile/key/(?P<key>[0-9a-z\-]+)", AuthUserProfileKeyHandler), (r"/auth/profile/key/(?P<key>[0-9a-z\-]+)" r"/role/(?P<role>[0-9a-zA-Z_]+)", AuthUserProfileKeyRoleHandler), (r"/auth/user_mgmt/?", AuthUserManagementHandler), (r"/auth/user_mgmt/data.json", AuthUserManagementDataHandler), (r"/auth/user_mgmt/user/?", UserAdminHandler), (r"/auth/user_mgmt/user/(?P<username>[0-9a-z\-_\.]+)", UserAdminHandler), (r"(.*)", AuthHandler), ] template_path=os.path.join(os.path.dirname(__file__), "templates"), static_path=os.path.join(os.path.dirname(__file__), "static"), debug=True, # cookie_secret="not so good secret", #must be set during startup login_url="/auth/login/", )
""" Basic request handler for the authentication server """
""" Generates the path to a static file.
:param filename: name of the file, relative to static file directory """
""" Redirect to login page if not accessed by script
:note: After calling this method, no further data can be sent. """ self.set_header('WWW-Authenticate', 'Basic realm="macsServer"') self.set_status(401) else: else: self.redirect(self.settings['login_url'] + '?next=' + next) self.finish()
def deny(self): """ Deny the request.
:note: After calling this method, no further data can be sent. """
def deny_quiet(self): """ Deny the request, but don't tell the client (i.e. no headers are set)
:note: After calling this method, no further data can be sent. """ if not self._finished: self.finish() yield self.logAccess(False)
def accept(self): """ Accept the request.
:note: After calling this method, no further data can be sent. """
def user_agent(self):
def is_script_client(self): return True
def originatingIP(self): """ The normalized IP address of the requesting client. """
def isFromLocalNet(self): """ True if request was originated in the MIM network. """
query = {'apikeys.%s' % apikey: {'$exists': True}} self._user_info = (yield self.user_coll.find_one(query)) if self._user_info is not None: self._authenticyted_by = (authmode, apikey) self._current_user = self._user_info['name'] raise gen.Return(self._current_user)
def get_current_user(self): else: current_user = (yield self.authenticate_by_api_key(apikey, 'apikey')) if current_user is not None: raise gen.Return(current_user) else: if auth_header[0] == 'Basic' and len(auth_header) == 2: auth_token = base64.b64decode(auth_header[1]).split(':')[0] current_user = (yield self.authenticate_by_api_key(auth_token, 'basic_auth_apikey')) if current_user is not None: raise gen.Return(current_user) self.get_secure_cookie("user")) else: self._current_user = self.originatingIP self._authenticyted_by = ('localnet',) self._user_info = {'name': self._current_user, 'roles': ['local'], 'apikeys': {}} raise gen.Return(self._current_user)
def user_info(self): {'name': (yield self.get_current_user())} )) self._user_info = {} else:
def user_roles(self): elif self.authmode in ('apikey', 'basic_auth_apikey'): try: keys_info = userInfo['apikeys'] key_info = keys_info[self._authenticyted_by[1]] self._active_user_roles = key_info['roles'] except KeyError: self._active_user_roles = [] elif self.authmode == 'localnet': self._active_user_roles = userInfo.get('roles', []) else: self._active_user_roles = []
def user_rights(self):
def authmode(self): else:
def isAPIUser(self):
def isAuthenticatedByLocalNet(self):
try: resolver = AsyncResolver() current_user = (yield resolver.gethostbyaddr(current_user)) except: pass uri, authok, user=current_user, authmode=self.authmode, oid=self.current_oid, method=self.request.method, useragent=self.user_agent)
""" Handler for pings from varnish liveness checks.
Has to answer, but no content is needed. """
continue
[ r"/info/(?P<objectId>[0-9a-f]+)\.html", r"/whatsmissing/(?P<objectId>[0-9a-f]+)\.html", r"/dep/(?P<objectId>[0-9a-f]+)\.svg", r"/get/(?P<objectId>[0-9a-f]+)(?:\.(?P<height>[0-9]+))?\.jpg", r"/get/(?:(?P<part>[a-zA-Z0-9]+)/)?(?P<objectId>[0-9a-f]+)(?:\.(?P<height>[0-9]+))?\.(?P<filetype>[a-zA-Z0-9]+)", r"/dap/(?P<objectId>[0-9a-f]+)\.das", r"/dap/(?P<objectId>[0-9a-f]+)\.dds", r"/dap/(?P<objectId>[0-9a-f]+)\.dods", r"/getCached/(?P<objectId>[0-9a-f]+)(?:\.(?P<height>[0-9]+))?\.jpg", r"/getCached/withCoordinates/(?P<objectId>[0-9a-f]+)(?:\.(?P<height>[0-9]+))?\.jpg", r"/plot/(?:(?P<part>[a-zA-Z0-9]+)/)?(?P<objectId>[0-9a-f]+)(?:\.(?P<height>[0-9]+))?\.(?P<filetype>[a-zA-Z0-9]+)", ])
""" collects all special rights from rights list """
""" Answers any request with return code representing the uses authentication.
Furthermore, all requests can be logged. """
'True' if do_recheck else 'False')
self.set_recheck(True) yield self.accept() return
try: query_restrictions.append(json.loads(right['queryRestriction'])) except KeyError: continue
query = {'_oid': oid, '$or': parse_dates_in_tree(query_restrictions)} if (yield self.product_coll.find_one(query, {})) is not None: # single products can always be delivered if they pass the check self.set_recheck(False) yield self.accept() return yield self.deny() return
return self.get(url, 'PUT')
def write_html(self):
"""create list element"""
item('not logged in') item('<a href="/auth/login/">Login</a>') else: item('access from: %s' % user) item('<a href="/help.html">Help</a>') item('<a href="/auth/login/">Login</a>') else:
def write_json(self):
'roles': userinfo['roles'], 'apikeys': userinfo['apikeys']} properties['addableRoles'] = \ sorted(baseRoles - set(properties['roles']))
def get(self):
def get(self): user = (yield self.get_current_user()) if user is None or self.isAuthenticatedByLocalNet: self.toLoginPage('/auth/profile/') return user_info = (yield self.user_info()) self.render('profile.html', prefix='', staticFile=self.staticFile, user_name=user_info['name'], user_roles=user_info['roles'], user_api_keys=user_info['apikeys']) yield self.accept()
def post(self): json.dump({'error': 'not logged in'}, self) yield self.deny_quiet() return json.dump({'error': 'cannot modify using API Key'}, self) yield self.deny_quiet() return
def delete(self, key): user = (yield self.get_current_user()) self.set_header('Content-Type', 'application/json') self.set_header('Expires', datetime.datetime.now()) self.set_header('Cache-Control', 'no-cache') if user is None or self.isAuthenticatedByLocalNet: json.dump({'error': 'not logged in'}, self) yield self.deny_quiet() return if self.isAPIUser: json.dump({'error': 'cannot modify using API Key'}, self) yield self.deny_quiet() return yield self.user_coll.update({'name': user}, {'$unset': {'apikeys.%s'%key: ""}}) json.dump({'ok': ''}, self) yield self.accept()
def put(self, key, role): user = (yield self.get_current_user()) self.set_header('Content-Type', 'application/json') self.set_header('Expires', datetime.datetime.now()) self.set_header('Cache-Control', 'no-cache') if user is None or self.isAuthenticatedByLocalNet: json.dump({'error': 'not logged in'}, self) yield self.deny_quiet() return if self.isAPIUser: json.dump({'error': 'cannot modify using API Key'}, self) yield self.deny_quiet() return userinfo = (yield self.user_info()) allowedRoles = userinfo['roles'] if role not in allowedRoles: json.dump({'error': 'role not allowed'}, self) yield self.deny_quiet() return yield self.user_coll.update({'name': user}, {'$addToSet': {'apikeys.%s.roles'%key: role}}) json.dump({'ok': ''}, self) yield self.accept()
def delete(self, key, role): user = (yield self.get_current_user()) self.set_header('Content-Type', 'application/json') self.set_header('Expires', datetime.datetime.now()) self.set_header('Cache-Control', 'no-cache') if user is None or self.isAuthenticatedByLocalNet: json.dump({'error': 'not logged in'}, self) yield self.deny_quiet() return if self.isAPIUser: json.dump({'error': 'cannot modify using API Key'}, self) yield self.deny_quiet() return yield self.user_coll.update({'name': user}, {'$pullAll': { 'apikeys.%s.roles' % key: [role] }}) json.dump({'ok': ''}, self) yield self.accept()
def get(self):
prefix='', staticFile=self.staticFile)
def get(self): rights = (yield self.user_rights()) special_rights = aggregate_special_rights(rights) if 'ADMIN' not in special_rights: yield self.deny() return
roles = (yield self.role_coll.find().to_list(None)) rolenames = set() for role in roles: role.pop('_id', None) rolenames.add(role['name']) users = (yield self.user_coll.find().to_list(None)) for user in users: user.pop('_id', None) user.pop('password', None) user['addableRoles'] = list(rolenames - set(user.get('roles', [])))
self.set_header('Content-Type', 'application/json') self.set_header('Expires', datetime.datetime.now()) self.set_header('Cache-Control', 'no-cache') json.dump({'users': users, 'roles': roles}, self, default=dthandler) yield self.accept()
def put(self): rights = (yield self.user_rights()) special_rights = aggregate_special_rights(rights) if 'ADMIN' not in special_rights: yield self.deny() return self.set_header('Content-Type', 'application/json') self.set_header('Expires', datetime.datetime.now()) self.set_header('Cache-Control', 'no-cache') data = json.loads(self.request.body) new_username = data['username'] new_password = data['password'] users = (yield self.user_coll.find().to_list(None)) if new_username in (user['name'] for user in users): json.dump({'error': 'user already exists'}, self) yield self.accept() return hashed_pw = bcrypt.hashpw(new_password.encode('utf-8'), bcrypt.gensalt()) yield self.user_coll.insert({'name': new_username, 'password': hashed_pw, 'roles': [], 'apikeys': {}}) json.dump({'ok': 'user added'}, self) yield self.accept()
def post(self, username): rights = (yield self.user_rights()) special_rights = aggregate_special_rights(rights) if 'ADMIN' not in special_rights: yield self.deny() return self.set_header('Content-Type', 'application/json') self.set_header('Expires', datetime.datetime.now()) self.set_header('Cache-Control', 'no-cache') data = json.loads(self.request.body) for role in data.get('add_roles', []): yield self.user_coll.update({'name': username}, {'$addToSet': {'roles': role}}) for role in data.get('remove_roles', []): yield self.user_coll.update({'name': username}, {'$pull': {'roles': role}})
json.dump({'ok': 'roles modified'}, self) yield self.accept()
def delete(self, username): rights = (yield self.user_rights()) special_rights = aggregate_special_rights(rights) if 'ADMIN' not in special_rights: yield self.deny() return self.set_header('Content-Type', 'application/json') self.set_header('Expires', datetime.datetime.now()) self.set_header('Cache-Control', 'no-cache') yield self.user_coll.remove({'name': username}) json.dump({'ok': 'user deleted'}, self) yield self.accept()
def check_permission(self, password, username): raise gen.Return(False) # raise gen.Return(bcrypt.checkpw(password, user_info['password']))
def get(self): errormessage = self.get_argument("error", None) successmessage = self.get_argument("success", None) redirectNext = self.get_argument("next", DEFAULT_REDIRECT) if self.current_user: self.redirect(redirectNext) yield self.accept() return self.render("login.html", redirectNext=redirectNext, prefix='', staticFile=self.staticFile, errormessage=errormessage, successmessage=successmessage) yield self.accept()
def post(self): else:
else: self.clear_cookie("user")
def get(self): self.clear_cookie("user") self.redirect("/auth/login?success=Logout successful") yield self.accept()
def post(self): user = (yield self.get_current_user()) if user is None: yield self.deny() return data = json.loads(self.request.body) oldpass = data['oldpass'] newpass = data['newpass'] auth = (yield self.check_permission(oldpass, user)) self.set_header('Content-Type', 'application/json') self.set_header('Expires', datetime.datetime.now()) self.set_header('Cache-Control', 'no-cache') if not auth: json.dump({'type': 'error', 'error': 'wrong password'}, self) yield self.accept() return if len(newpass) < 8: json.dump({'type': 'error', 'error': 'password too short'}, self) yield self.accept() return yield self.user_coll.update({'name': user}, {'$set': {'password': bcrypt.hashpw(newpass.encode('utf-8'), bcrypt.gensalt())}}) json.dump({'type': 'ok'}, self) yield self.accept() return
import tornado.options from tornado.options import define, options from runmacs.config import loadConfig from runmacs.processor.config import config
frontendConfig = loadConfig('frontend')
define("port", default=7778, help="run on the given port", type=int) tornado.options.parse_command_line()
application = Application() #application.settings['db'] = motor.MotorClient(config['database']['connection']).macsServer_test #application.settings['pdb'] = motor.MotorClient(config['database']['connection']).macsServer application.settings['db'] = motor.MotorClient(config['database']['connection'])[config['database']['collection']] application.settings['pdb'] = motor.MotorClient(config['database']['connection'])[config['database']['collection']] application.settings['cookie_secret'] = frontendConfig['cookieSecret'] if 'accesslogger' in config: alType = config['accesslogger'].get('type', 'influxdb') alArgs = config['accesslogger'].copy() if 'type' in alArgs: del alArgs['type'] if alType == 'influxdb': AL = AsyncInfluxDBAccessLogger elif alType == 'elasticsearch': AL = AsyncElasticSearchAccessLogger application.settings['access_logger'] = AL(**alArgs) else: application.settings['access_logger'] = AsyncNullAccessLogger() application.listen(options.port) tornado.ioloop.IOLoop.instance().start()
if __name__ == '__main__': _main() |