# -*- coding: utf-8 -*-
#
#  privacyIDEA is a fork of LinOTP
#  May 08, 2014 Cornelius Kölbel
#  License:  AGPLv3
#  contact:  http://www.privacyidea.org
#
#  2014-10-02 Code cleanup for code coverage
#             Cornelius Kölbel, cornelius@privacyidea.org
#
#  Copyright (C) 2010 - 2014 LSE Leading Security Experts GmbH
#  License:  AGPLv3
#  contact:  http://www.linotp.org
#            http://www.lsexperts.de
#            linotp@lsexperts.de
#
# This code is free software; you can redistribute it and/or
# modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
# License as published by the Free Software Foundation; either
# version 3 of the License, or any later version.
#
# This code 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 AFFERO GENERAL PUBLIC LICENSE for more details.
#
# You should have received a copy of the GNU Affero General Public
# License along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
'''
This file is part of the privacyidea service
It can retrieve information of the token,
exspecially the OTP value.
Use with care. This controller is usually disabled.
'''


import logging

from pylons import tmpl_context as c
from pylons import request, response
from pylons import config
from pylons.templating import render_mako as render


from privacyidea.lib.base import BaseController

from privacyidea.lib.util import getParam
from privacyidea.lib.util import get_client
from privacyidea.lib.user import getUserFromParam
from privacyidea.lib.user import getDefaultRealm
from privacyidea.lib.user import getUserFromRequest

from privacyidea.lib.token import get_token_type_list
from privacyidea.lib.token import (getTokenType,
                                   getOtp,
                                   get_multi_otp,
                                   getTokens4UserOrSerial,
                                   getTokenRealms)
from privacyidea.lib.policy import PolicyClass, PolicyException
from privacyidea.lib.reply import sendResult, sendError
from privacyidea.lib.config import get_privacyIDEA_config
from privacyidea.model.meta import Session
from privacyidea.lib.log import log_with

import traceback

ENCODING = "utf-8"

optional = True
required = False

log = logging.getLogger(__name__)


class GettokenController(BaseController):

    '''
    The privacyidea.controllers are the implementation of the web-API to talk
    to the privacyIDEA server.
    The ValidateController is used to validate the username with its given
    OTP value.

    The Tagespasswort Token uses this controller to retrieve the current OTP
    value of     the Token and be able to set it in the application
    The functions of the GettokenController are invoked like this

        https://server/gettoken/<functionname>

    The functions are described below in more detail.
    '''
    @log_with(log)
    def __before__(self, action, **params):
        try:
            c.audit['client'] = get_client()
            tokentype = None
            if request.params.get('serial'):
                tokentype = getTokenType(request.params.get('serial'))
                
            self.Policy = PolicyClass(request, config, c,
                                      get_privacyIDEA_config(),
                                      tokentype=tokentype,
                                      token_type_list=get_token_type_list())
            self.before_identity_check(action)

        except Exception as exx:  # pragma: no cover
            log.error("%r exception %r" % (action, exx))
            log.error(traceback.format_exc())
            Session.rollback()
            Session.close()
            return sendError(response, exx, context='before')

        finally:
            pass

    @log_with(log)
    def __after__(self, action, **params):
        c.audit['administrator'] = getUserFromRequest(request).get("login")
        if 'serial' in request.params:
                c.audit['serial'] = request.params['serial']
                c.audit['token_type'] = getTokenType(request.params['serial'])
        self.audit.log(c.audit)
        
    @log_with(log)
    def getmultiotp(self, action, **params):
        '''
        This function is used to retrieve multiple otp values for a given user
        or a given serial. If the user has more than one token, the list of
        the tokens is returned.

        method:
            gettoken/getmultiotp

        
        :param serial: the serial number of the token
        :param count: number of otp values to return
        :param curTime: ONLY for internal testing: datetime.datetime object
        :type curTime: datetime object
        :param timestamp: the unix time
        :type timestamp: int
        
        :return: JSON response
        '''

        getotp_active = config.get("privacyideaGetotp.active")
        if getotp_active != "True":  # pragma: no cover
            return sendError(response, "getotp is not activated.", 0)

        param = request.params
        ret = {}

        try:
            serial = getParam(param, "serial", required)
            tokenrealms = getTokenRealms(serial)
            count = int(getParam(param, "count", required))
            curTime = getParam(param, "curTime", optional)
            timestamp = getParam(param, "timestamp", optional)
            view = getParam(param, "view", optional)

            r1 = self.Policy.checkPolicyPre('admin', 'getotp', param,
                                            tokenrealms=tokenrealms)
            log.debug("admin-getotp returned %s" % r1)

            max_count = self.Policy.checkPolicyPre('gettoken', 'max_count',
                                                   param,
                                                   tokenrealms=tokenrealms)
            log.debug("checkpolicypre returned %s" % max_count)
            if count > max_count:
                count = max_count

            log.debug("retrieving OTP value for token %s" % serial)
            ret = get_multi_otp(serial, count=int(count),
                                curTime=curTime,
                                timestamp=timestamp)
            ret["serial"] = serial

            c.audit['success'] = True
            Session.commit()

            if view:
                c.ret = ret
                return render('/manage/multiotp_view.mako')
            else:
                return sendResult(response, ret, 0)

        except PolicyException as pe:  # pragma: no cover
            log.error("gettoken/getotp policy failed: %r" % pe)
            log.error(traceback.format_exc())
            Session.rollback()
            return sendError(response, unicode(pe), 1)

        except Exception as e:  # pragma: no cover
            log.error("gettoken/getmultiotp failed: %r" % e)
            log.error(traceback.format_exc())
            Session.rollback()
            return sendError(response, "gettoken/getmultiotp failed: %s"
                             % unicode(e), 0)

        finally:
            Session.close()

    @log_with(log)
    def getotp(self, action, **params):
        '''
        This function is used to retrieve the current otp value for a given
        user or a given serial
        If the user has more than one token, the list of the tokens is
        returned.

        method:
            gettoken/getotp

        arguments:
            user    - username / loginname
            realm   - additional realm to match the user to a useridresolver
            serial  - the serial number of the token
            curTime - used ONY for internal testing: datetime.datetime object

        returns:
            JSON response
        '''

        getotp_active = config.get("privacyideaGetotp.active")
        if getotp_active != "True":  # pragma: no cover
            return sendError(response, "getotp is not activated.", 0)

        param = request.params
        ret = {}
        res = -1
        otpval = ""
        passw = ""
        serials = []

        try:

            serial = getParam(param, "serial", optional)
            user = getUserFromParam(param, optional)
            curTime = getParam(param, "curTime", optional)

            c.audit['user'] = user.login
            
            if serial:
                log.debug("retrieving OTP value for token %s" % serial)
            elif user.login:
                c.audit['realm'] = user.realm or getDefaultRealm()
                log.debug("retrieving OTP value for token for user %s@%s" %
                          (user.login, user.realm))
                toks = getTokens4UserOrSerial(user, serial)
                tokennum = len(toks)
                if tokennum > 1:
                    log.debug("The user has more than one token. "
                              "Returning the list of serials")
                    res = -3
                    for token in toks:
                        serials.append(token.getSerial())
                elif 1 == tokennum:
                    serial = toks[0].getSerial()
                    # The policy needs the token type
                    self.Policy.set_tokentype(toks[0].getTokentype())
                    log.debug("retrieving OTP for token %s for user %s@%s" %
                              (serial, user.login, user.realm))
                else:
                    log.debug("no token found for user %s@%s" %
                              (user.login, user.realm))
                    res = -4
            else:
                res = -5

            # if a serial was given or a unique serial could be received
            # from the given user.
            if serial:
                tokenrealms = getTokenRealms(serial)
                max_count = self.Policy.checkPolicyPre('gettoken',
                                                       'max_count',
                                                       param,
                                                       tokenrealms=tokenrealms)
                log.debug("checkpolicypre returned %s" % max_count)
                if max_count <= 0:
                    return sendError(response,
                                     "The policy forbids receiving OTP values "
                                     "for the token %s in this realm" %
                                     serial,
                                     1)

                (res, pin, otpval, passw) = getOtp(serial, curTime=curTime)

            c.audit['success'] = True

            if int(res) < 0:
                ret['result'] = False
                if res == -3:
                    ret['description'] = "The user has more than one token"
                    ret['serials'] = serials
                if res == -4:
                    ret['description'] = "No Token found for this user"
                if res == -5:
                    ret['description'] = "you need to provide a user or a "\
                                         "serial"
            else:
                ret['result'] = True
                ret['otpval'] = otpval
                ret['pin'] = pin
                ret['pass'] = passw

            Session.commit()
            return sendResult(response, ret, 0)

        except PolicyException as pe:
            log.error("gettoken/getotp policy failed: %r" % pe)
            log.error(traceback.format_exc())
            Session.rollback()
            return sendError(response, unicode(pe), 1)

        except Exception as e:  # pragma: no cover
            log.error("gettoken/getotp failed: %r" % e)
            log.error(traceback.format_exc())
            Session.rollback()
            return sendError(response,
                             "gettoken/getotp failed: %s" % unicode(e), 0)

        finally:
            Session.close()


# eof######################################################################
