def checkBasicOps(self):
        from persistent.mapping import PersistentMapping
        m = PersistentMapping({'x': 1}, a=2, b=3)
        m['name'] = 'bob'
        self.assertEqual(m['name'], "bob")
        self.assertEqual(m.get('name', 42), "bob")
        self.assert_('name' in m)

        try:
            m['fred']
        except KeyError:
            pass
        else:
            self.fail("expected KeyError")
        self.assert_('fred' not in m)
        self.assertEqual(m.get('fred'), None)
        self.assertEqual(m.get('fred', 42), 42)

        keys = m.keys()
        keys.sort()
        self.assertEqual(keys, ['a', 'b', 'name', 'x'])

        values = m.values()
        values.sort()
        self.assertEqual(values, [1, 2, 3, 'bob'])

        items = m.items()
        items.sort()
        self.assertEqual(items,
                         [('a', 2), ('b', 3), ('name', 'bob'), ('x', 1)])

        keys = list(m.iterkeys())
        keys.sort()
        self.assertEqual(keys, ['a', 'b', 'name', 'x'])

        values = list(m.itervalues())
        values.sort()
        self.assertEqual(values, [1, 2, 3, 'bob'])

        items = list(m.iteritems())
        items.sort()
        self.assertEqual(items,
                         [('a', 2), ('b', 3), ('name', 'bob'), ('x', 1)])
Exemple #2
0
    def checkBasicOps(self):
        from persistent.mapping import PersistentMapping
        m = PersistentMapping({'x': 1}, a=2, b=3)
        m['name'] = 'bob'
        self.assertEqual(m['name'], "bob")
        self.assertEqual(m.get('name', 42), "bob")
        self.assert_('name' in m)

        try:
            m['fred']
        except KeyError:
            pass
        else:
            self.fail("expected KeyError")
        self.assert_('fred' not in m)
        self.assertEqual(m.get('fred'), None)
        self.assertEqual(m.get('fred', 42), 42)

        keys = m.keys()
        keys.sort()
        self.assertEqual(keys, ['a', 'b', 'name', 'x'])

        values = m.values()
        values.sort()
        self.assertEqual(values, [1, 2, 3, 'bob'])

        items = m.items()
        items.sort()
        self.assertEqual(items,
                         [('a', 2), ('b', 3), ('name', 'bob'), ('x', 1)])

        keys = list(m.iterkeys())
        keys.sort()
        self.assertEqual(keys, ['a', 'b', 'name', 'x'])

        values = list(m.itervalues())
        values.sort()
        self.assertEqual(values, [1, 2, 3, 'bob'])

        items = list(m.iteritems())
        items.sort()
        self.assertEqual(items,
                         [('a', 2), ('b', 3), ('name', 'bob'), ('x', 1)])
    def checkBasicOps(self):
        from persistent.mapping import PersistentMapping

        m = PersistentMapping({"x": 1}, a=2, b=3)
        m["name"] = "bob"
        self.assertEqual(m["name"], "bob")
        self.assertEqual(m.get("name", 42), "bob")
        self.assert_("name" in m)

        try:
            m["fred"]
        except KeyError:
            pass
        else:
            self.fail("expected KeyError")
        self.assert_("fred" not in m)
        self.assertEqual(m.get("fred"), None)
        self.assertEqual(m.get("fred", 42), 42)

        keys = m.keys()
        keys.sort()
        self.assertEqual(keys, ["a", "b", "name", "x"])

        values = m.values()
        values.sort()
        self.assertEqual(values, [1, 2, 3, "bob"])

        items = m.items()
        items.sort()
        self.assertEqual(items, [("a", 2), ("b", 3), ("name", "bob"), ("x", 1)])

        keys = list(m.iterkeys())
        keys.sort()
        self.assertEqual(keys, ["a", "b", "name", "x"])

        values = list(m.itervalues())
        values.sort()
        self.assertEqual(values, [1, 2, 3, "bob"])

        items = list(m.iteritems())
        items.sort()
        self.assertEqual(items, [("a", 2), ("b", 3), ("name", "bob"), ("x", 1)])
Exemple #4
0
class PatchedDirectory (FileMeta):

    ftype = fs.DIRECTORY

    EXISTING = 0
    ADDED    = 1
    REMOVED  = 2
    
    def __init__(self, name, parent, uid, gid, mode, mtime_ns):
        FileMeta.__init__(self, name, parent, uid, gid, mode, mtime_ns)
        
        self.adds         = PersistentMapping()
        self.removes      = PersistentMapping()
        self.meta_changes = PersistentMapping() # name => ((to_uid, to_gid, to_mode), (from_uid, from_gid, from_mode))

        self.subdirs      = PersistentMapping()

        self.state        = PatchedDirectory.EXISTING

    def pdbg(self, indent = ''):
        s = { 0 : 'EXISTING', 1 : 'ADDED', 2 : 'REMOVED' }
        
        print indent, 'Dir %s %s' % (s[self.state], self.dbg_meta())
        
        if self.adds:
            print indent, ' ** Adds **'
            for v in self.adds.itervalues():
                v.pdbg( indent + '   ' )
        if self.removes:
            print indent, ' ** Removes **'
            for v in self.removes.itervalues():
                v.pdbg( indent + '   ' )

        if self.meta_changes:
            print indent, ' ** Meta Mods **'
            for k, v in self.removes.iteritems():
                print indent + '   ', k, v

        for s in self.subdirs.itervalues():
            s.pdbg( indent + '   ' )
Exemple #5
0
class Book( Persistent ):
    ''' This is a user book to include user's orders, one per user '''
    def __init__(self, user=None):
        self.user = user
        self.orders = PersistentList()
        self.positions = PersistentMapping()

    def addOrder(self, order):
        if order.instrument in self.positions:
            self.positions[order.instrument].append(order)
        else:
            self.positions[order.instrument] = PersistentList(order)

    def cost(self):
        cost=0.0
        for orders in self.positions.itervalues():
            cost += sum([o.cost() for o in orders])
        return

    def marketValue(self):
        mktval = 0.0
        for inst, orders in self.positions.iteritems():
            mktval += inst.marketPrice * sum([o.amountMatched for o in orders])
        return mktval
Exemple #6
0
class Book( Persistent ):
    ''' This is a user book to include user's orders, one per user '''
    def __init__(self, user=None):
        self.user = user
        self.orders = PersistentList()
        self.positions = PersistentMapping()

    def addOrder(self, order):
        if order.instrument in self.positions:
            self.positions[order.instrument].append(order)
        else:
            self.positions[order.instrument] = PersistentList(order)

    def cost(self):
        cost=0.0
        for orders in self.positions.itervalues():
            cost += sum([o.cost() for o in orders])
        return

    def marketValue(self):
        mktval = 0.0
        for inst, orders in self.positions.iteritems():
            mktval += inst.marketPrice * sum([o.amountMatched for o in orders])
        return mktval
Exemple #7
0
class PasswordResetTool(SimpleItem):
    meta_type = 'Eionet Password Reset Tool'
    security = ClassSecurityInfo()
    icon = '++resource++eea.ldapadmin-www/eionet_password_reset_tool.gif'
    session_messages = SESSION_MESSAGES

    manage_options = (
        {'label': 'Configure', 'action': 'manage_edit'},
        {'label': 'View', 'action': ''},
    ) + SimpleItem.manage_options

    _render_template = TemplateRenderer(CommonTemplateLogic)

    def __init__(self, config={}):
        super(PasswordResetTool, self).__init__()
        self._config = PersistentMapping(config)
        self._tokens = PersistentMapping()

    security.declareProtected(view_management_screens, 'get_config')

    def get_config(self):
        return dict(self._config)

    security.declareProtected(view_management_screens, 'manage_edit')
    manage_edit = PageTemplateFile('zpt/pwreset_manage_edit', globals())
    manage_edit.ldap_config_edit_macro = ldap_config.edit_macro

    security.declareProtected(view_management_screens, 'manage_edit_save')

    def manage_edit_save(self, REQUEST):
        """ save changes to configuration """
        form = REQUEST.form
        new_config = ldap_config.read_form(form, edit=True)

        new_config['legacy_ldap_server'] = form.get('legacy_ldap_server', '')
        new_config['legacy_admin_dn'] = form.get('legacy_admin_dn', '')
        new_config['legacy_admin_pw'] = form.get('legacy_admin_pw', '')
        if not new_config['legacy_admin_pw']:
            del new_config['legacy_admin_pw']  # don't overwrite

        self._config.update(new_config)
        REQUEST.RESPONSE.redirect(self.absolute_url() + '/manage_edit')

    def _get_ldap_agent(self, bind=True):
        return ldap_config.ldap_agent_with_config(self._config, bind)

    def _predefined_filters(self):
        return sorted(self.objectValues([query.Query.meta_type]),
                      key=lambda ob: ob.getId())

    security.declareProtected(view, 'index_html')

    def index_html(self, REQUEST):
        """ view """
        email = REQUEST.get('email', '')
        options = {'email': email}
        return self._render_template('zpt/pwreset_index.zpt', **options)

    def _new_token(self, user_id):
        token = random_token()
        self._tokens[token] = TokenData(user_id, datetime.utcnow())
        return token

    def _send_token_email(self, addr_to, token, user_info):
        addr_from = "*****@*****.**"
        email_template = load_template('zpt/pwreset_token_email.zpt')
        expiration_time = datetime.utcnow() + timedelta(days=1)
        options = {
            'token_url': self.absolute_url() + "/confirm_email?token=" + token,
            'user_info': user_info,
            'context': self,
            'network_name': NETWORK_NAME,
            'expiration_time': expiration_time.strftime("%Y-%m-%d %H:%M:%S")
        }
        print options['token_url']
        message = MIMEText(email_template(**options).encode('utf-8'),
                           _charset='utf-8')
        message['From'] = addr_from
        message['To'] = addr_to
        message['Subject'] = "%s account password recovery" % NETWORK_NAME

        try:
            mailer = getUtility(IMailDelivery, name="Mail")
            mailer.send(addr_from, [addr_to], message.as_string())
        except ComponentLookupError:
            mailer = getUtility(IMailDelivery, name="naaya-mail-delivery")
            try:
                mailer.send(addr_from, [addr_to], message.as_string())
            except AssertionError:
                mailer.send(addr_from, [addr_to], message)

    security.declareProtected(view, 'ask_for_password_reset')

    def ask_for_password_reset(self, REQUEST=None, email=None):
        """ view """
        if REQUEST is None:
            REQUEST = self.REQUEST
        if not email:
            email = REQUEST.form['email']

        agent = self._get_ldap_agent()
        users = agent.search_user_by_email(email)   # , no_disabled=True)

        if users:
            # some people have multiple accounts; send mail for each account.
            for user_info in users:
                if user_info['status'] == 'disabled':
                    msg = "This email: %s belongs to a disabled account" % \
                        user_info['email']
                    _set_session_message(REQUEST, 'error', msg)
                    location = (
                        self.absolute_url() +
                        '/messages_html?msg=email-disabled')
                else:
                    token = self._new_token(user_info['id'])
                    log.info(
                        "Sending password recovery email to user %r at %r.",
                        user_info['id'], email)
                    self._send_token_email(email, token, user_info)

                    location = (self.absolute_url() +
                                '/messages_html?msg=email-sent')

        else:
            log.info("Requested password recovery with invalid email %r.",
                     email)
            msg = "Email address not found in database."
            _set_session_message(REQUEST, 'error', msg)
            location = self.absolute_url() + '/'

        if REQUEST:
            REQUEST.RESPONSE.redirect(location)

    security.declareProtected(view, 'messages_html')

    def messages_html(self, REQUEST):
        """ view """
        options = {
            'message-name': REQUEST.form['msg'],
        }
        return self._render_template('zpt/pwreset_message.zpt', **options)

    def _say_token_expired(self, REQUEST):
        msg = ("Password reset link is invalid, perhaps it has "
               "expired. Please try again.")
        _set_session_message(REQUEST, 'error', msg)
        location = self.absolute_url() + '/'
        REQUEST.RESPONSE.redirect(location)

    def _expire_tokens(self):
        expired = []
        cutoff_time = datetime.utcnow() - timedelta(days=1)
        for token, token_data in self._tokens.iteritems():
            if token_data.timestamp < cutoff_time:
                expired.append(token)
        for token in expired:
            log.info('Token %r expired.', token)
            del self._tokens[token]

    security.declareProtected(view, 'confirm_email')

    def confirm_email(self, REQUEST):
        """ view """

        token = REQUEST.form['token']
        self._expire_tokens()
        token_data = self._tokens.get(token, None)

        if token_data is None:
            return self._say_token_expired(REQUEST)

        options = {
            'token': token,
            'user_id': token_data.user_id,
        }
        return self._render_template('zpt/pwreset_new_password.zpt', **options)

    def reset_password(self, REQUEST):
        """ view """

        token = REQUEST.form['token']
        self._expire_tokens()
        token_data = self._tokens.get(token, None)

        if token_data is None:
            return self._say_token_expired(REQUEST)

        new_password = REQUEST.form['password']
        if new_password != REQUEST.form['password-confirm']:
            _set_session_message(REQUEST, 'error', "Passwords do not match.")
            location = self.absolute_url() + '/confirm_email?token=' + token

        else:
            log.info("Restting password for user %r with token %r",
                     token_data.user_id, token)
            agent = self._get_ldap_agent(bind=True)
            agent.set_user_password(token_data.user_id, None, new_password)
            del self._tokens[token]
            location = (self.absolute_url() +
                        '/messages_html?msg=password-reset')

        REQUEST.RESPONSE.redirect(location)

    security.declareProtected(view, 'can_edit_users')

    def can_edit_users(self):
        user = self.REQUEST.AUTHENTICATED_USER
        return bool(user.has_permission(eionet_edit_users, self))
Exemple #8
0
class PasswordResetTool(SimpleItem):
    meta_type = 'Eionet Password Reset Tool'
    security = ClassSecurityInfo()
    icon = '++resource++eea.ldapadmin-www/eionet_password_reset_tool.gif'
    session_messages = SESSION_MESSAGES

    manage_options = (
        {
            'label': 'Configure',
            'action': 'manage_edit'
        },
        {
            'label': 'View',
            'action': ''
        },
    ) + SimpleItem.manage_options

    _render_template = TemplateRenderer(CommonTemplateLogic)

    def __init__(self, config={}):
        super(PasswordResetTool, self).__init__()
        self._config = PersistentMapping(config)
        self._tokens = PersistentMapping()

    security.declareProtected(view_management_screens, 'get_config')

    def get_config(self):
        return dict(self._config)

    security.declareProtected(view_management_screens, 'manage_edit')
    manage_edit = PageTemplateFile('zpt/pwreset_manage_edit', globals())
    manage_edit.ldap_config_edit_macro = ldap_config.edit_macro

    security.declareProtected(view_management_screens, 'manage_edit_save')

    def manage_edit_save(self, REQUEST):
        """ save changes to configuration """
        form = REQUEST.form
        new_config = ldap_config.read_form(form, edit=True)

        new_config['legacy_ldap_server'] = form.get('legacy_ldap_server', '')
        new_config['legacy_admin_dn'] = form.get('legacy_admin_dn', '')
        new_config['legacy_admin_pw'] = form.get('legacy_admin_pw', '')
        if not new_config['legacy_admin_pw']:
            del new_config['legacy_admin_pw']  # don't overwrite

        self._config.update(new_config)
        REQUEST.RESPONSE.redirect(self.absolute_url() + '/manage_edit')

    def _get_ldap_agent(self, bind=True):
        return ldap_config.ldap_agent_with_config(self._config, bind)

    def _predefined_filters(self):
        return sorted(self.objectValues([query.Query.meta_type]),
                      key=lambda ob: ob.getId())

    security.declareProtected(view, 'index_html')

    def index_html(self, REQUEST):
        """ view """
        email = REQUEST.get('email', '')
        options = {'email': email}
        return self._render_template('zpt/pwreset_index.zpt', **options)

    def _new_token(self, user_id):
        token = random_token()
        self._tokens[token] = TokenData(user_id, datetime.utcnow())
        return token

    def _send_token_email(self, addr_to, token, user_info):
        addr_from = "*****@*****.**"
        email_template = load_template('zpt/pwreset_token_email.zpt')
        expiration_time = datetime.utcnow() + timedelta(days=1)
        options = {
            'token_url': self.absolute_url() + "/confirm_email?token=" + token,
            'user_info': user_info,
            'context': self,
            'network_name': NETWORK_NAME,
            'expiration_time': expiration_time.strftime("%Y-%m-%d %H:%M:%S")
        }
        print options['token_url']
        message = MIMEText(email_template(**options).encode('utf-8'),
                           _charset='utf-8')
        message['From'] = addr_from
        message['To'] = addr_to
        message['Subject'] = "%s account password recovery" % NETWORK_NAME

        try:
            mailer = getUtility(IMailDelivery, name="Mail")
            mailer.send(addr_from, [addr_to], message.as_string())
        except ComponentLookupError:
            mailer = getUtility(IMailDelivery, name="naaya-mail-delivery")
            try:
                mailer.send(addr_from, [addr_to], message.as_string())
            except AssertionError:
                mailer.send(addr_from, [addr_to], message)

    security.declareProtected(view, 'ask_for_password_reset')

    def ask_for_password_reset(self,
                               REQUEST=None,
                               email=None,
                               on_create=False):
        """ view """
        if REQUEST is None:
            REQUEST = self.REQUEST
        if not email:
            email = REQUEST.form['email']

        agent = self._get_ldap_agent()
        users = agent.search_user_by_email(email)  # , no_disabled=True)

        if users:
            # some people have multiple accounts; send mail for each account.
            for user_info in users:
                if user_info['status'] == 'disabled':
                    msg = "This email: %s belongs to a disabled account" % \
                        user_info['email']
                    _set_session_message(REQUEST, 'error', msg)
                    location = (self.absolute_url() +
                                '/messages_html?msg=email-disabled')
                else:
                    token = self._new_token(user_info['id'])
                    log.info(
                        "Sending password recovery email to user %r at %r.",
                        user_info['id'], email)
                    self._send_token_email(email, token, user_info)

                    location = (self.absolute_url() +
                                '/messages_html?msg=email-sent')

        else:
            log.info("Requested password recovery with invalid email %r.",
                     email)
            msg = "Email address not found in database."
            _set_session_message(REQUEST, 'error', msg)
            location = self.absolute_url() + '/'

        if REQUEST and not on_create:
            REQUEST.RESPONSE.redirect(location)

    security.declareProtected(view, 'messages_html')

    def messages_html(self, REQUEST):
        """ view """
        options = {
            'message-name': REQUEST.form['msg'],
        }
        return self._render_template('zpt/pwreset_message.zpt', **options)

    def _say_token_expired(self, REQUEST):
        msg = ("Password reset link is invalid, perhaps it has "
               "expired. Please try again.")
        _set_session_message(REQUEST, 'error', msg)
        location = self.absolute_url() + '/'
        REQUEST.RESPONSE.redirect(location)

    def _expire_tokens(self):
        expired = []
        cutoff_time = datetime.utcnow() - timedelta(days=1)
        for token, token_data in self._tokens.iteritems():
            if token_data.timestamp < cutoff_time:
                expired.append(token)
        for token in expired:
            log.info('Token %r expired.', token)
            del self._tokens[token]

    security.declareProtected(view, 'confirm_email')

    def confirm_email(self, REQUEST):
        """ view """

        token = REQUEST.form['token']
        self._expire_tokens()
        token_data = self._tokens.get(token, None)

        if token_data is None:
            return self._say_token_expired(REQUEST)

        options = {
            'token': token,
            'user_id': token_data.user_id,
        }
        return self._render_template('zpt/pwreset_new_password.zpt', **options)

    def reset_password(self, REQUEST):
        """ view """

        token = REQUEST.form['token']
        self._expire_tokens()
        token_data = self._tokens.get(token, None)

        if token_data is None:
            return self._say_token_expired(REQUEST)

        new_password = REQUEST.form['password']
        if new_password != REQUEST.form['password-confirm']:
            _set_session_message(REQUEST, 'error', "Passwords do not match.")
            location = self.absolute_url() + '/confirm_email?token=' + token

        else:
            log.info("Restting password for user %r with token %r",
                     token_data.user_id, token)
            agent = self._get_ldap_agent(bind=True)
            try:
                agent.set_user_password(token_data.user_id, None, new_password)
            except CONSTRAINT_VIOLATION, e:
                if e.message['info'] in [
                        'Password fails quality checking policy'
                ]:
                    try:
                        defaultppolicy = agent.conn.search_s(
                            'cn=defaultppolicy,ou=pwpolicies,o=EIONET,'
                            'l=Europe', SCOPE_BASE)
                        p_length = defaultppolicy[0][1]['pwdMinLength'][0]
                        message = '%s (min. %s characters)' % (
                            e.message['info'], p_length)
                    except NO_SUCH_OBJECT:
                        message = e.message['info']
                else:
                    message = e.message['info']
                _set_session_message(REQUEST, 'error', message)
                location = (self.absolute_url() + '/confirm_email?token=' +
                            token)
            else:
class Analysis(Persistent):
    """An Analysis object (and the referenced objects) stores all information the Ambrosia analysis found out.

    Attributes:
    * filename (str): the name of the APK
    * package (str): the package of the APK
    * start_time (datetime.datetime): when the ANANAS analysis started
    * end_time (datetime.datetime): when the ANANAS analysis ended
    * plugins (dict): the plugins (key) and wheter they were active during ANANAS analysis (value, bool)
    * hashes (dict): the hashes of the APK in the form {type: hash}

    Analysis also manages all (top-level) Events and Entities (see :class:`ambrosia_web.model.Event`,
    :class:`ambrosia_web.model.Entity`) and tries to optimize for searching performance.
    """
    def __init__(self):
        self.filename = None
        self.package = None
        self.start_time = None
        self.end_time = None

        self._entities = PersistentMapping()
        """contains all entities in the form of (simplified):
        {
            class_name: (
                [entitiy, ...],
                Binary tree of all entities (key: primary_identifier)
            )
        }
        """

        self._events = OOBTree.TreeSet()
        """contains all entities
        """

        self._event_index = PersistentMapping()
        """contains indices for events in the form of
        {
            class_name: {
                key: Binary Tree {
                        attribute_value: set(event, ...)
                    }
            }
        }
        """

        self.plugins = {}
        self.hashes = {}

    def add_entity(self, context, cls, *args):
        """Add an entity (alias for :func:`Analysis.get_entity`)
        """
        self.get_entity(context, cls, *args)

    def iter_entities(self, context, cls):
        """iterate all known entities of a specific class.

        Args:
            context (ambrosia_web.context.AmbrosiaContext): the current context
            cls (class): the class of the entity we are looking for
        """
        assert isinstance(context, ambrosia.context.AmbrosiaContext)
        assert issubclass(cls, Entity)

        if classname(cls) not in self._entities:
            return

        for entity in self._entities[classname(cls)][0]:
            yield entity

    def get_entity(self, context, cls, *args):
        """Search for a specific entity, if it does not exist, create a new one

        Args:
            context (ambrosia_web.context.AmbrosiaContext): the current context
            cls (class): the class of the entity we are looking for
            *args: the arguments that would construct an entity

        This method uses :func:`Entity.find` (of the specific entity class) to search for entities. This method
        receives a List of all known entitites of that class as well as a :class:`BTrees.OOBTree.BTree` also containing
        all entities (indexed by their *primary_identifier* to allow more efficient searching). Moreover this method
        relieves the \*args argument.

        The \*args argument contains all information that identifies a certain entity. This could be e.g. the IP address
        and the port of a server. Those values are passed to the find method. If the server is already known, the entity
        representing it is returned. If no such server entity exist a new one is created using those two parameters.

        This behaviour makes sure that multiple event referencing the same entity all have references to the exact same
        entity in memory.
        """
        assert isinstance(context, ambrosia.context.AmbrosiaContext)
        assert issubclass(cls, Entity)

        if classname(cls) not in self._entities:
            # self.entities contains a tuple 1. with all emlements and 2. with an BTree over the primary identifier
            self._entities[classname(cls)] = (PersistentList(), OOBTree.BTree())

        entity = self._entities[classname(cls)]

        res = cls.find(context, entity[0], entity[1], *args)

        if res is None:
            res = cls(context, *args)
            assert isinstance(res, Entity)
            entity[0].append(res)

            if res.primary_identifier not in entity[1]:
                entity[1][res.primary_identifier] = PersistentList()

            entity[1][res.primary_identifier].append(res)

        return res

    def iter_all_events(self, context, key=None, min_value=None, max_value=None, value=None):
        """Iterates over all event classes.

        Args:
            context (ambrosia_web.context.AmbrosiaContext): the current context
            key: the key we are searching for
            min_value: the minimum value
            max_value: the maximum value
            value: the specific value (to search for exactly one value)

        This method works just like :func:`Analysis.iter_events` but is not limited to a certain entity Type. Instead,
        it searches entitiy of all types.
        """
        for class_name, idx in self._event_index.iteritems():
            cls = get_class(class_name)
            assert issubclass(cls, Event)

            if key not in cls.indices:
                continue

            for evt in self.iter_events(context, cls, key, min_value, max_value, value):
                yield evt

    def iter_events(self, context, cls=None, key=None, min_value=None, max_value=None, value=None):
        """Iterates over all events matching specific conditions in an efficient manner.

        Args:
            context (ambrosia_web.context.AmbrosiaContext): the current context
            cls (class): the class of the events we are looking for
            key: the key we are searching for
            min_value: the minimum value
            max_value: the maximum value
            value: the specific value (to search for exactly one value)

        This method uses a internal indices to efficiently select specific events. Each event class defines attributes
        that should be indexed (:class:`Event`.indices). This class makes sure that those attributes can be searched for
        very fast.

        The method accepts the following combinations of argument:
        * nothing: return all events
        * *cls*: return all events of a specific class (inefficient)
        * *cls*, *key*, *min_value* and/or *max_value*: search for all events of a specific class where the attribute
        *key* is within the defined value constraints
        * *cls*, *key*, *value*: search all events of a specific class where the attribute *key* has the value *value*
        """
        assert isinstance(context, ambrosia.context.AmbrosiaContext)
        assert cls is None or issubclass(cls, Event)
        assert isinstance(key, str) or key is None

        if key is None:
            # no key -> just get all elements
            for el in self._events:
                # if cls is given: filter by class
                if cls is None or isinstance(el, cls):
                    yield el
        else:
            assert key in cls.indices, "key is not defined as index"

            if value is not None:
                # set min_value and max_value to value (if value is given)
                min_value, max_value = value, value

            class_name = classname(cls)

            # check if there is an index (there is none if no events of this class is known)
            if class_name in self._event_index and key in self._event_index[class_name]:

                # locate the index and use it
                for val, lst in self._event_index[class_name][key].iteritems(min_value, max_value):

                    # the value the index refers to is a list since multiple events may have the same value
                    for el in lst:
                        yield el

    def _event_index_set(self, evt, key):
        """Locates the index for a specific event and key (and creates it if it does not exist)
        """
        assert isinstance(key, str)
        assert isinstance(evt, Event)
        class_name = obj_classname(evt)

        if class_name not in self._event_index:
            self._event_index[class_name] = PersistentMapping()

        class_index = self._event_index[class_name]

        if key not in class_index:
            class_index[key] = OOBTree.BTree()

        key_index = class_index[key]

        key_val = getattr(evt, key)

        if key_val is None:
            return None

        ret = key_index.get(key_val)
        if ret is None:
            ret = OOBTree.OOTreeSet()
            key_index[key_val] = ret

        return ret

    def add_event(self, evt):
        """Add event and generate indices

        Args:
            evt (Event): the event to add

        .. warning::
            The indexed attributes of an event may not be altered after the event has been added (otherwise the indices
            are out of date). This means that only static values may be indexed.
        """
        assert isinstance(evt, Event)

        self._events.add(evt)

        for idx in getattr(evt.__class__, 'indices'):
            idxset = self._event_index_set(evt, idx)
            if idxset is not None:
                idxset.add(evt)

    def del_event(self, evt):
        """Delete event and update indices

        Args:
            evt (Event): the event to remove
        """
        assert isinstance(evt, Event)

        try:
            self._events.remove(evt)
        except ValueError:
            # event does not exist, that's fine too
            return

        for idx in getattr(evt.__class__, 'indices'):
            idxset = self._event_index_set(evt, idx)
            if idxset is not None:
                idxset.remove(evt)

    def to_serializeable(self):
        """Returns all results in a serializable form
        """
        for el in self._events:
            assert isinstance(el, Event)
            assert(el.start_ts > self.start_time)
            assert(el.start_ts < self.end_time)
            el.sort()

        events = list(self._events)
        events.sort(cmp=lambda x, y: x.cmp_by_time(y))

        entities = {}

        for entity_class, class_entities in self._entities.iteritems():
            for entity in class_entities[0]:
                assert isinstance(entity, Entity)
                entities[entity.primary_key] = entity.to_serializeable()

        return {
            'start_time': js_date(self.start_time),
            'end_time': js_date(self.end_time),
            'filename': self.filename,
            'package': self.package,
            'events': [e.to_serializeable() for e in events],
            'entities': entities
        }

    def adjust_times(self, context):
        """Goes through all events and calls adjust_times on all Events
        """
        assert isinstance(context, ambrosia.context.AmbrosiaContext)

        for el in self._events:
            el.adjust_times(context)