# Copyright (C) 1999 Milan Zamazal
#
# COPYRIGHT NOTICE
#
# This program is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the Free
# Software Foundation; either version 2, or (at your option) any later
# version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
# or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
# for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; see the file COPYING.  If not, write to the
# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
# Boston, MA 02111-1307, USA.


"""Access rights.

This module is an abstract layer of gathering identity information and
providing information about access rights to the bug tracking system.  Classes
of this module are supposed to be redefined by your own access classes.

When gathering identity of the accessor, the module classes are allowed to
check everything, including environment variables and configuration variables.
It is up to you what classes for what purposes you instantiate.  It is not care
of this module to choose proper accessing classes, e.g. for particular
connection type.

Identification of the accessor is composed from the three parts:

 identity -- key name as used in the `responsible' and `submitter' files
 name -- real human readable name
 email -- e-mail address

Generally, access classes provide three identity methods of corresponding
names.  Their return either the identity or return an empty string, if the
identity cannot be got.

Access rights are divided into three categories:

 submit -- right to submit new problem reports
 view -- viewing search results and individual problem reports
 edit -- editing problem reports

For each of this category, the basic rights can be:

 access denied -- no access allowed
 restricted access -- access to non-confidential problem reports only
 access granted -- full access to the problem

Corresponding constants are defined in the 'Access' class.  This class is a
basic abstract access.  It allows access to everything and returns empty
identity information.  It is supposed to be inherited by other clasess.

One of such classes is 'HTTPS'.  This is an identity class and gets identity
information of certificate based connections from the Apache environment.

Other class is 'Enumeration'.  It can be initialized with dictionary of
identities as keys and submit constants as values to classify access rights for
persons.

All classes are expected to have dynamic behavior, i.e. to check the identity
and access rights, when the appropriate method is called, not on
initialization.
"""

__author__  = "Milan Zamazal <pdm@freesoft.cz>"
__version__ = "$Id: access.py,v 1.7 1999/08/02 22:30:27 pdm Exp $"
__copyright__ = "GNU General Public License, version 2"


import os
import string

from gnats2w import mail


# Classes


class Access:
    """Access rights handling class.

    This class is very simple and is intended to be redefined.

    It defines the following constants:

     SUBMIT -- submit right
     VIEW -- search and view right
     EDIT -- edit right
     DENIED -- access denied
     RESTRICTED -- restricted access (non-confidential problems only)
     FULL -- full access granted

    Identity methods: 'identity', 'name', 'email'.

    Access methods: 'granted'.
    """

    SUBMIT = 'SUBMIT'
    VIEW = 'VIEW'
    EDIT = 'EDIT'

    DENIED = ''
    RESTRICTED = 'RESTRICTED'
    FULL = 'FULL'

    
    def identity (self):
        """Return identity of the accessor.

        It is an identifier as in 'responsible' or 'submitters' files.

        If identity cannot be found, return an empty string.
        """
        return ''


    def name (self):
        """Return human readable name of the accessor.

        If it cannot be found, return an empty string.
        """
        return ''


    def email (self):
        """Return e-mail of the accessor.

        If it cannot be found, return an empty string.
        """
        return ''


    def granted (self, access_type, *args):
        """Return whether access is granted for 'access_type'.

        'access_type' can be one of the following constants:

         SUBMIT -- Right to submit new problem reports.
         VIEW -- Right to search problem database and view individual problems.
         EDIT -- Right to edit problems.
        
        Return value:

         Access.DENIED (false) -- No access allowed.
         Access.RESTRICTED (true) -- Access to non-confidential problems
           allowed.
         Access.FULL (true) -- Full access allowed.
        """
        return self.FULL


class Email_Identity (Access):
    """This class gets identity from the e-mail address.
    """

    def identity (self):
        """Return identity based on a e-mail address.
        """
        return mail.user (self.email ())


class HTTPS (Access):
    """Get name and e-mail from SSL Apache variables.
    """

    def name (self):
        try:
            return os.environ['SSL_CLIENT_CN']
        except KeyError:
            return ''


    def email (self):
        try:
            return os.environ['SSL_CLIENT_EMAIL']
        except KeyError:
            return ''


class Enumeration (Access):
    """Define access by accessors enumeration.

    Access is defined by a dictionary with identities as keys and access
    definitions as values.

    Identity is defined by the 'identity' method of this class.
    """

    RESPONSIBLE = 'RESPONSIBLE'
    ORIGINATOR = 'ORIGINATOR'
    RELATED = 'RELATED'
    

    def __init__ (self, access_definition):
        """Initialize access definition.

        Format of the 'access_definition':

         - Keys are identities.
         
         - Values are sequences of triples
           (ACCESS, RESTRICTED_ALLOWED, SPECIAL), where the items mean:

           ACCESS -- One of the constants 'SUBMIT', 'VIEW', 'EDIT'.
           RESTRICTED_ALLOWED -- Iff true, access to restricted problems is
             allowed.
           SPECIAL -- One of the constants 'RESPONSIBLE' (allow access only for
             the responsible), 'ORIGINATOR' (allow access only for the
             originator), 'RELATED' (allow access only for the responsible or
             the originator), 'None' (allow access for all).

           Triples have no key, some their values can repeat.  The most liberal
           triple for particular type of access is considered.

        If the dictionary contains an item with an empty string as a key, it
        defines default access.  If there is no such an item, access is denied
        for accessors not presented in the dictionary.
        """
        self._definition = access_definition


    def granted (self, access_type, problem=None, id=None):
        """Decide access based on access definition.

        'problem' is needed, if decision considers originator/responsible of
        the problem.

        'id' is the id of the person to check the access for.  If it is false,
        get it from 'self.identity()'.
        """
        # Get access rights
        if not id:
            id = self.identity ()
        if self._definition.has_key (id):
            rights = self._definition[id]
        elif self._definition.has_key (''):
            rights = self._definition['']
        else:
            rights = ()
        # Check them
        final_right = self.DENIED
        for r in rights:
            if r[0] == access_type:
                restricted_allowed, affiliation = r[1:]
                if affiliation:
                    if not problem:
                        continue
                    responsible, _ = problem.responsible ()
                    originator, _ = problem.originator ()
                    if affiliation == self.RESPONSIBLE:
                        if id != problem.responsible ()[0]:
                            continue
                    elif affiliation == self.ORIGINATOR:
                        if id != problem.originator ()[0]:
                            continue
                    elif affiliation == self.RELATED:
                        if id != problem.responsible ()[0] and \
                           id != problem.originator ()[0]:
                            continue
                    else:
                        continue
                if restricted_allowed:
                    return self.FULL
                else:
                    final_right = self.RESTRICTED
        # Default is no access
        return final_right
