Mercurial > p > roundup > code
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') ...``) |
