def check_base_action(request=None, action=None, anonymous=False): """ This decorator function takes the request and verifies the given action for the SCOPE ADMIN or USER. :param request: :param action: :param anonymous: If set to True, the user data is taken from the request parameters. :return: True otherwise raises an Exception """ ERROR = { "user": "******" "allowed!" % action, "admin": "Admin actions are defined, but the action %s is not " "allowed!" % action } params = request.all_data policy_object = g.policy_object username = g.logged_in_user.get("username") role = g.logged_in_user.get("role") scope = SCOPE.ADMIN admin_realm = g.logged_in_user.get("realm") realm = params.get("realm") resolver = params.get("resolver") if type(realm) == list and len(realm) == 1: realm = realm[0] if role == ROLE.USER: scope = SCOPE.USER # Reset the admin realm admin_realm = None realm = realm or g.logged_in_user.get("realm") # In certain cases we can not resolve the user by the serial! if action not in [ACTION.AUDIT]: # get the realm by the serial: if not realm and params.get("serial"): realm = get_realms_of_token(params.get("serial"), only_first_realm=True) # get the realm by the serial, while the serial is part of the URL like # DELETE /token/serial if not realm and request.view_args and request.view_args.get("serial"): realm = get_realms_of_token(request.view_args.get("serial"), only_first_realm=True) action = policy_object.get_policies(action=action, user=username, realm=realm, scope=scope, resolver=resolver, client=g.client_ip, adminrealm=admin_realm, active=True) action_at_all = policy_object.get_policies(scope=scope, active=True, all_times=True) if action_at_all and len(action) == 0: raise PolicyError(ERROR.get(role)) return True
def auth_user_timelimit(wrapped_function, user_object, passw, options=None): """ This decorator checks the policy settings of ACTION.AUTHMAXSUCCESS, ACTION.AUTHMAXFAIL If the authentication was successful, it checks, if the number of allowed successful authentications is exceeded (AUTHMAXSUCCESS). If the AUTHMAXFAIL is exceed it denies even a successful authentication. The wrapped function is usually token.check_user_pass, which takes the arguments (user, passw, options={}) :param wrapped_function: :param user_object: :param passw: :param options: Dict containing values for "g" and "clientip" :return: Tuple of True/False and reply-dictionary """ # First we call the wrapped function res, reply_dict = wrapped_function(user_object, passw, options) options = options or {} g = options.get("g") if g: clientip = options.get("clientip") policy_object = g.policy_object max_success = policy_object.get_action_values( action=ACTION.AUTHMAXSUCCESS, scope=SCOPE.AUTHZ, realm=user_object.realm, resolver=user_object.resolver, user=user_object.login, client=clientip) max_fail = policy_object.get_action_values( action=ACTION.AUTHMAXFAIL, scope=SCOPE.AUTHZ, realm=user_object.realm, resolver=user_object.resolver, user=user_object.login, client=clientip) # Check for maximum failed authentications # Always - also in case of unsuccessful authentication if len(max_fail) > 1: raise PolicyError("Contradicting policies for {0!s}".format( ACTION.AUTHMAXFAIL)) if len(max_fail) == 1: policy_count, tdelta = parse_timelimit(max_fail[0]) fail_c = g.audit_object.get_count( { "user": user_object.login, "realm": user_object.realm, "action": "%/validate/check" }, success=False, timedelta=tdelta) log.debug("Checking users timelimit %s: %s " "failed authentications" % (max_fail[0], fail_c)) if fail_c >= policy_count: res = False reply_dict["message"] = ("Only %s failed authentications " "per %s" % (policy_count, tdelta)) if res: # Check for maximum successful authentications # Only in case of a successful authentication if len(max_success) > 1: raise PolicyError("Contradicting policies for {0!s}".format( ACTION.AUTHMAXSUCCESS)) if len(max_success) == 1: policy_count, tdelta = parse_timelimit(max_success[0]) # check the successful authentications for this user succ_c = g.audit_object.get_count( { "user": user_object.login, "realm": user_object.realm, "action": "%/validate/check" }, success=True, timedelta=tdelta) log.debug("Checking users timelimit %s: %s " "succesful authentications" % (max_success[0], succ_c)) if succ_c >= policy_count: res = False reply_dict["message"] = ("Only %s successfull " "authentications per %s" % (policy_count, tdelta)) return res, reply_dict
def config_lost_token(wrapped_function, *args, **kwds): """ Decorator to decorate the lib.token.lost_token function. Depending on ACTION.LOSTTOKENVALID, ACTION.LOSTTOKENPWCONTENTS, ACTION.LOSTTOKENPWLEN it sets the check_otp parameter, to signal how the lostToken should be generated. :param wrapped_function: Usually the function lost_token() :param args: argument "serial" as the old serial number :param kwds: keyword arguments like "validity", "contents", "pw_len" kwds["options"] contains the flask g :return: calls the original function with the modified "validity", "contents" and "pw_len" argument """ # if called in any other way, options may be None # or it might have no element "g". from privacyidea.lib.token import get_tokens options = kwds.get("options") or {} g = options.get("g") if g: # We need the old serial number, to determine the user - if it exist. serial = args[0] toks = get_tokens(serial=serial) if len(toks) == 1: username = None realm = None user_object = toks[0].user if user_object: username = user_object.login realm = user_object.realm clientip = options.get("clientip") # get the policy policy_object = g.policy_object contents_list = policy_object.get_action_values( ACTION.LOSTTOKENPWCONTENTS, scope=SCOPE.ENROLL, realm=realm, user=username, client=clientip) validity_list = policy_object.get_action_values( ACTION.LOSTTOKENVALID, scope=SCOPE.ENROLL, realm=realm, user=username, client=clientip) pw_len_list = policy_object.get_action_values( ACTION.LOSTTOKENPWLEN, scope=SCOPE.ENROLL, realm=realm, user=username, client=clientip) if contents_list: if len(contents_list) > 1: # pragma: no cover # We can not decide how to handle the request, so we raise an # exception raise PolicyError("There are contradicting policies for the " "action %s" % ACTION.LOSTTOKENPWCONTENTS) kwds["contents"] = contents_list[0] if validity_list: if len(validity_list) > 1: # pragma: no cover # We can not decide how to handle the request, so we raise an # exception raise PolicyError("There are contradicting policies for the " "action %s" % ACTION.LOSTTOKENVALID) kwds["validity"] = int(validity_list[0]) if pw_len_list: if len(pw_len_list) > 1: # pragma: no cover # We can not decide how to handle the request, so we raise an # exception raise PolicyError("There are contradicting policies for the " "action %s" % ACTION.LOSTTOKENPWLEN) kwds["pw_len"] = int(pw_len_list[0]) return wrapped_function(*args, **kwds)
def check_otp_pin(request=None, action=None): """ This policy function checks if the OTP PIN that is about to be set follows the OTP PIN policies ACTION.OTPPINMAXLEN, ACTION.OTPPINMINLEN and ACTION.OTPPINCONTENTS in the SCOPE.USER. It is used to decorate the API functions. The pin is investigated in the params as pin = params.get("pin") In case the given OTP PIN does not match the requirements an exception is raised. """ # This policy is only used for USER roles at the moment: if g.logged_in_user.get("role") == ROLE.USER: params = request.all_data pin = params.get("otppin", "") or params.get("pin", "") serial = params.get("serial") if serial: # if this is a token, that does not use a pin, we ignore this check # And immediately return true tokensobject_list = get_tokens(serial=serial) if (len(tokensobject_list) == 1 and tokensobject_list[0].using_pin is False): return True policy_object = g.policy_object user_object = get_user_from_param(params) # get the policies for minimum length, maximum length and PIN contents pol_minlen = policy_object.get_action_values( action=ACTION.OTPPINMINLEN, scope=SCOPE.USER, user=user_object.login, realm=user_object.realm, client=g.client_ip, unique=True) pol_maxlen = policy_object.get_action_values( action=ACTION.OTPPINMAXLEN, scope=SCOPE.USER, user=user_object.login, realm=user_object.realm, client=g.client_ip, unique=True) pol_contents = policy_object.get_action_values( action=ACTION.OTPPINCONTENTS, scope=SCOPE.USER, user=user_object.login, realm=user_object.realm, client=g.client_ip, unique=True) if len(pol_minlen) == 1 and len(pin) < int(pol_minlen[0]): # check the minimum length requirement raise PolicyError("The minimum OTP PIN length is {0!s}".format( pol_minlen[0])) if len(pol_maxlen) == 1 and len(pin) > int(pol_maxlen[0]): # check the maximum length requirement raise PolicyError("The maximum OTP PIN length is {0!s}".format( pol_minlen[0])) if len(pol_contents) == 1: # check the contents requirement chars = "[a-zA-Z]" # c digits = "[0-9]" # n special = "[.:,;-_<>+*!/()=?$§%&#~\^]" # s no_others = False grouping = False if pol_contents[0] == "-": no_others = True pol_contents = pol_contents[1:] elif pol_contents[0] == "+": grouping = True pol_contents = pol_contents[1:] # TODO implement grouping and substraction if "c" in pol_contents[0] and not re.search(chars, pin): raise PolicyError( "Missing character in PIN: {0!s}".format(chars)) if "n" in pol_contents[0] and not re.search(digits, pin): raise PolicyError( "Missing character in PIN: {0!s}".format(digits)) if "s" in pol_contents[0] and not re.search(special, pin): raise PolicyError( "Missing character in PIN: {0!s}".format(special)) return True
def check_otp(self, otpval, counter=None, window=None, options=None): """ This checks the response of a previous challenge. :param otpval: N/A :param counter: The authentication counter :param window: N/A :param options: contains "clientdata", "signaturedata" and "transaction_id" :return: A value > 0 in case of success """ ret = -1 clientdata = options.get("clientdata") signaturedata = options.get("signaturedata") transaction_id = options.get("transaction_id") # The challenge in the challenge DB object is saved in hex challenge = binascii.unhexlify(options.get("challenge", "")) if clientdata and signaturedata and transaction_id and challenge: # This is a valid response for a U2F token challenge_url = url_encode(challenge) clientdata = url_decode(clientdata) clientdata_dict = json.loads(clientdata) client_challenge = clientdata_dict.get("challenge") if challenge_url != client_challenge: return ret if clientdata_dict.get("typ") != "navigator.id.getAssertion": raise ValidateError("Incorrect navigator.id") #client_origin = clientdata_dict.get("origin") signaturedata = url_decode(signaturedata) signaturedata_hex = binascii.hexlify(signaturedata) user_presence, counter, signature = parse_response_data( signaturedata_hex) user_pub_key = self.get_tokeninfo("pubKey") app_id = self.get_tokeninfo("appId") if check_response(user_pub_key, app_id, clientdata, binascii.hexlify(signature), counter, user_presence): # Signature verified. # check, if the counter increased! if counter > self.get_otp_count(): self.set_otp_count(counter) ret = counter # At this point we can check, if the attestation # certificate is authorized. # If not, we can raise a policy exception g = options.get("g") if self.user: token_user = self.user.login token_realm = self.user.realm token_resolver = self.user.resolver else: token_realm = token_resolver = token_user = None allowed_certs_pols = g.policy_object.get_action_values( U2FACTION.REQ, scope=SCOPE.AUTHZ, realm=token_realm, user=token_user, resolver=token_resolver, client=g.client_ip) for allowed_cert in allowed_certs_pols: tag, matching, _rest = allowed_cert.split("/", 3) tag_value = self.get_tokeninfo( "attestation_{0!s}".format(tag)) # if we do not get a match, we bail out m = re.search(matching, tag_value) if not m: log.warning( "The U2F device {0!s} is not " "allowed to authenticate due to policy " "restriction".format(self.token.serial)) raise PolicyError("The U2F device is not allowed " "to authenticate due to policy " "restriction.") else: log.warning("The signature of %s was valid, but contained " "an old counter." % self.token.serial) else: log.warning("Checking response for token {0!s} failed.".format( self.token.serial)) return ret
def auth_otppin(wrapped_function, *args, **kwds): """ Decorator to decorate the tokenclass.check_pin function. Depending on the ACTION.OTPPIN it * either simply accepts an empty pin * checks the pin against the userstore * or passes the request to the wrapped_function :param wrapped_function: In this case the wrapped function should be tokenclass.check_ping :param *args: args[1] is the pin :param **kwds: kwds["options"] contains the flask g :return: True or False """ ERROR = "There are contradicting policies for the action {0!s}!".format( \ ACTION.OTPPIN) # if tokenclass.check_pin is called in any other way, options may be None # or it might have no element "g". options = kwds.get("options") or {} g = options.get("g") if g: token = args[0] pin = args[1] clientip = options.get("clientip") user_object = kwds.get("user") if not user_object: # No user in the parameters, so we need to determine the owner of # the token user_object = token.user realms = token.get_realms() if not user_object and len(realms): # if the token has not owner, we take a realm. user_object = User("", realm=realms[0]) if not user_object: # If we still have no user and no tokenrealm, we create an empty # user object. user_object=User("", realm="") # get the policy policy_object = g.policy_object otppin_list = policy_object.get_action_values(ACTION.OTPPIN, scope=SCOPE.AUTH, realm=user_object.realm, user=user_object.login, client=clientip) if otppin_list: # There is an otppin policy if len(otppin_list) > 1: # We can not decide how to handle the request, so we raise an # exception raise PolicyError(ERROR) if otppin_list[0] == ACTIONVALUE.NONE: if pin == "": # No PIN checking, we expect an empty PIN! return True else: return False if otppin_list[0] == ACTIONVALUE.USERSTORE: rv = user_object.check_password(pin) return rv is not None # call and return the original check_pin function return wrapped_function(*args, **kwds)
def check_otp_pin(request=None, action=None): """ This policy function checks if the OTP PIN that is about to be set follows the OTP PIN policies ACTION.OTPPINMAXLEN, ACTION.OTPPINMINLEN and ACTION.OTPPINCONTENTS and token-type-specific PIN policy actions in the SCOPE.USER or SCOPE.ADMIN. It is used to decorate the API functions. The pin is investigated in the params as "otppin" or "pin" In case the given OTP PIN does not match the requirements an exception is raised. """ params = request.all_data realm = params.get("realm") pin = params.get("otppin", "") or params.get("pin", "") serial = params.get("serial") tokentype = params.get("type") if not serial and action == ACTION.SETPIN: path_elems = request.path.split("/") serial = path_elems[-1] # Also set it for later use request.all_data["serial"] = serial if serial: # if this is a token, that does not use a pin, we ignore this check # And immediately return true tokensobject_list = get_tokens(serial=serial) if len(tokensobject_list) == 1: if tokensobject_list[0].using_pin is False: return True tokentype = tokensobject_list[0].token.tokentype # the default tokentype is still HOTP tokentype = tokentype or "hotp" policy_object = g.policy_object role = g.logged_in_user.get("role") username = g.logged_in_user.get("username") if role == ROLE.ADMIN: scope = SCOPE.ADMIN admin_realm = g.logged_in_user.get("realm") realm = params.get("realm", "") else: scope = SCOPE.USER realm = g.logged_in_user.get("realm") admin_realm = None # get the policies for minimum length, maximum length and PIN contents # first try to get a token specific policy - otherwise fall back to # default policy pol_minlen = policy_object.get_action_values( action="{0!s}_{1!s}".format(tokentype, ACTION.OTPPINMINLEN), scope=scope, user=username, realm=realm, adminrealm=admin_realm, client=g.client_ip, unique=True) or \ policy_object.get_action_values( action=ACTION.OTPPINMINLEN, scope=scope, user=username, realm=realm, adminrealm=admin_realm, client=g.client_ip, unique=True) pol_maxlen = policy_object.get_action_values( action="{0!s}_{1!s}".format(tokentype, ACTION.OTPPINMAXLEN), scope=scope, user=username, realm=realm, adminrealm=admin_realm, client=g.client_ip, unique=True) or \ policy_object.get_action_values( action=ACTION.OTPPINMAXLEN, scope=scope, user=username, realm=realm, adminrealm=admin_realm, client=g.client_ip, unique=True) pol_contents = policy_object.get_action_values( action="{0!s}_{1!s}".format(tokentype, ACTION.OTPPINCONTENTS), scope=scope, user=username, realm=realm, adminrealm=admin_realm, client=g.client_ip, unique=True) or \ policy_object.get_action_values( action=ACTION.OTPPINCONTENTS, scope=scope, user=username, realm=realm, adminrealm=admin_realm, client=g.client_ip, unique=True) if len(pol_minlen) == 1 and len(pin) < int(pol_minlen[0]): # check the minimum length requirement raise PolicyError("The minimum OTP PIN length is {0!s}".format( pol_minlen[0])) if len(pol_maxlen) == 1 and len(pin) > int(pol_maxlen[0]): # check the maximum length requirement raise PolicyError("The maximum OTP PIN length is {0!s}".format( pol_maxlen[0])) if len(pol_contents) == 1: # check the contents requirement chars = "[a-zA-Z]" # c digits = "[0-9]" # n special = "[.:,;_<>+*!/()=?$§%&#~\^-]" # s no_others = False grouping = False if pol_contents[0] == "-": no_others = True pol_contents = pol_contents[1:] elif pol_contents[0] == "+": grouping = True pol_contents = pol_contents[1:] # TODO implement grouping and substraction if "c" in pol_contents[0] and not re.search(chars, pin): raise PolicyError("Missing character in PIN: {0!s}".format(chars)) if "n" in pol_contents[0] and not re.search(digits, pin): raise PolicyError("Missing character in PIN: {0!s}".format(digits)) if "s" in pol_contents[0] and not re.search(special, pin): raise PolicyError( "Missing character in PIN: {0!s}".format(special)) return True
def twostep_enrollment_activation(request=None, action=None): """ This policy function enables the two-step enrollment process according to the configured policies. It is used to decorate the ``/token/init`` endpoint. If a ``<type>_2step`` policy matches, the ``2stepinit`` parameter is handled according to the policy. If no policy matches, the ``2stepinit`` parameter is removed from the request data. """ policy_object = g.policy_object user_object = get_user_from_param(request.all_data) serial = getParam(request.all_data, "serial", optional) token_type = getParam(request.all_data, "type", optional, "hotp") token_exists = False if serial: tokensobject_list = get_tokens(serial=serial) if len(tokensobject_list) == 1: token_type = tokensobject_list[0].token.tokentype token_exists = True token_type = token_type.lower() role = g.logged_in_user.get("role") # Differentiate between an admin enrolling a token for the # user and a user self-enrolling a token. if role == ROLE.ADMIN: scope = SCOPE.ADMIN adminrealm = g.logged_in_user.get("realm") else: scope = SCOPE.USER adminrealm = None realm = user_object.realm # In any case, the policy's user attribute is matched against the # currently logged-in user (which may be the admin or the # self-enrolling user). user = g.logged_in_user.get("username") # Tokentypes have separate twostep actions action = "{}_2step".format(token_type) twostep_enabled_pols = policy_object.get_action_values( action=action, scope=scope, unique=True, user=user, realm=realm, client=g.client_ip, adminrealm=adminrealm) if twostep_enabled_pols: enabled_setting = twostep_enabled_pols[0] if enabled_setting == "allow": # The user is allowed to pass 2stepinit=1 pass elif enabled_setting == "force": # We force 2stepinit to be 1 (if the token does not exist yet) if not token_exists: request.all_data["2stepinit"] = 1 else: raise PolicyError( "Unknown 2step policy setting: {}".format(enabled_setting)) else: # If no policy matches, the user is not allowed # to pass 2stepinit # Force two-step initialization to be None if "2stepinit" in request.all_data: del request.all_data["2stepinit"] return True
def u2ftoken_allowed(request, action): """ This is a token specific wrapper for u2f token for the endpoint /token/init. According to the policy scope=SCOPE.ENROLL, action=U2FACTINO.REQ it checks, if the assertion certificate is an allowed U2F token type. If the token, which is enrolled contains a non allowed attestation certificate, we bail out. :param request: :param action: :return: """ from privacyidea.lib.tokens.u2ftoken import (U2FACTION, parse_registration_data) from privacyidea.lib.tokens.u2f import x509name_to_string policy_object = g.policy_object # Get the registration data of the 2nd step of enrolling a U2F device reg_data = request.all_data.get("regdata") if reg_data: # We have a registered u2f device! serial = request.all_data.get("serial") user_object = request.User attestation_cert, user_pub_key, key_handle, \ signature, description = parse_registration_data(reg_data) cert_info = { "attestation_issuer": x509name_to_string(attestation_cert.get_issuer()), "attestation_serial": "{!s}".format(attestation_cert.get_serial_number()), "attestation_subject": x509name_to_string(attestation_cert.get_subject()) } if user_object: token_user = user_object.login token_realm = user_object.realm token_resolver = user_object.resolver else: token_realm = token_resolver = token_user = None allowed_certs_pols = policy_object.get_action_values( U2FACTION.REQ, scope=SCOPE.ENROLL, realm=token_realm, user=token_user, resolver=token_resolver, client=g.client_ip) for allowed_cert in allowed_certs_pols: tag, matching, _rest = allowed_cert.split("/", 3) tag_value = cert_info.get("attestation_{0!s}".format(tag)) # if we do not get a match, we bail out m = re.search(matching, tag_value) if not m: log.warning("The U2F device {0!s} is not " "allowed to be registered due to policy " "restriction".format(serial)) raise PolicyError("The U2F device is not allowed " "to be registered due to policy " "restriction.") # TODO: Maybe we should delete the token, as it is a not # usable U2F token, now. return True
def autoassign(request, response): """ This decorator decorates the function /validate/check. Depending on ACTION.AUTOASSIGN it checks if the user has no token and if the given OTP-value matches a token in the users realm, that is not yet assigned to any user. If a token can be found, it assigns the token to the user also taking into account ACTION.MAXTOKENUSER and ACTION.MAXTOKENREALM. :return: """ content = json.loads(response.data) # check, if the authentication was successful, then we need to do nothing if content.get("result").get("value") is False: user_obj = request.User #user_obj = get_user_from_param(request.all_data) password = request.all_data.get("pass", "") if user_obj.login and user_obj.realm: # If there is no user in the request (because it is a serial # authentication request) we immediately bail out # check if the policy is defined policy_object = g.policy_object autoassign_values = policy_object.\ get_action_values(action=ACTION.AUTOASSIGN, scope=SCOPE.ENROLL, user=user_obj.login, resolver=user_obj.resolver, realm=user_obj.realm, client=g.client_ip) if len(autoassign_values) > 1: raise PolicyError("Contradicting Autoassign policies.") # check if the user has no token if autoassign_values and get_tokens(user=user_obj, count=True) == 0: # Check is the token would match # get all unassigned tokens in the realm and look for # a matching OTP: realm_tokens = get_tokens(realm=user_obj.realm, assigned=False) for token_obj in realm_tokens: (res, pin, otp) = token_obj.split_pin_pass(password) if res: pin_check = True if autoassign_values[0] == \ AUTOASSIGNVALUE.USERSTORE: # If the autoassign policy is set to userstore, # we need to check against the userstore. pin_check = user_obj.check_password(pin) if pin_check: otp_check = token_obj.check_otp(otp) if otp_check >= 0: # we found a matching token # check MAXTOKENUSER and MAXTOKENREALM check_max_token_user(request=request) check_max_token_realm(request=request) # Assign token assign_token(serial=token_obj.token.serial, user=user_obj, pin=pin) # Set the response to true content.get("result")["value"] = True # Set the serial number if not content.get("detail"): content["detail"] = {} content.get("detail")["serial"] = \ token_obj.token.serial content.get("detail")["otplen"] = \ token_obj.token.otplen content.get("detail")["type"] = token_obj.type content.get("detail")["message"] = "Token " \ "assigned to " \ "user via " \ "Autoassignment" response.data = json.dumps(content) g.audit_object.log({ "success": True, "action_info": "Token assigned via auto assignment", "serial": token_obj.token.serial }) break return response
def check_pin_policy(pin, policy): """ The policy to check a PIN can contain of "c", "n" and "s". "cn" means, that the PIN should contain a character and a number. "+cn" means, that the PIN should contain elements from the group of characters and numbers "-ns" means, that the PIN must not contain numbers or special characters "[12345]" means, that the PIN may only consist of the characters 1,2,3,4 and 5. :param pin: The PIN to check :param policy: The policy that describes the allowed contents of the PIN. :return: Tuple of True or False and a description """ chars = { "c": r"[a-zA-Z]", "n": r"[0-9]", "s": r"[\[\].:,;_<>+*!/()=?$§%&#~^-]" } ret = True comment = [] if not policy: return False, "No policy given." if policy[0] in ["+", "-"] or policy[0] is not "[": for char in policy[1:]: if char not in chars.keys(): raise PolicyError("Unknown character specifier in PIN policy.") if policy[0] == "+": # grouping necessary = [] for char in policy[1:]: necessary.append(chars.get(char)) necessary = "|".join(necessary) if not re.search(necessary, pin): ret = False comment.append("Missing character in PIN: {0!s}".format(necessary)) elif policy[0] == "-": # exclusion not_allowed = [] for char in policy[1:]: not_allowed.append(chars.get(char)) not_allowed = "|".join(not_allowed) if re.search(not_allowed, pin): ret = False comment.append("Not allowed character in PIN!") elif policy[0] == "[" and policy[-1] == "]": # only allowed characters allowed_chars = policy[1:-1] for ch in pin: if ch not in allowed_chars: ret = False comment.append("Not allowed character in PIN!") else: for c in chars: if c in policy and not re.search(chars[c], pin): ret = False comment.append("Missing character in PIN: {0!s}".format( chars[c])) return ret, ",".join(comment)
def _api_endpoint_get(cls, g, request_data): """ Handle all GET requests to the api endpoint. Currently this is only used for polling. :param g: The Flask context :param request_data: Dictionary containing the parameters of the request :type request_data: dict :returns: Result of the polling operation, 'True' if an unanswered and matching challenge exists, 'False' otherwise. :rtype: bool """ # By default we allow polling if the policy is not set. allow_polling = get_action_values_from_options( SCOPE.AUTH, PUSH_ACTION.ALLOW_POLLING, options={'g': g}) or PushAllowPolling.ALLOW if allow_polling == PushAllowPolling.DENY: raise PolicyError('Polling not allowed!') serial = getParam(request_data, "serial", optional=False) timestamp = getParam(request_data, 'timestamp', optional=False) signature = getParam(request_data, 'signature', optional=False) # first check if the timestamp is in the required span cls._check_timestamp_in_range(timestamp, POLL_TIME_WINDOW) # now check the signature # first get the token try: tok = get_one_token(serial=serial, tokentype=cls.get_class_type()) # If the push_allow_polling policy is set to "token" we also # need to check the POLLING_ALLOWED tokeninfo. If it evaluated # to 'False', polling is not allowed for this token. If the # tokeninfo value evaluates to 'True' or is not set at all, # polling is allowed for this token. if allow_polling == PushAllowPolling.TOKEN: if not is_true(tok.get_tokeninfo(POLLING_ALLOWED, default='True')): log.debug('Polling not allowed for pushtoken {0!s} due to ' 'tokeninfo.'.format(serial)) raise PolicyError('Polling not allowed!') pubkey_obj = _build_verify_object(tok.get_tokeninfo(PUBLIC_KEY_SMARTPHONE)) sign_data = u"{serial}|{timestamp}".format(**request_data) pubkey_obj.verify(b32decode(signature), sign_data.encode("utf8"), padding.PKCS1v15(), hashes.SHA256()) # The signature was valid now check for an open challenge # we need the private server key to sign the smartphone data pem_privkey = tok.get_tokeninfo(PRIVATE_KEY_SERVER) # we also need the FirebaseGateway for this token fb_identifier = tok.get_tokeninfo(PUSH_ACTION.FIREBASE_CONFIG) if not fb_identifier: raise ResourceNotFoundError('The pushtoken {0!s} has no Firebase configuration ' 'assigned.'.format(serial)) fb_gateway = create_sms_instance(fb_identifier) options = {'g': g} challenges = [] challengeobject_list = get_challenges(serial=serial) for chal in challengeobject_list: # check if the challenge is active and not already answered _cnt, answered = chal.get_otp_status() if not answered and chal.is_valid(): # then return the necessary smartphone data to answer # the challenge sp_data = _build_smartphone_data(serial, chal.challenge, fb_gateway, pem_privkey, options) challenges.append(sp_data) # return the challenges as a list in the result value result = challenges except (ResourceNotFoundError, ParameterError, InvalidSignature, ConfigAdminError, BinasciiError) as e: # to avoid disclosing information we always fail with an invalid # signature error even if the token with the serial could not be found log.debug('{0!s}'.format(traceback.format_exc())) log.info('The following error occurred during the signature ' 'check: "{0!r}"'.format(e)) raise privacyIDEAError('Could not verify signature!') return result
def check_otp(self, otpval, counter=None, window=None, options=None): """ This checks the response of a previous challenge. :param otpval: N/A :param counter: The authentication counter :param window: N/A :param options: contains "clientdata", "signaturedata" and "transaction_id" :return: A value > 0 in case of success """ ret = -1 clientdata = options.get("clientdata") signaturedata = options.get("signaturedata") transaction_id = options.get("transaction_id") # The challenge in the challenge DB object is saved in hex challenge = binascii.unhexlify(options.get("challenge", "")) if clientdata and signaturedata and transaction_id and challenge: # This is a valid response for a U2F token challenge_url = url_encode(challenge) clientdata = url_decode(clientdata) clientdata_dict = json.loads(to_unicode(clientdata)) client_challenge = clientdata_dict.get("challenge") if challenge_url != client_challenge: return ret if clientdata_dict.get("typ") != "navigator.id.getAssertion": raise ValidateError("Incorrect navigator.id") #client_origin = clientdata_dict.get("origin") signaturedata = url_decode(signaturedata) signaturedata_hex = hexlify_and_unicode(signaturedata) user_presence, counter, signature = parse_response_data( signaturedata_hex) user_pub_key = self.get_tokeninfo("pubKey") app_id = self.get_tokeninfo("appId") if check_response(user_pub_key, app_id, clientdata, hexlify_and_unicode(signature), counter, user_presence): # Signature verified. # check, if the counter increased! if counter > self.get_otp_count(): self.set_otp_count(counter) ret = counter # At this point we can check, if the attestation # certificate is authorized. # If not, we can raise a policy exception if not attestation_certificate_allowed( { "attestation_issuer": self.get_tokeninfo("attestation_issuer"), "attestation_serial": self.get_tokeninfo("attestation_serial"), "attestation_subject": self.get_tokeninfo("attestation_subject") }, Match .user(options.get("g"), scope=SCOPE.AUTHZ, action=U2FACTION.REQ, user_object=self.user if self.user else None) .action_values(unique=False) ): log.warning( "The U2F device {0!s} is not allowed to authenticate due to policy restriction" .format(self.token.serial)) raise PolicyError("The U2F device is not allowed " "to authenticate due to policy " "restriction.") else: log.warning("The signature of %s was valid, but contained " "an old counter." % self.token.serial) else: log.warning("Checking response for token {0!s} failed.".format( self.token.serial)) return ret