comparison doc/reference.txt @ 8165:25950b620246

Merge permission-performance branch This fixes issue2551330: Add an optional 'filter' function to the Permission objects and the addPermission method. This is used to optimize search performance by not checking items returned from a database query one-by-one (using the check function) but instead offload the permission checks to the database. For SQL backends this performs the filtering in the database.
author Ralf Schlatterbeck <rsc@runtux.com>
date Tue, 26 Nov 2024 12:33:17 +0100
parents 0692740e9b56 dd60d604a852
children 7d72b9a9fe9c
comparison
equal deleted inserted replaced
8158:9fe279eef724 8165:25950b620246
1482 "``roundup-admin security``") 1482 "``roundup-admin security``")
1483 3. check it in the relevant HTML interface templates 1483 3. check it in the relevant HTML interface templates
1484 4. check it in the appropriate hasPermission methods in your tracker's 1484 4. check it in the appropriate hasPermission methods in your tracker's
1485 extensions/detectors/interfaces.py modules 1485 extensions/detectors/interfaces.py modules
1486 1486
1487 The ``addPermission`` method takes a three optional parameters: 1487 The ``addPermission`` method takes a four optional parameters:
1488 1488
1489 **check** 1489 **check**
1490 A function to be executed which returns boolean determining whether 1490 A function to be executed which returns boolean determining whether
1491 the Permission is allowed. If it returns True, the permission is 1491 the Permission is allowed. If it returns True, the permission is
1492 allowed, if it returns False the permission is denied. The function 1492 allowed, if it returns False the permission is denied. The function
1517 shows the use of ``ctx``. 1517 shows the use of ``ctx``.
1518 1518
1519 Note that the check option is not supported for the Search 1519 Note that the check option is not supported for the Search
1520 permission. When searching there is no item defined, so a check 1520 permission. When searching there is no item defined, so a check
1521 function does not make any sense. 1521 function does not make any sense.
1522
1523 **filter**
1524 This optional function returns parameters for the ``filter`` method
1525 when getting ``Class`` items (users, issues etc.) from the
1526 database. It filters items at the database level (using SQL where
1527 possible). This pre-filters the number of items returned from the
1528 database when displaying results in an ``index`` template. This
1529 filtering is usually faster than calling a ``check`` method (see
1530 previous argument) on *each individual result*.
1531
1532 The ``filter`` method has the signature::
1533
1534 filter(db, userid, klass)
1535
1536 where ``db`` is the database handle, ``userid`` is the user attempting
1537 access and ``klass`` is the ``Class`` in the schema.
1538
1539 The ``filter`` function must return a *list* of dictionaries of
1540 parameters of the
1541 `Class.filter <design.html#:~:text=search_matches>`_ call.
1542 This includes filterspec, retired and exact_match_specs.
1543 Note that sort and group parameters of the filter call should
1544 not be set by filter method (they will be overwritten) and the
1545 parameter search_matches must not be set.
1546
1547 The query executed by an index template is modified by the
1548 parameters computed by the ``filter`` function. An
1549 empty list of filter parameters (``[]``) indicates no access. When
1550 using a filter, a check function is still needed to test each
1551 individual item for visibility. When the filter function is defined
1552 but a check function is not defined, a check function is
1553 manufactured automatically from the ``filter`` function.
1554
1555 Note that the filter option is not supported for the Search
1556 permission. Since the filter function is called *after* the search was
1557 already performed a filter function does not make any sense.
1558
1559 An example ``filter`` function for the ``view_query`` check function
1560 in the query checks above would look like::
1561
1562 def filter_query(db, userid, klass):
1563 return [{'filterspec': {
1564 'private_for': ['-1', userid]
1565 }}]
1566
1567 This would be called by the framework for all queries found when
1568 displaying queries. It filters for all queries where the
1569 ``private_for`` field is the userid or empty. This matches the
1570 definition of the ``view_query`` function above where permission is
1571 granted if the ``private_for`` field indicates the query is owned by
1572 the user, or the ``private_for`` field is empty indicating that the
1573 query is public. If we want to modify the check to also allow acess if
1574 the user is the ``creator`` of a query we would change the filter
1575 function to::
1576
1577 def filter_query(db, userid, klass):
1578 f1 = {'filterspec': {'private_for': ['-1', userid]}}
1579 f2 = {'filterspec': {'creator': userid}}
1580 return [f1, f2]
1581
1582 This is an example where we need multiple filter calls to model an
1583 "or" condition, the user has access if either the ``private_for``
1584 check passes *or* the user is the creator of the query.
1585
1586 Consider an example where we have a class structure where both the
1587 ``issue`` class and the ``user`` class include a reference to an
1588 ``organization`` class. Users are permitted to view only those
1589 issues that are associated with their respective organizations. A
1590 check function or this could look like::
1591
1592 def view_issue(db, userid, itemid):
1593 user = db.user.getnode(userid)
1594 if not user.organisation:
1595 return False
1596 issue = db.issue.getnode(itemid)
1597 if user.organisation == issue.organisation:
1598 return True
1599
1600 The corresponding ``filter`` function::
1601
1602 def filter_issue(db, userid, klass):
1603 user = db.user.getnode(userid)
1604 if not user.organisation:
1605 return []
1606 return [{'filterspec': {
1607 'organisation': user.organisation
1608 }}]
1609
1610 This filters for all issues where the organisation is the same as the
1611 organisation of the user. Note how the filter fails early returning an
1612 empty list (meaning "no access") if the user happens to not have an
1613 organisation.
1522 1614
1523 **properties** 1615 **properties**
1524 A sequence of property names that are the only properties to apply the 1616 A sequence of property names that are the only properties to apply the
1525 new Permission to (eg. ``... klass='user', properties=('name', 1617 new Permission to (eg. ``... klass='user', properties=('name',
1526 'email') ...``) 1618 'email') ...``)

Roundup Issue Tracker: http://roundup-tracker.org/