def loadConfig(self, config): """ Load the config from conf. :param config: The configuration from the Config Table :type config: dict '#ldap_uri': 'LDAPURI', '#ldap_basedn': 'LDAPBASE', '#ldap_binddn': 'BINDDN', '#ldap_password': '******', '#ldap_timeout': 'TIMEOUT', '#ldap_sizelimit': 'SIZELIMIT', '#ldap_loginattr': 'LOGINNAMEATTRIBUTE', '#ldap_searchfilter': 'LDAPSEARCHFILTER', '#ldap_mapping': 'USERINFO', '#ldap_uidtype': 'UIDTYPE', '#ldap_noreferrals' : 'NOREFERRALS', '#ldap_editable' : 'EDITABLE', '#ldap_certificate': 'CACERTIFICATE', """ self.uri = config.get("LDAPURI") self.basedn = config.get("LDAPBASE") self.binddn = config.get("BINDDN") # object_classes is a comma separated list like # ["top", "person", "organizationalPerson", "user", "inetOrgPerson"] self.object_classes = [cl.strip() for cl in config.get("OBJECT_CLASSES", "").split(",")] self.dn_template = config.get("DN_TEMPLATE", "") self.bindpw = config.get("BINDPW") self.timeout = float(config.get("TIMEOUT", 5)) self.cache_timeout = int(config.get("CACHE_TIMEOUT", 120)) self.sizelimit = int(config.get("SIZELIMIT", 500)) self.loginname_attribute = config.get("LOGINNAMEATTRIBUTE") self.searchfilter = config.get("LDAPSEARCHFILTER") userinfo = config.get("USERINFO", "{}") self.userinfo = yaml.safe_load(userinfo) self.userinfo["username"] = self.loginname_attribute self.map = yaml.safe_load(userinfo) self.uidtype = config.get("UIDTYPE", "DN") self.noreferrals = is_true(config.get("NOREFERRALS", False)) self.start_tls = is_true(config.get("START_TLS", False)) self.get_info = get_info_configuration(is_true(config.get("NOSCHEMAS", False))) self._editable = config.get("EDITABLE", False) self.scope = config.get("SCOPE") or ldap3.SUBTREE self.resolverId = self.uri self.authtype = config.get("AUTHTYPE", AUTHTYPE.SIMPLE) self.tls_verify = is_true(config.get("TLS_VERIFY", False)) self.tls_ca_file = config.get("TLS_CA_FILE") or DEFAULT_CA_FILE if self.tls_verify and (self.uri.lower().startswith("ldaps") or self.start_tls): self.tls_context = Tls(validate=ssl.CERT_REQUIRED, version=ssl.PROTOCOL_TLSv1, ca_certs_file=self.tls_ca_file) else: self.tls_context = None return self
def get_resolver_list(filter_resolver_type=None, filter_resolver_name=None, editable=None, censor=False): """ Gets the list of configured resolvers from the database :param filter_resolver_type: Only resolvers of the given type are returned :type filter_resolver_type: basestring :param filter_resolver_name: Get the distinct resolver :type filter_resolver_name: basestring :param editable: Whether only return editable resolvers :type editable: bool :param censor: censor sensitive data. Each resolver class decides on its own, which data should be censored. :type censor: bool :rtype: Dictionary of the resolvers and their configuration """ # We need to check if we need to update the config object config_object = update_config_object() if censor: resolvers = copy.deepcopy(config_object.resolver) else: resolvers = config_object.resolver if filter_resolver_type: reduced_resolvers = {} for reso_name, reso in resolvers.items(): if reso.get("type") == filter_resolver_type: reduced_resolvers[reso_name] = resolvers[reso_name] resolvers = reduced_resolvers if filter_resolver_name: reduced_resolvers = {} for reso_name in resolvers: if reso_name.lower() == filter_resolver_name.lower(): reduced_resolvers[reso_name] = resolvers[reso_name] resolvers = reduced_resolvers if editable is not None: reduced_resolvers = {} if editable is True: for reso_name, reso in resolvers.items(): check_editable = is_true(reso["data"].get("Editable")) or \ is_true(reso["data"].get("EDITABLE")) if check_editable: reduced_resolvers[reso_name] = resolvers[reso_name] elif editable is False: for reso_name, reso in resolvers.items(): check_editable = is_true(reso["data"].get("Editable")) or \ is_true(reso["data"].get("EDITABLE")) if not check_editable: reduced_resolvers[reso_name] = resolvers[reso_name] resolvers = reduced_resolvers if censor: for reso_name, reso in resolvers.items(): for censor_key in reso.get("censor_keys", []): reso["data"][censor_key] = CENSORED return resolvers
def update(self, param, reset_failcount=True): """ process the initialization parameters Do we really always need an otpkey? the otpKey is handled in the parent class :param param: dict of initialization parameters :type param: dict :return: nothing """ # In case am Immutable MultiDict: upd_param = {} for k, v in param.items(): upd_param[k] = v # Special handling of 2-step enrollment if is_true(getParam(param, "2stepinit", optional)): # Use the 2step_serversize setting for the size of the server secret # (if it is set) if "2step_serversize" in upd_param: upd_param["keysize"] = int(getParam(upd_param, "2step_serversize", required)) # Add twostep settings to the tokeninfo for key, default in [ ("2step_difficulty", TWOSTEP_DEFAULT_DIFFICULTY), ("2step_clientsize", TWOSTEP_DEFAULT_CLIENTSIZE)]: self.add_tokeninfo(key, getParam(param, key, optional, default)) val = getParam(upd_param, "hashlib", optional) if val is not None: hashlibStr = val else: hashlibStr = self.hashlib # check if the key_size is provided # if not, we could derive it from the hashlib key_size = getParam(upd_param, 'key_size', optional) \ or getParam(upd_param, 'keysize', optional) if key_size is None: upd_param['keysize'] = keylen.get(hashlibStr) otpKey = getParam(upd_param, "otpkey", optional) genkey = is_true(getParam(upd_param, "genkey", optional)) if genkey and otpKey: # The Base TokenClass does not allow otpkey and genkey at the # same time del upd_param['otpkey'] upd_param['hashlib'] = hashlibStr # We first need to call the parent class. Since exceptions would be # raised here. TokenClass.update(self, upd_param, reset_failcount) self.add_tokeninfo("hashlib", hashlibStr) # check the tokenkind if self.token.serial.startswith("UB"): self.add_tokeninfo("tokenkind", TOKENKIND.HARDWARE)
def test_06_is_true(self): self.assertFalse(is_true(None)) self.assertFalse(is_true(0)) self.assertFalse(is_true("0")) self.assertFalse(is_true(False)) self.assertFalse(is_true("false")) self.assertTrue(is_true(1)) self.assertTrue(is_true("1")) self.assertTrue(is_true("True")) self.assertTrue(is_true("TRUE")) self.assertTrue(is_true(True))
def test_01_create_token(self): db_token = Token(self.serial1, tokentype="sms") db_token.save() token = SmsTokenClass(db_token) token.update({"phone": self.phone1}) token.save() self.assertTrue(token.token.serial == self.serial1, token) self.assertTrue(token.token.tokentype == "sms", token.token) self.assertTrue(token.type == "sms", token.type) class_prefix = token.get_class_prefix() self.assertTrue(class_prefix == "PISM", class_prefix) self.assertTrue(token.get_class_type() == "sms", token) db_token = Token(self.serial2, tokentype="sms") db_token.save() token = SmsTokenClass(db_token) token.update({"dynamic_phone": True}) token.save() self.assertTrue(token.token.serial == self.serial2, token) self.assertTrue(token.token.tokentype == "sms", token.token) self.assertTrue(is_true(token.get_tokeninfo("dynamic_phone"))) self.assertTrue(token.type == "sms", token.type) class_prefix = token.get_class_prefix() self.assertTrue(class_prefix == "PISM", class_prefix) self.assertTrue(token.get_class_type() == "sms", token) token.set_user(User(login="******", realm=self.realm1))
def check_otp(self, anOtpVal, counter=None, window=None, options=None): """ check the otpval of a token against a given counter and the window :param passw: the to be verified passw/pin :type passw: string :return: counter if found, -1 if not found :rtype: int """ options = options or {} ret = HotpTokenClass.check_otp(self, anOtpVal, counter, window, options) if ret < 0 and is_true(get_from_config("email.concurrent_challenges")): if options.get("data") == anOtpVal: # We authenticate from the saved challenge ret = 1 if ret >= 0 and self._get_auto_email(options): message, mimetype = self._get_email_text_or_subject(options) subject, _ = self._get_email_text_or_subject(options, action=EMAILACTION.EMAILSUBJECT, default="Your OTP") self.inc_otp_counter(ret, reset=False) success, message = self._compose_email(message=message, subject=subject, mimetype=mimetype) log.debug("AutoEmail: send new SMS: {0!s}".format(success)) log.debug("AutoEmail: {0!r}".format(message)) return ret
def do(self, params): for opt in self.options.keys(): if is_true(params.get(opt)): log.debug("Got param {0}".format(opt)) write_stats(opt, getattr(self, '_' + opt)) return True
def update(self, param, reset_failcount=True): """ update - process initialization parameters :param param: dict of initialization parameters :type param: dict :return: nothing """ if self.hKeyRequired is True: genkey = is_true(getParam(param, "genkey", optional)) if not param.get('keysize'): param['keysize'] = 16 if genkey: otpKey = generate_otpkey(param['keysize']) del param['genkey'] else: # genkey not set: check otpkey is given # this will raise an exception if otpkey is not present otpKey = getParam(param, "otpkey", required) param['otpkey'] = otpKey # motp token specific mOTPPin = getParam(param, "motppin", required) self.token.set_user_pin(mOTPPin) TokenClass.update(self, param, reset_failcount) return
def test_01_create_token(self): db_token = Token(self.serial1, tokentype="email") db_token.save() token = EmailTokenClass(db_token) token.update({"email": self.email}) token.save() self.assertTrue(token.token.serial == self.serial1, token) self.assertTrue(token.token.tokentype == "email", token.token) self.assertTrue(token.type == "email", token.type) class_prefix = token.get_class_prefix() self.assertTrue(class_prefix == "PIEM", class_prefix) self.assertTrue(token.get_class_type() == "email", token) # create token with dynamic email db_token = Token(self.serial2, tokentype="email") db_token.save() token = EmailTokenClass(db_token) token.update({"dynamic_email": True}) token.save() self.assertTrue(is_true(token.get_tokeninfo("dynamic_email"))) self.assertTrue(token.token.serial == self.serial2, token) self.assertTrue(token.token.tokentype == "email", token.token) self.assertTrue(token.type == "email", token.type) class_prefix = token.get_class_prefix() self.assertTrue(class_prefix == "PIEM", class_prefix) self.assertTrue(token.get_class_type() == "email", token) token.add_user(User(login="******", realm=self.realm1))
def update(self, param, reset_failcount=True): """ update - process initialization parameters :param param: dict of initialization parameters :type param: dict :return: nothing """ if is_true(getParam(param, 'genkey', optional)): raise ParameterError("Generating OTP keys is not supported") upd_param = param.copy() # If the OTP key is given, it is given as a 496-character hex string which # encodes a 248-byte blob. As we want to set a 248-byte OTPKey (= Blob), # we unhexlify the OTP key if 'otpkey' in param: if len(param['otpkey']) != 496: raise ParameterError('Expected OTP key as 496-character hex string, but length is {!s}'.format( len(param['otpkey']) )) try: upd_param['otpkey'] = binascii.unhexlify(upd_param['otpkey']) except (binascii.Error, TypeError): raise ParameterError('Expected OTP key as 496-character hex string, but it is malformed') TokenClass.update(self, upd_param, reset_failcount)
def get_resolver_list(filter_resolver_type=None, filter_resolver_name=None, editable=None): """ Gets the list of configured resolvers from the database :param filter_resolver_type: Only resolvers of the given type are returned :type filter_resolver_type: basestring :param filter_resolver_name: Get the distinct resolver :type filter_resolver_name: basestring :param editable: Whether only return editable resolvers :type editable: bool :rtype: Dictionary of the resolvers and their configuration """ # We need to check if we need to update the config object config_object = update_config_object() resolvers = config_object.resolver if filter_resolver_type: reduced_resolvers = {} for reso_name, reso in resolvers.items(): if reso.get("type") == filter_resolver_type: reduced_resolvers[reso_name] = resolvers[reso_name] resolvers = reduced_resolvers if filter_resolver_name: reduced_resolvers = {} for reso_name in resolvers: if reso_name.lower() == filter_resolver_name.lower(): reduced_resolvers[reso_name] = resolvers[reso_name] resolvers = reduced_resolvers if editable is not None: reduced_resolvers = {} if editable is True: for reso_name, reso in resolvers.items(): check_editable = is_true(reso["data"].get("Editable")) or \ is_true(reso["data"].get("EDITABLE")) if check_editable: reduced_resolvers[reso_name] = resolvers[reso_name] elif editable is False: for reso_name, reso in resolvers.items(): check_editable = is_true(reso["data"].get("Editable")) or \ is_true(reso["data"].get("EDITABLE")) if not check_editable: reduced_resolvers[reso_name] = resolvers[reso_name] resolvers = reduced_resolvers return resolvers
def editable(self): """ Return true, if the instance of the resolver is configured editable :return: """ # Depending on the database this might look different # Usually this is "1" return is_true(self._editable)
def create_challenge(self, transactionid=None, options=None): """ create a challenge, which is submitted to the user :param transactionid: the id of this challenge :param options: the request context parameters / data You can pass exception=1 to raise an exception, if the SMS could not be sent. Otherwise the message is contained in the response. :return: tuple of (bool, message and data) bool, if submit was successful message is submitted to the user data is preserved in the challenge attributes - additional attributes, which are displayed in the output """ success = False options = options or {} return_message = get_action_values_from_options(SCOPE.AUTH, "{0!s}_{1!s}".format(self.get_class_type(), ACTION.CHALLENGETEXT), options) or _("Enter the OTP from the SMS:") attributes = {'state': transactionid} validity = self._get_sms_timeout() if self.is_active() is True: counter = self.get_otp_count() log.debug("counter={0!r}".format(counter)) self.inc_otp_counter(counter, reset=False) # At this point we must not bail out in case of an # Gateway error, since checkPIN is successful. A bail # out would cancel the checking of the other tokens try: message_template = self._get_sms_text(options) success, sent_message = self._send_sms( message=message_template) # Create the challenge in the database db_challenge = Challenge(self.token.serial, transaction_id=transactionid, challenge=options.get("challenge"), session=options.get("session"), validitytime=validity) db_challenge.save() transactionid = transactionid or db_challenge.transaction_id except Exception as e: info = ("The PIN was correct, but the " "SMS could not be sent: %r" % e) log.warning(info) log.debug("{0!s}".format(traceback.format_exc())) return_message = info if is_true(options.get("exception")): raise Exception(info) expiry_date = datetime.datetime.now() + \ datetime.timedelta(seconds=validity) attributes['valid_until'] = "{0!s}".format(expiry_date) return success, return_message, transactionid, attributes
def check_pin_local(self): """ lookup if pin should be checked locally or on radius host :return: bool """ local_check = is_true(self.get_tokeninfo("radius.local_checkpin")) log.debug("local checking pin? {0!r}".format(local_check)) return local_check
def load_config(self, config): """ This loads the configuration dictionary, which contains the necessary information for the machine resolver to find and connect to the machine store. class=computer or sAMAccountType=805306369 (MachineAccount) * hostname: attribute dNSHostName * id: DN or objectSid * ip: N/A :param config: The configuration dictionary to run the machine resolver :type config: dict :return: None """ self.uri = config.get("LDAPURI") if self.uri is None: raise MachineResolverError("LDAPURI is missing!") self.basedn = config.get("LDAPBASE") if self.basedn is None: raise MachineResolverError("LDAPBASE is missing!") self.binddn = config.get("BINDDN") self.bindpw = config.get("BINDPW") self.timeout = float(config.get("TIMEOUT", 5)) self.sizelimit = config.get("SIZELIMIT", 500) self.hostname_attribute = config.get("HOSTNAMEATTRIBUTE") self.id_attribute = config.get("IDATTRIBUTE", "DN") self.ip_attribute = config.get("IPATTRIBUTE") self.search_filter = config.get("SEARCHFILTER", "(objectClass=computer)") self.noreferrals = is_true(config.get("NOREFERRALS", False)) self.authtype = config.get("AUTHTYPE", AUTHTYPE.SIMPLE) self.start_tls = is_true(config.get("START_TLS", False)) self.tls_verify = is_true(config.get("TLS_VERIFY", False)) self.tls_ca_file = config.get("TLS_CA_FILE") or DEFAULT_CA_FILE if self.tls_verify and (self.uri.lower().startswith("ldaps") or self.start_tls): self.tls_context = Tls(validate=ssl.CERT_REQUIRED, version=ssl.PROTOCOL_TLSv1, ca_certs_file=self.tls_ca_file) else: self.tls_context = None
def _email_address(self): if is_true(self.get_tokeninfo("dynamic_email")): email = self.user.info.get(self.EMAIL_ADDRESS_KEY) if type(email) == list and email: # If there is a non-empty list, we use the first entry email = email[0] else: email = self.get_tokeninfo(self.EMAIL_ADDRESS_KEY) if not email: # pragma: no cover log.warning("Token {0!s} does not have an email address!".format(self.token.serial)) return email
def testconnection(params): """ Test if the given filename exists. :param params: :return: """ success = False ldap_uri = params.get("LDAPURI") if is_true(params.get("TLS_VERIFY")) \ and (ldap_uri.lower().startswith("ldaps") or params.get("START_TLS")): tls_ca_file = params.get("TLS_CA_FILE") or DEFAULT_CA_FILE tls_context = Tls(validate=ssl.CERT_REQUIRED, version=ssl.PROTOCOL_TLSv1, ca_certs_file=tls_ca_file) else: tls_context = None try: server_pool = IdResolver.get_serverpool(ldap_uri, float(params.get( "TIMEOUT", 5)), tls_context=tls_context) l = IdResolver.create_connection(authtype=\ params.get("AUTHTYPE", AUTHTYPE.SIMPLE), server=server_pool, user=params.get("BINDDN"), password=params.get("BINDPW"), auto_referrals=not params.get( "NOREFERRALS"), start_tls=params.get("START_TLS", False)) if not l.bind(): raise Exception("Wrong credentials") # search for users... l.search(search_base=params["LDAPBASE"], search_scope=ldap3.SUBTREE, search_filter="(&" + params["SEARCHFILTER"] + ")", attributes=[ params["HOSTNAMEATTRIBUTE"] ]) count = len([x for x in l.response if x.get("type") == "searchResEntry"]) desc = _("Your LDAP config seems to be OK, %i machine objects " "found.")\ % count l.unbind() success = True except Exception as e: desc = "{0!r}".format(e) return success, desc
def twostep_enrollment_parameters(request=None, action=None): """ If the ``2stepinit`` parameter is set to true, this policy function reads additional configuration from policies and adds it to ``request.all_data``, that is: * ``{type}_2step_serversize`` is written to ``2step_serversize`` * ``{type}_2step_clientsize`` is written to ``2step_clientsize` * ``{type}_2step_difficulty`` is written to ``2step_difficulty`` If no policy matches, the value passed by the user is kept. This policy function is used to decorate the ``/token/init`` endpoint. """ 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") if serial: tokensobject_list = get_tokens(serial=serial) if len(tokensobject_list) == 1: token_type = tokensobject_list[0].token.tokentype 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: adminrealm = g.logged_in_user.get("realm") else: 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 if is_true(getParam(request.all_data, "2stepinit", optional)): parameters = ("2step_serversize", "2step_clientsize", "2step_difficulty") for parameter in parameters: action = u"{}_{}".format(token_type, parameter) action_values = policy_object.get_action_values(action=action, scope=SCOPE.ENROLL, unique=True, user=user, realm=realm, client=g.client_ip, adminrealm=adminrealm) if action_values: request.all_data[parameter] = action_values[0]
def do(self, params): event_counter = params.get("event_counter") stats_key = params.get("stats_key") reset_event_counter = params.get("reset_event_counter") counter_value = read(event_counter) if is_true(reset_event_counter) and counter_value: reset(event_counter) # now write the current value of the counter if counter_value is None: log.warning(u"Trying to create statistics of a counter_value '{0}', " u"that does not exist.".format(event_counter)) else: write_stats(stats_key, counter_value) return True
def set_eventhandling(): """ This creates a new event handling definition :param name: A describing name of the event.bool :param id: (optional) when updating an existing event you need to specify the id :param event: A comma seperated list of events :param handlermodule: A handlermodule :param action: The action to perform :param ordering: An integer number :param position: "pre" or "post" :param conditions: Conditions, when the event will trigger :param options.: A list of possible options. """ param = request.all_data name = getParam(param, "name", optional=False) event = getParam(param, "event", optional=False) eid = getParam(param, "id", optional=True) active = is_true(getParam(param, "active", default=True)) if eid: eid = int(eid) handlermodule = getParam(param, "handlermodule", optional=False) action = getParam(param, "action", optional=False) ordering = getParam(param, "ordering", optional=True, default=0) position = getParam(param, "position", optional=True, default="post") conditions = getParam(param, "conditions", optional=True, default={}) if type(conditions) is not dict: conditions = json.loads(conditions) options = {} for k, v in param.items(): if k.startswith("option."): options[k[7:]] = v res = set_event(name, event, handlermodule=handlermodule, action=action, conditions=conditions, ordering=ordering, id=eid, options=options, active=active, position=position) g.audit_object.log({"success": True, "info": res}) return send_result(res)
def set_periodic_task_api(): """ Create or replace an existing periodic task definition. :param id: ID of an existing periodic task definition that should be updated :param name: Name of the periodic task :param active: true if the periodic task should be active :param interval: Interval at which the periodic task should run (in cron syntax) :param nodes: Comma-separated list of nodes on which the periodic task should run :param taskmodule: Task module name of the task :param ordering: Ordering of the task, must be a number >= 0. :param options: A dictionary (possibly JSON) of periodic task options, mapping unicodes to unicodes :return: ID of the periodic task """ param = request.all_data ptask_id = getParam(param, "id", optional=True) if ptask_id is not None: ptask_id = int(ptask_id) name = getParam(param, "name", optional=False) active = is_true(getParam(param, "active", default=True)) interval = getParam(param, "interval", optional=False) node_string = getParam(param, "nodes", optional=False) if node_string.strip(): node_list = [node.strip() for node in node_string.split(",")] else: raise ParameterError(u"nodes: expected at least one node") taskmodule = getParam(param, "taskmodule", optional=False) if taskmodule not in get_available_taskmodules(): raise ParameterError("Unknown task module: {!r}".format(taskmodule)) ordering = int(getParam(param, "ordering", optional=False)) options = getParam(param, "options", optional=True) if options is None: options = {} elif not isinstance(options, dict): options = json.loads(options) if not isinstance(options, dict): raise ParameterError(u"options: expected dictionary, got {!r}".format(options)) result = set_periodic_task(name, interval, node_list, taskmodule, ordering, options, active, ptask_id) g.audit_object.log({"success": True, "info": result}) return send_result(result)
def update(self, param, reset_failcount=True): """ This method is called during the initialization process. :param param: parameters from the token init :type param: dict :return: None """ TokenClass.update(self, param) description = "U2F initialization" reg_data = getParam(param, "regdata") verify_cert = is_true(getParam(param, "u2f.verify_cert", default=True)) if reg_data: self.init_step = 2 attestation_cert, user_pub_key, key_handle, \ signature, description = parse_registration_data(reg_data, verify_cert=verify_cert) client_data = getParam(param, "clientdata", required) client_data_str = url_decode(client_data) app_id = self.get_tokeninfo("appId", "") # Verify the registration data # In case of any crypto error, check_data raises an exception check_registration_data(attestation_cert, app_id, client_data_str, user_pub_key, key_handle, signature) self.set_otpkey(key_handle) self.add_tokeninfo("pubKey", user_pub_key) # add attestation certificat info issuer = x509name_to_string(attestation_cert.get_issuer()) serial = "{!s}".format(attestation_cert.get_serial_number()) subject = x509name_to_string(attestation_cert.get_subject()) self.add_tokeninfo("attestation_issuer", issuer) self.add_tokeninfo("attestation_serial", serial) self.add_tokeninfo("attestation_subject", subject) # If a description is given we use the given description description = getParam(param, "description", default=description) self.set_description(description)
def check_otp(self, anOtpVal, counter=None, window=None, options=None): """ check the otpval of a token against a given counter and the window :param passw: the to be verified passw/pin :type passw: string :return: counter if found, -1 if not found :rtype: int """ options = options or {} ret = HotpTokenClass.check_otp(self, anOtpVal, counter, window, options) if ret < 0 and is_true(get_from_config("sms.concurrent_challenges")): if options.get("data") == anOtpVal: # We authenticate from the saved challenge ret = 1 if ret >= 0 and self._get_auto_sms(options): message = self._get_sms_text(options) self.inc_otp_counter(ret, reset=False) success, message = self._send_sms(message=message) log.debug("AutoSMS: send new SMS: {0!s}".format(success)) log.debug("AutoSMS: {0!r}".format(message)) return ret
def check_condition(self, options): """ Check if all conditions are met and if the action should be executed. The the conditions are met, we return "True" :return: True """ g = options.get("g") request = options.get("request") response = options.get("response") e_handler_def = options.get("handler_def") if not e_handler_def: # options is the handler definition return True # conditions can be corresponding to the property conditions conditions = e_handler_def.get("conditions") content = self._get_response_content(response) user = self._get_tokenowner(request) serial = request.all_data.get("serial") or \ content.get("detail", {}).get("serial") tokenrealms = [] tokentype = None token_obj = None if serial: # We have determined the serial number from the request. token_obj_list = get_tokens(serial=serial) else: # We have to determine the token via the user object. But only if # the user has only one token token_obj_list = get_tokens(user=user) if len(token_obj_list) == 1: token_obj = token_obj_list[0] tokenrealms = token_obj.get_realms() tokentype = token_obj.get_tokentype() if "realm" in conditions: if user.realm != conditions.get("realm"): return False if "logged_in_user" in conditions: # Determine the role of the user try: logged_in_user = g.logged_in_user user_role = logged_in_user.get("role") except Exception: # A non-logged-in-user is a User, not an admin user_role = ROLE.USER if user_role != conditions.get("logged_in_user"): return False if CONDITION.RESULT_VALUE in conditions: condition_value = conditions.get(CONDITION.RESULT_VALUE) result_value = content.get("result", {}).get("value") if is_true(condition_value) != is_true(result_value): return False if CONDITION.RESULT_STATUS in conditions: condition_value = conditions.get(CONDITION.RESULT_STATUS) result_status = content.get("result", {}).get("status") if is_true(condition_value) != is_true(result_status): return False # checking of max-failcounter state of the token if "token_locked" in conditions: if token_obj: locked = token_obj.get_failcount() >= \ token_obj.get_max_failcount() if (conditions.get("token_locked") in ["True", True]) != \ locked: return False else: # check all tokens of the user, if any token is maxfail token_objects = get_tokens(user=user, maxfail=True) if not ','.join([tok.get_serial() for tok in token_objects]): return False if "tokenrealm" in conditions and tokenrealms: res = False for trealm in tokenrealms: if trealm in conditions.get("tokenrealm").split(","): res = True break if not res: return False if "serial" in conditions and serial: serial_match = conditions.get("serial") if not bool(re.match(serial_match, serial)): return False if CONDITION.USER_TOKEN_NUMBER in conditions and user: num_tokens = get_tokens(user=user, count=True) if num_tokens != int(conditions.get( CONDITION.USER_TOKEN_NUMBER)): return False if CONDITION.DETAIL_ERROR_MESSAGE in conditions: message = content.get("detail", {}).get("error", {}).get("message") search_exp = conditions.get(CONDITION.DETAIL_ERROR_MESSAGE) m = re.search(search_exp, message) if not bool(m): return False if CONDITION.DETAIL_MESSAGE in conditions: message = content.get("detail", {}).get("message") search_exp = conditions.get(CONDITION.DETAIL_MESSAGE) m = re.search(search_exp, message) if not bool(m): return False # Token specific conditions if token_obj: if CONDITION.TOKENTYPE in conditions: if tokentype not in conditions.get(CONDITION.TOKENTYPE).split( ","): return False if CONDITION.TOKEN_HAS_OWNER in conditions: uid = token_obj.get_user_id() check = conditions.get(CONDITION.TOKEN_HAS_OWNER) if uid and check in ["True", True]: res = True elif not uid and check in ["False", False]: res = True else: log.debug("Condition token_has_owner for token {0!r} " "not fulfilled.".format(token_obj)) return False if CONDITION.TOKEN_IS_ORPHANED in conditions: uid = token_obj.get_user_id() orphaned = uid and not user check = conditions.get(CONDITION.TOKEN_IS_ORPHANED) if orphaned and check in ["True", True]: res = True elif not orphaned and check in ["False", False]: res = True else: log.debug("Condition token_is_orphaned for token {0!r} not " "fulfilled.".format(token_obj)) return False if CONDITION.TOKEN_VALIDITY_PERIOD in conditions: valid = token_obj.check_validity_period() if (conditions.get(CONDITION.TOKEN_VALIDITY_PERIOD) in ["True", True]) != valid: return False if CONDITION.OTP_COUNTER in conditions: if token_obj.token.count != \ int(conditions.get(CONDITION.OTP_COUNTER)): return False if CONDITION.LAST_AUTH in conditions: if token_obj.check_last_auth_newer(conditions.get( CONDITION.LAST_AUTH)): return False if CONDITION.COUNT_AUTH in conditions: count = token_obj.get_count_auth() cond = conditions.get(CONDITION.COUNT_AUTH) if not compare_condition(cond, count): return False if CONDITION.COUNT_AUTH_SUCCESS in conditions: count = token_obj.get_count_auth_success() cond = conditions.get(CONDITION.COUNT_AUTH_SUCCESS) if not compare_condition(cond, count): return False if CONDITION.COUNT_AUTH_FAIL in conditions: count = token_obj.get_count_auth() c_success = token_obj.get_count_auth_success() c_fail = count - c_success cond = conditions.get(CONDITION.COUNT_AUTH_FAIL) if not compare_condition(cond, c_fail): return False if CONDITION.TOKENINFO in conditions: cond = conditions.get(CONDITION.TOKENINFO) # replace {now} in condition cond, td = parse_time_offset_from_now(cond) s_now = (datetime.datetime.now(tzlocal()) + td).strftime( DATE_FORMAT) cond = cond.format(now=s_now) if len(cond.split("==")) == 2: key, value = [x.strip() for x in cond.split("==")] if not compare_value_value(token_obj.get_tokeninfo(key), "==", value): return False elif len(cond.split(">")) == 2: key, value = [x.strip() for x in cond.split(">")] if not compare_value_value(token_obj.get_tokeninfo(key), ">", value): return False elif len(cond.split("<")) == 2: key, value = [x.strip() for x in cond.split("<")] if not compare_value_value(token_obj.get_tokeninfo(key), "<", value): return False else: # There is a condition, but we do not know it! log.warning("Misconfiguration in your tokeninfo " "condition: {0!s}".format(cond)) return False return True
def do(self, action, options=None): """ This method executes the defined action in the given event. :param action: :param options: Contains the flask parameters g, request, response and the handler_def configuration :type options: dict :return: """ ret = True script_name = self.script_directory + "/" + action proc_args = [script_name] g = options.get("g") request = options.get("request") response = options.get("response") content = self._get_response_content(response) handler_def = options.get("handler_def") handler_options = handler_def.get("options", {}) if hasattr(g, "logged_in_user"): logged_in_user = g.logged_in_user else: logged_in_user = { "username": "******", "realm": "none", "role": "none" } serial = request.all_data.get("serial") or \ content.get("detail", {}).get("serial") or \ g.audit_object.audit_data.get("serial") if handler_options.get("serial"): proc_args.append("--serial") proc_args.append(serial or "none") if handler_options.get("user"): proc_args.append("--user") proc_args.append(request.User.login or "none") if handler_options.get("realm"): proc_args.append("--realm") proc_args.append(request.User.realm or "none") if handler_options.get("logged_in_user"): proc_args.append("--logged_in_user") proc_args.append("{username}@{realm}".format(**logged_in_user)) if handler_options.get("logged_in_role"): proc_args.append("--logged_in_role") proc_args.append(logged_in_user.get("role", "none")) rcode = 0 try: log.info("Starting script {script!r}.".format(script=script_name)) db.session.commit() p = subprocess.Popen(proc_args, cwd=self.script_directory, universal_newlines=True) if handler_options.get("background") == SCRIPT_WAIT: rcode = p.wait() except Exception as e: log.warning("Failed to execute script {0!r}: {1!r}".format( script_name, e)) log.warning(traceback.format_exc()) if handler_options.get("background") == SCRIPT_WAIT and is_true( handler_options.get("raise_error")): raise ServerError("Failed to start script.") if rcode: log.warning( "Script {script!r} failed to execute with error code {error!r}" .format(script=script_name, error=rcode)) if is_true(handler_options.get("raise_error")): raise ServerError("Error during execution of the script.") return ret
def get_init_detail(self, params=None, user=None): """ to complete the token initialization some additional details should be returned, which are displayed at the end of the token initialization. This is the e.g. the enrollment URL for a Google Authenticator. """ response_detail = TokenClass.get_init_detail(self, params, user) params = params or {} tokenlabel = params.get("tokenlabel", "<s>") tokenissuer = params.get("tokenissuer", "privacyIDEA") # If the init_details contain an OTP key the OTP key # should be displayed as an enrollment URL otpkey = self.init_details.get('otpkey') # Add rollout state the response response_detail['rollout_state'] = self.token.rollout_state # Add two-step initialization parameters to response and QR code extra_data = {} if is_true(params.get("2stepinit")): twostep_parameters = self._get_twostep_parameters() extra_data.update(twostep_parameters) response_detail.update(twostep_parameters) imageurl = params.get("appimageurl") if imageurl: extra_data.update({"image": imageurl}) force_app_pin = params.get('force_app_pin') if force_app_pin: extra_data.update({'pin': True}) if otpkey: tok_type = self.type.lower() if user is not None: try: key_bin = binascii.unhexlify(otpkey) # also strip the padding =, as it will get problems with the google app. value_b32_str = b32encode_and_unicode(key_bin).strip('=') response_detail["otpkey"]["value_b32"] = value_b32_str goo_url = cr_google(key=otpkey, user=user.login, realm=user.realm, tokentype=tok_type.lower(), serial=self.get_serial(), tokenlabel=tokenlabel, hash_algo=params.get( "hashlib", "sha1"), digits=params.get("otplen", 6), period=params.get("timeStep", 30), issuer=tokenissuer, user_obj=user, extra_data=extra_data) response_detail["googleurl"] = { "description": _("URL for google " "Authenticator"), "value": goo_url, "img": create_img(goo_url, width=250) } oath_url = cr_oath(otpkey=otpkey, user=user.login, realm=user.realm, type=tok_type, serial=self.get_serial(), tokenlabel=tokenlabel, extra_data=extra_data) response_detail["oathurl"] = { "description": _("URL for" " OATH " "token"), "value": oath_url, "img": create_img(oath_url, width=250) } except Exception as ex: # pragma: no cover log.error("{0!s}".format((traceback.format_exc()))) log.error( 'failed to set oath or google url: {0!r}'.format(ex)) return response_detail
def get_init_detail(self, params=None, user=None): """ to complete the token initialization some additional details should be returned, which are displayed at the end of the token initialization. This is the e.g. the enrollment URL for a Google Authenticator. """ response_detail = TokenClass.get_init_detail(self, params, user) params = params or {} tokenlabel = params.get("tokenlabel", "<s>") tokenissuer = params.get("tokenissuer", "privacyIDEA") # If the init_details contain an OTP key the OTP key # should be displayed as an enrollment URL otpkey = self.init_details.get('otpkey') # Add rollout state the response response_detail['rollout_state'] = self.token.rollout_state # Add two-step initialization parameters to response and QR code extra_data = {} if is_true(params.get("2stepinit")): twostep_parameters = self._get_twostep_parameters() extra_data.update(twostep_parameters) response_detail.update(twostep_parameters) imageurl = params.get("appimageurl") if imageurl: extra_data.update({"image": imageurl}) if otpkey: tok_type = self.type.lower() if user is not None: try: key_bin = binascii.unhexlify(otpkey) # also strip the padding =, as it will get problems with the google app. value_b32_str = b32encode_and_unicode(key_bin).strip('=') response_detail["otpkey"]["value_b32"] = value_b32_str goo_url = cr_google(key=otpkey, user=user.login, realm=user.realm, tokentype=tok_type.lower(), serial=self.get_serial(), tokenlabel=tokenlabel, hash_algo=params.get("hashlib", "sha1"), digits=params.get("otplen", 6), period=params.get("timeStep", 30), issuer=tokenissuer, user_obj=user, extra_data=extra_data) response_detail["googleurl"] = {"description": _("URL for google " "Authenticator"), "value": goo_url, "img": create_img(goo_url, width=250) } oath_url = cr_oath(otpkey=otpkey, user=user.login, realm=user.realm, type=tok_type, serial=self.get_serial(), tokenlabel=tokenlabel, extra_data=extra_data) response_detail["oathurl"] = {"description": _("URL for" " OATH " "token"), "value": oath_url, "img": create_img(oath_url, width=250) } except Exception as ex: # pragma: no cover log.error("{0!s}".format((traceback.format_exc()))) log.error('failed to set oath or google url: {0!r}'.format(ex)) return response_detail
def testconnection(cls, param): """ This function lets you test the to be saved LDAP connection. :param param: A dictionary with all necessary parameter to test the connection. :type param: dict :return: Tuple of success and a description :rtype: (bool, string) Parameters are: BINDDN, BINDPW, LDAPURI, TIMEOUT, LDAPBASE, LOGINNAMEATTRIBUTE, LDAPSEARCHFILTER, USERINFO, SIZELIMIT, NOREFERRALS, CACERTIFICATE, AUTHTYPE, TLS_VERIFY, TLS_VERSION, TLS_CA_FILE, SERVERPOOL_ROUNDS, SERVERPOOL_SKIP """ success = False uidtype = param.get("UIDTYPE") timeout = float(param.get("TIMEOUT", 5)) ldap_uri = param.get("LDAPURI") size_limit = int(param.get("SIZELIMIT", 500)) serverpool_rounds = int( param.get("SERVERPOOL_ROUNDS") or SERVERPOOL_ROUNDS) serverpool_skip = int(param.get("SERVERPOOL_SKIP") or SERVERPOOL_SKIP) if is_true(param.get("TLS_VERIFY")) \ and (ldap_uri.lower().startswith("ldaps") or param.get("START_TLS")): tls_version = int(param.get("TLS_VERSION") or ssl.PROTOCOL_TLSv1) tls_ca_file = param.get("TLS_CA_FILE") or DEFAULT_CA_FILE tls_context = Tls(validate=ssl.CERT_REQUIRED, version=tls_version, ca_certs_file=tls_ca_file) else: tls_context = None get_info = get_info_configuration(is_true(param.get("NOSCHEMAS"))) try: server_pool = cls.get_serverpool(ldap_uri, timeout, tls_context=tls_context, get_info=get_info, rounds=serverpool_rounds, exhaust=serverpool_skip) l = cls.create_connection( authtype=param.get("AUTHTYPE", AUTHTYPE.SIMPLE), server=server_pool, user=param.get("BINDDN"), password=param.get("BINDPW"), receive_timeout=timeout, auto_referrals=not param.get("NOREFERRALS"), start_tls=param.get("START_TLS", False)) #log.error("LDAP Server Pool States: %s" % server_pool.pool_states) if not l.bind(): raise Exception("Wrong credentials") # create searchattributes attributes = list(yaml.safe_load(param["USERINFO"]).values()) if uidtype.lower() != "dn": attributes.append(str(uidtype)) # search for users... g = l.extend.standard.paged_search( search_base=param["LDAPBASE"], search_filter=u"(&" + param["LDAPSEARCHFILTER"] + ")", search_scope=param.get("SCOPE") or ldap3.SUBTREE, attributes=attributes, paged_size=100, size_limit=size_limit, generator=True) # returns a generator of dictionaries count = 0 uidtype_count = 0 for entry in ignore_sizelimit_exception(l, g): try: userid = cls._get_uid(entry, uidtype) count += 1 if userid: uidtype_count += 1 except Exception as exx: # pragma: no cover log.warning("Error during fetching LDAP objects:" " {0!r}".format(exx)) log.debug("{0!s}".format(traceback.format_exc())) if uidtype_count < count: # pragma: no cover desc = _( "Your LDAP config found {0!s} user objects, but only {1!s} " "with the specified uidtype").format(count, uidtype_count) else: desc = _("Your LDAP config seems to be OK, {0!s} user objects " "found.").format(count) l.unbind() success = True except Exception as e: desc = "{0!r}".format(e) log.debug("{0!s}".format(traceback.format_exc())) return success, desc
def _send_sms(self, message="<otp>"): """ send sms :param message: the sms submit message - could contain placeholders like <otp> or <serial> :type message: string :return: submitted message :rtype: string """ if is_true(self.get_tokeninfo("dynamic_phone")): phone = self.user.get_user_phone("mobile") if type(phone) == list and phone: # if there is a non-empty list, we use the first entry phone = phone[0] else: phone = self.get_tokeninfo("phone") if not phone: # pragma: no cover log.warning("Token {0!s} does not have a phone number!".format(self.token.serial)) otp = self.get_otp()[2] serial = self.get_serial() message = message.replace("<otp>", otp) message = message.replace("<serial>", serial) log.debug("sending SMS to phone number {0!s} ".format(phone)) # First we try to get the new SMS gateway config style # The token specific identifier has priority over the system wide identifier sms_gateway_identifier = self.get_tokeninfo("sms.identifier") or get_from_config("sms.identifier") if sms_gateway_identifier: # New style sms = create_sms_instance(sms_gateway_identifier) else: # Old style (SMSProvider, SMSProviderClass) = self._get_sms_provider() log.debug("smsprovider: {0!s}, class: {1!s}".format(SMSProvider, SMSProviderClass)) try: sms = get_sms_provider_class(SMSProvider, SMSProviderClass)() except Exception as exc: log.error("Failed to load SMSProvider: {0!r}".format(exc)) log.debug("{0!s}".format(traceback.format_exc())) raise exc try: # now we need the config from the env log.debug("loading SMS configuration for class {0!s}".format(sms)) config = self._get_sms_provider_config() log.debug("config: {0!r}".format(config)) sms.load_config(config) except Exception as exc: log.error("Failed to load sms.providerConfig: {0!r}".format(exc)) log.debug("{0!s}".format(traceback.format_exc())) raise Exception("Failed to load sms.providerConfig: {0!r}".format(exc)) log.debug("submitMessage: {0!r}, to phone {1!r}".format(message, phone)) ret = sms.submit_message(phone, message) return ret, message
def do(self, action, options=None): """ This method executes the defined action in the given event. :param action: :param options: Contains the flask parameters g, request, response and the handler_def configuration :type options: dict :return: """ ret = True script_name = self.script_directory + "/" + action proc_args = [script_name] g = options.get("g") request = options.get("request") response = options.get("response") content = self._get_response_content(response) handler_def = options.get("handler_def") handler_options = handler_def.get("options", {}) if hasattr(g, "logged_in_user"): logged_in_user = g.logged_in_user else: logged_in_user = {"username": "******", "realm": "none", "role": "none"} serial = request.all_data.get("serial") or \ content.get("detail", {}).get("serial") or \ g.audit_object.audit_data.get("serial") if handler_options.get("serial"): proc_args.append("--serial") proc_args.append(serial or "none") if handler_options.get("user"): proc_args.append("--user") proc_args.append(request.User.login or "none") if handler_options.get("realm"): proc_args.append("--realm") proc_args.append(request.User.realm or "none") if handler_options.get("logged_in_user"): proc_args.append("--logged_in_user") proc_args.append("{username}@{realm}".format( **logged_in_user)) if handler_options.get("logged_in_role"): proc_args.append("--logged_in_role") proc_args.append(logged_in_user.get("role", "none")) rcode = 0 try: log.info("Starting script {script!r}.".format(script=script_name)) p = subprocess.Popen(proc_args, cwd=self.script_directory) if handler_options.get("background") == SCRIPT_WAIT: rcode = p.wait() except Exception as e: log.warning("Failed to execute script {0!r}: {1!r}".format( script_name, e)) log.warning(traceback.format_exc()) if handler_options.get("background") == SCRIPT_WAIT and is_true(handler_options.get("raise_error")): raise ServerError("Failed to start script.") if rcode: log.warning("Script {script!r} failed to execute with error code {error!r}".format(script=script_name, error=rcode)) if is_true(handler_options.get("raise_error")): raise ServerError("Error during execution of the script.") return ret
def check_otp(self, otpval, counter=None, window=None, options=None): """ run the http request against the remote host :param otpval: the OTP value :param counter: The counter for counter based otp values :type counter: int :param window: a counter window :type counter: int :param options: additional token specific options :type options: dict :return: counter of the matching OTP value. :rtype: int """ otp_count = -1 otpval = otpval.encode("utf-8") remoteServer = self.get_tokeninfo("remote.server") or "" remoteServer = remoteServer.encode("utf-8") # in preparation of the ability to relocate privacyidea urls, # we introduce the remote url path remotePath = self.get_tokeninfo("remote.path") or "" remotePath = remotePath.strip().encode('utf-8') remoteSerial = self.get_tokeninfo("remote.serial") or "" remoteSerial = remoteSerial.encode('utf-8') remoteUser = self.get_tokeninfo("remote.user") or "" remoteUser = remoteUser.encode('utf-8') remoteRealm = self.get_tokeninfo("remote.realm") or "" remoteRealm = remoteRealm.encode('utf-8') remoteResolver = self.get_tokeninfo("remote.resolver") or "" remoteResolver = remoteResolver.encode('utf-8') ssl_verify = get_from_config( "remote.verify_ssl_certificate", False, return_bool=True) or False if type(ssl_verify) in [str, unicode]: ssl_verify = is_true(ssl_verify.lower()) # here we also need to check for remote.user and so on.... log.debug("checking OTP len:%r remotely on server: %r," " serial: %r, user: %r" % (len(otpval), remoteServer, remoteSerial, remoteUser)) params = {} remotePath = remotePath or "/validate/check" if remoteSerial: params['serial'] = remoteSerial elif remoteUser: params['user'] = remoteUser params['realm'] = remoteRealm params['resolver'] = remoteResolver else: log.warning("The remote token does neither contain a " "remote.serial nor a remote.user.") return otp_count params['pass'] = otpval request_url = "{0!s}{1!s}".format(remoteServer, remotePath) try: r = requests.post(request_url, data=params, verify=ssl_verify) if r.status_code == requests.codes.ok: response = r.json() result = response.get("result") if result.get("value"): otp_count = 1 except Exception as exx: # pragma: no cover log.error("Error getting response from " "remote Server (%r): %r" % (request_url, exx)) log.debug("{0!s}".format(traceback.format_exc())) return otp_count
def check_condition(self, options): """ Check if all conditions are met and if the action should be executed. The the conditions are met, we return "True" :return: True """ g = options.get("g") request = options.get("request") response = options.get("response") e_handler_def = options.get("handler_def") if not e_handler_def: # options is the handler definition return True # conditions can be corresponding to the property conditions conditions = e_handler_def.get("conditions") content = self._get_response_content(response) user = self._get_tokenowner(request) serial = request.all_data.get("serial") or \ content.get("detail", {}).get("serial") tokenrealms = [] tokentype = None token_obj = None if serial: # We have determined the serial number from the request. token_obj_list = get_tokens(serial=serial) else: # We have to determine the token via the user object. But only if # the user has only one token token_obj_list = get_tokens(user=user) if len(token_obj_list) == 1: token_obj = token_obj_list[0] tokenrealms = token_obj.get_realms() tokentype = token_obj.get_tokentype() if "realm" in conditions: if user.realm != conditions.get("realm"): return False if "logged_in_user" in conditions: # Determine the role of the user try: logged_in_user = g.logged_in_user user_role = logged_in_user.get("role") except Exception: # A non-logged-in-user is a User, not an admin user_role = ROLE.USER if user_role != conditions.get("logged_in_user"): return False if CONDITION.RESULT_VALUE in conditions: condition_value = conditions.get(CONDITION.RESULT_VALUE) result_value = content.get("result", {}).get("value") if is_true(condition_value) != is_true(result_value): return False if CONDITION.RESULT_STATUS in conditions: condition_value = conditions.get(CONDITION.RESULT_STATUS) result_status = content.get("result", {}).get("status") if is_true(condition_value) != is_true(result_status): return False # checking of max-failcounter state of the token if "token_locked" in conditions: if token_obj: locked = token_obj.get_failcount() >= \ token_obj.get_max_failcount() if (conditions.get("token_locked") in ["True", True]) != \ locked: return False else: # check all tokens of the user, if any token is maxfail token_objects = get_tokens(user=user, maxfail=True) if not ','.join([tok.get_serial() for tok in token_objects]): return False if "tokenrealm" in conditions and tokenrealms: res = False for trealm in tokenrealms: if trealm in conditions.get("tokenrealm").split(","): res = True break if not res: return False if "serial" in conditions and serial: serial_match = conditions.get("serial") if not bool(re.match(serial_match, serial)): return False if CONDITION.USER_TOKEN_NUMBER in conditions and user: num_tokens = get_tokens(user=user, count=True) if num_tokens != int(conditions.get(CONDITION.USER_TOKEN_NUMBER)): return False if CONDITION.DETAIL_ERROR_MESSAGE in conditions: message = content.get("detail", {}).get("error", {}).get("message") search_exp = conditions.get(CONDITION.DETAIL_ERROR_MESSAGE) m = re.search(search_exp, message) if not bool(m): return False if CONDITION.DETAIL_MESSAGE in conditions: message = content.get("detail", {}).get("message") search_exp = conditions.get(CONDITION.DETAIL_MESSAGE) m = re.search(search_exp, message) if not bool(m): return False # Token specific conditions if token_obj: if CONDITION.TOKENTYPE in conditions: if tokentype not in conditions.get( CONDITION.TOKENTYPE).split(","): return False if CONDITION.TOKEN_HAS_OWNER in conditions: uid = token_obj.get_user_id() check = conditions.get(CONDITION.TOKEN_HAS_OWNER) if uid and check in ["True", True]: res = True elif not uid and check in ["False", False]: res = True else: log.debug("Condition token_has_owner for token {0!r} " "not fulfilled.".format(token_obj)) return False if CONDITION.TOKEN_IS_ORPHANED in conditions: uid = token_obj.get_user_id() orphaned = uid and not user check = conditions.get(CONDITION.TOKEN_IS_ORPHANED) if orphaned and check in ["True", True]: res = True elif not orphaned and check in ["False", False]: res = True else: log.debug( "Condition token_is_orphaned for token {0!r} not " "fulfilled.".format(token_obj)) return False if CONDITION.TOKEN_VALIDITY_PERIOD in conditions: valid = token_obj.check_validity_period() if (conditions.get(CONDITION.TOKEN_VALIDITY_PERIOD) in ["True", True]) != valid: return False if CONDITION.OTP_COUNTER in conditions: cond = conditions.get(CONDITION.OTP_COUNTER) if not compare_condition(cond, token_obj.token.count): return False if CONDITION.LAST_AUTH in conditions: if token_obj.check_last_auth_newer( conditions.get(CONDITION.LAST_AUTH)): return False if CONDITION.COUNT_AUTH in conditions: count = token_obj.get_count_auth() cond = conditions.get(CONDITION.COUNT_AUTH) if not compare_condition(cond, count): return False if CONDITION.COUNT_AUTH_SUCCESS in conditions: count = token_obj.get_count_auth_success() cond = conditions.get(CONDITION.COUNT_AUTH_SUCCESS) if not compare_condition(cond, count): return False if CONDITION.COUNT_AUTH_FAIL in conditions: count = token_obj.get_count_auth() c_success = token_obj.get_count_auth_success() c_fail = count - c_success cond = conditions.get(CONDITION.COUNT_AUTH_FAIL) if not compare_condition(cond, c_fail): return False if CONDITION.TOKENINFO in conditions: cond = conditions.get(CONDITION.TOKENINFO) # replace {now} in condition cond, td = parse_time_offset_from_now(cond) s_now = (datetime.datetime.now(tzlocal()) + td).strftime(DATE_FORMAT) cond = cond.format(now=s_now) if len(cond.split("==")) == 2: key, value = [x.strip() for x in cond.split("==")] if not compare_value_value(token_obj.get_tokeninfo(key), "==", value): return False elif len(cond.split(">")) == 2: key, value = [x.strip() for x in cond.split(">")] if not compare_value_value(token_obj.get_tokeninfo(key), ">", value): return False elif len(cond.split("<")) == 2: key, value = [x.strip() for x in cond.split("<")] if not compare_value_value(token_obj.get_tokeninfo(key), "<", value): return False else: # There is a condition, but we do not know it! log.warning("Misconfiguration in your tokeninfo " "condition: {0!s}".format(cond)) return False return True
def update(self, param, reset_failcount=True): """ process the initialization parameters Do we really always need an otpkey? the otpKey is handled in the parent class :param param: dict of initialization parameters :type param: dict :return: nothing """ # In case am Immutable MultiDict: upd_param = {} for k, v in param.items(): upd_param[k] = v # Special handling of 2-step enrollment if is_true(getParam(param, "2stepinit", optional)): # Use the 2step_serversize setting for the size of the server secret # (if it is set) if "2step_serversize" in upd_param: upd_param["keysize"] = int( getParam(upd_param, "2step_serversize", required)) # Add twostep settings to the tokeninfo for key, default in [ ("2step_difficulty", TWOSTEP_DEFAULT_DIFFICULTY), ("2step_clientsize", TWOSTEP_DEFAULT_CLIENTSIZE) ]: self.add_tokeninfo(key, getParam(param, key, optional, default)) val = getParam(upd_param, "hashlib", optional) if val is not None: hashlibStr = val else: hashlibStr = 'sha1' # check if the key_size is provided # if not, we could derive it from the hashlib key_size = getParam(upd_param, 'key_size', optional) \ or getParam(upd_param, 'keysize', optional) if key_size is None: upd_param['keysize'] = keylen.get(hashlibStr) otpKey = getParam(upd_param, "otpkey", optional) genkey = is_true(getParam(upd_param, "genkey", optional)) if genkey and otpKey: # The Base TokenClass does not allow otpkey and genkey at the # same time del upd_param['otpkey'] upd_param['hashlib'] = hashlibStr # We first need to call the parent class. Since exceptions would be # raised here. TokenClass.update(self, upd_param, reset_failcount) # add_tokeninfo and set_otplen save the token object to the database. self.add_tokeninfo("hashlib", hashlibStr) val = getParam(upd_param, "otplen", optional) if val is not None: self.set_otplen(int(val)) else: self.set_otplen(get_from_config("DefaultOtpLen", 6))
def do(self, action, options=None): """ This method executes the defined action in the given event. :param action: :param options: Contains the flask parameters g, request, response and the handler_def configuration :type options: dict :return: """ ret = True g = options.get("g") request = options.get("request") response = options.get("response") content = self._get_response_content(response) handler_def = options.get("handler_def") handler_options = handler_def.get("options", {}) serial = request.all_data.get("serial") or \ content.get("detail", {}).get("serial") or \ g.audit_object.audit_data.get("serial") if action.lower() in [ACTION_TYPE.SET_TOKENREALM, ACTION_TYPE.SET_DESCRIPTION, ACTION_TYPE.DELETE, ACTION_TYPE.DISABLE, ACTION_TYPE.ENABLE, ACTION_TYPE.UNASSIGN, ACTION_TYPE.SET_VALIDITY, ACTION_TYPE.SET_COUNTWINDOW, ACTION_TYPE.SET_TOKENINFO, ACTION_TYPE.SET_FAILCOUNTER, ACTION_TYPE.DELETE_TOKENINFO]: if serial: log.info("{0!s} for token {1!s}".format(action, serial)) if action.lower() == ACTION_TYPE.SET_TOKENREALM: realm = handler_options.get("realm") only_realm = is_true(handler_options.get("only_realm")) # Set the realm.. log.info("Setting realm of token {0!s} to {1!s}".format( serial, realm)) # Add the token realm set_realms(serial, [realm], add=not only_realm) elif action.lower() == ACTION_TYPE.DELETE: remove_token(serial=serial) elif action.lower() == ACTION_TYPE.DISABLE: enable_token(serial, enable=False) elif action.lower() == ACTION_TYPE.ENABLE: enable_token(serial, enable=True) elif action.lower() == ACTION_TYPE.UNASSIGN: unassign_token(serial) elif action.lower() == ACTION_TYPE.SET_DESCRIPTION: description = handler_options.get("description") or "" description, td = parse_time_offset_from_now(description) s_now = (datetime.datetime.now(tzlocal()) + td).strftime( AUTH_DATE_FORMAT) set_description(serial, description.format( current_time=s_now, now=s_now, client_ip=g.client_ip, ua_browser=request.user_agent.browser, ua_string=request.user_agent.string)) elif action.lower() == ACTION_TYPE.SET_COUNTWINDOW: set_count_window(serial, int(handler_options.get("count window", 50))) elif action.lower() == ACTION_TYPE.SET_TOKENINFO: tokeninfo = handler_options.get("value") or "" tokeninfo, td = parse_time_offset_from_now(tokeninfo) s_now = (datetime.datetime.now(tzlocal()) + td).strftime( AUTH_DATE_FORMAT) try: username = request.User.loginname realm = request.User.realm except Exception: username = "******" realm = "N/A" add_tokeninfo(serial, handler_options.get("key"), tokeninfo.format( current_time=s_now, now=s_now, client_ip=g.client_ip, username=username, realm=realm, ua_browser=request.user_agent.browser, ua_string=request.user_agent.string)) elif action.lower() == ACTION_TYPE.DELETE_TOKENINFO: delete_tokeninfo(serial, handler_options.get("key")) elif action.lower() == ACTION_TYPE.SET_VALIDITY: start_date = handler_options.get(VALIDITY.START) end_date = handler_options.get(VALIDITY.END) if start_date: d = parse_date(start_date) set_validity_period_start(serial, None, d.strftime(DATE_FORMAT)) if end_date: d = parse_date(end_date) set_validity_period_end(serial, None, d.strftime(DATE_FORMAT)) elif action.lower() == ACTION_TYPE.SET_FAILCOUNTER: try: set_failcounter(serial, int(handler_options.get("fail counter"))) except Exception as exx: log.warning("Misconfiguration: Failed to set fail " "counter!") else: log.info("Action {0!s} requires serial number. But no serial " "number could be found in request.") if action.lower() == ACTION_TYPE.INIT: log.info("Initializing new token") init_param = {"type": handler_options.get("tokentype"), "genkey": 1, "realm": handler_options.get("realm", "")} user = None if is_true(handler_options.get("user")): user = self._get_tokenowner(request) tokentype = handler_options.get("tokentype") # Some tokentypes need additional parameters if handler_options.get("additional_params"): add_params = yaml.safe_load(handler_options.get("additional_params")) if type(add_params) == dict: init_param.update(add_params) if tokentype == "sms": if handler_options.get("dynamic_phone"): init_param["dynamic_phone"] = 1 else: init_param['phone'] = user.get_user_phone( phone_type='mobile', index=0) if not init_param['phone']: log.warning("Enrolling SMS token. But the user " "{0!r} has no mobile number!".format(user)) elif tokentype == "email": if handler_options.get("dynamic_email"): init_param["dynamic_email"] = 1 else: init_param['email'] = user.info.get("email", "") if not init_param['email']: log.warning("Enrolling EMail token. But the user {0!s}" "has no email address!".format(user)) elif tokentype == "motp": init_param['motppin'] = handler_options.get("motppin") t = init_token(param=init_param, user=user) log.info("New token {0!s} enrolled.".format(t.token.serial)) return ret
def do(self, action, options=None): """ This method executes the defined action in the given event. :param action: :param options: Contains the flask parameters g, request, response and the handler_def configuration :type options: dict :return: """ ret = True g = options.get("g") request = options.get("request") response = options.get("response") content = json.loads(response.data) handler_def = options.get("handler_def") handler_options = handler_def.get("options", {}) serial = request.all_data.get("serial") or \ content.get("detail", {}).get("serial") or \ g.audit_object.audit_data.get("serial") if action.lower() in [ACTION_TYPE.SET_TOKENREALM, ACTION_TYPE.SET_DESCRIPTION, ACTION_TYPE.DELETE, ACTION_TYPE.DISABLE, ACTION_TYPE.ENABLE, ACTION_TYPE.UNASSIGN, ACTION_TYPE.SET_VALIDITY, ACTION_TYPE.SET_COUNTWINDOW, ACTION_TYPE.SET_TOKENINFO]: if serial: log.info("{0!s} for token {1!s}".format(action, serial)) if action.lower() == ACTION_TYPE.SET_TOKENREALM: realm = handler_options.get("realm") only_realm = handler_options.get("only_realm") # Set the realm.. log.info("Setting realm of token {0!s} to {1!s}".format( serial, realm)) # Add the token realm set_realms(serial, [realm], add=True) elif action.lower() == ACTION_TYPE.DELETE: remove_token(serial=serial) elif action.lower() == ACTION_TYPE.DISABLE: enable_token(serial, enable=False) elif action.lower() == ACTION_TYPE.ENABLE: enable_token(serial, enable=True) elif action.lower() == ACTION_TYPE.UNASSIGN: unassign_token(serial) elif action.lower() == ACTION_TYPE.SET_DESCRIPTION: s_now = datetime.datetime.now(tzlocal()).strftime(AUTH_DATE_FORMAT) set_description(serial, (handler_options.get("description") or "").format(current_time=s_now, client_ip=g.client_ip, ua_browser=request.user_agent.browser, ua_string=request.user_agent.string)) elif action.lower() == ACTION_TYPE.SET_COUNTWINDOW: set_count_window(serial, int(handler_options.get("count window", 50))) elif action.lower() == ACTION_TYPE.SET_TOKENINFO: s_now = datetime.datetime.now(tzlocal()).strftime(AUTH_DATE_FORMAT) add_tokeninfo(serial, handler_options.get("key"), (handler_options.get("value") or "").format( current_time=s_now, client_ip=g.client_ip, ua_browser=request.user_agent.browser, ua_string=request.user_agent.string)) elif action.lower() == ACTION_TYPE.SET_VALIDITY: start_date = handler_options.get(VALIDITY.START) end_date = handler_options.get(VALIDITY.END) if start_date: d = parse_date(start_date) set_validity_period_start(serial, None, d.strftime(DATE_FORMAT)) if end_date: d = parse_date(end_date) set_validity_period_end(serial, None, d.strftime(DATE_FORMAT)) else: log.info("Action {0!s} requires serial number. But no serial " "number could be found in request.") if action.lower() == ACTION_TYPE.INIT: log.info("Initializing new token") init_param = {"type": handler_options.get("tokentype"), "genkey": 1, "realm": handler_options.get("realm", "")} user = None if is_true(handler_options.get("user")): user = self._get_tokenowner(request) tokentype = handler_options.get("tokentype") # Some tokentypes need additional parameters or otherwise # will fail to enroll. # TODO: Other tokentypes will require additional parameters if tokentype == "sms": init_param['phone'] = user.get_user_phone( phone_type='mobile') if not init_param['phone']: log.warning("Enrolling SMS token. But the user " "{0!s} has no mobile number!".format(user)) elif tokentype == "email": init_param['email'] = user.info.get("email", "") if not init_param['email']: log.warning("Enrolling EMail token. But the user {0!s}" "has no email address!".format(user)) elif tokentype == "motp": init_param['motppin'] = handler_options.get("motppin") t = init_token(param=init_param, user=user) log.info("New token {0!s} enrolled.".format(t.token.serial)) return ret
def loadConfig(self, config): """ Load the config from conf. :param config: The configuration from the Config Table :type config: dict '#ldap_uri': 'LDAPURI', '#ldap_basedn': 'LDAPBASE', '#ldap_binddn': 'BINDDN', '#ldap_password': '******', '#ldap_timeout': 'TIMEOUT', '#ldap_sizelimit': 'SIZELIMIT', '#ldap_loginattr': 'LOGINNAMEATTRIBUTE', '#ldap_searchfilter': 'LDAPSEARCHFILTER', '#ldap_mapping': 'USERINFO', '#ldap_uidtype': 'UIDTYPE', '#ldap_noreferrals' : 'NOREFERRALS', '#ldap_editable' : 'EDITABLE', '#ldap_certificate': 'CACERTIFICATE', """ self.uri = config.get("LDAPURI") self.basedn = config.get("LDAPBASE") self.binddn = config.get("BINDDN") # object_classes is a comma separated list like # ["top", "person", "organizationalPerson", "user", "inetOrgPerson"] self.object_classes = [ cl.strip() for cl in config.get("OBJECT_CLASSES", "").split(",") ] self.dn_template = config.get("DN_TEMPLATE", "") self.bindpw = config.get("BINDPW") self.timeout = float(config.get("TIMEOUT", 5)) self.cache_timeout = int(config.get("CACHE_TIMEOUT", 120)) self.sizelimit = int(config.get("SIZELIMIT", 500)) self.loginname_attribute = config.get("LOGINNAMEATTRIBUTE", "").split(",") self.searchfilter = config.get("LDAPSEARCHFILTER") userinfo = config.get("USERINFO", "{}") self.userinfo = yaml.safe_load(userinfo) self.userinfo["username"] = self.loginname_attribute[0] multivalueattributes = config.get( "MULTIVALUEATTRIBUTES") or '["mobile"]' self.multivalueattributes = yaml.safe_load(multivalueattributes) self.map = yaml.safe_load(userinfo) self.uidtype = config.get("UIDTYPE", "DN") self.noreferrals = is_true(config.get("NOREFERRALS", False)) self.start_tls = is_true(config.get("START_TLS", False)) self.get_info = get_info_configuration( is_true(config.get("NOSCHEMAS", False))) self._editable = config.get("EDITABLE", False) self.scope = config.get("SCOPE") or ldap3.SUBTREE self.resolverId = self.uri self.authtype = config.get("AUTHTYPE", AUTHTYPE.SIMPLE) self.tls_verify = is_true(config.get("TLS_VERIFY", False)) # Fallback to TLSv1. (int: 3, TLSv1.1: 4, v1.2: 5) self.tls_version = int(config.get("TLS_VERSION") or ssl.PROTOCOL_TLSv1) self.tls_ca_file = config.get("TLS_CA_FILE") or DEFAULT_CA_FILE if self.tls_verify and (self.uri.lower().startswith("ldaps") or self.start_tls): self.tls_context = Tls(validate=ssl.CERT_REQUIRED, version=self.tls_version, ca_certs_file=self.tls_ca_file) else: self.tls_context = None self.serverpool_rounds = int( config.get("SERVERPOOL_ROUNDS") or SERVERPOOL_ROUNDS) self.serverpool_skip = int( config.get("SERVERPOOL_SKIP") or SERVERPOOL_SKIP) return self
def _send_sms(self, message="<otp>"): """ send sms :param message: the sms submit message - could contain placeholders like <otp> or <serial> :type message: string :return: submitted message :rtype: string """ if is_true(self.get_tokeninfo("dynamic_phone")): phone = self.user.get_user_phone("mobile") if type(phone) == list and phone: # if there is a non-empty list, we use the first entry phone = phone[0] else: phone = self.get_tokeninfo("phone") if not phone: # pragma: no cover log.warning("Token {0!s} does not have a phone number!".format( self.token.serial)) otp = self.get_otp()[2] serial = self.get_serial() message = message.replace("<otp>", otp) message = message.replace("<serial>", serial) log.debug("sending SMS to phone number {0!s} ".format(phone)) # First we try to get the new SMS gateway config style # The token specific identifier has priority over the system wide identifier sms_gateway_identifier = self.get_tokeninfo( "sms.identifier") or get_from_config("sms.identifier") if sms_gateway_identifier: # New style sms = create_sms_instance(sms_gateway_identifier) else: # Old style (SMSProvider, SMSProviderClass) = self._get_sms_provider() log.debug("smsprovider: {0!s}, class: {1!s}".format( SMSProvider, SMSProviderClass)) try: sms = get_sms_provider_class(SMSProvider, SMSProviderClass)() except Exception as exc: log.error("Failed to load SMSProvider: {0!r}".format(exc)) log.debug("{0!s}".format(traceback.format_exc())) raise exc try: # now we need the config from the env log.debug( "loading SMS configuration for class {0!s}".format(sms)) config = self._get_sms_provider_config() log.debug("config: {0!r}".format(config)) sms.load_config(config) except Exception as exc: log.error( "Failed to load sms.providerConfig: {0!r}".format(exc)) log.debug("{0!s}".format(traceback.format_exc())) raise Exception( "Failed to load sms.providerConfig: {0!r}".format(exc)) log.debug("submitMessage: {0!r}, to phone {1!r}".format( message, phone)) ret = sms.submit_message(phone, message) return ret, message
def do(self, action, options=None): """ This method executes the defined action in the given event. :param action: :param options: Contains the flask parameters g, request, response and the handler_def configuration :type options: dict :return: """ ret = True g = options.get("g") request = options.get("request") response = options.get("response") content = json.loads(response.data) handler_def = options.get("handler_def") handler_options = handler_def.get("options", {}) serial = request.all_data.get("serial") or \ content.get("detail", {}).get("serial") or \ g.audit_object.audit_data.get("serial") if action.lower() in [ ACTION_TYPE.SET_TOKENREALM, ACTION_TYPE.SET_DESCRIPTION, ACTION_TYPE.DELETE, ACTION_TYPE.DISABLE, ACTION_TYPE.ENABLE, ACTION_TYPE.UNASSIGN, ACTION_TYPE.SET_VALIDITY, ACTION_TYPE.SET_COUNTWINDOW, ACTION_TYPE.SET_TOKENINFO ]: if serial: log.info("{0!s} for token {1!s}".format(action, serial)) if action.lower() == ACTION_TYPE.SET_TOKENREALM: realm = handler_options.get("realm") only_realm = handler_options.get("only_realm") # Set the realm.. log.info("Setting realm of token {0!s} to {1!s}".format( serial, realm)) # Add the token realm set_realms(serial, [realm], add=True) elif action.lower() == ACTION_TYPE.DELETE: remove_token(serial=serial) elif action.lower() == ACTION_TYPE.DISABLE: enable_token(serial, enable=False) elif action.lower() == ACTION_TYPE.ENABLE: enable_token(serial, enable=True) elif action.lower() == ACTION_TYPE.UNASSIGN: unassign_token(serial) elif action.lower() == ACTION_TYPE.SET_DESCRIPTION: s_now = datetime.datetime.now().strftime(AUTH_DATE_FORMAT) set_description(serial, (handler_options.get("description") or "").format(current_time=s_now)) elif action.lower() == ACTION_TYPE.SET_COUNTWINDOW: set_count_window( serial, int(handler_options.get("count window", 50))) elif action.lower() == ACTION_TYPE.SET_TOKENINFO: s_now = datetime.datetime.now().strftime(AUTH_DATE_FORMAT) add_tokeninfo(serial, handler_options.get("key"), (handler_options.get("value") or "").format(current_time=s_now)) elif action.lower() == ACTION_TYPE.SET_VALIDITY: start_date = handler_options.get(VALIDITY.START) end_date = handler_options.get(VALIDITY.END) if start_date: d = parse_date(start_date) set_validity_period_start(serial, None, d.strftime(DATE_FORMAT)) if end_date: d = parse_date(end_date) set_validity_period_end(serial, None, d.strftime(DATE_FORMAT)) else: log.info("Action {0!s} requires serial number. But no serial " "number could be found in request.") if action.lower() == ACTION_TYPE.INIT: log.info("Initializing new token") init_param = { "type": handler_options.get("tokentype"), "genkey": 1, "realm": handler_options.get("realm", "") } user = None if is_true(handler_options.get("user")): user = self._get_tokenowner(request) tokentype = handler_options.get("tokentype") # Some tokentypes need additional parameters or otherwise # will fail to enroll. # TODO: Other tokentypes will require additional parameters if tokentype == "sms": init_param['phone'] = user.get_user_phone( phone_type='mobile') if not init_param['phone']: log.warning("Enrolling SMS token. But the user " "{0!s} has no mobile number!".format(user)) elif tokentype == "email": init_param['email'] = user.info.get("email", "") if not init_param['email']: log.warning("Enrolling EMail token. But the user {0!s}" "has no email address!".format(user)) elif tokentype == "motp": init_param['motppin'] = handler_options.get("motppin") t = init_token(param=init_param, user=user) log.info("New token {0!s} enrolled.".format(t.token.serial)) return ret
def do(self, action, options=None): """ This method executes the defined action in the given event. :param action: :param options: Contains the flask parameters g, request, response and the handler_def configuration :type options: dict :return: """ ret = True response = options.get("response") try: content = self._get_response_content(response) except Exception: # The body could not be decoded to JSON. Actually this would be # a BadRequest, but we refrain from importing from werkzeug, here. log.info("The body could not be decoded to JSON.") # Early exist return ret handler_def = options.get("handler_def") handler_options = handler_def.get("options", {}) json_pointer = handler_options.get("JSON pointer", "") value = handler_options.get("value") type = handler_options.get("type") comp = json_pointer.split("/") comp = [x for x in comp if x] if action.lower() == ACTION_TYPE.DELETE: try: if len(comp) == 1: del (content[comp[0]]) elif len(comp) == 2: del (content[comp[0]][comp[1]]) elif len(comp) == 3: del (content[comp[0]][comp[1]][comp[2]]) else: log.warning( "JSON pointer length of {0!s} not supported.".format( len(comp))) options.get("response").data = json.dumps(content) except KeyError: log.warning( "Can not delete response JSON Pointer {0!s}.".format( json_pointer)) elif action.lower() == ACTION_TYPE.SET: try: if type == "integer": value = int(value) elif type == "bool": value = is_true(value) except ValueError: log.warning("Failed to convert value") if len(comp) == 1: content[comp[0]] = value elif len(comp) == 2: content[comp[0]] = content.get(comp[0]) or {} content[comp[0]][comp[1]] = value elif len(comp) == 3: content[comp[0]] = content.get(comp[0]) or {} content[comp[0]][comp[1]] = content[comp[0]].get(comp[1]) or {} content[comp[0]][comp[1]][comp[2]] = value else: log.warning( "JSON pointer of length {0!s} not supported.".format( len(comp))) options.get("response").data = json.dumps(content) return ret
def create_challenge(self, transactionid=None, options=None): """ This method creates a challenge, which is submitted to the user. The submitted challenge will be preserved in the challenge database. If no transaction id is given, the system will create a transaction id and return it, so that the response can refer to this transaction. :param transactionid: the id of this challenge :param options: the request context parameters / data :type options: dict :return: tuple of (bool, message, transactionid, attributes) :rtype: tuple The return tuple builds up like this: ``bool`` if submit was successful; ``message`` which is displayed in the JSON response; additional ``attributes``, which are displayed in the JSON response. """ options = options or {} message = get_action_values_from_options(SCOPE.AUTH, ACTION.CHALLENGETEXT, options) or DEFAULT_CHALLENGE_TEXT attributes = None data = None fb_identifier = self.get_tokeninfo(PUSH_ACTION.FIREBASE_CONFIG) if fb_identifier: challenge = b32encode_and_unicode(geturandom()) fb_gateway = create_sms_instance(fb_identifier) pem_privkey = self.get_tokeninfo(PRIVATE_KEY_SERVER) smartphone_data = _build_smartphone_data(self.token.serial, challenge, fb_gateway, pem_privkey, options) res = fb_gateway.submit_message(self.get_tokeninfo("firebase_token"), smartphone_data) # Create the challenge in the challenge table if either the message # was successfully submitted to the Firebase API or if polling is # allowed in general or for this specific token. allow_polling = get_action_values_from_options( SCOPE.AUTH, PUSH_ACTION.ALLOW_POLLING, options=options) or PushAllowPolling.ALLOW if ((allow_polling == PushAllowPolling.ALLOW or (allow_polling == PushAllowPolling.TOKEN and is_true(self.get_tokeninfo(POLLING_ALLOWED, default='True')))) or res): validity = int(get_from_config('DefaultChallengeValidityTime', 120)) tokentype = self.get_tokentype().lower() # Maybe there is a PushChallengeValidityTime... lookup_for = tokentype.capitalize() + 'ChallengeValidityTime' validity = int(get_from_config(lookup_for, validity)) # Create the challenge in the database db_challenge = Challenge(self.token.serial, transaction_id=transactionid, challenge=challenge, data=data, session=options.get("session"), validitytime=validity) db_challenge.save() self.challenge_janitor() # If sending the Push message failed, we still raise an error. if not res: raise ValidateError("Failed to submit message to firebase service.") else: log.warning(u"The token {0!s} has no tokeninfo {1!s}. " u"The message could not be sent.".format(self.token.serial, PUSH_ACTION.FIREBASE_CONFIG)) raise ValidateError("The token has no tokeninfo. Can not send via firebase service.") return True, message, db_challenge.transaction_id, attributes
def do(self, action, options=None): """ This method executes the defined action in the given event. :param action: :param options: Contains the flask parameters g, request, response and the handler_def configuration :type options: dict :return: """ ret = True g = options.get("g") request = options.get("request") response = options.get("response") content = self._get_response_content(response) handler_def = options.get("handler_def") handler_options = handler_def.get("options", {}) notify_type = handler_options.get("To", NOTIFY_TYPE.TOKENOWNER) reply_to_type = handler_options.get("reply_to") try: logged_in_user = g.logged_in_user except Exception: logged_in_user = {} tokenowner = self._get_tokenowner(request) log.debug(u"Executing event for action {0!r}, user {1!r}, " u"logged_in_user {2!r}".format(action, tokenowner, logged_in_user)) # Determine recipient recipient = None reply_to = None if reply_to_type == NOTIFY_TYPE.NO_REPLY_TO: reply_to = "" elif reply_to_type == NOTIFY_TYPE.TOKENOWNER and not tokenowner.is_empty(): reply_to = tokenowner.info.get("email") elif reply_to_type == NOTIFY_TYPE.INTERNAL_ADMIN: username = handler_options.get("reply_to " + NOTIFY_TYPE.INTERNAL_ADMIN) internal_admin = get_db_admin(username) reply_to = internal_admin.email if internal_admin else "" elif reply_to_type == NOTIFY_TYPE.ADMIN_REALM: # Adds all email addresses from a specific admin realm to the reply-to-header admin_realm = handler_options.get("reply_to " + NOTIFY_TYPE.ADMIN_REALM) attr = is_attribute_at_all() ulist = get_user_list({"realm": admin_realm}, custom_attributes=attr) # create a list of all user-emails, if the user has an email emails = [u.get("email") for u in ulist if u.get("email")] reply_to = ",".join(emails) elif reply_to_type == NOTIFY_TYPE.LOGGED_IN_USER: # Add email address from the logged in user into the reply-to header if logged_in_user.get("username") and not logged_in_user.get( "realm"): # internal admins have no realm internal_admin = get_db_admin(logged_in_user.get("username")) if internal_admin: reply_to = internal_admin.email if internal_admin else "" else: # Try to find the user in the specified realm user_obj = User(logged_in_user.get("username"), logged_in_user.get("realm")) if user_obj: reply_to = user_obj.info.get("email") if user_obj else "" elif reply_to_type == NOTIFY_TYPE.EMAIL: email = handler_options.get("reply_to " + NOTIFY_TYPE.EMAIL, "").split(",") reply_to = email[0] else: log.warning("Was not able to determine the email for the reply-to " "header: {0!s}".format(handler_def)) if notify_type == NOTIFY_TYPE.TOKENOWNER and not tokenowner.is_empty(): recipient = { "givenname": tokenowner.info.get("givenname"), "surname": tokenowner.info.get("surname"), "username": tokenowner.login, "userrealm": tokenowner.realm, "email": tokenowner.info.get("email"), "mobile": tokenowner.info.get("mobile") } elif notify_type == NOTIFY_TYPE.INTERNAL_ADMIN: username = handler_options.get("To " + NOTIFY_TYPE.INTERNAL_ADMIN) internal_admin = get_db_admin(username) recipient = { "givenname": username, "email": internal_admin.email if internal_admin else "" } elif notify_type == NOTIFY_TYPE.ADMIN_REALM: # Send emails to all the users in the specified admin realm admin_realm = handler_options.get("To " + NOTIFY_TYPE.ADMIN_REALM) attr = is_attribute_at_all() ulist = get_user_list({"realm": admin_realm}, custom_attributes=attr) # create a list of all user-emails, if the user has an email emails = [u.get("email") for u in ulist if u.get("email")] recipient = { "givenname": "admin of realm {0!s}".format(admin_realm), "email": emails } elif notify_type == NOTIFY_TYPE.LOGGED_IN_USER: # Send notification to the logged in user if logged_in_user.get("username") and not logged_in_user.get( "realm"): # internal admins have no realm internal_admin = get_db_admin(logged_in_user.get("username")) if internal_admin: recipient = { "givenname": logged_in_user.get("username"), "email": internal_admin.email if internal_admin else "" } else: # Try to find the user in the specified realm user_obj = User(logged_in_user.get("username"), logged_in_user.get("realm")) if user_obj: recipient = { "givenname": user_obj.info.get("givenname"), "surname": user_obj.info.get("surname"), "email": user_obj.info.get("email"), "mobile": user_obj.info.get("mobile") } elif notify_type == NOTIFY_TYPE.EMAIL: email = handler_options.get("To " + NOTIFY_TYPE.EMAIL, "").split(",") recipient = { "email": email } else: log.warning("Was not able to determine the recipient for the user " "notification: {0!s}".format(handler_def)) if recipient or action.lower() == "savefile": # In case of "savefile" we do not need a recipient # Collect all data body = handler_options.get("body") or DEFAULT_BODY if body.startswith("file:"): # pragma no cover # We read the template from the file. filename = body[5:] try: with open(filename, "r", encoding="utf-8") as f: body = f.read() except Exception as e: log.warning(u"Failed to read email template from file {0!r}: {1!r}".format(filename, e)) log.debug(u"{0!s}".format(traceback.format_exc())) subject = handler_options.get("subject") or \ "An action was performed on your token." serial = request.all_data.get("serial") or \ content.get("detail", {}).get("serial") or \ g.audit_object.audit_data.get("serial") registrationcode = content.get("detail", {}).get("registrationcode") pin = content.get("detail", {}).get("pin") googleurl_value = content.get("detail", {}).get("googleurl", {}).get("value") googleurl_img = content.get("detail", {}).get("googleurl", {}).get("img") tokentype = None if serial: tokens = get_tokens(serial=serial) if tokens: tokentype = tokens[0].get_tokentype() else: token_objects = get_tokens(user=tokenowner) serial = ','.join([tok.get_serial() for tok in token_objects]) tags = create_tag_dict(logged_in_user=logged_in_user, request=request, client_ip=g.client_ip, pin=pin, googleurl_value=googleurl_value, recipient=recipient, tokenowner=tokenowner, serial=serial, tokentype=tokentype, registrationcode=registrationcode, escape_html=action.lower() == "sendmail" and handler_options.get("mimetype", "").lower() == "html") body = to_unicode(body).format(googleurl_img=googleurl_img, **tags) subject = subject.format(**tags) # Send notification if action.lower() == "sendmail": emailconfig = handler_options.get("emailconfig") mimetype = handler_options.get("mimetype", "plain") useremail = recipient.get("email") attach_qrcode = is_true(handler_options.get("attach_qrcode")) if attach_qrcode and googleurl_img: # get the image part of the googleurl googleurl = urlopen(googleurl_img) mail_body = MIMEMultipart('related') mail_body.attach(MIMEText(body, mimetype)) mail_img = MIMEImage(googleurl.read()) mail_img.add_header('Content-ID', '<token_image>') mail_img.add_header('Content-Disposition', 'inline; filename="{0!s}.png"'.format(serial)) mail_body.attach(mail_img) body = mail_body try: ret = send_email_identifier(emailconfig, recipient=useremail, subject=subject, body=body, reply_to=reply_to, mimetype=mimetype) except Exception as exx: log.error("Failed to send email: {0!s}".format(exx)) ret = False if ret: log.info("Sent a notification email to user {0}".format( recipient)) else: log.warning("Failed to send a notification email to user " "{0}".format(recipient)) elif action.lower() == "savefile": spooldir = get_app_config_value("PI_NOTIFICATION_HANDLER_SPOOLDIRECTORY", "/var/lib/privacyidea/notifications/") filename = handler_options.get("filename") random = get_alphanum_str(16) filename = filename.format(random=random, **tags).lstrip(os.path.sep) outfile = os.path.normpath(os.path.join(spooldir, filename)) if not outfile.startswith(spooldir): log.error(u'Cannot write outside of spooldir {0!s}!'.format(spooldir)) else: try: with open(outfile, "w") as f: f.write(body) except Exception as err: log.error(u"Failed to write notification file: {0!s}".format(err)) elif action.lower() == "sendsms": smsconfig = handler_options.get("smsconfig") userphone = recipient.get("mobile") try: ret = send_sms_identifier(smsconfig, userphone, body) except Exception as exx: log.error("Failed to send sms: {0!s}".format(exx)) ret = False if ret: log.info("Sent a notification sms to user {0}".format( recipient)) else: log.warning("Failed to send a notification email to user " "{0}".format(recipient)) return ret
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 get_policy(name=None, export=None): """ this function is used to retrieve the policies that you defined. It can also be used to export the policy to a file. :query name: will only return the policy with the given name :query export: The filename needs to be specified as the third part of the URL like policy.cfg. It will then be exported to this file. :query realm: will return all policies in the given realm :query scope: will only return the policies within the given scope :query active: Set to true or false if you only want to display active or inactive policies. :return: a json result with the configuration of the specified policies :rtype: json :status 200: Policy created or modified. :status 401: Authentication failed **Example request**: In this example a policy "pol1" is created. .. sourcecode:: http GET /policy/pol1 HTTP/1.1 Host: example.com Accept: application/json **Example response**: .. sourcecode:: http HTTP/1.0 200 OK Content-Type: application/json { "id": 1, "jsonrpc": "2.0", "result": { "status": true, "value": { "pol_update_del": { "action": "enroll", "active": true, "client": "1.1.1.1", "name": "pol_update_del", "realm": "r1", "resolver": "test", "scope": "selfservice", "time": "", "user": "******" } } }, "version": "privacyIDEA unknown" } """ param = getLowerParams(request.all_data) realm = getParam(param, "realm") scope = getParam(param, "scope") active = getParam(param, "active") if active is not None: active = is_true(active) P = g.policy_object if not export: log.debug( "retrieving policy name: {0!s}, realm: {1!s}, scope: {2!s}".format( name, realm, scope)) pol = P.list_policies(name=name, realm=realm, scope=scope, active=active) ret = send_result(pol) else: # We want to export all policies pol = P.list_policies() ret = send_file(export_policies(pol), export, content_type='text/plain') g.audit_object.log({ "success": True, 'info': u"name = {0!s}, realm = {1!s}, scope = {2!s}".format( name, realm, scope) }) return ret
def do(self, action, options=None): """ This method executes the defined action in the given event. :param action: :param options: Contains the flask parameters g, request, response and the handler_def configuration :type options: dict :return: """ ret = True g = options.get("g") request = options.get("request") response = options.get("response") content = json.loads(response.data) handler_def = options.get("handler_def") handler_options = handler_def.get("options", {}) serial = request.all_data.get("serial") or \ content.get("detail", {}).get("serial") or \ g.audit_object.audit_data.get("serial") if action.lower() in [ ACTION_TYPE.SET_TOKENREALM, ACTION_TYPE.SET_DESCRIPTION, ACTION_TYPE.DELETE, ACTION_TYPE.DISABLE, ACTION_TYPE.ENABLE, ACTION_TYPE.UNASSIGN, ACTION_TYPE.SET_VALIDITY, ACTION_TYPE.SET_COUNTWINDOW, ACTION_TYPE.SET_TOKENINFO, ACTION_TYPE.SET_FAILCOUNTER, ACTION_TYPE.DELETE_TOKENINFO ]: if serial: log.info("{0!s} for token {1!s}".format(action, serial)) if action.lower() == ACTION_TYPE.SET_TOKENREALM: realm = handler_options.get("realm") only_realm = is_true(handler_options.get("only_realm")) # Set the realm.. log.info("Setting realm of token {0!s} to {1!s}".format( serial, realm)) # Add the token realm set_realms(serial, [realm], add=not only_realm) elif action.lower() == ACTION_TYPE.DELETE: remove_token(serial=serial) elif action.lower() == ACTION_TYPE.DISABLE: enable_token(serial, enable=False) elif action.lower() == ACTION_TYPE.ENABLE: enable_token(serial, enable=True) elif action.lower() == ACTION_TYPE.UNASSIGN: unassign_token(serial) elif action.lower() == ACTION_TYPE.SET_DESCRIPTION: description = handler_options.get("description") or "" description, td = parse_time_offset_from_now(description) s_now = (datetime.datetime.now(tzlocal()) + td).strftime(AUTH_DATE_FORMAT) set_description( serial, description.format( current_time=s_now, now=s_now, client_ip=g.client_ip, ua_browser=request.user_agent.browser, ua_string=request.user_agent.string)) elif action.lower() == ACTION_TYPE.SET_COUNTWINDOW: set_count_window( serial, int(handler_options.get("count window", 50))) elif action.lower() == ACTION_TYPE.SET_TOKENINFO: tokeninfo = handler_options.get("value") or "" tokeninfo, td = parse_time_offset_from_now(tokeninfo) s_now = (datetime.datetime.now(tzlocal()) + td).strftime(AUTH_DATE_FORMAT) try: username = request.User.loginname realm = request.User.realm except Exception: username = "******" realm = "N/A" add_tokeninfo( serial, handler_options.get("key"), tokeninfo.format(current_time=s_now, now=s_now, client_ip=g.client_ip, username=username, realm=realm, ua_browser=request.user_agent.browser, ua_string=request.user_agent.string)) elif action.lower() == ACTION_TYPE.DELETE_TOKENINFO: delete_tokeninfo(serial, handler_options.get("key")) elif action.lower() == ACTION_TYPE.SET_VALIDITY: start_date = handler_options.get(VALIDITY.START) end_date = handler_options.get(VALIDITY.END) if start_date: d = parse_date(start_date) set_validity_period_start(serial, None, d.strftime(DATE_FORMAT)) if end_date: d = parse_date(end_date) set_validity_period_end(serial, None, d.strftime(DATE_FORMAT)) elif action.lower() == ACTION_TYPE.SET_FAILCOUNTER: try: set_failcounter( serial, int(handler_options.get("fail counter"))) except Exception as exx: log.warning("Misconfiguration: Failed to set fail " "counter!") else: log.info("Action {0!s} requires serial number. But no serial " "number could be found in request.") if action.lower() == ACTION_TYPE.INIT: log.info("Initializing new token") init_param = { "type": handler_options.get("tokentype"), "genkey": 1, "realm": handler_options.get("realm", "") } user = None if is_true(handler_options.get("user")): user = self._get_tokenowner(request) tokentype = handler_options.get("tokentype") # Some tokentypes need additional parameters if handler_options.get("additional_params"): add_params = yaml.safe_load( handler_options.get("additional_params")) if type(add_params) == dict: init_param.update(add_params) if tokentype == "sms": if handler_options.get("dynamic_phone"): init_param["dynamic_phone"] = 1 else: init_param['phone'] = user.get_user_phone( phone_type='mobile') if not init_param['phone']: log.warning( "Enrolling SMS token. But the user " "{0!r} has no mobile number!".format(user)) elif tokentype == "email": if handler_options.get("dynamic_email"): init_param["dynamic_email"] = 1 else: init_param['email'] = user.info.get("email", "") if not init_param['email']: log.warning( "Enrolling EMail token. But the user {0!s}" "has no email address!".format(user)) elif tokentype == "motp": init_param['motppin'] = handler_options.get("motppin") t = init_token(param=init_param, user=user) log.info("New token {0!s} enrolled.".format(t.token.serial)) return ret
def testconnection(cls, param): """ This function lets you test the to be saved LDAP connection. :param param: A dictionary with all necessary parameter to test the connection. :type param: dict :return: Tuple of success and a description :rtype: (bool, string) Parameters are: BINDDN, BINDPW, LDAPURI, TIMEOUT, LDAPBASE, LOGINNAMEATTRIBUTE, LDAPSEARCHFILTER, USERINFO, SIZELIMIT, NOREFERRALS, CACERTIFICATE, AUTHTYPE, TLS_VERIFY, TLS_CA_FILE """ success = False uidtype = param.get("UIDTYPE") timeout = float(param.get("TIMEOUT", 5)) ldap_uri = param.get("LDAPURI") size_limit = int(param.get("SIZELIMIT", 500)) if is_true(param.get("TLS_VERIFY")) \ and (ldap_uri.lower().startswith("ldaps") or param.get("START_TLS")): tls_ca_file = param.get("TLS_CA_FILE") or DEFAULT_CA_FILE tls_context = Tls(validate=ssl.CERT_REQUIRED, version=ssl.PROTOCOL_TLSv1, ca_certs_file=tls_ca_file) else: tls_context = None get_info = get_info_configuration(is_true(param.get("NOSCHEMAS"))) try: server_pool = cls.get_serverpool(ldap_uri, timeout, tls_context=tls_context, get_info=get_info) l = cls.create_connection(authtype=param.get("AUTHTYPE", AUTHTYPE.SIMPLE), server=server_pool, user=param.get("BINDDN"), password=param.get("BINDPW"), receive_timeout=timeout, auto_referrals=not param.get( "NOREFERRALS"), start_tls=param.get("START_TLS", False)) #log.error("LDAP Server Pool States: %s" % server_pool.pool_states) if not l.bind(): raise Exception("Wrong credentials") # create searchattributes attributes = yaml.safe_load(param["USERINFO"]).values() if uidtype.lower() != "dn": attributes.append(str(uidtype)) # search for users... g = l.extend.standard.paged_search( search_base=param["LDAPBASE"], search_filter="(&" + param["LDAPSEARCHFILTER"] + ")", search_scope=param.get("SCOPE") or ldap3.SUBTREE, attributes=attributes, paged_size=100, size_limit=size_limit, generator=True) # returns a generator of dictionaries count = 0 uidtype_count = 0 for entry in g: try: userid = cls._get_uid(entry, uidtype) count += 1 if userid: uidtype_count += 1 except Exception as exx: # pragma: no cover log.warning("Error during fetching LDAP objects:" " {0!r}".format(exx)) log.debug("{0!s}".format(traceback.format_exc())) if uidtype_count < count: # pragma: no cover desc = _("Your LDAP config found %i user objects, but only %i " "with the specified uidtype" % (count, uidtype_count)) else: desc = _("Your LDAP config seems to be OK, %i user objects " "found.") % count l.unbind() success = True except Exception as e: desc = "{0!r}".format(e) return success, desc
def create_challenge(self, transactionid=None, options=None): """ create a challenge, which is submitted to the user :param transactionid: the id of this challenge :param options: the request context parameters / data You can pass exception=1 to raise an exception, if the SMS could not be sent. Otherwise the message is contained in the response. :return: tuple of (bool, message and data) bool, if submit was successful message is submitted to the user data is preserved in the challenge attributes - additional attributes, which are displayed in the output """ success = False options = options or {} return_message = get_action_values_from_options( SCOPE.AUTH, "{0!s}_{1!s}".format(self.get_class_type(), ACTION.CHALLENGETEXT), options) or _("Enter the OTP from the Email:") attributes = {'state': transactionid} validity = int(get_from_config("email.validtime", 120)) if self.is_active() is True: counter = self.get_otp_count() log.debug("counter={0!r}".format(counter)) self.inc_otp_counter(counter, reset=False) # At this point we must not bail out in case of an # Gateway error, since checkPIN is successful. A bail # out would cancel the checking of the other tokens try: message_template, mimetype = self._get_email_text_or_subject( options) subject_template, _n = self._get_email_text_or_subject( options, EMAILACTION.EMAILSUBJECT, "Your OTP") # Create the challenge in the database if is_true(get_from_config("email.concurrent_challenges")): data = self.get_otp()[2] else: data = None db_challenge = Challenge(self.token.serial, transaction_id=transactionid, challenge=options.get("challenge"), data=data, session=options.get("session"), validitytime=validity) db_challenge.save() transactionid = transactionid or db_challenge.transaction_id # We send the email after creating the challenge for testing. success, sent_message = self._compose_email( message=message_template, subject=subject_template, mimetype=mimetype) except Exception as e: info = ("The PIN was correct, but the " "EMail could not be sent: %r" % e) log.warning(info) log.debug(u"{0!s}".format(traceback.format_exc())) return_message = info if is_true(options.get("exception")): raise Exception(info) expiry_date = datetime.datetime.now() + \ datetime.timedelta(seconds=validity) attributes['valid_until'] = "{0!s}".format(expiry_date) return success, return_message, transactionid, attributes