### Navigation

- index
- modules |
- next |
- previous |
- repoze.what 1.0.9 documentation »
- The `repoze.what` Manual »

# How to manage groups and permission sources

Overview

It’s possible for you to manage your groups and permissions under a source-independent API and here you will learn how to do it.

Source adapters enable you to retrieve information from your groups and permissions, as well as manage them, under an API absolutely independent of the source type. You may take advantage of this functionality to manage your sources from your own application or to write an application-independent front-end to manage groups and permissions in arbitrary WSGI applications using `repoze.what`.

This functionality will also enable you to switch from one back-end to another with no need to update your code (except for the part where you instance the source adapter).

## Managing your sources

Sources are managed from their respective adapters. For example, to manage the groups defined in a database, you can use:

    from repoze.what.plugins.sql import SqlGroupsAdapter
    from your_model import User, Group, DBSession

    groups = SqlGroupsAdapter(Group, User, DBSession)

Or to manage the permissions defined in an XML file, you could use:

    from repoze.what.plugins.xml import XMLGroupsAdapter

    permissions = XMLGroupsAdapter('/path/to/permissions.xml')

Tip

As of v1.0.1, you can re-use the same adapters used by `repoze.what` to control access. You will find them in the WSGI environment:

    # This is where repoze.what adapters are kept:
    adapters = environ['repoze.what.adapters']

    # Now let's extract the group and permission adapters:
    group_adapters = adapters['groups']
    permission_adapters = adapters['permissions']

### Retrieving all the available sections from a source

To get all the groups from the group source above, you may use the code below, which will return a dictionary whose keys are the name of the groups and the items are the username of the users that belong to such groups:

    >>> groups.get_all_sections()
    {u'admins': set([u'gustavo', u'adolfo']), u'developers': set([u'narea'])}

And to get all the permissions from the permission source above, you may use the code below, which will return a dictionary whose keys are the name of the permissions and the items are the name of the groups that are granted such permissions:

    >>> permissions.get_all_sections()
    {u'upload-images': set([u'admins', u'developers']), u'write-post': set()}

### Retrieving all the items from a given section

To get all the users that belong to a given group in the group source above, you may use:

    >>> groups.get_section_items(u'admins')
    set([u'gustavo', u'adolfo'])

And to get all the groups that are granted a given permission in the permission source above:

    >>> permissions.get_section_items(u'upload-images')
    set([u'admins', u'developers'])

### Setting the items of a given section

To set the members of a given group in the group source above, you may use:

    >>> groups.set_section_items(u'admins', [u'rms', u'guido'])

And to set the groups that are granted a given permission in the permission source above:

    >>> permissions.set_section_items(u'write-post', [u'admins'])

Warning

`set_section_items` will override the previous set of items. See, for example:

    >>> groups.get_all_sections()
    {u'admins': set([u'gustavo', u'adolfo']), u'developers': set([u'narea'])}
    >>> groups.set_section_items(u'admins', [u'rms', u'guido'])
    >>> groups.get_all_sections()
    {u'admins': set([u'rms', u'guido']), u'developers': set([u'narea'])}

### Including items in a section

To add one the item to a given group of the group source above, you may use:

    >>> groups.include_item(u'admins', u'rms')

Or to include many users at once:

    >>> groups.include_items(u'admins', [u'rms', u'guido'])

And to grant a given permission to one group in the permission source above:

    >>> permissions.include_item(u'write-post', u'admins')

Or to grant the same permission to many groups at once:

    >>> permissions.include_items(u'write-post', [u'admins', u'developers'])

### Excluding items from a section

To remove one the items from a given group of the group source above, you may use:

    >>> groups.exclude_item(u'admins', u'gustavo')

Or to exclude many items at once:

    >>> groups.exclude_items(u'admins', [u'gustavo', u'adolfo'])

And to deny a given permission to one group in the permission source above:

    >>> permissions.exclude_item(u'upload-images', u'developers')

Or to grant the same permission to many groups at once:

    >>> permissions.exclude_items(u'upload-images', [u'admins', u'developers'])

### Adding a section to a source

To create a group in the group source above, you may use:

    >>> groups.create_section(u'designers')

And to create a permission in the permission source above:

    >>> permissions.create_section(u'edit-post')

### Renaming a section

To rename a group in the group source above, you may use:

    >>> groups.edit_section(u'designers', u'graphic-designers')

And to rename a permission in the permission source above:

    >>> permissions.edit_section(u'write-post', u'create-post')

### Removing a section from a source

To remove a group from the group source above, you may use:

    >>> groups.delete_section(u'developers')

And to remove a permission from the permission source above:

    >>> permissions.delete_section(u'write-post')

### Checking whether the source is writable

Some adapters may not support writting the source, or some source types may be read-only (e.g., a source served over HTTP), or some source types may be writable but the current source itself may be read-only (e.g., a read-only file). For this reason, you should check whether you can write to the source – You will get a `SourceError` exception if you try to write to a read-only source.

To check whether the group source above is writable, you may use:

    >>> groups.is_writable
    True

And to check whether the permission source above is writable:

    >>> permissions.is_writable
    False

## Possible problems

While dealing with an adapter, the following exceptions may be raised if an error occurs:

exception `repoze.what.adapters.``AdapterError`

  Base exception for problems in the source adapters.

  It’s never raised directly.

exception `repoze.what.adapters.``SourceError`

  Exception for problems with the source itself.

  Attention

  If you are creating a source adapter, this is the only exception you should raise.

exception `repoze.what.adapters.``ExistingSectionError`

  Exception raised when trying to add an existing group.

exception `repoze.what.adapters.``NonExistingSectionError`

  Exception raised when trying to use a non-existing group.

exception `repoze.what.adapters.``ItemPresentError`

  Exception raised when trying to add an item to a group that already contains it.

exception `repoze.what.adapters.``ItemNotPresentError`

  Exception raised when trying to remove an item from a group that doesn’t contain it.

## Writing your own source adapters

Note

It’s very unlikely that you’ll want to write a source adapter, so if you get bored reading this section, it’s absolutely safe for you to skip it and come back later if you ever need to create an adapter.

Both group and permission adapters must extend the abstract class `BaseSourceAdapter`:

class `repoze.what.adapters.``BaseSourceAdapter`(writable=True)

  Base class for source adapters.

  Please note that these abstract methods may only raise one exception: `SourceError`, which is raised if there was a problem while dealing with the source. They may not raise other exceptions because they should not validate anything but the source (not even the parameters they get).

  `is_writable = True`

    Type : | bool

    Whether the adapter can write to the source.

    If the source type handled by your adapter doesn’t support write access, or if your adapter itself doesn’t support writting to the source (yet), then you should set this value to `False` in the class itself; it will get overriden if the `writable` parameter in `the contructor` is set, unless you explicitly disable that parameter:

        # ...
        class MyFakeAdapter(BaseSourceAdapter):
            def __init__():
                super(MyFakeAdapter, self).__init__(writable=False)
        # ...

    Note

    If it’s `False`, then you don’t have to define the methods that modify the source because they won’t be used:

    - `_include_items()`
    - `_exclude_items()`
    - `_create_section()`
    - `_edit_section()`
    - `_delete_section()`

  Warning

  Do not ever cache the results – that is `BaseSourceAdapter`‘s job. It requests a given datum once, not multiple times, thanks to its internal cache.

  `__init__`(writable=True)

    Run common setup for source adapters.

    Parameters: | writable (bool) – Whether the source is writable.

  `_get_all_sections`()

    Return all the sections found in the source.

    Returns: | All the sections found in the source.
    Return type: | dict
    Raises SourceError:
    | If there was a problem with the source while retrieving the sections.

  `_get_section_items`(section)

    Return the items of the section called `section`.

    Parameters: | section (unicode) – The name of the section to be fetched.
    Returns: | The items of the section.
    Return type: | set
    Raises SourceError:
    | If there was a problem with the source while retrieving the section.

    Attention

    When implementing this method, don’t check whether the section really exists; that’s already done when this method is called.

  `_find_sections`(hint)

    Return the sections that meet a given criteria.

    Parameters: | hint (dict or unicode) – repoze.what’s credentials dictionary or a group name.
    Returns: | The sections that meet the criteria.
    Return type: | tuple
    Raises SourceError:
    | If there was a problem with the source while retrieving the sections.

    This method depends on the type of adapter that is implementing it:

    - If it’s a `group` source adapter, it returns the groups the authenticated user belongs to. In this case, hint represents repoze.what’s credentials dict. Please note that hint is not an user name because some adapters may need something else to find the groups the authenticated user belongs to. For example, LDAP adapters need the full Distinguished Name (DN) in the credentials dict, or a given adapter may only need the email address, so the user name alone would be useless in both situations.
    - If it’s a `permission` source adapter, it returns the name of the permissions granted to the group in question; here hint represents the name of such a group.
  `_include_items`(section, items)

    Add `items` to the `section`, in the source.

    Parameters:

    - section (unicode) – The section to contain the items.
    - items (tuple) – The new items of the section.

    Raises SourceError:
    | If the items could not be added to the section.

    Attention

    When implementing this method, don’t check whether the section really exists or the items are already included; that’s already done when this method is called.

  `_exclude_items`(section, items)

    Remove `items` from the `section`, in the source.

    Parameters:

    - section (unicode) – The section that contains the items.
    - items (tuple) – The items to be removed from section.

    Raises SourceError:
    | If the items could not be removed from the section.

    Attention

    When implementing this method, don’t check whether the section really exists or the items are already included; that’s already done when this method is called.

  `_item_is_included`(section, item)

    Check whether `item` is included in `section`.

    Parameters:

    - section (unicode) – The name of the item to look for.
    - section – The name of the section that may include the item.

    Returns: | Whether the item is included in section or not.
    Return type: | bool
    Raises SourceError:
    | If there was a problem with the source.

    Attention

    When implementing this method, don’t check whether the section really exists; that’s already done when this method is called.

  `_create_section`(section)

    Add `section` to the source.

    Parameters: | section (unicode) – The section name.
    Raises SourceError:
    | If the section could not be added.

    Attention

    When implementing this method, don’t check whether the section already exists; that’s already done when this method is called.

  `_edit_section`(section, new_section)

    Edit `section`‘s properties.

    Parameters:

    - section (unicode) – The current name of the section.
    - new_section (unicode) – The new name of the section.

    Raises SourceError:
    | If the section could not be edited.

    Attention

    When implementing this method, don’t check whether the section really exists; that’s already done when this method is called.

  `_delete_section`(section)

    Delete `section`.

    It removes the `section` from the source.

    Parameters: | section (unicode) – The name of the section to be deleted.
    Raises SourceError:
    | If the section could not be deleted.

    Attention

    When implementing this method, don’t check whether the section really exists; that’s already done when this method is called.

  `_section_exists`(section)

    Check whether `section` is defined in the source.

    Parameters: | section (unicode) – The name of the section to check.
    Returns: | Whether the section is the defined in the source or not.
    Return type: | bool
    Raises SourceError:
    | If there was a problem with the source.

### Sample source adapters

The following class illustrates how a group adapter may look like:

    from repoze.what.adapters import BaseSourceAdapter

    class FakeGroupSourceAdapter(BaseSourceAdapter):
        """Mock group source adapter"""

        def __init__(self, *args, **kwargs):
            super(FakeGroupSourceAdapter, self).__init__(*args, **kwargs)
            self.fake_sections = {
                u'admins': set([u'rms']),
                u'developers': set([u'rms', u'linus']),
                u'trolls': set([u'sballmer']),
                u'python': set(),
                u'php': set()
                }

        def _get_all_sections(self):
            return self.fake_sections

        def _get_section_items(self, section):
            return self.fake_sections[section]

        def _find_sections(self, credentials):
            username = credentials['repoze.what.userid']
            return set([n for (n, g) in self.fake_sections.items()
                        if username in g])

        def _include_items(self, section, items):
            self.fake_sections[section] |= items

        def _exclude_items(self, section, items):
            for item in items:
                self.fake_sections[section].remove(item)

        def _item_is_included(self, section, item):
            return item in self.fake_sections[section]

        def _create_section(self, section):
            self.fake_sections[section] = set()

        def _edit_section(self, section, new_section):
            self.fake_sections[new_section] = self.fake_sections[section]
            del self.fake_sections[section]

        def _delete_section(self, section):
            del self.fake_sections[section]

        def _section_exists(self, section):
            return self.fake_sections.has_key(section)

And the following class illustrates how a permission adapter may look like:

    from repoze.what.adapters import BaseSourceAdapter

    class FakePermissionSourceAdapter(BaseSourceAdapter):
        """Mock permissions source adapter"""

        def __init__(self, *args, **kwargs):
            super(FakePermissionSourceAdapter, self).__init__(*args, **kwargs)
            self.fake_sections = {
                u'see-site': set([u'trolls']),
                u'edit-site': set([u'admins', u'developers']),
                u'commit': set([u'developers'])
                }

        def _get_all_sections(self):
            return self.fake_sections

        def _get_section_items(self, section):
            return self.fake_sections[section]

        def _find_sections(self, group_name):
            return set([n for (n, p) in self.fake_sections.items()
                        if group_name in p])

        def _include_items(self, section, items):
            self.fake_sections[section] |= items

        def _exclude_items(self, section, items):
            for item in items:
                self.fake_sections[section].remove(item)

        def _item_is_included(self, section, item):
            return item in self.fake_sections[section]

        def _create_section(self, section):
            self.fake_sections[section] = set()

        def _edit_section(self, section, new_section):
            self.fake_sections[new_section] = self.fake_sections[section]
            del self.fake_sections[section]

        def _delete_section(self, section):
            del self.fake_sections[section]

        def _section_exists(self, section):
            return self.fake_sections.has_key(section)

### Testing your source adapters with `testutil`

`repoze.what` provides convenient utilities to automate the verification of your adapters. This utility is the `repoze.what.adapters.testutil` module, made up four test cases, which when extended must define the adapter (as `self.adapter`) in the setup, as well as call this class’ `setUp()` method:

class `repoze.what.adapters.testutil.``ReadOnlyGroupsAdapterTester`

  Test case for read-only groups source adapters.

  The groups source used for the tests must only contain the following groups (aka “sections”) and their relevant users (aka “items”; if any):

  - admins
      - rms
  - developers
      - rms
      - linus
  - trolls
      - sballmer

  - python

  - php

  `adapter`

    An instance of the group adapter to be tested.

  For example, a test case for the mock group adapter `FakeReadOnlyGroupSourceAdapter` may look like this:

      from repoze.what.adapters.testutil import ReadOnlyGroupsAdapterTester

      class TestReadOnlyGroupsAdapterTester(ReadOnlyGroupsAdapterTester,
                                            unittest.TestCase):
          def setUp(self):
              super(TestReadOnlyGroupsAdapterTester, self).setUp()
              self.adapter = FakeReadOnlyGroupSourceAdapter()

  Note

  `GroupsAdapterTester` extends this test case to check write operations.

class `repoze.what.adapters.testutil.``ReadOnlyPermissionsAdapterTester`

  Test case for read-only permissions source adapters.

  The permissions source used for the tests must only contain the following permissions (aka “sections”) and their relevant groups (aka “items”; if any):

  - see-site
      - trolls
  - edit-site
      - admins
      - developers
  - commit
      - developers
  `adapter`

    An instance of the permission adapter to be tested.

  For example, a test case for the mock permission adapter defined above (`FakeReadOnlyPermissionSourceAdapter`) may look like this:

      from repoze.what.adapters.testutil import ReadOnlyPermissionsAdapterTester

      class TestReadOnlyPermissionsAdapterTester(ReadOnlyPermissionsAdapterTester,
                                                 unittest.TestCase):
          def setUp(self):
              super(TestReadOnlyPermissionsAdapterTester, self).setUp()
              self.adapter = FakeReadOnlyPermissionSourceAdapter()

  Note

  `PermissionsAdapterTester` extends this test case to check write operations.

class `repoze.what.adapters.testutil.``GroupsAdapterTester`

  Test case for groups source adapters.

  This test case extends `ReadOnlyGroupsAdapterTester` to test write operations in read & write adapters and it should be set up the same way as its parent. For example, a test case for the mock group adapter `FakeGroupSourceAdapter` may look like this:

      from repoze.what.adapters.testutil import GroupsAdapterTester

      class TestGroupsAdapterTester(GroupsAdapterTester, unittest.TestCase):
          def setUp(self):
              super(TestGroupsAdapterTester, self).setUp()
              self.adapter = FakeGroupSourceAdapter()

class `repoze.what.adapters.testutil.``PermissionsAdapterTester`

  Test case for permissions source adapters.

  This test case extends `ReadOnlyPermissionsAdapterTester` to test write operations in read & write adapters and it should be set up the same way as its parent. For example, a test case for the mock group adapter `FakePermissionSourceAdapter` may look like this:

  For example, a test case for the mock permission adapter defined above (`FakePermissionSourceAdapter`) may look like this:

      from repoze.what.adapters.testutil import PermissionsAdapterTester

      class TestPermissionsAdapterTester(PermissionsAdapterTester,
                                         unittest.TestCase):
          def setUp(self):
              super(TestPermissionsAdapterTester, self).setUp()
              self.adapter = FakePermissionSourceAdapter()

Attention

`repoze.what.adapters.testutil` is not a full replacement for a test suite, so you are still highly encouraged to write the relevant/missing tests to lead the code coverage of your adapters to 100%.

### Navigation

- index
- modules |
- next |
- previous |
- repoze.what 1.0.9 documentation »
- The `repoze.what` Manual »