def no_detail_on_success(request, response): """ This policy function is used with the AUTHZ scope. If the boolean value no_detail_on_success is set, the details will be stripped if the authentication request was successful. :param request: :param response: :return: """ content = response.json # get the serials from a policy definition detailPol = Match.action_only(g, scope=SCOPE.AUTHZ, action=ACTION.NODETAILSUCCESS)\ .policies(write_to_audit_log=False) if detailPol and content.get("result", {}).get("value"): # The policy was set, we need to strip the details, if the # authentication was successful. (value=true) # None assures that we do not get an error, if "detail" does not exist. content.pop("detail", None) response.set_data(json.dumps(content)) g.audit_object.add_policy([p.get("name") for p in detailPol]) return response
def api_endpoint(cls, request, g): """ This provides a function to be plugged into the API endpoint /ttype/u2f The u2f token can return the facet list at this URL. :param request: The Flask request :param g: The Flask global object g :return: Flask Response or text """ configured_app_id = get_from_config("u2f.appId") if configured_app_id is None: raise ParameterError("u2f is not configured") app_id = configured_app_id.strip("/") # Read the facets from the policies pol_facets = Match.action_only( g, scope=SCOPE.AUTH, action=U2FACTION.FACETS).action_values(unique=False) facet_list = ["https://{0!s}".format(x) for x in pol_facets] facet_list.append(app_id) log.debug("Sending facets lists for appId {0!s}: {1!s}".format( app_id, facet_list)) res = { "trustedFacets": [{ "version": { "major": 1, "minor": 0 }, "ids": facet_list }] } return "fido.trusted-apps+json", res
def check_serial(request, response): """ This policy function is to be used in a decorator of an API function. It checks, if the token, that was used in the API call has a serial number that is allowed to be used. If not, a PolicyException is raised. :param response: The response of the decorated function :type response: Response object :return: A new (maybe modified) response """ serial = response.json.get("detail", {}).get("serial") # get the serials from a policy definition allowed_serials = Match.action_only(g, scope=SCOPE.AUTHZ, action=ACTION.SERIAL).action_values(unique=False) # If we can compare a serial and if we do serial matching! if serial and allowed_serials: serial_matches = False for allowed_serial in allowed_serials: if re.search(allowed_serial, serial): serial_matches = True break if serial_matches is False: g.audit_object.log({"action_detail": "Serial is not allowed for " "authentication!"}) raise PolicyError("Serial is not allowed for authentication!") return response
def no_detail_on_fail(request, response): """ This policy function is used with the AUTHZ scope. If the boolean value no_detail_on_fail is set, the details will be stripped if the authentication request failed. :param request: :param response: :return: """ content = response.json # get the serials from a policy definition detailPol = Match.action_only( g, scope=SCOPE.AUTHZ, action=ACTION.NODETAILFAIL).policies(write_to_audit_log=False) if detailPol and content.get("result", {}).get("value") is False: # The policy was set, we need to strip the details, if the # authentication was successful. (value=true) del content["detail"] response.set_data(json.dumps(content)) g.audit_object.add_policy([p.get("name") for p in detailPol]) return response
def register_status(): """ This endpoint returns the information if registration is allowed or not. This is used by the UI to either display the registration button or not. :return: JSON with value=True or value=False """ resolvername = Match.action_only(g, scope=SCOPE.REGISTER, action=ACTION.RESOLVER)\ .action_values(unique=True) result = bool(resolvername) g.audit_object.log({"info": result, "success": True}) return send_result(result)
def add_user_detail_to_response(request, response): """ This policy decorated is used in the AUTHZ scope. If the boolean value add_user_in_response is set, the details will contain a dictionary "user" with all user details. :param request: :param response: :return: """ content = response.json # Check for ADD USER IN RESPONSE detail_pol = Match.action_only(g, scope=SCOPE.AUTHZ, action=ACTION.ADDUSERINRESPONSE)\ .policies(write_to_audit_log=False) if detail_pol and content.get("result", {}).get("value") and request.User: # The policy was set, we need to add the user # details ui = request.User.info.copy() ui["password"] = "" for key, value in ui.items(): if type(value) == datetime.datetime: ui[key] = str(value) content.setdefault("detail", {})["user"] = ui g.audit_object.add_policy([p.get("name") for p in detail_pol]) # Check for ADD RESOLVER IN RESPONSE detail_pol = Match.action_only(g, scope=SCOPE.AUTHZ, action=ACTION.ADDRESOLVERINRESPONSE)\ .policies(write_to_audit_log=False) if detail_pol and content.get("result", {}).get("value") and request.User: # The policy was set, we need to add the resolver and the realm content.setdefault("detail", {})["user-resolver"] = request.User.resolver content["detail"]["user-realm"] = request.User.realm g.audit_object.add_policy([p.get("name") for p in detail_pol]) response.set_data(json.dumps(content)) return response
def check_tokeninfo(request, response): """ This policy function is used as a decorator for the validate API. It checks after a successful authentication if the token has a matching tokeninfo field. If it does not match, authorization is denied. Then a PolicyException is raised. :param response: The response of the decorated function :type response: Response object :return: A new modified response """ serial = response.json.get("detail", {}).get("serial") if serial: tokeninfos_pol = Match.action_only(g, scope=SCOPE.AUTHZ, action=ACTION.TOKENINFO)\ .action_values(unique=False, allow_white_space_in_action=True) if tokeninfos_pol: tokens = get_tokens(serial=serial) if len(tokens) == 1: token_obj = tokens[0] for tokeninfo_pol in tokeninfos_pol: try: key, regex, _r = tokeninfo_pol.split("/") value = token_obj.get_tokeninfo(key, "") if re.search(regex, value): log.debug( u"Regular expression {0!s} " u"matches the tokeninfo field {1!s}.".format( regex, key)) else: log.info( u"Tokeninfo field {0!s} with contents {1!s} " u"does not match {2!s}".format( key, value, regex)) raise PolicyError( "Tokeninfo field {0!s} with contents does not" " match regular expression.".format(key)) except ValueError: log.warning(u"invalid tokeinfo policy: {0!s}".format( tokeninfo_pol)) return response
def get_webui_settings(request, response): """ This decorator is used in the /auth API to add configuration information like the logout_time or the policy_template_url to the response. :param request: flask request object :param response: flask response object :return: the response """ content = response.json # check, if the authentication was successful, then we need to do nothing if content.get("result").get("status") is True: role = content.get("result").get("value").get("role") loginname = content.get("result").get("value").get("username") realm = content.get("result").get("value").get("realm") or get_default_realm() # At this point the logged in user is not necessarily a user object. It can # also be a local admin. logout_time_pol = Match.generic(g, scope=SCOPE.WEBUI, action=ACTION.LOGOUTTIME, user=loginname, realm=realm).action_values(unique=True) timeout_action_pol = Match.generic(g, scope=SCOPE.WEBUI, action=ACTION.TIMEOUT_ACTION, user=loginname, realm=realm).action_values(unique=True) token_page_size_pol = Match.generic(g, scope=SCOPE.WEBUI, action=ACTION.TOKENPAGESIZE, user=loginname, realm=realm).action_values(unique=True) user_page_size_pol = Match.generic(g, scope=SCOPE.WEBUI, action=ACTION.USERPAGESIZE, user=loginname, realm=realm).action_values(unique=True) token_wizard_2nd = bool(role == ROLE.USER and Match.generic(g, scope=SCOPE.WEBUI, action=ACTION.TOKENWIZARD2ND, user=loginname, realm=realm).policies()) admin_dashboard = (role == ROLE.ADMIN and Match.generic(g, scope=SCOPE.WEBUI, action=ACTION.ADMIN_DASHBOARD, user=loginname, realm=realm).any()) token_wizard = False dialog_no_token = False if role == ROLE.USER: user_obj = User(loginname, realm) user_token_num = get_tokens(user=user_obj, count=True) token_wizard_pol = Match.user(g, scope=SCOPE.WEBUI, action=ACTION.TOKENWIZARD, user_object=user_obj).any() # We also need to check, if the user has not tokens assigned. # If the user has no tokens, we run the wizard. If the user # already has tokens, we do not run the wizard. token_wizard = token_wizard_pol and (user_token_num == 0) dialog_no_token_pol = Match.user(g, scope=SCOPE.WEBUI, action=ACTION.DIALOG_NO_TOKEN, user_object=user_obj).any() dialog_no_token = dialog_no_token_pol and (user_token_num == 0) user_details_pol = Match.generic(g, scope=SCOPE.WEBUI, action=ACTION.USERDETAILS, user=loginname, realm=realm).policies() search_on_enter = Match.generic(g, scope=SCOPE.WEBUI, action=ACTION.SEARCH_ON_ENTER, user=loginname, realm=realm).policies() hide_welcome = Match.generic(g, scope=SCOPE.WEBUI, action=ACTION.HIDE_WELCOME, user=loginname, realm=realm).any() hide_buttons = Match.generic(g, scope=SCOPE.WEBUI, action=ACTION.HIDE_BUTTONS, user=loginname, realm=realm).any() default_tokentype_pol = Match.generic(g, scope=SCOPE.WEBUI, action=ACTION.DEFAULT_TOKENTYPE, user=loginname, realm=realm).action_values(unique=True) show_seed = Match.generic(g, scope=SCOPE.WEBUI, action=ACTION.SHOW_SEED, user=loginname, realm=realm).any() show_node = Match.generic(g, scope=SCOPE.WEBUI, action=ACTION.SHOW_NODE, realm=realm).any() qr_ios_authenticator = Match.generic(g, scope=SCOPE.WEBUI, action=ACTION.SHOW_IOS_AUTHENTICATOR, user=loginname, realm=realm).any() qr_android_authenticator = Match.generic(g, scope=SCOPE.WEBUI, action=ACTION.SHOW_ANDROID_AUTHENTICATOR, user=loginname, realm=realm).any() qr_custom_authenticator_url = Match.generic(g, scope=SCOPE.WEBUI, action=ACTION.SHOW_CUSTOM_AUTHENTICATOR, user=loginname, realm=realm).action_values(unique=True) qr_image_android = create_img(DEFAULT_ANDROID_APP_URL) if qr_android_authenticator else None qr_image_ios = create_img(DEFAULT_IOS_APP_URL) if qr_ios_authenticator else None qr_image_custom = create_img(list(qr_custom_authenticator_url)[0]) if qr_custom_authenticator_url else None token_page_size = DEFAULT_PAGE_SIZE user_page_size = DEFAULT_PAGE_SIZE default_tokentype = DEFAULT_TOKENTYPE if len(token_page_size_pol) == 1: token_page_size = int(list(token_page_size_pol)[0]) if len(user_page_size_pol) == 1: user_page_size = int(list(user_page_size_pol)[0]) if len(default_tokentype_pol) == 1: default_tokentype = list(default_tokentype_pol)[0] logout_time = DEFAULT_LOGOUT_TIME if len(logout_time_pol) == 1: logout_time = int(list(logout_time_pol)[0]) timeout_action = DEFAULT_TIMEOUT_ACTION if len(timeout_action_pol) == 1: timeout_action = list(timeout_action_pol)[0] policy_template_url_pol = Match.action_only(g, scope=SCOPE.WEBUI, action=ACTION.POLICYTEMPLATEURL).action_values(unique=True) policy_template_url = DEFAULT_POLICY_TEMPLATE_URL if len(policy_template_url_pol) == 1: policy_template_url = list(policy_template_url_pol)[0] indexed_preset_attribute = Match.realm(g, scope=SCOPE.WEBUI, action="indexedsecret_preset_attribute", realm=realm).action_values(unique=True) if len(indexed_preset_attribute) == 1: content["result"]["value"]["indexedsecret_preset_attribute"] = list(indexed_preset_attribute)[0] # This only works for users, because the value of the policy does not change while logged in. if role == ROLE.USER and \ Match.user(g, SCOPE.USER, "indexedsecret_force_attribute", user_obj).action_values(unique=False): content["result"]["value"]["indexedsecret_force_attribute"] = 1 content["result"]["value"]["logout_time"] = logout_time content["result"]["value"]["token_page_size"] = token_page_size content["result"]["value"]["user_page_size"] = user_page_size content["result"]["value"]["policy_template_url"] = policy_template_url content["result"]["value"]["default_tokentype"] = default_tokentype content["result"]["value"]["user_details"] = len(user_details_pol) > 0 content["result"]["value"]["token_wizard"] = token_wizard content["result"]["value"]["token_wizard_2nd"] = token_wizard_2nd content["result"]["value"]["admin_dashboard"] = admin_dashboard content["result"]["value"]["dialog_no_token"] = dialog_no_token content["result"]["value"]["search_on_enter"] = len(search_on_enter) > 0 content["result"]["value"]["timeout_action"] = timeout_action content["result"]["value"]["hide_welcome"] = hide_welcome content["result"]["value"]["hide_buttons"] = hide_buttons content["result"]["value"]["show_seed"] = show_seed content["result"]["value"]["show_node"] = get_privacyidea_node() if show_node else "" content["result"]["value"]["subscription_status"] = subscription_status() content["result"]["value"]["qr_image_android"] = qr_image_android content["result"]["value"]["qr_image_ios"] = qr_image_ios content["result"]["value"]["qr_image_custom"] = qr_image_custom response.set_data(json.dumps(content)) return response
def single_page_application(): instance = request.script_root if instance == "/": instance = "" # The backend URL should come from the configuration of the system. backend_url = "" if current_app.config.get("PI_UI_DEACTIVATED"): # Do not provide the UI return send_html(render_template("deactivated.html")) # The default theme. We can change this later theme = current_app.config.get("PI_CSS", DEFAULT_THEME) theme = theme.strip('/') # Get further customizations customization = current_app.config.get("PI_CUSTOMIZATION", "/static/customize/") customization = customization.strip('/') custom_css = customization + "/css/custom.css" if current_app.config.get("PI_CUSTOM_CSS") else "" # Enrollment-Wizard: # PI_CUSTOMIZATION/views/includes/token.enroll.pre.top.html # PI_CUSTOMIZATION/views/includes/token.enroll.pre.bottom.html # PI_CUSTOMIZATION/views/includes/token.enroll.post.top.html # PI_CUSTOMIZATION/views/includes/token.enroll.post.bottom.html # Get the hidden external links external_links = current_app.config.get("PI_EXTERNAL_LINKS", True) # Read the UI translation warning translation_warning = current_app.config.get("PI_TRANSLATION_WARNING", False) # Get the logo file logo = current_app.config.get("PI_LOGO", "privacyIDEA1.png") browser_lang = request.accept_languages.best_match(["en", "de", "de-DE", "nl"], default="en").split("-")[0] # The page title can be configured in pi.cfg page_title = current_app.config.get("PI_PAGE_TITLE", "privacyIDEA Authentication System") # check if login with REMOTE_USER is allowed. remote_user = "" password_reset = False if not hasattr(request, "all_data"): request.all_data = {} # Depending on displaying the realm dropdown, we fill realms or not. realms = "" realm_dropdown = Match.action_only(g, scope=SCOPE.WEBUI, action=ACTION.REALMDROPDOWN)\ .policies(write_to_audit_log=False) if realm_dropdown: try: realm_dropdown_values = Match.action_only(g, scope=SCOPE.WEBUI, action=ACTION.REALMDROPDOWN) \ .action_values(unique=False, write_to_audit_log=False) # Use the realms from the policy. realms = ",".join(realm_dropdown_values) except AttributeError as _e: # The policy is still a boolean realm_dropdown action # Thus we display ALL realms realms = ",".join(get_realms()) try: if is_remote_user_allowed(request): remote_user = request.remote_user password_reset = is_password_reset(g) hsm_ready = True except HSMException: hsm_ready = False # Use policies to determine the customization of menu # and baseline. get_action_values returns an array! sub_state = subscription_status() customization_menu_file = Match.action_only(g, action=ACTION.CUSTOM_MENU, scope=SCOPE.WEBUI)\ .action_values(unique=True, allow_white_space_in_action=True, write_to_audit_log=False) if len(customization_menu_file) and list(customization_menu_file)[0] \ and sub_state not in [1, 2]: customization_menu_file = list(customization_menu_file)[0] else: customization_menu_file = "templates/menu.html" customization_baseline_file = Match.action_only(g, action=ACTION.CUSTOM_BASELINE, scope=SCOPE.WEBUI) \ .action_values(unique=True, allow_white_space_in_action=True, write_to_audit_log=False) if len(customization_baseline_file) and list(customization_baseline_file)[0] \ and sub_state not in [1, 2]: customization_baseline_file = list(customization_baseline_file)[0] else: customization_baseline_file = "templates/baseline.html" login_text = Match.action_only(g, action=ACTION.LOGIN_TEXT, scope=SCOPE.WEBUI) \ .action_values(unique=True, allow_white_space_in_action=True, write_to_audit_log=False) if len(login_text) and list(login_text)[0] and sub_state not in [1, 2]: login_text = list(login_text)[0] else: login_text = "" render_context = { 'instance': instance, 'backendUrl': backend_url, 'browser_lang': browser_lang, 'remote_user': remote_user, 'theme': theme, 'translation_warning': translation_warning, 'password_reset': password_reset, 'hsm_ready': hsm_ready, 'has_job_queue': str(has_job_queue()), 'customization': customization, 'custom_css': custom_css, 'customization_menu_file': customization_menu_file, 'customization_baseline_file': customization_baseline_file, 'realms': realms, 'external_links': external_links, 'login_text': login_text, 'logo': logo, 'page_title': page_title } return send_html(render_template("index.html", **render_context))
def get_webui_settings(request, response): """ This decorator is used in the /auth API to add configuration information like the logout_time or the policy_template_url to the response. :param request: flask request object :param response: flask response object :return: the response """ content = response.json # check, if the authentication was successful, then we need to do nothing if content.get("result").get("status") is True: role = content.get("result").get("value").get("role") loginname = content.get("result").get("value").get("username") realm = content.get("result").get("value").get("realm") realm = realm or get_default_realm() logout_time_pol = Match.realm(g, scope=SCOPE.WEBUI, action=ACTION.LOGOUTTIME, realm=realm).action_values(unique=True) timeout_action_pol = Match.realm( g, scope=SCOPE.WEBUI, action=ACTION.TIMEOUT_ACTION, realm=realm).action_values(unique=True) token_page_size_pol = Match.realm( g, scope=SCOPE.WEBUI, action=ACTION.TOKENPAGESIZE, realm=realm).action_values(unique=True) user_page_size_pol = Match.realm( g, scope=SCOPE.WEBUI, action=ACTION.USERPAGESIZE, realm=realm).action_values(unique=True) token_wizard_2nd = (role == ROLE.USER and Match.realm( g, scope=SCOPE.WEBUI, action=ACTION.TOKENWIZARD2ND, realm=realm).policies()) token_wizard = False dialog_no_token = False if role == ROLE.USER: user_obj = User(loginname, realm) user_token_num = get_tokens(user=user_obj, count=True) token_wizard_pol = Match.user(g, scope=SCOPE.WEBUI, action=ACTION.TOKENWIZARD, user_object=user_obj).any() # We also need to check, if the user has not tokens assigned. # If the user has no tokens, we run the wizard. If the user # already has tokens, we do not run the wizard. token_wizard = token_wizard_pol and (user_token_num == 0) dialog_no_token_pol = Match.user(g, scope=SCOPE.WEBUI, action=ACTION.DIALOG_NO_TOKEN, user_object=user_obj).any() dialog_no_token = dialog_no_token_pol and (user_token_num == 0) user_details_pol = Match.realm(g, scope=SCOPE.WEBUI, action=ACTION.USERDETAILS, realm=realm).policies() search_on_enter = Match.realm(g, scope=SCOPE.WEBUI, action=ACTION.SEARCH_ON_ENTER, realm=realm).policies() hide_welcome = Match.realm(g, scope=SCOPE.WEBUI, action=ACTION.HIDE_WELCOME, realm=realm).any() hide_buttons = Match.realm(g, scope=SCOPE.WEBUI, action=ACTION.HIDE_BUTTONS, realm=realm).any() default_tokentype_pol = Match.realm( g, scope=SCOPE.WEBUI, action=ACTION.DEFAULT_TOKENTYPE, realm=realm).action_values(unique=True) show_seed = Match.realm(g, scope=SCOPE.WEBUI, action=ACTION.SHOW_SEED, realm=realm).any() token_page_size = DEFAULT_PAGE_SIZE user_page_size = DEFAULT_PAGE_SIZE default_tokentype = DEFAULT_TOKENTYPE if len(token_page_size_pol) == 1: token_page_size = int(list(token_page_size_pol)[0]) if len(user_page_size_pol) == 1: user_page_size = int(list(user_page_size_pol)[0]) if len(default_tokentype_pol) == 1: default_tokentype = list(default_tokentype_pol)[0] logout_time = DEFAULT_LOGOUT_TIME if len(logout_time_pol) == 1: logout_time = int(list(logout_time_pol)[0]) timeout_action = DEFAULT_TIMEOUT_ACTION if len(timeout_action_pol) == 1: timeout_action = list(timeout_action_pol)[0] policy_template_url_pol = Match.action_only( g, scope=SCOPE.WEBUI, action=ACTION.POLICYTEMPLATEURL).action_values(unique=True) policy_template_url = DEFAULT_POLICY_TEMPLATE_URL if len(policy_template_url_pol) == 1: policy_template_url = list(policy_template_url_pol)[0] content["result"]["value"]["logout_time"] = logout_time content["result"]["value"]["token_page_size"] = token_page_size content["result"]["value"]["user_page_size"] = user_page_size content["result"]["value"]["policy_template_url"] = policy_template_url content["result"]["value"]["default_tokentype"] = default_tokentype content["result"]["value"]["user_details"] = len(user_details_pol) > 0 content["result"]["value"]["token_wizard"] = token_wizard content["result"]["value"]["token_wizard_2nd"] = token_wizard_2nd content["result"]["value"]["dialog_no_token"] = dialog_no_token content["result"]["value"]["search_on_enter"] = len( search_on_enter) > 0 content["result"]["value"]["timeout_action"] = timeout_action content["result"]["value"]["hide_welcome"] = hide_welcome content["result"]["value"]["hide_buttons"] = hide_buttons content["result"]["value"]["show_seed"] = show_seed content["result"]["value"][ "subscription_status"] = subscription_status() response.set_data(json.dumps(content)) return response
def register_post(): """ Register a new user in the realm/userresolver. To do so, the user resolver must be writeable like an SQLResolver. Registering a user in fact creates a new user and also creates the first token for the user. The following values are needed to register the user: * username (mandatory) * givenname (mandatory) * surname (mandatory) * email address (mandatory) * password (mandatory) * mobile phone (optional) * telephone (optional) The user receives a registration token via email to be able to login with his self chosen password and the registration token. :jsonparam username: The login name of the new user. Check if it already exists :jsonparam givenname: The givenname of the new user :jsonparam surname: The surname of the new user :jsonparam email: The email address of the new user :jsonparam password: The password of the new user. This is the resolver password of the new user. :jsonparam mobile: The mobile phone number :jsonparam phone: The phone number (land line) of the new user :return: a json result with a boolean "result": true """ username = getParam(request.all_data, "username", required) surname = getParam(request.all_data, "surname", required) givenname = getParam(request.all_data, "givenname", required) email = getParam(request.all_data, "email", required) password = getParam(request.all_data, "password", required) mobile = getParam(request.all_data, "mobile") phone = getParam(request.all_data, "phone") options = {"g": g, "clientip": g.client_ip} g.audit_object.log({"info": username}) # Add all params to the options for key, value in request.all_data.items(): if value and key not in ["g", "clientip"]: options[key] = value # 0. check, if we can do the registration at all! smtpconfig = Match.action_only(g, scope=SCOPE.REGISTER, action=ACTION.EMAILCONFIG)\ .action_values(unique=True) if not smtpconfig: raise RegistrationError("No SMTP server configuration specified!") # 1. determine, in which resolver/realm the user should be created realm = Match.action_only(g, scope=SCOPE.REGISTER, action=ACTION.REALM)\ .action_values(unique=True) if not realm: # No policy for realm, so we use the default realm realm = get_default_realm else: # we use the first realm in the list realm = list(realm)[0] resolvername = Match.action_only(g, scope=SCOPE.REGISTER, action=ACTION.RESOLVER)\ .action_values(unique=True) if not resolvername: raise RegistrationError("No resolver specified to register in!") resolvername = list(resolvername)[0] # Check if the user exists user = User(username, realm=realm, resolver=resolvername) if user.exist(): raise RegistrationError("The username is already registered!") # Create user uid = create_user( resolvername, { "username": username, "email": email, "phone": phone, "mobile": mobile, "surname": surname, "givenname": givenname, "password": password }) # 3. create a registration token for this user user = User(username, realm=realm, resolver=resolvername) token = init_token({"type": "registration"}, user=user) # 4. send the registration token to the users email registration_key = token.init_details.get("otpkey") smtpconfig = list(smtpconfig)[0] # Send the registration key via email body = Match.action_only(g, scope=SCOPE.REGISTER, action=ACTION.REGISTERBODY)\ .action_values(unique=True) body = body or DEFAULT_BODY email_sent = send_email_identifier(smtpconfig, email, "Your privacyIDEA registration", body.format(regkey=registration_key)) if not email_sent: log.warning("Failed to send registration email to {0!r}".format(email)) # delete registration token token.delete() # delete user user.delete() raise RegistrationError("Failed to send email!") log.debug("Registration email sent to {0!r}".format(email)) g.audit_object.log({"success": email_sent}) return send_result(email_sent)