Пример #1
0
 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'
         )
Пример #2
0
 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
Пример #3
0
    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
Пример #4
0
    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
Пример #5
0
    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
Пример #6
0
        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()
Пример #7
0
    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
Пример #8
0
    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
Пример #9
0
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
Пример #10
0
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
Пример #11
0
    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
Пример #12
0
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
Пример #13
0
    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
Пример #14
0
        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
Пример #15
0
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()
Пример #16
0
 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)
Пример #17
0
 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)
Пример #18
0
        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)
Пример #19
0
        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)
Пример #20
0
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
Пример #21
0
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