# Twisted, the Framework of Your Internet
# Copyright (C) 2001 Matthew W. Lefkowitz
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of version 2.1 of the GNU Lesser General Public
# License as published by the Free Software Foundation.
#
# This library 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
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
"""
Database backend for L{twisted.cred}.
"""

from twisted.enterprise import adbapi, row, reflector
from twisted.cred import authorizer, identity
import base64
import string


class IdentityRow(row.RowObject):
    rowColumns     = [
        ("identity_name", "varchar"),
        ("password", "varchar")
        ]
    rowKeyColumns  = [("identity_name", "varchar")]
    rowTableName   = "twisted_identities"

class PerspectiveRow(row.RowObject):
    rowColumns     = [
        ("identity_name",    "varchar"),
        ("perspective_name", "varchar"),
        ("service_name",     "varchar"),
        ("perspective_type", "varchar")
        ]
    rowKeyColumns  = [("identity_name", "varchar"),
                      ("perspective_name","varchar"),
                      ("service_name", "varchar")]
    rowTableName   = "twisted_perspectives"
    rowForeignKeys = [("twisted_identities", [("identity_name","varchar")],[("identity_name","varchar")], None, 1)]

    def __repr__(self):
        return "identity: %s perspective: %s service: %s" % ( self.identity_name, self.perspective_name, self.service_name)

class ReflectorAuthorizer(authorizer.Authorizer):
    """An authorizer that uses a given row reflector.
    """
    def __init__(self, refl, serviceCollection=None):
        authorizer.Authorizer.__init__(self, serviceCollection)
        self.perspectiveCreators = {}
        self.reflector = refl

    def getIdentityRequest(self, name):
        """get the identity from the database with the specified name.
        """
        w=[("identity_name", reflector.EQUAL, name)]
        return self.reflector.loadObjectsFrom(
            "twisted_identities",
            whereClause=w,
            ).addCallbacks(self._cbIdentity, self._ebIdentity)

    def _ebIdentity(self, data):
        print "ERROR:", data
        raise KeyError("Failed to load identity")

    def _cbIdentity(self, newIdentityRows):
        if len(newIdentityRows) == 0:
            # no rows! User doesnt exist
            raise KeyError("Identity not found")

        irow = newIdentityRows[0]
        hashedPass = base64.decodestring(irow.password)

        i = identity.Identity(irow.identity_name, self)
        i.setAlreadyHashedPassword(hashedPass)
        i.rows = [] #keep the rows around for now...
        i.rows.append(irow)

        for prow in irow.childRows:
            i.addKeyByString(prow.service_name, prow.perspective_name)
            i.rows.append(prow) #keep the rows around for now...

        return i

class DatabaseAuthorizer(authorizer.Authorizer, adbapi.Augmentation):
    """A PyPgSQL authorizer for Twisted Cred
    """

    schema = """
    CREATE TABLE twisted_identities
    (
      identity_name     varchar(64) PRIMARY KEY,
      password          varchar(64)
    );

    CREATE TABLE twisted_services
    (
      service_name      varchar(64) PRIMARY KEY
    );

    CREATE TABLE twisted_perspectives
    (
      identity_name     varchar(64) NOT NULL,
      perspective_name  varchar(64) NOT NULL,
      service_name      varchar(64) NOT NULL,
      perspective_type  varchar(64)
    );

    """

    def __init__(self, dbpool, serviceCollection=None):
        authorizer.Authorizer.__init__(self, serviceCollection)
        self.perspectiveCreators = {}

        adbapi.Augmentation.__init__(self, dbpool)

    def addIdentity(self, identity):
        """Store an identity in the database.
        """
        passwd = base64.encodestring(identity.hashedPassword)
        username = identity.name
        createIdentity = "INSERT INTO twisted_identities VALUES ('%s', '%s')" % (adbapi.safe(username), adbapi.safe(passwd) )
        s = [createIdentity]
        for (svcname, pname) in identity.keyring.keys():
            # note, we don't actually know perspective type at this point...
            s.append("INSERT INTO twisted_perspectives VALUES ('%s', '%s', '%s', NULL)" %
                     (adbapi.safe(username), adbapi.safe(pname), adbapi.safe(svcname)) )
        sql = string.join(s, '; \n')
        return self.runOperation(sql)


    def getIdentityRequest(self, name):
        """get the identity from the database with the specified name.
        """
        sql = """
        SELECT   twisted_identities.identity_name,
                 twisted_identities.password,
                 twisted_perspectives.perspective_name,
                 twisted_perspectives.service_name
        FROM     twisted_identities,
                 twisted_perspectives
        WHERE    twisted_identities.identity_name = twisted_perspectives.identity_name
        AND      twisted_identities.identity_name = '%s'
        """ % adbapi.safe(name)
        return self.runQuery(sql).addCallbacks(self._cbIdentity)

    def _cbIdentity(self, identData):
        if len(identData) == 0:
            # no rows! User doesnt exist
            raise KeyError("Identity not found")

        realIdentName = identData[0][0]
        base64pass = identData[0][1]
        hashedPass = base64.decodestring(base64pass)
        i = identity.Identity(realIdentName, self)
        i.setAlreadyHashedPassword(hashedPass)
        for ign, ign2, pname, sname in identData:
            i.addKeyByString(sname, pname)
        return i

    #################### Web Admin Interface Below ##############################

    def getIdentities(self):
        """Get the identities in the db. Used by web admin interface.
        """
        sql="""SELECT identity_name, password, (SELECT count(*)
                                                FROM twisted_perspectives
                                                WHERE twisted_perspectives.identity_name = twisted_identities.identity_name)
               FROM twisted_identities"""
        return self.runQuery(sql)

    def getPerspectives(self, identity_name):
        """Get the perspectives for an identity. Used by the web admin interface.
        """
        sql="""SELECT identity_name, perspective_name, service_name
               FROM twisted_perspectives
               WHERE identity_name = '%s'""" % adbapi.safe(identity_name)
        return self.runQuery(sql)

    def getServices(self):
        """Get the known services. Used by the web admin interface.
        """
        sql="""SELECT service_name FROM twisted_services"""
        return self.runQuery(sql)

    def addEmptyIdentity(self, identityName, hashedPassword, callback=None, errback=None):
        """Create an empty identity (no perspectives). Used by web admin interface.
        """
        passwd = base64.encodestring(hashedPassword)
        sql = "INSERT INTO twisted_identities VALUES ('%s', '%s')" % (adbapi.safe(identityName), adbapi.safe(passwd))
        return self.runOperation(sql).addCallbacks(callback, errback)

    def addPerspective(self, identityName, perspectiveName, serviceName, callback=None, errback=None):
        """Add a perspective by name to an identity.
        """
        sql = "INSERT INTO twisted_perspectives VALUES ('%s', '%s', '%s', NULL)" %\
                (adbapi.safe(identityName), adbapi.safe(perspectiveName), adbapi.safe(serviceName))
        return self.runOperation(sql).addCallbacks(callback, errback)

    def removeIdentity(self, identityName):
        """Delete an identity
        """
        sql = """DELETE FROM twisted_identities WHERE identity_name = '%s';
                 DELETE FROM twisted_perspectives WHERE identity_name = '%s'""" %\
                     (adbapi.safe(identityName), adbapi.safe(identityName) )
        return self.runOperation(sql)

    def removePerspective(self, identityName, perspectiveName, callback=None, errback=None):
        """Delete a perspective for an identity
        """
        sql = """DELETE FROM twisted_perspectives
                 WHERE identity_name = '%s'
                 AND perspective_name = '%s'""" %\
                   (adbapi.safe(identityName), adbapi.safe(perspectiveName))
        return self.runOperation(sql).addCallbacks(callback, errback)

    def changePassword(self, identityName, hashedPassword, callback=None, errback=None):
        passwd = base64.encodestring(hashedPassword)
        sql = """UPDATE twisted_identities
                 SET password = '%s'
                 WHERE identity_name = '%s'""" %\
                   (adbapi.safe(passwd), adbapi.safe(identityName) )
        return self.runOperation(sql).addCallbacks(callback, errback)
