def check_privileged(): """Check if request is trying to make an actor privileged.""" logger.debug("top of check_privileged") # admins have access to all actors: if g.admin: return True data = request.get_json() if not data: data = request.form # various APIs (e.g., the state api) allow an arbitary JSON serializable objects which won't have a get method: if not hasattr(data, 'get'): return True if data.get('privileged'): logger.debug("User is trying to set privileged") # if we're here, user isn't an admin so must have privileged role: if not codes.PRIVILEGED_ROLE in g.roles: logger.info("User does not have privileged role.") raise PermissionsException("Not authorized -- only admins and privileged users can make privileged actors.") else: logger.debug("user allowed to set privileged.") # when using the UID associated with the user in TAS, admins can still register actors # to use the UID built in the container using the use_container_uid flag: if Config.get('workers', 'use_tas_uid'): if data.get('use_container_uid') or data.get('useContainerUid'): logger.debug("User is trying to use_container_uid") # if we're here, user isn't an admin so must have privileged role: if not codes.PRIVILEGED_ROLE in g.roles: logger.info("User does not have privileged role.") raise PermissionsException("Not authorized -- only admins and privileged users can use container uid.") else: logger.debug("user allowed to use container uid.") else: logger.debug("not trying to use privileged options.") return True
def check_nonce(): """ This function is an agaveflask authentication callback used to process the existence of a query parameter, x-nonce, an alternative authentication mechanism to JWT. When an x-nonce query parameter is provided, the request context is updated with the identity of the user owning the actor to which the nonce belongs. Note that the roles of said user will not be calculated so, in particular, any privileged action cannot be taken via a nonce. """ logger.debug("top of check_nonce") try: nonce_id = request.args['x-nonce'] except KeyError: raise PermissionsException("No JWT or nonce provided.") logger.debug("checking nonce with id: {}".format(nonce_id)) # the nonce encodes the tenant in its id: g.tenant = Nonce.get_tenant_from_nonce_id(nonce_id) g.api_server = get_api_server(g.tenant) logger.debug("tenant associated with nonce: {}".format(g.tenant)) # get the actor_id base on the request path actor_id = get_db_id() logger.debug("db_id: {}".format(actor_id)) level = required_level(request) Nonce.check_and_redeem_nonce(actor_id, nonce_id, level) # if we were able to redeem the nonce, update auth context with the actor owner data: logger.debug("nonce valid and redeemed.") nonce = Nonce.get_nonce(actor_id, nonce_id) g.user = nonce.owner # update roles data with that stored on the nonce: g.roles = nonce.roles # now, manually call our authorization function: authorization()
def get_db_id(): """Get the db_id from the request path.""" # logger.debug("top of get_db_id. request.path: {}".format(request.path)) path_split = request.path.split("/") if len(path_split) < 3: logger.error( "Unrecognized request -- could not find the actor id. path_split: {}" .format(path_split)) raise PermissionsException("Not authorized.") # logger.debug("path_split: {}".format(path_split)) actor_identifier = path_split[2] # logger.debug("actor_identifier: {}; tenant: {}".format(actor_identifier, g.tenant)) try: actor_id = Actor.get_actor_id(g.tenant, actor_identifier) except KeyError: logger.info( "Unrecoginzed actor_identifier: {}. Actor not found".format( actor_identifier)) raise ResourceError( "Actor with identifier '{}' not found".format(actor_identifier), 404) except Exception as e: msg = "Unrecognized exception trying to resolve actor identifier: {}; " \ "exception: {}".format(actor_identifier, e) logger.error(msg) raise ResourceError(msg) logger.debug("actor_id: {}".format(actor_id)) return Actor.get_dbid(g.tenant, actor_id)
def get_db_id(): """Get the db_id from the request path.""" path_split = request.path.split("/") if len(path_split) < 3: logger.error("Unrecognized request -- could not find the actor id. path_split: {}".format(path_split)) raise PermissionsException("Not authorized.") actor_id = path_split[2] logger.debug("actor_id: {}".format(actor_id)) return Actor.get_dbid(g.tenant, actor_id)
def get_alias_id(): """Get the alias from the request path.""" path_split = request.path.split("/") if len(path_split) < 4: logger.error( "Unrecognized request -- could not find the alias. path_split: {}". format(path_split)) raise PermissionsException("Not authorized.") alias = path_split[3] logger.debug("alias: {}".format(alias)) return Alias.generate_alias_id(g.tenant, alias)
def get_config_name(): """Get the config name from the request path.""" logger.debug("top of auth.get_config_id()") path_split = request.path.split("/") if len(path_split) < 4: logger.error( "Unrecognized request -- could not find the config. path_split: {}" .format(path_split)) raise PermissionsException("Not authorized.") config_name = path_split[3] logger.debug("returning config_name from path: {}".format(config_name)) return config_name
def get_db_id(): """Get the db_id and actor_identifier from the request path.""" # the location of the actor identifier is different for aliases vs actor_id's. # for actors, it is in index 2: # /actors/<actor_id> # for aliases, it is in index 3: # /actors/aliases/<alias_id> idx = 2 if 'aliases' in request.path: idx = 3 path_split = request.path.split("/") if len(path_split) < 3: logger.error( "Unrecognized request -- could not find the actor id. path_split: {}" .format(path_split)) raise PermissionsException("Not authorized.") logger.debug("path_split: {}".format(path_split)) try: actor_identifier = path_split[idx] except IndexError: raise ResourceError( "Unable to parse actor identifier: is it missing from the URL?", 404) logger.debug("actor_identifier: {}; tenant: {}".format( actor_identifier, g.tenant)) if actor_identifier == 'search': raise ResourceError( "'x-nonce' query parameter on the '/actors/search/{database}' endpoint does not resolve.", 404) try: actor_id = Actor.get_actor_id(g.tenant, actor_identifier) except KeyError: logger.info( "Unrecognized actor_identifier: {}. Actor not found".format( actor_identifier)) raise ResourceError( "Actor with identifier '{}' not found".format(actor_identifier), 404) except Exception as e: msg = "Unrecognized exception trying to resolve actor identifier: {}; " \ "exception: {}".format(actor_identifier, e) logger.error(msg) raise ResourceError(msg) logger.debug("actor_id: {}".format(actor_id)) return Actor.get_dbid(g.tenant, actor_id), actor_identifier
def authorization(): """Entry point for authorization. Use as follows: import auth my_app = Flask(__name__) @my_app.before_request def authz_for_my_app(): auth.authorization() """ if request.method == 'OPTIONS': # allow all users to make OPTIONS requests return # all other checks are based on actor-id; if that is not present then let # request through to fail. actor_id = request.args.get('actor_id', None) if not actor_id: return if request.method == 'GET': has_pem = check_permissions(user=g.user, actor_id=actor_id, level='READ') else: print(request.url_rule.rule) if request.method == 'POST': # creating a new actor requires no permissions if 'actors' == request.url_rule.rule or 'actors/' == request.url_rule.rule: has_pem = True # POST to the messages endpoint requires EXECUTE elif 'messages' in request.url_rule.rule: has_pem = check_permissions(user=g.user, actor_id=actor_id, level='EXECUTE') # otherwise, we require UPDATE else: has_pem = check_permissions(user=g.user, actor_id=actor_id, level='UPDATE') if not has_pem: raise PermissionsException("Not authorized")
def check_privileged(): """Check if request is trying to make an actor privileged.""" logger.debug("top of check_privileged") # admins have access to all actors: if g.admin: return True data = request.get_json() if not data: data = request.form if data.get('privileged'): logger.debug("User is trying to set privileged") # if we're here, user isn't an admin so must have privileged role: if not codes.PRIVILEGED_ROLE in g.roles: logger.info("User does not have privileged role.") raise PermissionsException( "Not authorized -- only admins and privileged users can make privileged actors." ) else: logger.debug("user allowed to set privileged.") return True else: logger.debug("not trying to set privileged.") return True
def get_db_id(): """Get the db_id and actor_identifier from the request path.""" # the location of the actor identifier is different for aliases vs actor_id's. # for actors, it is in index 2: # /actors/<actor_id> # for aliases, it is in index 3: # /actors/aliases/<alias_id> idx = 2 if 'aliases' in request.path: idx = 3 path_split = request.path.split("/") if len(path_split) < 3: logger.error( "Unrecognized request -- could not find the actor id. path_split: {}" .format(path_split)) raise PermissionsException("Not authorized.") logger.debug("path_split: {}".format(path_split)) actor_identifier = path_split[idx] logger.debug("actor_identifier: {}; tenant: {}".format( actor_identifier, g.tenant)) try: actor_id = Actor.get_actor_id(g.tenant, actor_identifier) except KeyError: logger.info( "Unrecoginzed actor_identifier: {}. Actor not found".format( actor_identifier)) raise ResourceError( "Actor with identifier '{}' not found".format(actor_identifier), 404) except Exception as e: msg = "Unrecognized exception trying to resolve actor identifier: {}; " \ "exception: {}".format(actor_identifier, e) logger.error(msg) raise ResourceError(msg) logger.debug("actor_id: {}".format(actor_id)) return Actor.get_dbid(g.tenant, actor_id), actor_identifier
def authorization(): """This is the agaveflask authorization callback and implements the main Abaco authorization logic. This function is called by agaveflask after all authentication processing and initial authorization logic has run. """ g.admin = False if request.method == 'OPTIONS': # allow all users to make OPTIONS requests logger.info("Allowing request because of OPTIONS method.") return True # the 'ALL' role is a role set by agaveflask in case the access_control_type is none if codes.ALL_ROLE in g.roles: g.admin = True logger.info("Allowing request because of ALL role.") return True # all other requests require some kind of abaco role: if set(g.roles).isdisjoint(codes.roles): logger.info("NOT allowing request - user has no abaco role.") raise PermissionsException("Not authorized -- missing required role.") else: logger.debug("User has an abaco role.") if hasattr(request, 'url_rule'): logger.debug("request.url_rule: {}".format(request.url_rule)) if hasattr(request.url_rule, 'rule'): logger.debug("url_rule.rule: {}".format(request.url_rule.rule)) else: logger.info("url_rule has no rule.") raise ResourceError("Invalid request: the API endpoint does not exist or the provided HTTP method is not allowed.", 405) else: logger.info("Request has no url_rule") raise ResourceError( "Invalid request: the API endpoint does not exist or the provided HTTP method is not allowed.", 405) logger.debug("request.path: {}".format(request.path)) # the admin role when JWT auth is configured: if codes.ADMIN_ROLE in g.roles: g.admin = True logger.info("Allowing request because of ADMIN_ROLE.") return True # the admin API requires the admin role: if 'admin' in request.path or '/actors/admin' in request.url_rule.rule or '/actors/admin/' in request.url_rule.rule: if g.admin: return True else: raise PermissionsException("Abaco Admin role required.") # there are special rules on the root collection: if '/actors' == request.url_rule.rule or '/actors/' == request.url_rule.rule: logger.debug("Checking permissions on root collection.") # first, only admins can create/update actors to be privileged, so check that: if request.method == 'POST': check_privileged() # if we are here, it is either a GET or a new actor, so the request is allowed: logger.debug("new actor or GET on root connection. allowing request.") return True # all other checks are based on actor-id: db_id = get_db_id() logger.debug("db_id: {}".format(db_id)) if request.method == 'GET': # GET requests require READ access has_pem = check_permissions(user=g.user, actor_id=db_id, level=codes.READ) elif request.method == 'DELETE': has_pem = check_permissions(user=g.user, actor_id=db_id, level=codes.UPDATE) else: logger.debug("URL rule in request: {}".format(request.url_rule.rule)) # first, only admins can create/update actors to be privileged, so check that: if request.method == 'POST' or request.method == 'PUT': check_privileged() # only admins have access to the workers endpoint, and if we are here, the user is not an admin: if 'workers' in request.url_rule.rule: raise PermissionsException("Not authorized -- only admins are authorized to update workers.") # POST to the messages endpoint requires EXECUTE if 'messages' in request.url_rule.rule: has_pem = check_permissions(user=g.user, actor_id=db_id, level=codes.EXECUTE) # otherwise, we require UPDATE else: has_pem = check_permissions(user=g.user, actor_id=db_id, level=codes.UPDATE) if not has_pem: logger.info("NOT allowing request.") raise PermissionsException("Not authorized -- you do not have access to this actor.")
def check_nonce(): """ This function is an agaveflask authentication callback used to process the existence of a query parameter, x-nonce, an alternative authentication mechanism to JWT. When an x-nonce query parameter is provided, the request context is updated with the identity of the user owning the actor to which the nonce belongs. Note that the roles of said user will not be calculated so, in particular, any privileged action cannot be taken via a nonce. """ logger.debug("top of check_nonce") # first check whether the request is even valid - if hasattr(request, 'url_rule'): logger.debug("request.url_rule: {}".format(request.url_rule)) if hasattr(request.url_rule, 'rule'): logger.debug("url_rule.rule: {}".format(request.url_rule.rule)) else: logger.info("url_rule has no rule.") raise ResourceError( "Invalid request: the API endpoint does not exist or the provided HTTP method is not allowed.", 405) else: logger.info("Request has no url_rule") raise ResourceError( "Invalid request: the API endpoint does not exist or the provided HTTP method is not allowed.", 405) try: nonce_id = request.args['x-nonce'] except KeyError: raise PermissionsException("No JWT or nonce provided.") logger.debug("checking nonce with id: {}".format(nonce_id)) # the nonce encodes the tenant in its id: g.tenant = Nonce.get_tenant_from_nonce_id(nonce_id) g.api_server = get_api_server(g.tenant) logger.debug( "tenant associated with nonce: {}; api_server assoicated with nonce: {}" .format(g.tenant, g.api_server)) # get the actor_id base on the request path actor_id, actor_identifier = get_db_id() logger.debug("db_id: {}; actor_identifier: {}".format( actor_id, actor_identifier)) level = required_level(request) # if the actor_identifier is an alias, then the nonce must be attached to that, so we must pass that in the # nonce check: if is_hashid(actor_identifier): Nonce.check_and_redeem_nonce(actor_id=actor_id, alias=None, nonce_id=nonce_id, level=level) else: alias_id = Alias.generate_alias_id(tenant=g.tenant, alias=actor_identifier) Nonce.check_and_redeem_nonce(actor_id=None, alias=alias_id, nonce_id=nonce_id, level=level) # if we were able to redeem the nonce, update auth context with the actor owner data: logger.debug("nonce valid and redeemed.") if is_hashid(actor_identifier): nonce = Nonce.get_nonce(actor_id=actor_id, alias=None, nonce_id=nonce_id) else: nonce = Nonce.get_nonce(actor_id=None, alias=alias_id, nonce_id=nonce_id) g.user = nonce.owner # update roles data with that stored on the nonce: g.roles = nonce.roles # now, manually call our authorization function: authorization()
def authorization(): """This is the agaveflask authorization callback and implements the main Abaco authorization logic. This function is called by agaveflask after all authentication processing and initial authorization logic has run. """ # first check whether the request is even valid - if hasattr(request, 'url_rule'): logger.debug("request.url_rule: {}".format(request.url_rule)) if hasattr(request.url_rule, 'rule'): logger.debug("url_rule.rule: {}".format(request.url_rule.rule)) else: logger.info("url_rule has no rule.") raise ResourceError( "Invalid request: the API endpoint does not exist or the provided HTTP method is not allowed.", 405) else: logger.info("Request has no url_rule") raise ResourceError( "Invalid request: the API endpoint does not exist or the provided HTTP method is not allowed.", 405) # get the actor db_id from a possible identifier once and for all - # these routes do not have an actor id in them: if request.url_rule.rule == '/actors' \ or request.url_rule.rule == '/actors/' \ or '/actors/admin' in request.url_rule.rule \ or '/actors/aliases' in request.url_rule.rule \ or '/actors/utilization' in request.url_rule.rule \ or '/actors/search/' in request.url_rule.rule: db_id = None logger.debug("setting db_id to None; rule: {}".format( request.url_rule.rule)) else: # every other route should have an actor identifier logger.debug("fetching db_id; rule: {}".format(request.url_rule.rule)) db_id, _ = get_db_id() g.db_id = db_id logger.debug("db_id: {}".format(db_id)) g.admin = False if request.method == 'OPTIONS': # allow all users to make OPTIONS requests logger.info("Allowing request because of OPTIONS method.") return True # the 'ALL' role is a role set by agaveflask in case the access_control_type is None if codes.ALL_ROLE in g.roles: g.admin = True logger.info("Allowing request because of ALL role.") return True # there is a bug in wso2 that causes the roles claim to sometimes be missing; this should never happen: if not g.roles: g.roles = ['Internal/everyone'] # all other requests require some kind of abaco role: if set(g.roles).isdisjoint(codes.roles): logger.info("NOT allowing request - user has no abaco role.") raise PermissionsException("Not authorized -- missing required role.") else: logger.debug("User has an abaco role.") logger.debug("request.path: {}".format(request.path)) # the admin role when JWT auth is configured: if codes.ADMIN_ROLE in g.roles: g.admin = True logger.info("Allowing request because of ADMIN_ROLE.") return True # the admin API requires the admin role: if 'admin' in request.path or '/actors/admin' in request.url_rule.rule or '/actors/admin/' in request.url_rule.rule: if g.admin: return True else: raise PermissionsException("Abaco Admin role required.") # the utilization endpoint is available to every authenticated user if '/actors/utilization' == request.url_rule.rule or '/actors/utilization/' == request.url_rule.rule: return True if '/actors/search/<string:search_type>' == request.url_rule.rule: return True # there are special rules on the actors root collection: if '/actors' == request.url_rule.rule or '/actors/' == request.url_rule.rule: logger.debug("Checking permissions on root collection.") # first, only admins can create/update actors to be privileged, so check that: if request.method == 'POST': check_privileged() # if we are here, it is either a GET or a new actor, so the request is allowed: logger.debug("new actor or GET on root connection. allowing request.") return True # aliases root collection has special rules as well - if '/actors/aliases' == request.url_rule.rule or '/actors/aliases/' == request.url_rule.rule: return True # request to a specific alias needs to check aliases permissions if '/actors/aliases' in request.url_rule.rule: alias_id = get_alias_id() noun = 'alias' # we need to compute the db_id since it is not computed in the general case for # alias endpoints db_id, _ = get_db_id() # reading/creating/updating nonces for an alias requires permissions for both the # alias itself and the underlying actor if 'nonce' in request.url_rule.rule: noun = 'alias and actor' # logger.debug("checking user {} has permissions for " # "alias: {} and actor: {}".format(g.user, alias_id, db_id)) if request.method == 'GET': # GET requests require READ access has_pem = check_permissions(user=g.user, identifier=alias_id, level=codes.READ) has_pem = has_pem and check_permissions( user=g.user, identifier=db_id, level=codes.READ) elif request.method in ['DELETE', 'POST', 'PUT']: has_pem = check_permissions(user=g.user, identifier=alias_id, level=codes.UPDATE) has_pem = has_pem and check_permissions( user=g.user, identifier=db_id, level=codes.UPDATE) # otherwise, this is a request to manage the alias itself; only requires permissions on the alias else: if request.method == 'GET': # GET requests require READ access has_pem = check_permissions(user=g.user, identifier=alias_id, level=codes.READ) # all other requests require UPDATE access elif request.method in ['DELETE', 'POST', 'PUT']: has_pem = check_permissions(user=g.user, identifier=alias_id, level=codes.UPDATE) else: # all other checks are based on actor-id: noun = 'actor' if request.method == 'GET': # GET requests require READ access has_pem = check_permissions(user=g.user, identifier=db_id, level=codes.READ) elif request.method == 'DELETE': has_pem = check_permissions(user=g.user, identifier=db_id, level=codes.UPDATE) else: logger.debug("URL rule in request: {}".format( request.url_rule.rule)) # first, only admins can create/update actors to be privileged, so check that: if request.method == 'POST' or request.method == 'PUT': check_privileged() # only admins have access to the workers endpoint, and if we are here, the user is not an admin: if 'workers' in request.url_rule.rule: raise PermissionsException( "Not authorized -- only admins are authorized to update workers." ) # POST to the messages endpoint requires EXECUTE if 'messages' in request.url_rule.rule: has_pem = check_permissions(user=g.user, identifier=db_id, level=codes.EXECUTE) # otherwise, we require UPDATE else: has_pem = check_permissions(user=g.user, identifier=db_id, level=codes.UPDATE) if not has_pem: logger.info("NOT allowing request.") raise PermissionsException( "Not authorized -- you do not have access to this {}.".format( noun))
def authorization(): """Entry point for authorization. Use as follows: import auth my_app = Flask(__name__) @my_app.before_request def authz_for_my_app(): auth.authorization() """ g.admin = False if request.method == 'OPTIONS': # allow all users to make OPTIONS requests logger.info("Allowing request because of OPTIONS method.") return True # the 'ALL' role is a role set by agaveflask in case the access_control_type is none if codes.ALL_ROLE in g.roles: g.admin = True logger.info("Allowing request because of ALL role.") return True # the admin role when JWT auth is configured: if codes.ADMIN_ROLE in g.roles: g.admin = True logger.info("Allowing request because of ADMIN_ROLE.") return True # all other requests require some kind of abaco role: if set(g.roles).isdisjoint(codes.roles): logger.info("NOT allowing request - user has no abaco role.") raise PermissionsException("Not authorized -- missing required role.") else: logger.debug("User has an abaco role.") logger.debug("URL rule: {}".format(request.url_rule.rule or 'actors/')) logger.debug("request.path: {}".format(request.path)) # there are special rules on the root collection: if '/actors' == request.url_rule.rule or '/actors/' == request.url_rule.rule: logger.debug("Checking permissions on root collection.") # first, only admins can create/update actors to be privileged, so check that: if request.method == 'POST': check_privileged() # if we are here, it is either a GET or a new actor, so the request is allowed: logger.debug("new actor or GET on root connection. allowing request.") return True # all other checks are based on actor-id: db_id = get_db_id() logger.debug("db_id: {}".format(db_id)) if request.method == 'GET': # GET requests require READ access has_pem = check_permissions(user=g.user, actor_id=db_id, level=codes.READ) elif request.method == 'DELETE': has_pem = check_permissions(user=g.user, actor_id=db_id, level=codes.UPDATE) else: logger.debug("URL rule in request: {}".format(request.url_rule.rule)) # first, only admins can create/update actors to be privileged, so check that: if request.method == 'POST' or request.method == 'PUT': check_privileged() # only admins have access to the workers endpoint, and if we are here, the user is not an admin: if 'workers' in request.url_rule.rule: raise PermissionsException( "Not authorized -- only admins are authorized to update workers." ) if request.method == 'POST': # POST to the messages endpoint requires EXECUTE if 'messages' in request.url_rule.rule: has_pem = check_permissions(user=g.user, actor_id=db_id, level=codes.EXECUTE) # otherwise, we require UPDATE else: has_pem = check_permissions(user=g.user, actor_id=db_id, level=codes.UPDATE) if not has_pem: logger.info("NOT allowing request.") raise PermissionsException( "Not authorized -- you do not have access to this actor.")