Skip to content

Commit

Permalink
Make SMS text policy available during verify (privacyidea#4002)
Browse files Browse the repository at this point in the history
- This also makes the `emailtext` and `emailsubject` actions available
  for email tokens during verify.
- The `challenge_text` action or the token specific challenge message
  will be returned to the client instead of the generic message.
- The tokens available for verification during enrollment can now be
  chosen with a multi-select element in the UI.
- Added the token-locked check to more `set` functions and removed it
  for `get_otplen` to avoid an error during export.
- Fixed some spellings, removed unused code and fixed some formatting

Closes privacyidea#3971
  • Loading branch information
plettich authored Jul 8, 2024
1 parent 14533d8 commit 5c1242f
Show file tree
Hide file tree
Showing 10 changed files with 387 additions and 389 deletions.
3 changes: 2 additions & 1 deletion privacyidea/api/lib/postpolicy.py
Original file line number Diff line number Diff line change
Expand Up @@ -963,7 +963,8 @@ def check_verify_enrollment(request, response):
g.audit_object.add_policy(verify_pol_dict.get(toks))
if do_verify_enrollment:
content = response.json
content["detail"]["verify"] = tokenobj.prepare_verify_enrollment()
options = {"g": g, "user": request.User, "exception": request.all_data.get("exception", 0)}
content["detail"]["verify"] = tokenobj.prepare_verify_enrollment(options=options)
content["detail"]["rollout_state"] = ROLLOUTSTATE.VERIFYPENDING
tokenobj.token.rollout_state = ROLLOUTSTATE.VERIFYPENDING
tokenobj.token.save()
Expand Down
2 changes: 1 addition & 1 deletion privacyidea/lib/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -524,7 +524,7 @@ def get_token_classes():
<class 'privacyidea.lib.tokens.hotptoken.HotpTokenClass'>]
:return: array of token classes
:rtype: array
:rtype: list
"""
if "pi_token_classes" not in this.config:
(t_classes, t_types) = get_token_class_dict()
Expand Down
14 changes: 9 additions & 5 deletions privacyidea/lib/policy.py
Original file line number Diff line number Diff line change
Expand Up @@ -2305,9 +2305,13 @@ def get_static_policy_definitions(scope=None):
'value': list(get_email_validators().keys())},
ACTION.VERIFY_ENROLLMENT: {
'type': 'str',
'desc': _("Specify a white space separated list of token types, "
"that should be verified during enrollment."),
'group': GROUP.TOKEN}
'desc': _("Specify the list of token types, "
"that must be verified during enrollment."),
'group': GROUP.TOKEN,
'multiple': True,
'value': [token_obj.get_class_type() for token_obj in get_token_classes() if
token_obj.can_verify_enrollment]
}
},
SCOPE.AUTH: {
ACTION.OTPPIN: {
Expand All @@ -2319,8 +2323,8 @@ def get_static_policy_definitions(scope=None):
'component.')},
ACTION.CHALLENGERESPONSE: {
'type': 'str',
'desc': _('This is a whitespace separated list of tokentypes, '
'that can be used with challenge response.'),
'desc': _('Specify the list of token types, '
'that must be used with challenge response.'),
'multiple': True,
'value': [token_obj.get_class_type() for token_obj in get_token_classes() if "challenge" in token_obj.mode and len(token_obj.mode) > 1]
},
Expand Down
89 changes: 43 additions & 46 deletions privacyidea/lib/tokenclass.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@
It depends on lib.user and lib.config.
The token object also contains a database token object as self.token.
The token object runs the self.update() method during the initialization
The token object runs the self.update() method during the initialization
process in the API /token/init.
The update method takes a dictionary. Some of the following parameters:
Expand All @@ -70,8 +70,8 @@
2stepinit -> Will do a two step rollout.
privacyIDEA creates the first part of the OTPKey, sends it
to the client and the clients needs to send back the second part.
In case of 2stepinit the key is generated from the server_component and the
In case of 2stepinit the key is generated from the server_component and the
client_component using the TokenClass method generate_symmetric_key.
This method is supposed to be overwritten by the corresponding token classes.
"""
Expand All @@ -89,7 +89,7 @@
from .config import (get_from_config, get_prepend_pin)
from .user import (User,
get_username)
from ..models import (TokenOwner, TokenTokengroup, Tokengroup, Challenge, cleanup_challenges)
from ..models import (TokenOwner, TokenTokengroup, Challenge, cleanup_challenges)
from .challenge import get_challenges
from privacyidea.lib.crypto import (encryptPassword, decryptPassword,
generate_otpkey)
Expand All @@ -106,10 +106,7 @@
from binascii import unhexlify



#DATE_FORMAT = "%d/%m/%y %H:%M"
DATE_FORMAT = '%Y-%m-%dT%H:%M%z'
# LASTAUTH is utcnow()
AUTH_DATE_FORMAT = "%Y-%m-%d %H:%M:%S.%f%z"
optional = True
required = False
Expand Down Expand Up @@ -150,7 +147,7 @@ class CLIENTMODE(object):
U2F = 'u2f'
WEBAUTHN = 'webauthn'


class ROLLOUTSTATE(object):
CLIENTWAIT = 'clientwait'
# The rollout is pending in the backend, like CSRs that need to be approved
Expand All @@ -175,12 +172,11 @@ class TokenClass(object):
# If the token is enrollable via multichallenge
is_multichallenge_enrollable = False


@log_with(log)
def __init__(self, db_token):
"""
Create a new token object.
:param db_token: A database token object
:type db_token: Token
:return: A TokenClass object
Expand All @@ -200,7 +196,7 @@ def set_type(self, tokentype):
"""
Set the tokentype in this object and
also in the underlying database-Token-object.
:param tokentype: The type of the token like HOTP or TOTP
:type tokentype: string
"""
Expand Down Expand Up @@ -231,7 +227,7 @@ def get_type(self):
def add_user(self, user, report=None):
"""
Set the user attributes (uid, resolvername, resolvertype) of a token.
:param user: a User() object, consisting of loginname and realm
:param report: tbdf.
:return: None
Expand Down Expand Up @@ -307,8 +303,8 @@ def user(self):
def is_orphaned(self):
"""
Return True if the token is orphaned.
An orphaned token means, that it has a user assigned, but the user
An orphaned token means, that it has a user assigned, but the user
does not exist in the user store (anymore)
:return: True / False
Expand Down Expand Up @@ -337,7 +333,7 @@ def get_user_displayname(self):
user_info = user_object.info
user_identifier = "{0!s}_{1!s}".format(user_object.login, user_object.realm)
user_displayname = "{0!s} {1!s}".format(user_info.get("givenname", "."),
user_info.get("surname", "."))
user_info.get("surname", "."))
return user_identifier, user_displayname

@check_token_locked
Expand Down Expand Up @@ -445,7 +441,7 @@ def get_otp(self, current_time=""):
The default token does not support getting the otp value
will return a tuple of four values
a negative value is a failure.
:return: something like: (1, pin, otpval, combined)
"""
return -2, 0, 0, 0
Expand Down Expand Up @@ -535,7 +531,6 @@ def authenticate(self, passw, user=None, options=None):
pin_match = self.check_pin(pin, user=user, options=options)
if pin_match is True:
otp_counter = self.check_otp(otpval, options=options)
#self.set_otp_count(otp_counter)

return pin_match, otp_counter, reply

Expand Down Expand Up @@ -565,7 +560,7 @@ def decode_otpkey(otpkey, otpkeyformat):
def update(self, param, reset_failcount=True):
"""
Update the token object
:param param: a dictionary with different params like keysize,
description, genkey, otpkey, pin
:type: param: dict
Expand Down Expand Up @@ -610,8 +605,7 @@ def update(self, param, reset_failcount=True):
# The token is disabled
self.token.active = False


#if genkey not in [0, 1]:
# if genkey not in [0, 1]:
# raise ParameterError("TokenClass supports only genkey in range ["
# "0,1] : %r" % genkey)

Expand All @@ -622,7 +616,7 @@ def update(self, param, reset_failcount=True):
if otpKey is None and genkey:
otpKey = self._genOtpKey_(key_size)

# otpKey still None?? - raise the exception, if an otpkey is required and we are not in verify state
# otpKey still None?? - raise the exception, if an otpkey is required, and we are not in verify state
if otpKey is None and self.hKeyRequired is True and not verify:
otpKey = getParam(param, "otpkey", required)

Expand Down Expand Up @@ -667,9 +661,9 @@ def update(self, param, reset_failcount=True):
return

def _genOtpKey_(self, otpkeylen=None):
'''
"""
private method, to create an otpkey
'''
"""
if otpkeylen is None:
if hasattr(self, 'otpkeylen'):
otpkeylen = getattr(self, 'otpkeylen')
Expand All @@ -681,13 +675,14 @@ def _genOtpKey_(self, otpkeylen=None):
def set_description(self, description):
"""
Set the description on the database level
:param description: description of the token
:type description: string
"""
self.token.set_description('' + description)
return

@check_token_locked
def set_defaults(self):
"""
Set the default values on the database level
Expand Down Expand Up @@ -764,6 +759,7 @@ def is_fit_for_challenge(self, messages, options=None):
def get_failcount(self):
return self.token.failcount

@check_token_locked
def set_failcount(self, failcount):
"""
Set the failcounter in the database
Expand All @@ -790,6 +786,7 @@ def set_tokengroups(self, tokengroups, add=False):
"""
self.token.set_tokengroups(tokengroups, add=add)

@check_token_locked
def set_realms(self, realms, add=False):
"""
Set the list of the realms of a token.
Expand All @@ -800,7 +797,7 @@ def set_realms(self, realms, add=False):
:type add: boolean
"""
self.token.set_realms(realms, add=add)

def get_realms(self):
"""
Return a list of realms the token is assigned to
Expand All @@ -809,10 +806,10 @@ def get_realms(self):
:rtype: list
"""
return self.token.get_realms()

def get_serial(self):
return self.token.serial

def get_tokentype(self):
return self.token.tokentype

Expand All @@ -832,7 +829,6 @@ def set_otpkey(self, otpKey):
def set_otplen(self, otplen):
self.token.otplen = int(otplen)

@check_token_locked
def get_otplen(self):
return self.token.otplen

Expand Down Expand Up @@ -871,7 +867,7 @@ def enable(self, enable=True):
def revoke(self):
"""
This revokes the token.
By default it
By default, it
1. sets the revoked-field
2. set the locked field
3. disables the token.
Expand Down Expand Up @@ -1090,8 +1086,8 @@ def get_count_auth(self):

def get_validity_period_end(self):
"""
returns the end of validity period (if set)
if not set, "" is returned.
Returns the end of validity period (if set).
If it is not set, "" is returned.
:return: the end of the validity period
:rtype: str
Expand Down Expand Up @@ -1156,6 +1152,7 @@ def set_validity_period_start(self, start_date):

self.add_tokeninfo("validity_period_start", d.strftime(DATE_FORMAT))

@check_token_locked
def set_next_pin_change(self, diff=None, password=False):
"""
Sets the timestamp for the next_pin_change. Provide a
Expand Down Expand Up @@ -1258,8 +1255,8 @@ def check_auth_counter(self):
"""
This function checks the count_auth and the count_auth_success.
If the counters are less or equal than the maximum allowed counters
it returns True. Otherwise False.
it returns True. Otherwise, False.
:return: success if the counter is less than max
:rtype: bool
"""
Expand Down Expand Up @@ -1370,7 +1367,7 @@ def check_otp_exist(self, otp, window=None):
checks if the given OTP value is/are values of this very token.
This is used to autoassign and to determine the serial number of
a token.
:param otp: the OTP value
:param window: The look ahead window
:type window: int
Expand Down Expand Up @@ -1451,13 +1448,13 @@ def __repr__(self):
def get_init_detail(self, params=None, user=None):
"""
to complete the token initialization, the response of the initialization
should be build by this token specific method.
should be built by this token specific method.
This method is called from api/token after the token is enrolled
get_init_detail returns additional information after an admin/init
like the QR code of an HOTP/TOTP token.
Can be anything else.
:param params: The request params during token creation token/init
:type params: dict
:param user: the user, token owner
Expand Down Expand Up @@ -1795,7 +1792,7 @@ def get_default_settings(cls, g, params):

@classmethod
def _get_default_settings(cls, g, role="user", username=None, userrealm=None,
adminuser=None, adminrealm=None):
adminuser=None, adminrealm=None):
"""
Internal function that can be called either during enrollment via /token/init or during
enrollment via validate/check.
Expand All @@ -1818,7 +1815,7 @@ def check_last_auth_newer(self, last_auth):
:type last_auth: basestring
:return: bool
"""
# per default we return True
# By default, we return True
res = True
# The tdelta in the policy
tdelta = parse_timedelta(last_auth)
Expand Down Expand Up @@ -1852,20 +1849,20 @@ def check_last_auth_newer(self, last_auth):
def generate_symmetric_key(self, server_component, client_component,
options=None):
"""
This method generates a symmetric key, from a server component and a
client component.
This method generates a symmetric key, from a server component and a
client component.
This key generation could be based on HMAC, KDF or even Diffie-Hellman.
The basic key-generation is simply replacing the last n byte of the
The basic key-generation is simply replacing the last n byte of the
server component with bytes of the client component.
:param server_component: The component usually generated by privacyIDEA.
This is a hex string
:type server_component: str
:param client_component: The component usually generated by the
client (e.g. smartphone). This is a hex string.
:type client_component: str
:param options:
:param options:
:return: the new generated key as hex string
:rtype: str
"""
Expand Down Expand Up @@ -1912,7 +1909,7 @@ def get_import_csv(l):

return params

def prepare_verify_enrollment(self):
def prepare_verify_enrollment(self, options=None):
"""
This is called, if the token should be enrolled in a way, that the user
needs to provide a proof, that the server can verify, that the token
Expand Down Expand Up @@ -1992,4 +1989,4 @@ def _to_dict(self, b32=False):
token_dict["otpkey"] = b32encode(unhexlify(token_dict.get("otpkey")))
token_dict["otpkey"] = to_unicode(token_dict.get("otpkey"))
token_dict["info_list"] = self.get_tokeninfo(decrypted=True)
return token_dict
return token_dict
Loading

0 comments on commit 5c1242f

Please sign in to comment.