# -*- coding: utf-8 -*-
#
# Copyright 2006 Waldemar Kornewald, wkornewald@haiku-os.org
# All rights reserved.
#
# This software is licensed as described in the file COPYING, which
# you should have received as part of this distribution. The terms
# are also available at http://trac.edgewall.org/wiki/TracLicense.
#
# This software consists of voluntary contributions made by many
# individuals. For the exact contribution history, see the revision
# history and logs, available at http://trac.edgewall.org/log/.
#
# Author: Waldemar Kornewald, wkornewald@haiku-os.org

from trac.core import *
from trac.config import *


class IUserStore(Interface):
    """
    Extension point interface for backends that store known users.
    """

    def supports_user_operation(self, operation):
        """
        Returns whether the operation (a method name) is supported.

        @return supported
        """
        return hasattr(self, operation)

    def create_user(self, username, password):
        """
        Creates a new user with the given username and password.

        @return success
        """

    def get_usernames(self):
        """
        Generator that yields an ordered list of known usernames.

        @return username
        """

    def check_password(self, username, password):
        """
        Checks if the password is correct for the given user.
        """

    def change_password(self, username, password):
        """
        Changes a user's password.

        @return success
        """

    def delete_user(self, username):
        """
        Deletes a user.

        @return success
            Returns False if the user didn't exist.
        """


class IUserAttributeProvider(Interface):
    """
    Extension point interface for backends that store user attributes.
    """

    def supports_attribute_operation(self, operation):
        """
        Returns whether the operation (a method name) is supported.

        @return supported
        """
        return hasattr(self, operation)

    def get_user_attribute(self, username, attribute):
        """
        Returns a user attribute.

        If the attribute is not set it returs an empty string.
        If the attribute is not supported None is returned.
        """

    def set_user_attribute(self, username, attribute, value):
        """
        Sets a user attribute.

        @return success
            Returns False if setting the attribute is not supported.
        """

    def delete_all_user_attributes(self, username):
        """
        Deletes all of the given user's attributes.

        @return success
        """


class UserManager(Component):
    """
    Component responsible for managing users and user attributes.
    """

    store = ExtensionOption('users',
        'store', IUserStore, 'SessionUserStore',
        doc="""The user store that should be used for authentication
            (''since 0.11'').""")
    attribute_providers = OrderedExtensionsOption('users',
        'attribute_providers', IUserAttributeProvider,
        doc="""Ordered list of user attribute providers (''since 0.11'').""")

    # public API

    def supports_operation(self, operation):
        if self.store.supports_user_operation(operation):
            return True
        for provider in self.attribute_providers:
            if self.provider.supports_attribute_operation(operation):
                return True
        return False

    # IUserStore methods

    def create_user(self, username, password):
        """
        Creates a new user with the given username and password.

        @return User object or None if the user couldn't be created
        """
        if not self.store.supports_user_operation('create_user'):
            return None
        if self.store.create_user(username, password):
            return User(username, self.store, self.attribute_providers)

    def get_user(self, username):
        """
        Returns a User object for the username.

        @return User object or None if the user doesn't exist
        """
        if username in self.get_usernames():
            return User(username, self.store, self.attribute_providers)
        return None

    def get_usernames(self):
        if not self.store.supports_user_operation('get_usernames'):
            return []
        return self.store.get_usernames()



class User(object):
    """
    Object representing a user.
    """

    def __init__(self, username, store, attribute_providers):
        self._username = username
        self.store = store
        self.attribute_providers = attribute_providers

    # public API

    @property
    def username():
        return self._username

    # IUserStore methods

    def check_password(self, password):
        if not self.store.supports_user_operation('check_password'):
            return False
        return self.store.check_password(self.username, password)

    def change_password(self, password):
        if not self.store.supports_user_operation('change_password'):
            return False
        return self.store.change_password(self.username, password)

    def delete(self):
        if not self.store.supports_user_operation('delete_user') or \
                not self.delete_all_attributes(self.username):
            return False
        return self.store.delete_user(self.username)

    # IUserAttributeProvider methods

    def __getattr__(self, attribute):
        for provider in self.attribute_providers:
            if not provider.supports_attribute_operation('get_user_attribute'):
                continue
            value = provider.get_user_attribute(self.username, attribute)
            if value is not None:
                return value
        return None

    def __setattr__(self, attribute, value):
        for provider in self.attribute_providers:
            if not provider.supports_attribute_operation('set_user_attribute'):
                continue
            result = provider.set_user_attribute(self.username, attribute,
                                                 value)
            if result:
                return True
        return False

    def delete_all_attributes(self):
        for provider in self.attribute_providers:
            if not provider.supports_attribute_operation('delete_all_user_attributes'):
                continue
            result = provider.delete_all_user_attributes(username)
            if result:
                return True
        return False


class SessionUserStore(Component):
    """
    Component for managing authenticated users stored in sessions.
    """

    implements(IUserStore)

    def get_usernames(self):
        db = self.env.get_db_cnx()
        cursor = db.cursor()
        cursor.execute("SELECT sid FROM session "
                       "WHERE authenticated=1 "
                       "ORDER BY sid")
        for row in cursor:
            yield row[0]


class SessionUserAttributeProvider(Component):
    """
    Component for providing user attributes via Trac sessions.
    """

    implements(IUserAttributeProvider)

    def get_user_attribute(self, username, attribute):
        db = self.env.get_db_cnx()
        cursor = db.cursor()
        cursor.execute("SELECT value FROM session_attribute "
                       "WHERE sid=%s AND name=%s AND authenticated=1",
                       (username, attribute))
        row = cursor.fetchone()
        if row:
            return row[0]

        # if the attribute isn't set we return empty string to indicate
        # that the attribute is supported, but empty
        return ''

    def set_user_attribute(self, username, attribute, value):
        db = self.env.get_db_cnx()
        cursor = db.cursor()

        # check if attribute exists
        cursor.execute("SELECT value FROM session_attribute "
                       "WHERE sid=%s AND name=%s AND authenticated=1",
                       (username, attribute))
        if cursor.fetchone():
            # update the attribute
            cursor.execute("UPDATE session_attribute SET value=%s "
                           "WHERE sid=%s AND name=%s AND authenticated=1",
                           (username, attribute, value))
        else:
            # create new attribute
            cursor.execute("INSERT INTO session_attribute "
                           "(sid,authenticated,name,value) "
                           "VALUES(%s,1,%s,%s)",
                           (username, attribute, value))
        db.commit()
        return True

    def delete_all_user_attributes(self, username):
        db = self.env.get_db_cnx()
        cursor = db.cursor()
        cursor.execute("DELETE FROM session_attribute WHERE sid=%s",
                      (username,))
        db.commit()
        return True

