# -*- coding: UTF-8 -*-

import cPickle
import re
import lasso
import string
import urllib

from quixote import get_request, get_response, get_session, redirect
from quixote.directory import Directory

from qommon import get_cfg, get_logger
from qommon import errors

from menu import *

from qommon.form import *
from authentic import identities
from authentic import misc

from rfc822 import formatdate
import configuration

module_mtime = misc.mtime(__file__)

class IdentityObjectUI:
    def __init__(self, identity):
        self.identity = identity

    def edit_form_ui (self):
        form = Form(enctype='multipart/form-data')
        form.keep_referer()

        store = identities.get_store()
        if not getattr(store, 'ldap_read_only', False):
            for field in identities.get_store_class().fields:
                field.add_to_form(form, identity = self.identity, admin = True)

        form.add(WidgetList, 'roles', title = _('Roles'), element_type = SingleSelectWidget,
                value = self.identity.roles, add_element_label = _('Add Role'),
                element_kwargs = {str('render_br'): False,
                str('options'): [ (identities.ROLE_NONE, '---'),
                (identities.ROLE_ADMIN, _('Administrator'))]})

        if not getattr(store, 'ldap_read_only', False):
            for account in self.identity.accounts or []:
                if len(self.identity.accounts) > 1:
                    form.widgets.append(HtmlWidget(
                                htmltext('<fieldset><legend>%s</legend>') % account.type()))
                PasswordAccountObjectUI(account).edit_form_ui(form)
                if len(self.identity.accounts) > 1:
                    form.widgets.append(HtmlWidget(htmltext('</fieldset>')))

        form.add(CheckboxWidget, 'disabled', title = _('Disabled'),
                value = self.identity.disabled)

        if not self.identity.locked_down:
            form.add_submit('submit', _('Submit'))
        form.add_submit('cancel', _('Cancel'))
        return form

    def submit_form(self, form):
        store = identities.get_store()
        if not getattr(store, 'ldap_read_only', False):
            for field in identities.get_store_class().fields:
                if form.get_widget(field.key):
                    setattr(self.identity, field.key, form.get_widget(field.key).parse())
        self.identity.disabled = form.get_widget('disabled').parse()
        self.identity.roles = [x for x in form.get_widget('roles').parse() if x]
        if not getattr(store, 'ldap_read_only', False):
            for account in self.identity.accounts or []:
                PasswordAccountObjectUI(account).submit_form(form)

    def html_view [html] (self):
        '<div class="form">'
        for field in identities.get_store_class().fields:
            if hasattr(self.identity, field.key) and getattr(self.identity, field.key):
                '<div class="title">%s</div>' % _(field.title)
                '<div class="StringWidget content">%s</div>' % field.get_tabular_representation(self.identity)

        """<div class="title">%s</div>
        <div class="StringWidget content">""" % _('Roles')
        if self.identity.roles:
            for k in self.identity.roles:
                {identities.ROLE_ADMIN : _('Administrator')}[k]
        else:
            _('None')
        "</div>"
        for account in self.identity.accounts or []:
            if len(self.identity.accounts) > 1:
                '<br /><fieldset><legend>%s</legend>' % account.type()
            PasswordAccountObjectUI(account).view_ui()
            if len(self.identity.accounts) > 1:
                '</fieldset>'
        if self.identity.lasso_dump:
            identity = lasso.Identity.newFromDump(self.identity.lasso_dump)
            server = misc.get_lasso_server()
            if len(identity.providerIds) and server:
                '<br/><fieldset><legend>%s</legend>' % _('Current Federations')
                for pid in identity.providerIds:
                    klp = misc.get_provider_key(pid)
                    try:
                        provider, label = misc.get_provider_and_label(klp)
                    except KeyError:
                        continue
                    federation = identity.getFederation(pid)
                    """<div>
                    <div class="title">%s</div>
                    <div class="StringWidget content">""" % htmltext(label)
                    if federation.localNameIdentifier:
                        _("local: ") + federation.localNameIdentifier.content
                    if federation.remoteNameIdentifier:
                        "<br />"
                        _("remote: ") + federation.remoteNameIdentifier.content
                    """</div>
                    </div>"""
                "</fieldset>"

            if lasso.WSF_SUPPORT:
                offerings = identity.getOfferings()
                if offerings:
                    offerings = [x for x in offerings]
                else:
                    offerings = []
                if len(offerings):
                    "<br/><fieldset><legend>%s</legend>" % _('Resource Offerings')
                    for offering in offerings:
                        '<div class="title">%s (%s)</div>' % (
                                offering.serviceInstance.serviceType,
                                offering.serviceInstance.providerId)
                        '<div class="content"><a href="remove_offering?id=%s">%s</a></div>' % (
                                offering.entryId, _('Remove'))
                    '</fieldset>'

            "<br /><fieldset><legend>Lasso Identity Dump</legend><pre>"
            self.identity.lasso_dump
            "</pre></fieldset>"

        if self.identity.lasso_proxy_dump:
            "<fieldset><legend>Lasso Proxy Identity Dump</legend><pre>"
            self.identity.lasso_proxy_dump
            "</pre></fieldset>"


        """</div>"""
        

class PasswordAccountObjectUI:
    def __init__(self, account):
        self.account = account

    def edit_form_ui(self, form):
        passwords_cfg = get_cfg('passwords', {})
        form.add(StringWidget, "username", title=_("Username"), size=20, required=True,
            value = self.account.username, autocomplete = 'off')
        form.add(PasswordWidget, "password", title=_("Password"), size=20, required=True,
            value = self.account.password, autocomplete = 'off')
        if passwords_cfg.get('lost_password_behaviour', 'nothing') == 'dumb_question':
            dumb_questions_options = [(str(x), _(identities.dumb_questions[x])) \
                                        for x in identities.available_dumb_questions]
            form.add(SingleSelectWidget, 'dumb_question',
                    title = _('Security Question'),
                    options = [(str(''), _('[Select a question]'))] + dumb_questions_options,
                    value = self.account.dumb_question)
            form.add(StringWidget, 'smart_answer', title = _('Answer'),
                    value = self.account.smart_answer)

    def view_ui [html] (self):
        passwords_cfg = get_cfg('passwords', {})
        '<div class="title">%s</div>' % _('Username')
        '<div class="StringWidget content">%s</div>' % self.account.username
        if self.account.password:
            '<div class="title">%s</div>' % _('Password')
            '<div class="StringWidget content">%s</div>' % ('*' * len(self.account.password))
        if passwords_cfg.get('lost_password_behaviour', 'nothing') == 'dumb_question':
            if self.account.dumb_question:
                '<div class="title">%s</div>' % _('Security Question')
                '<div class="StringWidget content">%s</div>' % _(
                        identities.dumb_questions[self.account.dumb_question])
            if self.account.smart_answer:
                '<div class="title">%s</div>' % _('Answer')
                '<div class="StringWidget content">%s</div>' % '***'

    def submit_form(self, form):
        scheme = configuration.get_configuration('passwords').get('hashed_scheme')
        self.account.username = form.get_widget('username').parse()
        pwd = form.get_widget('password').parse()
        self.account.password = identities.hash_password(pwd, scheme)
        if form.get_widget('dumb_question'):
            self.account.dumb_question = form.get_widget('dumb_question').parse()
            self.account.smart_answer = form.get_widget('smart_answer').parse()


class IdentitiesDirectory(Directory):

    _q_exports = ['', 'new', 'import_csv']

    def _q_traverse(self, path):
        get_response().breadcrumb.append(('identities/', _('Identities')))
        return Directory._q_traverse(self, path)

    def is_cached(self):
        last_modified = max(identities.get_store().last_modified(), module_mtime)
        return misc.is_cached(get_request(), get_response(), last_modified)

    def _q_index [html] (self):
        if not self.is_cached():
            return ''
        html_top('identities', title = _('Identities Management'))
        if hasattr(identities.get_store(), str('add')):
            """<ul id="main-actions">
              <li><a class="new-item" href="new">%s</a></li>
            </ul>""" % _('Add Identity')

        # Import csv does not work with LDAP
        if not isinstance(identities.get_store(),
                identities.IdentitiesStoreLdap):
            get_response().filter['sidebar'] = """<ul>
                              <li><a href="import_csv">%s</a></li>
                            </ul>""" % _('Import identities from CSV file')

        limit_by_page = 100
        offset = int(get_request().form.get('page', 0))*limit_by_page
        current_letter = None
        try:
            if identities.get_store().is_big():
                current_letter = get_request().form.get('letter', str('A'))
                '<div id="letters-nav">'
                for letter in string.uppercase:
                    if letter == current_letter:
                        '<span class="current-letter">%s</span>' % letter
                    else:
                        '<a href="?letter=%s">%s</a>' % (letter, letter)
                    if letter != string.uppercase[-1]:
                        '-'
                ' <form action="" method="get" style="display: inline"><input type="text" size="10" name="letter" value="%s"></input></form>' % current_letter
                '</div>'
                identities_list = identities.get_store().values_by_letter(current_letter,
                        limit = limit_by_page, offset = offset)
            else:
                identities_list = identities.get_store().values(limit = limit_by_page, offset = offset)
        except identities.IdentityStoreException, e:
            identities_list = []
            _('Error accessing identity get_store(): %s') % str(e)

        identities_list.sort(lambda x,y: cmp(x.name, y.name))
        identities_list = [x for x in identities_list if not x.disabled] + \
                [x for x in identities_list if x.disabled]
        session = get_session()
        debug_cfg = get_cfg('debug', {})
        proxied_identity_origin_keys = {}

        def identity_li [html] (identity):
            store = identities.get_store()
            if identity.disabled:
                '<li class="disabled">'
            else:
                '<li>'
            '<strong>%s</strong>' % str(identity)
            '<p class="details">'
            '<span class="id">%s</span> ' % identity.id
            if identity.name:
                '<span class="name">%s</span> ' % identity.name
            if identity.lasso_dump:
                lasso_identity = lasso.Identity.newFromDump(identity.lasso_dump)
                nb_federations = len(lasso_identity.providerIds)
                if nb_federations == 1:
                    '<span class="federations">(%d %s)</span>' % (
                            nb_federations, _('federation'))
                else:
                    '<span class="federations">(%d %s)</span>' % (
                            nb_federations, _('federations'))
            '</p>'
            '<p class="commands">'
            command_icon('%s/' % urllib.quote(str(identity.id)), 'view')
            command_icon('%s/edit' % identity.id, 'edit')
            if not getattr(store, str('ldap_read_only'), False):
                if hasattr(identities.get_store(), str('remove')):
                    command_icon('%s/delete' % identity.id, 'remove')
            if debug_cfg.get('logger', True):
                command_icon('../logger/by_identity/%s/' % identity.id, 'logs',
                        label = _('Logs'), icon = 'stock_harddisk_16.png')
            '</p></li>'

        def paginate [html] ():
            identities_count = identities.get_store().count(current_letter)
            if len(identities_list) < identities_count:
                '<div id="page-nav">'
                if current_letter:
                    base_url = '?letter=%s&amp;' % current_letter
                else:
                    base_url = '?'
                if offset > 0:
                    command_icon('%spage=%d' % (base_url, (offset/limit_by_page)-1), 'previous',
                            label = _('Previous Page'))

                '<span class="page-count-label">'
                _('Page %(no)d/%(total)d') % {
                    'no': offset/limit_by_page+1,
                    'total': identities_count/limit_by_page+1}
                '</span>'
                if offset + len(identities_list) < identities_count:
                    command_icon('%spage=%d' % (base_url, (offset/limit_by_page)+1), 'next',
                            label = _('Next Page'))

                '</div>'
        pagination_bar = paginate()
        pagination_bar
        '<ul class="biglist">'
        for identity in identities_list:
            if identity.proxied_identity_origin:
                if not proxied_identity_origin_keys.has_key(identity.proxied_identity_origin):
                    proxied_identity_origin_keys[identity.proxied_identity_origin] = []
                proxied_identity_origin_keys[identity.proxied_identity_origin].append(identity)
                continue
            identity_li(identity)
        '</ul>'

        if proxied_identity_origin_keys.keys():
            '<h3>%s</h3>' % _('Identities proxied from other identity providers')

        for k, v in proxied_identity_origin_keys.items():
            key_provider_id = misc.get_provider_key(k)
            try:
                p, label = misc.get_provider_and_label(key_provider_id)
            except KeyError:
                label = 'XXX %s' % key_provider_id
            '<h4>%s</h4>' % label
            '<ul class="biglist">'
            for identity in v:
                identity_li(identity)
            '</ul>'
        pagination_bar



    def new [html] (self):
        identity = identities.Identity()
        identity.accounts = [identities.PasswordAccount()]
        identity_oui = IdentityObjectUI(identity)
        form = identity_oui.edit_form_ui()
        if form.get_submit() == 'cancel':
            return redirect(form.referer() or '.')

        if form.get_widget('roles').get_widget('add_element').parse():
            form.clear_errors()
            redisplay = True
        else:
            redisplay = form.has_errors()

        error = None
        try:
            if form.is_submitted() and not redisplay:
                identity_oui.submit_form(form)
                identities.get_store().add(identity)
                identities.get_store().save(identity)
                get_logger().info(str('Administrator %s created new identity (%s)') % (get_session().user, identity.name))
                return redirect(form.referer() or '.')
        except identities.AlreadyExists:
            error = _('User already exists...')
        except Exception, e:
            error = "Internal server error: %s" % e
            get_logger().error('New identity error: %s' % e)

        get_response().breadcrumb.append(('new', _('New Identity')))
        html_top('identities', title = _('New Identity'))
        '<h2>%s</h2>' % _('New Identity')
        if error:
            '<div class="error"><p>%s</p></div>' % error
        form.render()

    def import_csv [html] (self):
        form = Form(enctype='multipart/form-data')
        form.keep_referer()
        identities_cfg = get_cfg('identities', {})
        email_as_username = identities_cfg.get('email-as-username', False)
        if email_as_username:
            hint = _('Format is email;password;name')
        else:
            hint = _('Format is username;password;name;email')
        form.add(FileWidget, 'csvfile', title = _('CSV File'), required=True, hint = hint)
        form.add_submit('submit', _('Submit'))
        form.add_submit('cancel', _('Cancel'))
        if form.get_submit() == 'cancel':
            return redirect(form.referer() or '.')

        if form.is_submitted() and not form.has_errors():
            return self.import_csv_submit(form)

        get_response().breadcrumb.append(('import_csv', _('CSV Import')))
        html_top('identities', title = _('Import identities'))
        '<h2>%s</h2>' % _('Import identities')
        form.render()

    def import_csv_submit [plain] (self, form):
        file_content = form.get_widget('csvfile').parse().fp.read()
        identities_cfg = get_cfg('identities', {})
        email_as_username = identities_cfg.get('email-as-username', False)
        for line in file_content.splitlines():
            if email_as_username:
                email, password, name = line.split(';')
                username = email
            else:
                username, password, name, email = line.split(';')
            identity = identities.Identity()
            identity.name = name
            identity.email = email
            identity.accounts = [identities.PasswordAccount()]
            identity.accounts[0].username = username
            identity.accounts[0].password = password
            
            identities.get_store().add(identity)
            identities.get_store().save(identity)
        return redirect(form.referer() or '.')

    def _q_lookup(self, component):
        return IdentityUI(component)

class IdentityUI(Directory):
    _q_exports = ['', 'edit', 'delete', 'kill', 'killproxy', 'remove_offering']

    def __init__(self, component):
        if not identities.get_store().has_identity(component):
            raise errors.TraversalError("No identity with this id")
        self.identity = identities.get_store().get_identity(component)
        get_response().breadcrumb.append(('%s/' % component, self.identity.name))
        self.identity_oui = IdentityObjectUI(self.identity)

    def is_cached(self):
        last_modified = max(identities.get_store().last_modified_uid(self.identity.id), module_mtime)
        return misc.is_cached(get_request(), get_response(), last_modified)

    def _q_index [html] (self):
        if not self.is_cached():
            return ''
        html_top('identities', title = 'Identity - %s' % self.identity.name)
        '<h2>%s</h2>' % self.identity
        if self.identity.locked_down:
            '<div class="infonotice">'
            _('This identity is locked.')
            '</div>'
        if self.identity.disabled:
            '<div class="infonotice">'
            _('This identity is disabled.')
            '</div>'
        self.identity_oui.html_view()
        show_debug = get_cfg('debug', {}).get('debug_mode', False)
        '<p>'
        '<a href="edit">%s</a> ' % _('Edit')
        if self.identity.lasso_dump and show_debug:
            '<a href="kill">kill lasso dump</a> '
        if self.identity.lasso_proxy_dump and show_debug:
            '<a href="killproxy">kill lasso proxy dump</a> '
        '<a href="..">%s</a></p>' % _('Back to List')

    def kill(self):
        if self.identity.locked_down:
            return redirect('.')
        self.identity.lasso_dump = None
        self.identity.lasso_proxy_dump = None
        identities.get_store().save(self.identity)
        return redirect('.')

    def killproxy(self):
        if self.identity.locked_down:
            return redirect('.')
        self.identity.lasso_proxy_dump = None
        identities.get_store().save(self.identity)
        return redirect('.')


    def edit [html] (self):
        form = self.identity_oui.edit_form_ui()
        if form.get_submit() == 'cancel':
            return redirect(form.referer() or '.')
        if form.get_widget('roles').get_widget('add_element').parse():
            form.clear_errors()
            redisplay = True
        else:
            redisplay = form.has_errors()

        if self.identity.locked_down:
            redisplay = True

        if form.is_submitted() and not redisplay:
            self.identity_oui.submit_form(form)
            identities.get_store().save(self.identity)
            return redirect(form.referer() or '.')

        get_response().breadcrumb.append( ('edit', _('Edit')) )
        html_top('identities', title = '%s - %s' % (_('Identity'), self.identity))
        '<h2>%s %s</h2>' % (_('Editing'), self.identity)
        if self.identity.locked_down:
            '<div class="infonotice">'
            _('This identity is locked.')
            '</div>'
        form.render()

    def delete [html] (self):
        form = Form(enctype='multipart/form-data')
        form.keep_referer()
        if not self.identity.locked_down:
            form.add_submit('submit', _('Submit'))
        form.add_submit('cancel', _('Cancel'))

        if form.get_submit() == 'cancel':
            return redirect(form.referer() or '..')

        if form.is_submitted() and not form.has_errors():
            if not self.identity.locked_down:
                identities.get_store().remove(self.identity)
                get_logger().info(str('Deleted identity (%s)' % self.identity.name))
            if self.identity.id == get_session().user:
                # user removed self, log him out
                logout_url = get_request().environ['SCRIPT_NAME'] + '/logout'
                return redirect(logout_url)
            return redirect(form.referer() or '..')

        get_response().breadcrumb.append( ('delete', _('Delete')) )
        html_top('identities', title = '%s - %s' % (_('Identity'), self.identity))
        '<h2>%s %s</h2>' % (_('Deleting'), self.identity)
        '<p>%s</p>' % _('You are about to irrevocably delete this identity.')
        if self.identity.id == get_session().user:
            '<p class="warningnotice">'
            _('Note you will be automatically unlogged after doing so.')
            '</p>'
        form.render()
    
    def remove_offering [html] (self):
        if self.identity.locked_down:
            return redirect('.')
        entry_id = get_request().get_field('id')
        identity = lasso.Identity.newFromDump(self.identity.lasso_dump)
        identity.removeResourceOffering(entry_id)
        self.identity.lasso_dump = identity.dump()
        identities.get_store().save(self.identity)
        return redirect('.')
