def validate_permission_set(self, permissions): defined_tokens = set(tolist(perm)[0] for perm in self.permissions) undefined_tokens = set(tolist(permissions)) - defined_tokens if undefined_tokens: raise Exception( f'permission(s) {undefined_tokens} not specified in the auth manager' )
def __init__(self, mail_manager=None, blueprint='auth', endpoints=None, cli_group_name=None, grid_cls=None, login_authenticator=KegAuthenticator, request_loaders=None, permissions=None, entity_registry=None, password_policy_cls=DefaultPasswordPolicy): self.mail_manager = mail_manager self.blueprint_name = blueprint self.entity_registry = entity_registry self.password_policy_cls = password_policy_cls self.endpoints = self.endpoints.copy() if endpoints: self.endpoints.update(endpoints) self.cli_group_name = cli_group_name or self.cli_group_name self.cli_group = None self.grid_cls = grid_cls self.login_authenticator_cls = login_authenticator self.request_loader_cls = tolist(request_loaders or []) self.request_loaders = dict() self.menus = dict() self.permissions = tolist(permissions or []) self._model_initialized = False self._loaders_initialized = False
def __init__(self, mail_manager=None, blueprint='auth', endpoints=None, cli_group_name=None, grid_cls=None, login_authenticator=KegAuthenticator, request_loaders=None, permissions=None, entity_registry=None, password_policy_cls=DefaultPasswordPolicy): """Set up an auth management extension Main manager for keg-auth authentication/authorization functions, and provides a central location and handle on the flask app to access CLI setup, navigation, authenticators, etc. :param mail_manager: AuthMailManager instance used for mail functions. Can be None. :param blueprint: name to use for the blueprint containing auth views :param endpoints: dict of overrides to auth view endpoints :param cli_group_name: name of the CLI group under which auth commands appear :param grid_cls: webgrid class to serve as a base class to auth CRUD grids :param login_authenticator: login authenticator class used by login view default: KegAuthenticator :param request_loaders: registered loaders used for loading a user at request time from information not contained in the session (e.g. with an authorization header token). Can be scalar or an iterable :param permissions: permission strings defined for the app, which will be synced to the database on app init. Can be a single string or an iterable :param entity_registry: EntityRegistry instance on which User, Group, etc. are registered :param password_policy_cls: A PasswordPolicy class to check password requirements in forms and CLI """ self.mail_manager = mail_manager self.blueprint_name = blueprint self.entity_registry = entity_registry self.password_policy_cls = password_policy_cls self.endpoints = self.endpoints.copy() if endpoints: self.endpoints.update(endpoints) self.cli_group_name = cli_group_name or self.cli_group_name self.cli_group = None self.grid_cls = grid_cls self.login_authenticator_cls = login_authenticator self.request_loader_cls = tolist(request_loaders or []) self.request_loaders = dict() self.menus = dict() self.permissions = tolist(permissions or []) self._model_initialized = False self._loaders_initialized = False
def begin(self): # Add test apps to import path sys.path.append(os.path.join(os.path.dirname(os.path.dirname(__file__)), 'tests', 'apps')) if self.val_disable: return # import here so we can avoid test coverage issues from blazeweb.events import signal from blazeweb.globals import ag, settings from blazeweb.hierarchy import findobj from blazeweb.scripting import load_current_app, UsageError try: _, _, _, wsgiapp = load_current_app(self.val_app_name, self.val_app_profile) # make the app available to the tests ag.wsgi_test_app = wsgiapp # an application can define functions to be called after the app # is initialized but before any test inspection is done or tests # are ran. We call those functions here: for callstring in tolist(settings.testing.init_callables): tocall = findobj(callstring) tocall() # we also support events for pre-test setup signal('blazeweb.pre_test_init').send() except UsageError: if self.val_debug: raise self.val_disable = True
def set(self, op, values, value2=None): self.default_op = self._default_op() if callable( self._default_op) else self._default_op if not op and not self.default_op: return self.op = op or self.default_op self.using_default_op = (self.default_op is not None) if self.using_default_op and op is None and self.default_value1 is not None: values = tolist(self._default_value(self.default_value1)) self.value1 = [] if values is not None: for v in values: try: v = self.process(v) if v is not _NoValue: self.value1.append(v) except formencode.Invalid: # A normal user should be selecting from the options given, # so if we encounter an Invalid exception, we are going to # assume the value is erronious and just ignore it pass # if there are no values after processing, the operator is irrelevent # and should be set to None so that it is as if the filter # is not set. # # however, we have to test the operator first, because if it is empty # or !empty, then it would make sense for self.value1 to be empty. if self.op in (ops.is_, ops.not_is) and not (self.value1 or self.default_op): self.op = None
def sync_permissions(app): with app.app_context(): db_permissions = db.session.query(Permission).all() # sync permission presence desired_tokens = set( tolist(perm)[0] for perm in app.auth_manager.permissions) current_tokens = { permission.token for permission in db_permissions } for permission in desired_tokens - current_tokens: db_permissions.append( Permission.add(token=permission, _commit=False)) for permission in current_tokens - desired_tokens: Permission.query.filter_by(token=permission).delete() # sync permission description permission_descriptions = dict([ perm for perm in app.auth_manager.permissions if isinstance(perm, (list, tuple)) ]) for db_permission in db_permissions: if (db_permission.token in permission_descriptions and db_permission.description != permission_descriptions[db_permission.token]): db_permission.description = permission_descriptions[ db_permission.token] elif (db_permission.token not in permission_descriptions and db_permission.description): db_permission.description = None db.session.commit()
def begin(self): # Add test apps to import path sys.path.append( os.path.join(os.path.dirname(os.path.dirname(__file__)), 'tests', 'apps')) if self.val_disable: return # import here so we can avoid test coverage issues from blazeweb.events import signal from blazeweb.globals import ag, settings from blazeweb.hierarchy import findobj from blazeweb.scripting import load_current_app, UsageError try: _, _, _, wsgiapp = load_current_app(self.val_app_name, self.val_app_profile) # make the app available to the tests ag.wsgi_test_app = wsgiapp # an application can define functions to be called after the app # is initialized but before any test inspection is done or tests # are ran. We call those functions here: for callstring in tolist(settings.testing.init_callables): tocall = findobj(callstring) tocall() # we also support events for pre-test setup signal('blazeweb.pre_test_init').send() except UsageError: if self.val_debug: raise self.val_disable = True
def set(self, op, values, value2=None): self.default_op = self._default_op() if callable(self._default_op) else self._default_op if not op and not self.default_op: return self.op = op or self.default_op self.using_default_op = (self.default_op is not None) if self.using_default_op and op is None and self.default_value1 is not None: values = tolist(self._default_value(self.default_value1)) self.value1 = [] if values is not None: for v in values: try: v = self.process(v) if v is not _NoValue: self.value1.append(v) except formencode.Invalid: # A normal user should be selecting from the options given, # so if we encounter an Invalid exception, we are going to # assume the value is erronious and just ignore it pass # if there are no values after processing, the operator is irrelevent # and should be set to None so that it is as if the filter # is not set. # # however, we have to test the operator first, because if it is empty # or !empty, then it would make sense for self.value1 to be empty. if self.op in (ops.is_, ops.not_is) and not (self.value1 or self.default_op): self.op = None
def _is_unique_error_saval(validation_errors): if not len(validation_errors): return False for field, error_msgs in validation_errors.items(): for err in tolist(error_msgs): if 'unique' not in err: return False return True
def _is_null_error_saval(validation_errors, field_name): if not len(validation_errors): return False for field, error_msgs in validation_errors.items(): if field != field_name: return False for err in tolist(error_msgs): if 'Please enter a value' != err: return False return True
def testing_create(cls, **kwargs): kwargs['password'] = kwargs.get('password') or randchars() if 'permissions' in kwargs: perm_cls = registry().permission_cls # ensure all of the tokens exists flask.current_app.auth_manager.validate_permission_set( list( filter(lambda perm: not isinstance(perm, perm_cls), tolist(kwargs['permissions'])))) kwargs['permissions'] = [ perm_cls.testing_create( token=perm) if not isinstance(perm, perm_cls) else perm for perm in tolist(kwargs['permissions']) ] user = super(UserMixin, cls).testing_create(**kwargs) user._plaintext_pass = kwargs['password'] return user
def create_user_with_permissions(approved_perms=None, denied_perms=None, super_user=False): from compstack.auth.model.orm import User, Permission appr_perm_ids = [] denied_perm_ids = [] # create the permissions for perm in tolist(approved_perms): p = Permission.get_by(name=perm) if p is None: raise ValueError('permission %s does not exist' % perm) appr_perm_ids.append(p.id) for perm in tolist(denied_perms): p = Permission.get_by(name=perm) if p is None: raise ValueError('permission %s does not exist' % perm) denied_perm_ids.append(p.id) # create the user username = u'user_for_testing_%s' % randchars(15) password = randchars(15) user = User.add(login_id=username, email_address=u'*****@*****.**' % username, password=password, super_user=super_user, assigned_groups=[], approved_permissions=appr_perm_ids, denied_permissions=denied_perm_ids) # turn login flag off user.reset_required = False db.sess.commit() # make text password available user.text_password = password return user
def testing_create(cls, **kwargs): kwargs['password'] = kwargs.get('password') or randchars() if 'permissions' in kwargs: perm_cls = registry().permission_cls kwargs['permissions'] = [ perm_cls.get_by_token(perm) if not isinstance(perm, perm_cls) else perm for perm in tolist(kwargs['permissions']) ] user = super(UserMixin, cls).testing_create(**kwargs) user._plaintext_pass = kwargs['password'] return user
def sync_permissions(app): with app.app_context(): db_permissions = db.session.query(Permission).all() # sync permission presence desired_tokens = set( tolist(perm)[0] for perm in app.auth_manager.permissions) current_tokens = { permission.token for permission in db_permissions } for permission in desired_tokens - current_tokens: db_permissions.append( Permission.add(token=permission, _commit=False)) for permission in current_tokens - desired_tokens: Permission.query.filter_by(token=permission).delete() # sync permission description permission_descriptions = dict([ perm for perm in app.auth_manager.permissions if isinstance(perm, (list, tuple)) ]) for db_permission in db_permissions: if (db_permission.token in permission_descriptions and db_permission.description != permission_descriptions[db_permission.token]): db_permission.description = permission_descriptions[ db_permission.token] elif (db_permission.token not in permission_descriptions and db_permission.description): db_permission.description = None try: db.session.commit() except sa.exc.IntegrityError as exc: # We have a possible race condition here, in that if another app process starts # while we are computing permission sync, we could get an integrity error. # Check that it is a unique exception, but note that we're not able to check # the constraint name here (too many assumptions to be made). from keg_elements.db.utils import validate_unique_exc if not validate_unique_exc(exc): raise
def pytest_configure(config): from blazeutils import tolist from blazeweb.events import signal from blazeweb.globals import ag, settings from blazeweb.hierarchy import findobj from blazeweb.scripting import load_current_app _, _, _, wsgiapp = load_current_app(config.getoption('blazeweb_package'), config.getoption('blazeweb_profile')) # make the app available to the tests ag.wsgi_test_app = wsgiapp # an application can define functions to be called after the app # is initialized but before any test inspection is done or tests # are ran. We call those functions here: for callstring in tolist(settings.testing.init_callables): tocall = findobj(callstring) tocall() # we also support events for pre-test setup signal('blazeweb.pre_test_init').send()
def __call__(self, environ, start_response): if self.enabled: self.headers = EnvironHeaders(environ) should_log = True if self.pi_filter is not None and self.pi_filter not in environ[ 'PATH_INFO']: should_log = False if self.rm_filter is not None and environ['REQUEST_METHOD'].lower( ) not in [x.lower() for x in tolist(self.rm_filter)]: should_log = False if should_log: wsgi_input = self.replace_wsgi_input(environ) fname = '%s_%s' % (time.time(), randchars()) fh = open(path.join(self.log_dir, fname), 'wb+') try: fh.write(pformat(environ)) fh.write('\n') fh.write(wsgi_input.read()) wsgi_input.seek(0) finally: fh.close() return self.application(environ, start_response)
def __call__(self, environ, start_response): if self.enabled: self.headers = EnvironHeaders(environ) should_log = True if self.pi_filter is not None and self.pi_filter not in environ['PATH_INFO']: should_log = False if self.rm_filter is not None and environ['REQUEST_METHOD'].lower() not in [ x.lower() for x in tolist(self.rm_filter) ]: should_log = False if should_log: wsgi_input = self.replace_wsgi_input(environ) fname = '%s_%s' % (time.time(), randchars()) fh = open(path.join(self.log_dir, fname), 'wb+') try: fh.write(pformat(environ)) fh.write('\n') fh.write(wsgi_input.read()) wsgi_input.seek(0) finally: fh.close() return self.application(environ, start_response)
def setup_class(cls): # these lines necessary because we are sharing test space with a Flask # app built with FlaskSQLAlchemy. That lib places listeners on the # session class but expects its own code will run to set up a session SASession._model_changes = mock.Mock() SASession.app = mock.Mock() _, _, _, wsgiapp = load_current_app('webgrid_blazeweb_ta', 'Test') # make the app available to the tests ag.wsgi_test_app = wsgiapp # an application can define functions to be called after the app # is initialized but before any test inspection is done or tests # are ran. We call those functions here: for callstring in tolist(settings.testing.init_callables): tocall = findobj(callstring) tocall() # we also support events for pre-test setup signal('blazeweb.pre_test_init').send() cls.ta = TestApp(ag.wsgi_test_app)
def run_tasks(tasks, print_call=True, test_only=False, *args, **kwargs): tasks = tolist(tasks) retval = OrderedDict() for task in tasks: log.application('task {}: starting'.format(task)) # split off the attribute if it is present: if ':' in task: task, attr = task.split(':', 1) # get the soft attribute flag if attr.startswith('~'): soft_attribute_matching = True attr = attr[1:] else: soft_attribute_matching = False else: attr = None # allow tasks to be defined with dashes, but convert to # underscore to follow file naming conventions underscore_task = task.replace('-', '_') collection = gatherobjs( 'tasks.%s' % underscore_task, lambda objname, obj: objname.startswith('action_')) callables = [] for modkey, modattrs in six.iteritems(collection): for actname, actobj in six.iteritems(modattrs): plus_exit = False callable_attrs = getattr(actobj, '__blazeweb_task_attrs', tuple()) # if callable has a "+" attribute for cattr in callable_attrs: if cattr.startswith('+') and cattr[1:] != attr: plus_exit = True break if plus_exit: continue # attribute given, callable is required to have it if attr is not None: if soft_attribute_matching: if '-' + attr in callable_attrs: continue elif attr not in callable_attrs and '+' + attr not in callable_attrs: continue # function name, module name, function object # we added module name as the second value for # sorting purposes, it gives us a predictable # order callables.append((actname, modkey, actobj, None)) retval[task] = [] for call_tuple in sorted(callables): if print_call is True: print('--- Calling: %s:%s ---' % (call_tuple[1], call_tuple[0])) if test_only: callable_retval = 'test_only=True' else: try: callable_retval = call_tuple[2]() except Exception as e: log.application('task {}: an exception occurred') ag.app.handle_exception(e) raise retval[task].append( (call_tuple[0], call_tuple[1], callable_retval)) log.application('task {}: completed'.format(task)) if print_call and test_only: print('*** NOTICE: test_only=True, no actions called ***') return retval
def run_tasks(tasks, print_call=True, test_only=False, *args, **kwargs): tasks = tolist(tasks) retval = OrderedDict() for task in tasks: log.application('task {}: starting'.format(task)) # split off the attribute if it is present: if ':' in task: task, attr = task.split(':', 1) # get the soft attribute flag if attr.startswith('~'): soft_attribute_matching = True attr = attr[1:] else: soft_attribute_matching = False else: attr = None # allow tasks to be defined with dashes, but convert to # underscore to follow file naming conventions underscore_task = task.replace('-', '_') collection = gatherobjs('tasks.%s' % underscore_task, lambda objname, obj: objname.startswith('action_')) callables = [] for modkey, modattrs in six.iteritems(collection): for actname, actobj in six.iteritems(modattrs): plus_exit = False callable_attrs = getattr(actobj, '__blazeweb_task_attrs', tuple()) # if callable has a "+" attribute for cattr in callable_attrs: if cattr.startswith('+') and cattr[1:] != attr: plus_exit = True break if plus_exit: continue # attribute given, callable is required to have it if attr is not None: if soft_attribute_matching: if '-' + attr in callable_attrs: continue elif attr not in callable_attrs and '+' + attr not in callable_attrs: continue # function name, module name, function object # we added module name as the second value for # sorting purposes, it gives us a predictable # order callables.append((actname, modkey, actobj, None)) retval[task] = [] for call_tuple in sorted(callables): if print_call is True: print('--- Calling: %s:%s ---' % (call_tuple[1], call_tuple[0])) if test_only: callable_retval = 'test_only=True' else: try: callable_retval = call_tuple[2]() except Exception as e: log.application('task {}: an exception occurred') ag.app.handle_exception(e) raise retval[task].append(( call_tuple[0], call_tuple[1], callable_retval )) log.application('task {}: completed'.format(task)) if print_call and test_only: print('*** NOTICE: test_only=True, no actions called ***') return retval