#-*- coding: utf-8 -*-

# Copyright 2010-2012 Calculate Ltd. http://www.calculate-linux.org
#
#  Licensed under the Apache License, Version 2.0 (the "License");
#  you may not use this file except in compliance with the License.
#  You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
#  Unless required by applicable law or agreed to in writing, software
#  distributed under the License is distributed on an "AS IS" BASIS,
#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#  See the License for the specific language governing permissions and
#  limitations under the License.

import sys, os
import subprocess

from soaplib.serializers.primitive import String, Integer
from soaplib.serializers.clazz import Array, ClassSerializer
from soaplib.service import rpc
from calculate.core.server.api_types import ReturnedMessage
from calculate.core.server.api_types import (Field, GroupField, ViewInfo,
                                             ViewParams)
from calculate.lib.datavars import VariableError
from calculate.lib.cl_lang import getLazyLocalTranslate, setLocalTranslate
setLocalTranslate('cl_core3',sys.modules[__name__])

from calculate.core import datavars
import traceback

__ = getLazyLocalTranslate(_)

class RequestInfo(ClassSerializer):
    """Parameters for method install"""
    cl_page_count = String
    cl_page_offset = String

class DetailRequestInfo(ClassSerializer):
    """Parameters for method install"""
    cl_req_id = String
    cl_req_group = String

def catchExcept():
    class wrapper:
        def __init__(self,f):
            self.f = f
            self.func_name = f.func_name
            self.func_code = f.func_code
            self.__doc__ = f.__doc__
            self.__name__ = f.__name__

        def __call__(self,selfobj,*args,**kwargs):
            try:
                return self.f(selfobj,*args,**kwargs)
            except BaseException as e:
                view = ViewInfo(groups=[])
                group = GroupField(name=_("Error"),last=True)
                group.fields = []
                group.fields.append(Field(
                        name = "error",
                        label = str(e),
                        default = 'color:red;',
                        element = "error"))
                view.groups.append(group)
                print "!!!!EXCEPTION!!!!"
                for i in apply(traceback.format_exception, sys.exc_info()):
                    print i
                return view
    return wrapper

class CoreWsdl:
    #def create_req_table(self, )
    def show_request_meth (self, dv):
        try:
            page_count = int(dv.Get('cl_page_count'))
            page_offset = int(dv.Get('cl_page_offset'))
            #self.printSUCCESS('Current offset = %d' %page_offset)

            if not page_offset:
                page_offset = 0

            list_req_id = dv.Get('cl_all_req_id')

            if not len(list_req_id):
                self.printSUCCESS(_('No requests'))
                return True

            for i in range(len(list_req_id)):
                list_req_id[i] = int(list_req_id[i])
            list_req_id.sort()
            max_id = list_req_id[len(list_req_id)-1]

            if not page_count:
                page_count = len(list_req_id)

            head = ['Id',_('UserName'),'IP','MAC',_('Date'), _('Location'),
                    _('Group')]
            body = []
            fields = ['cl_req_id','','','','','','']

            #i = page_offset + 1
            num_id = len(list_req_id)
            if page_offset > num_id - 1:
                i = list_req_id[num_id - 1]
            else:
                i = list_req_id[page_offset]
            while True:
                if len(body) == page_count or i > max_id:
                    break

                if not i in list_req_id:
                    i+=1
                    continue

                dv.Set('cl_req_id', i)
                mac = dv.Get('cl_req_mac')
                ip = dv.Get('cl_req_ip')
                date = dv.Get('cl_req_date')
                username = dv.Get('cl_req_user_name')
                location = dv.Get('cl_req_location')
                #self.printSUCCESS ('page number = %d' %i)
                group = dv.Get('cl_req_group')
                if not group:
                    group = _('Not signed')
                body.append([str(i),username,ip,mac,date,location,group])
                i+=1

            if body:
                self.printTable(_("List of requests"), head, body,
                             fields = fields, onClick = 'core_detail_request')
                if not page_offset:
                    return True
                count_page = len(list_req_id) / page_count + 1
                num_page = page_offset / page_count + 1
                self.printSUCCESS(_('page %d from ')%num_page+str(count_page))
                return True
        except Exception,e:
            for i in apply(traceback.format_exception, sys.exc_info()):
                print i
            print 'EXCEPT!!!!!!!!!!', e
        return True

    def check_req_params (self, dv, info,allvars=False,ordered=None):
        errors = []
        keys = sorted(filter(lambda x:x.lower() == x,
               info._type_info.keys()))
        if ordered:
            keys = ordered + filter(lambda x:not x in ordered,
                             keys)
        for var in keys:
            # get value of variable from info
            val = getattr(info, var)
            # check value if value send of check allvariables
            if val != None or allvars:
                try:
                    # if value not send, then get from datavars
                    if val == None:
                        val = dv.Get(var)
                    else:
                        uncomperr = dv.Uncompatible(var)
                        if uncomperr:
                            raise VariableError(uncomperr)
                    if not dv.Uncompatible(var):
                        dv.Set(var, val)
                except VariableError, e:
                    mess = ''
                    messages = e.message if type(e.message) == list \
                                         else [e.message]
                    for error in messages:
                        mess += str(error) + '\n'
                        errors.append(ReturnedMessage(type = 'error', 
                                                field = var, message = mess))
        return errors

    from calculate.core.server.baseClass import Basic
    from calculate.core.server.decorators import Dec

    def requestCommon(self,sid,info,methodname):
        """
        Install common method
        """
        try:
            dv = self.get_cache(sid,methodname,"vars")
            if not dv:
                #reload(cl_install)
                dv = datavars.DataVarsCore()
                dv.importCore()
                dv.flIniFile()
            errors = self.check_req_params(dv, info,
                                           ordered=['cl_page_count',
                                                    'cl_page_offset'],
                                           allvars=True)
            if errors:
                return errors
            request_meth = type("requestCommon",(self.Common,
                                CoreWsdl, object), {})
            pid = self.startprocess(sid, target=request_meth,
                                    method="show_request_meth",
                                    method_name='core_show_request',
                                    auto_delete = True,
                                    args_proc = (dv,))
            returnmess = ReturnedMessage(type = 'pid', message = pid, 
                                         expert = True)
            returnmess.type = "pid"
            returnmess.message = pid
            dv = self.clear_cache(sid,methodname)
            return [returnmess]
        finally:
            if dv:
                self.set_cache(sid,methodname,"vars",dv,smart=False)
        return []

    @rpc(Integer, RequestInfo, _returns = Array(ReturnedMessage))
    @Dec.core_method(gui=True, title=__('Show Requests'),category=\
                    __('Utilities'), image='view-certificate-import,'\
                    'application-certificate',command='cl-core-request-show',
                    rights=["request"])
    def core_show_request(self, sid, info):
        if not info:
            mess = 'Field must be int!'
            errors = []
            for field in ['cl_page_count', 'cl_page_offset']:
                errors.append(ReturnedMessage(type = 'error', field = field,
                                      message = mess, expert = True))
            return errors
        dv = datavars.DataVarsCore()
        dv.importCore()
        dv.flIniFile()
        dv.Set('cl_page_count', info.cl_page_count)
        dv.Set('cl_page_offset', info.cl_page_offset)
        self.set_cache(sid, 'core_show_request', "vars", dv, smart=False)
        return self.requestCommon(sid,info,'core_show_request')

    @rpc(Integer, ViewParams, _returns = ViewInfo)
    @catchExcept()
    def core_show_request_view (self, sid, params):
        dv = datavars.DataVarsCore()
        dv.importCore()
        dv.flIniFile()
        dv.addGroup(_("Requests"),
            normal=('cl_page_count','cl_page_offset'),
            next_label=_("Next"))
        view = ViewInfo (dv)
        self.set_cache(sid, 'core_show_request', "vars", dv, smart=False)
        return view

##################### BEGIN DETAIL REQUEST METHOD ##############################
    @rpc(Integer, DetailRequestInfo, _returns = Array(ReturnedMessage))
    @Dec.core_method(gui=True, title=__('Request details'), rights=["request"])
    def core_detail_request ( self, sid, info):
        if not info:
            return [ReturnedMessage(type = 'error', field = 'cl_req_id',
                                    message = "use option -r to select the request")]

        dv = datavars.DataVarsCore()
        dv.importCore()
        dv.flIniFile()
        try:
            dv.Set('cl_req_id', info.cl_req_id)
        except VariableError, e:
            return [ReturnedMessage(type = 'error', field = 'cl_req_id',
                                    message = e.message)]
        self.set_cache(sid, 'core_detail_request', 'dv', dv, smart = False)
        return []

    @rpc(Integer, ViewParams, _returns = ViewInfo)
    @catchExcept()
    def core_detail_request_view (self, sid, params):
        dv = self.get_cache(sid, 'core_detail_request', 'dv')
        if not dv:
            dv = datavars.DataVarsCore()
            dv.importCore()
            dv.flIniFile()
            dv.Get('cl_req_id')

        dv.addGroup(None,
            normal=('cl_req_id', 'cl_req_user_name', 'cl_req_ip', 'cl_req_mac',
                    'cl_req_date', 'cl_req_location', 'cl_req_group'))

        view = ViewInfo (dv)

        group = GroupField(name='',nextlabel=_("Done"),last=True)
        group.fields = []
        group.fields.append(Field(
            name = "but0",
            label = _("Back"),
            value = "core_show_request",
            element = "button"))
        group.fields.append(Field(
            name = "but1",
            label = _("Confirm"),
            value = "core_confirm_request",
            element = "button"))
        group.fields.append(Field(
            name = "but2",
            label = _("Delete"),
            value = "core_del_request",
            element = "button"))
        view.groups.append(group)
        self.set_cache(sid, 'view_request', "vars", dv, smart=False)
        return view
############## END DETAIL REQUEST METHOD #######################################
############## BEGIN CONFIRM REQUEST METHODS ###################################
    def confirmRequestCommon(self,sid,info,methodname):
        """
        Install common method
        """
        try:
            dv = self.get_cache(sid,methodname,"vars")
            #if not dv:
                #reload(cl_install)
                #dv = cl_install.DataVarsInstall()
                #dv.importInstall()
                #dv.flIniFile()
                #initfunc(dv)
            errors = self.check_req_params(dv, info,
                       ordered=['cl_req_id'],
                       allvars=True)
            if errors:
                return errors 
            request_meth = type("delRequestCommon",(self.Common,
                                CoreWsdl, object), {})
            pid = self.startprocess(sid, target=request_meth,
                                method="confirm_request_meth",
                                method_name='core_confirm_request',
                                args_proc = (dv,))
            returnmess = ReturnedMessage(type = 'pid', message = pid)
            returnmess.type = "pid"
            returnmess.message = pid
            dv = self.clear_cache(sid,methodname)
            return [returnmess]
        finally:
            if dv:
                self.set_cache(sid,methodname,"vars",dv,smart=False)
        return []

    def confirm_request_meth(self, dv):
        try:
            data_path = dv.Get('cl_core_data')
            cert_path = dv.Get('cl_core_cert_path')

            cl_req_id = str(dv.Get('cl_req_id'))
            cl_req_group = str(dv.Get('cl_req_group'))
            #self.startTask('Request confirmation')
            self.printSUCCESS ('<b>'+_('Request confirmation')+'</b>')
            self.printSUCCESS (_('Request number') + " = %s" %cl_req_id)
            self.printSUCCESS (_('Request group') + " = %s" %cl_req_group)

            server_cert = cert_path + '/root.crt'
            server_key = cert_path + '/root.key'

            cl_req = data_path + '/client_certs/%s.csr' %cl_req_id
            cl_cert = data_path + '/client_certs/%s.crt' %cl_req_id
            if not os.path.exists(cl_req):
                self.printERROR (_("Signature request %s not found") %cl_req)
                return False

            if os.path.exists(cl_cert):
                self.printERROR (_("Certificate %s now exists") %cl_cert)
                return False

            group = "group:%s" %cl_req_group
            config = data_path + '/client_certs/ssl-client.cfg'
            if os.path.exists(config):
                os.unlink(config)

            cfg_text = ("[ ssl_client ]\n"
                "basicConstraints = CA:FALSE\n"
                "nsCertType = client\n"
                "keyUsage = digitalSignature, keyEncipherment\n"
                "extendedKeyUsage = clientAuth\n"
                "nsComment = %s") %group
            fc = open(config, 'w')
            fc.write(cfg_text)
            fc.close()

            cmd = ("openssl x509 -req -days 11000 -CA %s -CAkey %s "
                        "-CAcreateserial "
                        "-extfile %s -extensions ssl_client -in %s -out %s") \
                        %(server_cert, server_key, config, cl_req, cl_cert)
            print cmd
            PIPE = subprocess.PIPE
            p = subprocess.Popen(cmd, shell=True, stdin=PIPE, stdout=PIPE,
            stderr=subprocess.STDOUT, close_fds=True)
            p.wait()
            self.printSUCCESS ('<b>' + _("Certificate %s is signed") %cl_cert \
                               + '</b>')
            #self.endTask()
            return True
        except (KeyboardInterrupt,):
            self.printERROR('Process is interrupted!')
            return False

    @rpc(Integer, DetailRequestInfo, _returns = Array(ReturnedMessage))
    @Dec.core_method(gui=True, title=__('Request confirmation'),
                     command='cl-core-request-confirm',rights=["request"])
    @catchExcept()
    def core_confirm_request ( self, sid, info):
        dv = self.get_cache(sid,'core_confirm_request',"vars")
        if not dv:
            dv = datavars.DataVarsCore()
            dv.importCore()
            dv.flIniFile()
        if not info:
            return [ReturnedMessage(type = 'error', field = 'cl_req_id', \
                                 message = _("use option -r to select the request"))]
        try:
            dv.Set('cl_req_id', info.cl_req_id)
        except VariableError, e:
            errors = []
            mess = ''
            messages = e.message if type(e.message) == list else [e.message]
            for error in messages:
                mess += str(error) + '\n'
                errors.append(ReturnedMessage(type = 'error', 
                              field = 'cl_req_id', message = mess))
            return errors

        if not info.cl_req_group:
            return [ReturnedMessage(type = 'error', field = 'cl_req_group', \
                       message = _("use option -g to select the certificate group"))]
        try:
            dv.Set('cl_req_group', info.cl_req_group)
        except VariableError, e:
            return [ReturnedMessage(type = 'error', field = 'cl_req_group', \
                       message = e.message)]

        self.set_cache(sid, 'core_confirm_request', "vars", dv, smart=False)
        return self.confirmRequestCommon(sid,info,'core_confirm_request')

    @rpc(Integer, ViewParams, _returns = ViewInfo)
    @catchExcept()
    def core_confirm_request_view (self, sid, params):
        dv = self.get_cache(sid, 'core_confirm_request', 'dv')
        if not dv:
            dv = datavars.DataVarsCore()
            dv.importCore()
            dv.flIniFile()
            #dv.Get('cl_req_id')

        dv.addGroup(_("Confirm the request"),
            normal=('cl_req_id', 'cl_req_user_name', 'cl_req_ip', 'cl_req_mac',
              'cl_req_date', 'cl_req_location', 'cl_req_group'),
            next_label=_("Done"))

        view = ViewInfo (dv)

        group = GroupField(name=_("Request details"),nextlabel=_("Done"),
                           last=True)
        group.fields = []
        group.fields.append(Field(
            name = "but2",
            label = "Confirm",
            value = "core_confirm_request",
            element = "button"))
        view.groups.append(group)
        self.set_cache(sid, 'core_del_request', "vars", dv, smart=False)
        return view
############## END CONFIRM REQUEST METHODS ###################################


    def delRequestCommon(self,sid,info,methodname):
        """
        Install common method
        """
        try:
            dv = self.get_cache(sid,methodname,"vars")
            #if not dv:
                #reload(cl_install)
                #dv = cl_install.DataVarsInstall()
                #dv.importInstall()
                #dv.flIniFile()
                #initfunc(dv)
            errors = self.check_req_params(dv, info,
                       ordered=['cl_req_id'],
                       allvars=True)
            if errors:
                return errors 
            request_meth = type("delRequestCommon",(self.Common,
                                CoreWsdl, object), {})
            pid = self.startprocess(sid, target=request_meth,
                                method="del_request_meth",
                                method_name='core_del_request',
                                args_proc = (dv,))
            returnmess = ReturnedMessage(type = 'pid', message = pid)
            returnmess.type = "pid"
            returnmess.message = pid
            dv = self.clear_cache(sid,methodname)
            return [returnmess]
        finally:
            if dv:
                self.set_cache(sid,methodname,"vars",dv,smart=False)
        return []

    def del_request_meth(self, dv):
        #ob = datavars.DataVarsCore()
        #ob.importCore()

        # set var env
        #ob.flIniFile()
        try:
            data_path = dv.Get('cl_core_data')
            certbase = dv.Get('cl_core_database')

            id_del_req = str(dv.Get('cl_req_id'))
#            self.startTask("id_del_req = %s" %id_del_req)
            print "id_del_req = ", id_del_req
            request = data_path + '/client_certs/%s.csr' %id_del_req
            cert = data_path + '/client_certs/%s.crt' %id_del_req

            # chect exists request and certificate files 
            if not os.path.exists(request) and not os.path.exists(cert):
                self.printERROR (_('Request or certificate with ID = %s not '
                                                        'found!')%id_del_req)
                return False

            if not os.path.exists(request):
                self.printERROR (_("request %s not found!") %request)

            if os.path.exists(cert):
                self.printWARNING (_("Request duly signed"))

            # create temp file
            ft = open(certbase + '_temp', 'w')
            with open(certbase) as fd:
                t = fd.read()
                # See each line
                for line in t.splitlines():
                    # and each word in line
                    words = line.split()
                        # if in line present certificate id
                    if not words[0] == id_del_req:
                        ft.write(line + '\n')
            ft.close()
            fd.close()

            ft = open(certbase + '_temp', 'rb')
            fc = open(certbase, 'wb')
            ft.seek(0)
            fc.write(ft.read())
            ft.close()
            fc.close()
            os.unlink(certbase + '_temp')
            try:
                if os.path.exists(request):
                    os.unlink (request)
                    self.printSUCCESS (_("signature request deleted"))
                if os.path.exists(cert):
                    os.unlink (cert)
                    self.printSUCCESS (_("certificate deleted"))
            except:
                self.printERROR (_("failed to delete the file!"))
#            self.endTask()
        except KeyboardInterrupt:
#            self.endTask()
            return False
        except Exception, e:
            msg = e.message
            if not msg:
                msg = e.reason
            self.printERROR (_("Exception: %s") %msg)
            return False

        
    @rpc(Integer, DetailRequestInfo, _returns = Array(ReturnedMessage))
    @Dec.core_method(gui=True, title=__('Delete the request'),
                     command='cl-core-request-del',rights=["request"])
    @catchExcept()
    def core_del_request ( self, sid, info):
        #dv = self.get_cache(sid,'core_del_request',"vars")
        #if not dv:
        dv = datavars.DataVarsCore()
        dv.importCore()
        dv.flIniFile()
        if not info:
            return [ReturnedMessage(type = 'error', field = 'cl_req_id', \
                                 message = _("use option -r to select the request"))]
        try:
            dv.Set('cl_req_id', info.cl_req_id)
        except VariableError, e:
            return [ReturnedMessage(type = 'error', field = 'cl_req_id', \
                       message = e.message)]
        self.set_cache(sid, 'core_del_request', "vars", dv, smart=False)
        return self.delRequestCommon(sid,info,'core_del_request')

    @rpc(Integer, ViewParams, _returns = ViewInfo)
    @catchExcept()
    def core_del_request_view (self, sid, params):
        dv = self.get_cache(sid, 'core_detail_request', 'dv')
        if not dv:
            dv = datavars.DataVarsCore()
            dv.importCore()
            dv.flIniFile()
            dv.Get('cl_req_id')

        dv.addGroup(_("Signature request deleted"),
            normal=('cl_req_id', 'cl_req_user_name', 'cl_req_ip', 'cl_req_mac',
              'cl_req_date', 'cl_req_location'),
            next_label=_("Done"))

        view = ViewInfo (dv)

        group = GroupField(name=_("Request details"),nextlabel=_("Done"),
                           last=True)
        group.fields = []
        group.fields.append(Field(
            name = "but2",
            label = "Delete",
            value = "core_del_request",
            element = "button"))
        view.groups.append(group)
        self.set_cache(sid, 'core_del_request', "vars", dv, smart=False)
        return view
