view doc/security.txt @ 902:b0d3d3535998

Bugger it. Here's the current shape of the new security implementation. Still to do: . call the security funcs from cgi and mailgw . change shipped templates to include correct initialisation and remove the old config vars ... that seems like a lot. The bulk of the work has been done though. Honest :)
author Richard Jones <richard@users.sourceforge.net>
date Thu, 25 Jul 2002 07:14:06 +0000
parents 7d41d4dae378
children 502a5ae11cc5
line wrap: on
line source

===================
Security Mechanisms
===================

:Version: $Revision: 1.12 $

Current situation
=================

Current logical controls:

ANONYMOUS_ACCESS = 'deny'
 Deny or allow anonymous access to the web interface
ANONYMOUS_REGISTER = 'deny'
 Deny or allow anonymous users to register through the web interface
ANONYMOUS_REGISTER_MAIL = 'deny'
 Deny or allow anonymous users to register through the mail interface

Current user interface authentication and controls:

- command-line tool access controlled with passwords, but no logical controls
- CGI access is by username and password and has some logical controls
- mailgw access is through identification using sender email address, with
  limited functionality available

The web interface implements has specific logical controls,
preventing non-admin users from accessing:

 - other user's details pages
 - listing the base classes (not issues or their user page)
 - editing base classes

Issues
======

1. The current implementation is ad-hoc, and not complete for all `use cases`_.
2. Currently it is not possible to allow submission of issues through email
   but restrict those users from accessing the web interface.
3. Only one user may perform admin functions.
4. There is no verification of users in the mail gateway by any means other
   than the From address. Support for strong identification through digital
   signatures should be added.
5. The command-line tool has no logical controls.
6. The anonymous control needs revising - there should only be one way to be
   an anonymous user, not two (currently there is user==None and
   user=='anonymous).


Possible approaches
===================

Security controls in Roundup could be approached in three ways:

1) at the hyperdb level, with read/write/modify permissions on classes, nodes
   and node properties for all or specific transitions.
2) at the user interface level, with access permissions on CGI interface
   methods, mailgw methods, roundup-admin methods, and so on.
3) at a logical permission level, checked as needed.

In all cases, the security built into roundup assumes restricted access to the
hyperdatabase itself, through Operating System controls such as user or group
permissions.


Hyperdb-level control
---------------------

Control is implemented at the Class.get, Class.set and Class.create level. All
other methods must access nodes through these methods. Since all accesses go
through the database, we can implement deny by default.

Pros:

   - easier to implement as it only affects one module
   - smaller number of permissions to worry about

Cons:

   - harder to determine the relationship between user interaction and hyperdb
     permission.
   - a lot of work to define
   - must special-case to handle by-node permissions (editing user details,
     having private messages)


User-interface control
----------------------

The user interfaces would have an extra layer between that which
parses the request to determine action and the action method. This layer
controls access. Since it is possible to require methods be registered
with the security mechanisms to be accessed by the user, deny by default
is possible.

Pros:

   - much more obvious at the user level what the controls are

Cons:

   - much more work to implement
   - most user interfaces have multiple uses which can't be covered by a
     single permission


Logical control
---------------

At each point that requires an action to be performed, the security mechanisms
are asked if the current user has permission. Since code must call the
check function to raise a denial, there is no possibility to have automatic
default of deny in this situation.

Pros:

   - quite obvious what is going on
   - is very similar to the current system

Cons:

   - large number of possible permissions that may be defined, possibly
     mirroring actual user interface controls.
   - access to the hyperdb must be strictly controlled through program code
     that implements the logical controls.


Applying controls to users
==========================

Individual assignment of Permission to User is unwieldy. The concept of a
Role, which encompasses several Permissions and may be assigned to many Users,
is quite well developed in many projects. Roundup will take this path, and
allow the multiple assignment of Roles to Users, and multiple Permissions to
Roles. These definitions will be stored in the hyperdb. They don't need to be
pushed to the actual database though.

There will be two levels of Permission. The Class level permissions define
logical permissions associated with all nodes of a particular class (or all
classes). The Node level permissions define logical permissions associated
with specific nodes by way of their user-linked properties.

A security module defines::

    class InMemoryClass(hyperdb.Class):
        ''' Just be an in-memory class
        '''
        def __init__(self, db, classname, **properties):
            ''' Set up an in-memory store for the nodes of this class
            '''

        def create(self, **propvalues):
            ''' Create a new node in the in-memory store
            '''

        def get(self, nodeid, propname, default=_marker, cache=1):
            ''' Get the node from the in-memory store
            '''

        def set(self, *args):
            ''' Set values on the node
            '''

    class PermissionClass(InMemoryClass):
        ''' Include the default attributes:
            - name (String)
            - klass (String)
            - description (String)

            The klass may be unset, indicating that this permission is not
            locked to a particular class. That means there may be multiple
            Permissions for the same name for different classes.
        '''

    class RoleClass(InMemoryClass):
        ''' Include the default attributes:
            - name (String, key)
            - description (String)
            - permissions (PermissionClass Multilink)
        '''

    class Security:
        def __init__(self, db):
            ''' Initialise the permission and role classes, and add in the
                base roles (for admin user).
            '''

        def hasClassPermission(self, db, classname, permission, userid):
            ''' Look through all the Roles, and hence Permissions, and see if
                "permission" is there for the specified classname.

            '''

        def hasNodePermission(self, db, classname, nodeid, **propspec):
            ''' Check the named properties of the given node to see if the
                userid appears in them. If it does, then the user is granted
                this permission check.

                'propspec' consists of a set of properties and values that
                must be present on the given node for access to be granted.

                If a property is a Link, the value must match the property
                value. If a property is a Multilink, the value must appear
                in the Multilink list.
            '''

        def addPermission(self, **propspec):
            ''' Create a new Permission with the properties defined in
                'propspec'
            '''

        def addRole(self, **propspec):
            ''' Create a new Role with the properties defined in 'propspec'
            '''

        def addPermissionToRole(self, rolename, permissionid):
            ''' Add the permission to the role's permission list.

                'rolename' is the name of the role to add 'permissionid'.
            '''

Modules such as ``cgi_client.py`` and ``mailgw.py`` define their own
permissions like so (this example is ``cgi_client.py``)::

    def initialiseSecurity(security):
        ''' Create some Permissions and Roles on the security object

            This function is directly invoked by security.Security.__init__()
            as a part of the Security object instantiation.
        '''
        newid = security.addPermission(name="Web Registration",
            description="Anonymous users may register through the web")
        security.addToRole('Anonymous', newid)

The instance dbinit module then has in ``open()``::

    # open the database - it must be modified to init the Security class
    # from security.py as db.security
    db = Database(instance_config, name)

    # add some extra permissions and associate them with roles
    ei = db.security.addPermission(name="Edit", klass="issue",
                    description="User is allowed to edit issues")
    db.security.addPermissionToRole('User', ei)
    ai = db.security.addPermission(name="Assign", klass="issue",
                    description="User may be assigned to issues")
    db.security.addPermissionToRole('User', ei)

In the dbinit ``init()``::

    r = db.getclass('role').lookup('Admin')
    user.create(username="admin", password=Password(adminpw),
                address=instance_config.ADMIN_EMAIL, roles=[r])

    # choose your anonymous user access permission here
    #r = db.getclass('role').lookup('No Rego')
    r = db.getclass('role').lookup('User')
    user.create(username="anonymous", roles=[r])

Then in the code that matters, calls to ``hasClassPermission`` and
``hasNodePermission`` are made to determine if the user has permission
to perform some action::

    if db.security.hasClassPermission('issue', 'Edit', userid):
        # all ok

    if db.security.hasNodePermission('issue', nodeid, assignedto=userid):
        # all ok

Code in the core will make use of these methods, as should code in auditors in
custom templates. The htmltemplate will implement a new tag, ``<require>``
which has the form::

  <require permission="name,name,name" assignedto="$userid" status="open">
   HTML to display if the user has the permission.
  <else>
   HTML to display if the user does not have the permission.
  </require>

where:

- the permission attribute gives a comma-separated list of permission names.
  These are checked in turn using ``hasClassPermission`` and requires one to
  be OK.
- the other attributes are lookups on the node using ``hasNodePermission``. If
  the attribute value is "$userid" then the current user's userid is tested.

Any of these tests must pass or the ``<require>`` check will fail. The section
of html within the side of the ``<else>`` that fails is remove from processing.

Implementation as shipped
-------------------------

A set of Permissions are built in to the security module by default:

- Edit (everything)
- Access (everything)
- Assign (everything)

The default interfaces define:

- Web Registration
- Email Registration

These are hooked into the default Roles:

- Admin (Edit everything, Access everything, Assign everything)
- User ()
- Anonymous (Web Registration, Email Registration)

And finally, the "admin" user gets the "Admin" Role, and the "anonymous" user
gets the "Anonymous" assigned when the database is initialised on installation.
The two default schemas then define:

- Edit issue, Access issue (both)
- Edit support, Access support (extended only)

and assign those Permissions to the "User" Role.


Authentication of Users
-----------------------

Users must be authenticated correctly for the above controls to work. This is
not done in the current mail gateway at all. Use of digital signing of
messages could alleviate this problem.

The exact mechanism of registering the digital signature should be flexible,
with perhaps a level of trust. Users who supply their signature through their
first message into the tracker should be at a lower level of trust to those
who supply their signature to an admin for submission to their user details.


Anonymous Users
---------------

The "anonymous" user must always exist, and defines the access permissions for
anonymous users. The three ANONYMOUS_ configuration variables are subsumed by
this new functionality.


Action
======

The CGI interface must be changed to:

- authenticate over a secure connection
- use unique tokens as a result of authentication, rather than pass the user's
  real credentials (username/password) around for each request (this means
  sessions and hence a session database)
- use the new logical control mechanisms

  - implement the permission module
  - implement a Role editing interface for users
  - implement htmltemplate tests on permissions
  - switch all code over from using config vars for permission checks to using
    permissions
  - include config vars for initial Roles for anonymous web, new web and new
    email users

The mail gateway must be changed to:

- use digital signatures
- use the new logical control mechanisms

  - switch all code over from using config vars for permission checks to using
    permissions

The command-line tool must be changed to:

- use the new logical control mechanisms (only allowing write
  access by admin users, and read-only by everyone else)


Use cases
=========

public
  end users that can submit bugs, request new features, request support
developer
  developers that can fix bugs, implement new features provide support
manager
  approvers/managers that can approve new features and signoff bug fixes
admin
  administrators that can add users and set user's roles
system
  automated request handlers running various report/escalation scripts
privacy
  issues that are only visible to some users


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