# privacyIDEA is a fork of LinOTP
# 2014-12-05 Cornelius Kölbel <cornelius@privacyidea.org>
# Migration to flask
#
# May 08, 2014 Cornelius Kölbel
# License: AGPLv3
# contact: http://www.privacyidea.org
#
# Copyright (C) 2010 - 2014 LSE Leading Security Experts GmbH
# License: LSE
# contact: http://www.linotp.org
# http://www.lsexperts.de
# linotp@lsexperts.de
"""
This file contains the definition of the password token class
"""
import logging
from privacyidea.lib.crypto import zerome, safe_compare
from privacyidea.lib.utils import to_unicode
from privacyidea.lib.tokenclass import TokenClass
from privacyidea.lib.log import log_with
from privacyidea.lib.decorators import check_token_locked
from privacyidea.lib import _
from privacyidea.lib.policy import SCOPE, ACTION, GROUP
from privacyidea.api.lib.prepolicy import _generate_pin_from_policy
from privacyidea.api.lib.utils import getParam
from privacyidea.lib.utils import is_true
optional = True
required = False
# We use an easier length of 12 for password tokens
DEFAULT_LENGTH = 12
DEFAULT_CONTENTS = 'cn'
log = logging.getLogger(__name__)
[docs]class PasswordTokenClass(TokenClass):
"""
This Token does use a fixed Password as the OTP value.
In addition, the OTP PIN can be used with this token.
This Token can be used for a scenario like losttoken
"""
password_detail_key = "password" # nosec B105 # key name
default_length = DEFAULT_LENGTH
default_contents = DEFAULT_CONTENTS
[docs] class SecretPassword(object):
def __init__(self, secObj):
self.secretObject = secObj
[docs] def get_password(self):
return self.secretObject.getKey()
[docs] def check_password(self, password):
"""
:param password:
:type password: str
:return: result of password check: 0 if success, -1 if failed
:rtype: int
"""
res = -1
key = self.secretObject.getKey()
# getKey() returns bytes and since we can not assume, that the
# password only contains printable characters, we need to compare
# bytes strings here. This also avoids making another copy of 'key'.
if safe_compare(key, password):
res = 0
zerome(key)
del key
return res
def __init__(self, aToken):
TokenClass.__init__(self, aToken)
self.otp_len = self.default_length
self.otp_contents = self.default_contents
self.hKeyRequired = True
self.set_type("pw")
[docs] @staticmethod
def get_class_type():
return "pw"
[docs] @staticmethod
def get_class_prefix():
return "PW"
[docs] @staticmethod
@log_with(log)
def get_class_info(key=None, ret='all'):
"""
returns a subtree of the token definition
:param key: subsection identifier
:type key: string
:param ret: default return value, if nothing is found
:type ret: user defined
:return: subsection if key exists or user defined
:rtype: dict or scalar
"""
res = {'type': 'pw',
'title': 'Password Token',
'description': _('A token with a fixed password. Can be '
'combined with the OTP PIN. Is used for the '
'lost token scenario.'),
'init': {},
'config': {},
'user': [],
# This tokentype is enrollable in the UI for...
'ui_enroll': [],
'policy': {
SCOPE.ENROLL: {
ACTION.MAXTOKENUSER: {
'type': 'int',
'desc': _("The user may only have this maximum number of password tokens assigned."),
'group': GROUP.TOKEN
},
ACTION.MAXACTIVETOKENUSER: {
'type': 'int',
'desc': _("The user may only have this maximum number of active password tokens assigned."),
'group': GROUP.TOKEN
}
}
},
}
# I don't think we need to define the lost token policies here...
if key:
ret = res.get(key)
else:
if ret == 'all':
ret = res
return ret
[docs] @log_with(log, log_entry=False)
def update(self, param):
"""
This method is called during the initialization process.
:param param: parameters from the token init
:type param: dict
:return: None
"""
genkey = is_true(getParam(param, "genkey", optional=True))
if genkey:
# Otherwise genkey and otpkey will raise an exception in
# PasswordTokenClass
del param["genkey"]
type_prefix = self.get_class_type()
length_param = "{0!s}.length".format(type_prefix)
contents_param = "{0!s}.contents".format(type_prefix)
if length_param in param:
size = param[length_param]
del param[length_param]
else:
size = self.otp_len
if contents_param in param:
contents = param[contents_param]
del param[contents_param]
else:
contents = self.otp_contents
param["otpkey"] = _generate_pin_from_policy(contents, size=int(size))
if "otpkey" in param:
param["otplen"] = len(param["otpkey"])
TokenClass.update(self, param)
[docs] @log_with(log, log_entry=False)
@check_token_locked
def check_otp(self, anOtpVal, counter=None, window=None, options=None):
"""
This checks the static password
:param anOtpVal: This contains the "OTP" value, which is the static
password
:return: result of password check, 0 in case of success, -1 if fail
:rtype: int
"""
secretHOtp = self.token.get_otpkey()
sp = PasswordTokenClass.SecretPassword(secretHOtp)
res = sp.check_password(anOtpVal)
return res
[docs] @log_with(log)
def get_init_detail(self, params=None, user=None):
"""
At the end of the initialization we return the registration code.
"""
response_detail = TokenClass.get_init_detail(self, params, user)
secretHOtp = self.token.get_otpkey()
password = secretHOtp.getKey()
response_detail[self.password_detail_key] = to_unicode(password)
return response_detail