def test_01_config(self): self.assertEqual(get_app_config(), current_app.config) self.assertEqual(get_app_config()["SUPERUSER_REALM"], ["adminrealm"]) self.assertEqual(get_app_config_value("SUPERUSER_REALM"), ["adminrealm"]) self.assertEqual(get_app_config_value("DOES_NOT_EXIST"), None) self.assertEqual(get_app_config_value("DOES_NOT_EXIST", 1337), 1337)
def test_01_config(self): self.assertEqual(get_app_config(), current_app.config) self.assertEqual(get_app_config()["SUPERUSER_REALM"], ["adminrealm"]) self.assertEqual(get_app_config_value("SUPERUSER_REALM"), ["adminrealm"]) self.assertEqual(get_app_config_value("DOES_NOT_EXIST"), None) self.assertEqual(get_app_config_value("DOES_NOT_EXIST", 1337), 1337)
def get_privacyidea_node(): """ This returns the node name of the privacyIDEA node as found in the pi.cfg file in PI_NODE. If it does not exist, the PI_AUDIT_SERVERNAME is used. :return: the destinct node name """ node_name = get_app_config_value("PI_NODE", get_app_config_value("PI_AUDIT_SERVERNAME", "localnode")) return node_name
def get_privacyidea_node(): """ This returns the node name of the privacyIDEA node as found in the pi.cfg file in PI_NODE. If it does not exist, the PI_AUDIT_SERVERNAME is used. :return: the destinct node name """ node_name = get_app_config_value("PI_NODE", get_app_config_value("PI_AUDIT_SERVERNAME", "localnode")) return node_name
def pass_hash(password): """ Hash password with crypt context :param password: The password to hash :type password: str :return: The hash string of the password """ DEFAULT_HASH_ALGO_PARAMS.update( get_app_config_value("PI_HASH_ALGO_PARAMS", default={})) pass_ctx = CryptContext( get_app_config_value("PI_HASH_ALGO_LIST", default=DEFAULT_HASH_ALGO_LIST), **DEFAULT_HASH_ALGO_PARAMS) pw_dig = pass_ctx.hash(password) return pw_dig
def get_registry(): """ Return the ``EngineRegistry`` object associated with the current application. If there is no such object yet, create one and write it to the app-local store. This respects the ``PI_ENGINE_REGISTRY_CLASS`` config option. :return: an ``EngineRegistry`` object """ # This function will be called concurrently by multiple threads. # This is no problem when we already have an engine registry object. # However, if there is no registry object yet, two threads may concurrently # decide to create a new one. But as ``setdefault`` is atomic, only the # first one will be the written to ``app_store['config']``. The latter # one will not be referenced and will be garbage-collected at some point. app_store = get_app_local_store() try: return app_store["engine_registry"] except KeyError: # create a new engine registry of the appropriate class registry_class_name = get_app_config_value("PI_ENGINE_REGISTRY_CLASS", DEFAULT_REGISTRY_CLASS_NAME) if registry_class_name not in ENGINE_REGISTRY_CLASSES: log.warning(u"Unknown engine registry class: {!r}".format(registry_class_name)) registry_class_name = DEFAULT_REGISTRY_CLASS_NAME registry = ENGINE_REGISTRY_CLASSES[registry_class_name]() log.info(u"Created a new engine registry: {!r}".format(registry)) return app_store.setdefault("engine_registry", registry)
def __init__(self, config=None): self.name = "sqlaudit" self.config = config or {} self.audit_data = {} self.sign_data = not self.config.get("PI_AUDIT_NO_SIGN") self.sign_object = None self.verify_old_sig = get_app_config_value('PI_CHECK_OLD_SIGNATURES') if self.sign_data: self.read_keys(self.config.get("PI_AUDIT_KEY_PUBLIC"), self.config.get("PI_AUDIT_KEY_PRIVATE")) self.sign_object = Sign(self.private, self.public) # We can use "sqlaudit" as the key because the SQLAudit connection # string is fixed for a running privacyIDEA instance. # In other words, we will not run into any problems with changing connect strings. self.engine = get_engine(self.name, self._create_engine) # create a configured "Session" class. ``scoped_session`` is not # necessary because we do not share session objects among threads. # We use it anyway as a safety measure. Session = scoped_session(sessionmaker(bind=self.engine)) self.session = Session() # Ensure that the connection gets returned to the pool when the request has # been handled. This may close an already-closed session, but this is not a problem. register_finalizer(self.session.close) self.session._model_changes = {}
def __init__(self, config=None): self.name = "sqlaudit" self.config = config or {} self.audit_data = {} self.sign_data = not self.config.get("PI_AUDIT_NO_SIGN") self.sign_object = None self.verify_old_sig = get_app_config_value('PI_CHECK_OLD_SIGNATURES') if self.sign_data: self.read_keys(self.config.get("PI_AUDIT_KEY_PUBLIC"), self.config.get("PI_AUDIT_KEY_PRIVATE")) self.sign_object = Sign(self.private, self.public) # We can use "sqlaudit" as the key because the SQLAudit connection # string is fixed for a running privacyIDEA instance. # In other words, we will not run into any problems with changing connect strings. self.engine = get_engine(self.name, self._create_engine) # create a configured "Session" class. ``scoped_session`` is not # necessary because we do not share session objects among threads. # We use it anyway as a safety measure. Session = scoped_session(sessionmaker(bind=self.engine)) self.session = Session() # Ensure that the connection gets returned to the pool when the request has # been handled. This may close an already-closed session, but this is not a problem. register_finalizer(self.session.close) self.session._model_changes = {}
def get_registry(): """ Return the ``EngineRegistry`` object associated with the current application. If there is no such object yet, create one and write it to the app-local store. This respects the ``PI_ENGINE_REGISTRY_CLASS`` config option. :return: an ``EngineRegistry`` object """ # This function will be called concurrently by multiple threads. # This is no problem when we already have an engine registry object. # However, if there is no registry object yet, two threads may concurrently # decide to create a new one. But as ``setdefault`` is atomic, only the # first one will be the written to ``app_store['config']``. The latter # one will not be referenced and will be garbage-collected at some point. app_store = get_app_local_store() try: return app_store["engine_registry"] except KeyError: # create a new engine registry of the appropriate class registry_class_name = get_app_config_value( "PI_ENGINE_REGISTRY_CLASS", DEFAULT_REGISTRY_CLASS_NAME) if registry_class_name not in ENGINE_REGISTRY_CLASSES: log.warning(u"Unknown engine registry class: {!r}".format( registry_class_name)) registry_class_name = DEFAULT_REGISTRY_CLASS_NAME registry = ENGINE_REGISTRY_CLASSES[registry_class_name]() log.info(u"Created a new engine registry: {!r}".format(registry)) return app_store.setdefault("engine_registry", registry)
def reload_from_db(self): """ Read the timestamp from the database. If the timestamp is newer than the internal timestamp, then read the complete data :return: """ check_reload_config = get_app_config_value("PI_CHECK_RELOAD_CONFIG", 0) if not self.timestamp or \ self.timestamp + datetime.timedelta(seconds=check_reload_config) < datetime.datetime.now(): db_ts = Config.query.filter_by(Key=PRIVACYIDEA_TIMESTAMP).first() if reload_db(self.timestamp, db_ts): self.config = {} self.resolver = {} self.realm = {} self.default_realm = None for sysconf in Config.query.all(): self.config[sysconf.Key] = { "Value": sysconf.Value, "Type": sysconf.Type, "Description": sysconf.Description } for resolver in Resolver.query.all(): resolverdef = { "type": resolver.rtype, "resolvername": resolver.name, "censor_keys": [] } data = {} for rconf in resolver.config_list: if rconf.Type == "password": value = decryptPassword(rconf.Value, convert_unicode=True) resolverdef["censor_keys"].append(rconf.Key) else: value = rconf.Value data[rconf.Key] = value resolverdef["data"] = data self.resolver[resolver.name] = resolverdef for realm in Realm.query.all(): if realm.default: self.default_realm = realm.name realmdef = { "option": realm.option, "default": realm.default, "resolver": [] } for x in realm.resolver_list: realmdef["resolver"].append({ "priority": x.priority, "name": x.resolver.name, "type": x.resolver.rtype }) self.realm[realm.name] = realmdef self.timestamp = datetime.datetime.now()
def get_privacyidea_nodes(): """ This returns the list of the nodes, including the own local node name :return: list of nodes """ own_node_name = get_privacyidea_node() nodes = get_app_config_value("PI_NODES", [])[:] if own_node_name not in nodes: nodes.append(own_node_name) return nodes
def get_privacyidea_nodes(): """ This returns the list of the nodes, including the own local node name :return: list of nodes """ own_node_name = get_privacyidea_node() nodes = get_app_config_value("PI_NODES", [])[:] if own_node_name not in nodes: nodes.append(own_node_name) return nodes
def __init__(self, script_directory=None): if not script_directory: try: self.script_directory = get_app_config_value("PI_SCRIPT_HANDLER_DIRECTORY", "/etc/privacyidea/scripts") except RuntimeError as e: # In case of the tests we are outside of the application context self.script_directory = "tests/testdata/scripts" else: self.script_directory = script_directory
def __init__(self, script_directory=None): if not script_directory: try: self.script_directory = get_app_config_value("PI_SCRIPT_HANDLER_DIRECTORY", "/etc/privacyidea/scripts") except RuntimeError as e: # In case of the tests we are outside of the application context self.script_directory = "tests/testdata/scripts" else: self.script_directory = script_directory
def hash_with_pepper(password, rounds=10023, salt_size=10): """ Hash function to hash with salt and pepper. The pepper is read from "PI_PEPPER" from pi.cfg. Is used with admins and passwordReset :return: Hash string """ key = get_app_config_value("PI_PEPPER", "missing") pw_dig = passlib.hash.pbkdf2_sha512.encrypt(key + password, rounds=rounds, salt_size=salt_size) return pw_dig
def hash_with_pepper(password, rounds=10023, salt_size=10): """ Hash function to hash with salt and pepper. The pepper is read from "PI_PEPPER" from pi.cfg. Is used with admins and passwordReset :return: Hash string """ key = get_app_config_value("PI_PEPPER", "missing") pw_dig = passlib.hash.pbkdf2_sha512.encrypt(key + password, rounds=rounds, salt_size=salt_size) return pw_dig
def hash_with_pepper(password): """ Hash function to hash with salt and pepper. The pepper is read from "PI_PEPPER" from pi.cfg. Is used with admins and passwordReset :param password: the password to hash :type password: str :return: hashed password string :rtype: str """ key = get_app_config_value("PI_PEPPER", "missing") return pass_hash(key + password)
def verify_pass_hash(password, hvalue): """ Verify the hashed password value :param password: The plaintext password to verify :type password: str :param hvalue: The hashed password :type hvalue: str :return: True if the password matches :rtype: bool """ pass_ctx = CryptContext( get_app_config_value("PI_HASH_ALGO_LIST", default=DEFAULT_HASH_ALGO_LIST)) return pass_ctx.verify(password, hvalue)
def reload_from_db(self): """ Read the timestamp from the database. If the timestamp is newer than the internal timestamp, then read the complete data :return: """ check_reload_config = get_app_config_value("PI_CHECK_RELOAD_CONFIG", 0) if not self.timestamp or \ self.timestamp + datetime.timedelta(seconds=check_reload_config) < datetime.datetime.now(): db_ts = Config.query.filter_by(Key=PRIVACYIDEA_TIMESTAMP).first() if reload_db(self.timestamp, db_ts): self.config = {} self.resolver = {} self.realm = {} self.default_realm = None for sysconf in Config.query.all(): self.config[sysconf.Key] = { "Value": sysconf.Value, "Type": sysconf.Type, "Description": sysconf.Description} for resolver in Resolver.query.all(): resolverdef = {"type": resolver.rtype, "resolvername": resolver.name, "censor_keys": []} data = {} for rconf in resolver.config_list: if rconf.Type == "password": value = decryptPassword(rconf.Value) resolverdef["censor_keys"].append(rconf.Key) else: value = rconf.Value data[rconf.Key] = value resolverdef["data"] = data self.resolver[resolver.name] = resolverdef for realm in Realm.query.all(): if realm.default: self.default_realm = realm.name realmdef = {"option": realm.option, "default": realm.default, "resolver": []} for x in realm.resolver_list: realmdef["resolver"].append({"priority": x.priority, "name": x.resolver.name, "type": x.resolver.rtype}) self.realm[realm.name] = realmdef self.timestamp = datetime.datetime.now()
def verify_with_pepper(passwordhash, password): """ verify the password hash with the given password and pepper :param passwordhash: the passwordhash :type passwordhash: str :param password: the password to verify :type password: str :return: whether the password matches the hash :rtype: bool """ # get the password pepper password = password or "" key = get_app_config_value("PI_PEPPER", "missing") success = passlib.hash.pbkdf2_sha512.verify(key + password, passwordhash) return success
def check_signature(subscription): """ This function checks the signature of a subscription. If the signature checking fails, a SignatureError / Exception is raised. :param subscription: The dict of the subscription :return: True """ vendor = subscription.get("by_name").split()[0] enckey = get_app_config_value("PI_ENCFILE", "/etc/privacyidea/enckey") dirname = os.path.dirname(enckey) # In dirname we are searching for <vendor>.pem filename = u"{0!s}/{1!s}.pem".format(dirname, vendor) with open(filename, "r") as file_handle: public = file_handle.read() r = False try: # remove the minutes 00:00:00 subscription["date_from"] = subscription.get("date_from").strftime( SUBSCRIPTION_DATE_FORMAT) subscription["date_till"] = subscription.get("date_till").strftime( SUBSCRIPTION_DATE_FORMAT) sign_string = SIGN_FORMAT.format(**subscription) RSAkey = RSA.importKey(public) hashvalue = SHA256.new(sign_string.encode("utf-8")).digest() signature = long(subscription.get("signature") or "100") r = RSAkey.verify(hashvalue, (signature, )) subscription["date_from"] = datetime.datetime.strptime( subscription.get("date_from"), SUBSCRIPTION_DATE_FORMAT) subscription["date_till"] = datetime.datetime.strptime( subscription.get("date_till"), SUBSCRIPTION_DATE_FORMAT) except Exception as exx: log.debug(traceback.format_exc()) raise SubscriptionError( "Verifying the signature of your subscription " "failed.", application=subscription.get("application")) if not r: raise SubscriptionError( "Signature of your subscription does not " "match.", application=subscription.get("application")) return r
def check_signature(subscription): """ This function checks the signature of a subscription. If the signature checking fails, a SignatureError / Exception is raised. :param subscription: The dict of the subscription :return: True """ vendor = subscription.get("by_name").split()[0] enckey = get_app_config_value("PI_ENCFILE", "/etc/privacyidea/enckey") dirname = os.path.dirname(enckey) # In dirname we are searching for <vendor>.pem filename = "{0!s}/{1!s}.pem".format(dirname, vendor) with open(filename, "r") as file_handle: public = file_handle.read() r = False try: # remove the minutes 00:00:00 subscription["date_from"] = subscription.get("date_from").strftime(SUBSCRIPTION_DATE_FORMAT) subscription["date_till"] = subscription.get("date_till").strftime(SUBSCRIPTION_DATE_FORMAT) sign_string = SIGN_FORMAT.format(**subscription) RSAkey = RSA.importKey(public) hashvalue = SHA256.new(sign_string).digest() signature = long(subscription.get("signature") or "100") r = RSAkey.verify(hashvalue, (signature,)) subscription["date_from"] = datetime.datetime.strptime( subscription.get("date_from"), SUBSCRIPTION_DATE_FORMAT) subscription["date_till"] = datetime.datetime.strptime( subscription.get("date_till"), SUBSCRIPTION_DATE_FORMAT) except Exception as exx: log.debug(traceback.format_exc()) raise SubscriptionError("Verifying the signature of your subscription " "failed.", application=subscription.get("application")) if not r: raise SubscriptionError("Signature of your subscription does not " "match.", application=subscription.get("application")) return r
def __init__(self, db_smsprovider_object=None, smsgateway=None, directory=None): """ Create a new SMS Provider object fom a DB SMS provider object :param db_smsprovider_object: The database object :param smsgateway: The SMS gateway object from the database table SMS gateway. The options can be accessed via self.smsgateway.option_dict :param directory: The directory where the SMS sending scripts are located. :type directory: str :return: An SMS provider object """ self.config = db_smsprovider_object or {} self.smsgateway = smsgateway self.script_directory = directory or get_app_config_value( "PI_SCRIPT_SMSPROVIDER_DIRECTORY", "/etc/privacyidea/scripts")
def check_signature(subscription): """ This function checks the signature of a subscription. If the signature checking fails, a SignatureError / Exception is raised. :param subscription: The dict of the subscription :return: True """ vendor = subscription.get("by_name").split()[0] enckey = get_app_config_value("PI_ENCFILE", "/etc/privacyidea/enckey") dirname = os.path.dirname(enckey) # In dirname we are searching for <vendor>.pem filename = u"{0!s}/{1!s}.pem".format(dirname, vendor) try: # remove the minutes 00:00:00 subscription["date_from"] = subscription.get("date_from").strftime(SUBSCRIPTION_DATE_FORMAT) subscription["date_till"] = subscription.get("date_till").strftime(SUBSCRIPTION_DATE_FORMAT) sign_string = SIGN_FORMAT.format(**subscription) with open(filename, 'rb') as key_file: sign_obj = Sign(private_key=None, public_key=key_file.read()) signature = subscription.get('signature', '100') r = sign_obj.verify(sign_string, signature, verify_old_sigs=True) subscription["date_from"] = datetime.datetime.strptime( subscription.get("date_from"), SUBSCRIPTION_DATE_FORMAT) subscription["date_till"] = datetime.datetime.strptime( subscription.get("date_till"), SUBSCRIPTION_DATE_FORMAT) except Exception as _e: log.debug(traceback.format_exc()) raise SubscriptionError("Verifying the signature of your subscription " "failed.", application=subscription.get("application")) if not r: raise SubscriptionError("Signature of your subscription does not " "match.", application=subscription.get("application")) return r
def check_signature(subscription): """ This function checks the signature of a subscription. If the signature checking fails, a SignatureError / Exception is raised. :param subscription: The dict of the subscription :return: True """ vendor = subscription.get("by_name").split()[0] enckey = get_app_config_value("PI_ENCFILE", "/etc/privacyidea/enckey") dirname = os.path.dirname(enckey) # In dirname we are searching for <vendor>.pem filename = u"{0!s}/{1!s}.pem".format(dirname, vendor) try: # remove the minutes 00:00:00 subscription["date_from"] = subscription.get("date_from").strftime(SUBSCRIPTION_DATE_FORMAT) subscription["date_till"] = subscription.get("date_till").strftime(SUBSCRIPTION_DATE_FORMAT) sign_string = SIGN_FORMAT.format(**subscription) with open(filename, 'rb') as key_file: sign_obj = Sign(private_key=None, public_key=key_file.read()) signature = subscription.get('signature', '100') r = sign_obj.verify(sign_string, signature, verify_old_sigs=True) subscription["date_from"] = datetime.datetime.strptime( subscription.get("date_from"), SUBSCRIPTION_DATE_FORMAT) subscription["date_till"] = datetime.datetime.strptime( subscription.get("date_till"), SUBSCRIPTION_DATE_FORMAT) except Exception as _e: log.debug(traceback.format_exc()) raise SubscriptionError("Verifying the signature of your subscription " "failed.", application=subscription.get("application")) if not r: raise SubscriptionError("Signature of your subscription does not " "match.", application=subscription.get("application")) return r
def get_token_list(): """ get the list of the tokens :return: list of token names from the config file """ module_list = set() module_list.add("privacyidea.lib.tokens.daplugtoken") module_list.add("privacyidea.lib.tokens.hotptoken") module_list.add("privacyidea.lib.tokens.motptoken") module_list.add("privacyidea.lib.tokens.passwordtoken") module_list.add("privacyidea.lib.tokens.remotetoken") module_list.add("privacyidea.lib.tokens.spasstoken") module_list.add("privacyidea.lib.tokens.sshkeytoken") module_list.add("privacyidea.lib.tokens.totptoken") module_list.add("privacyidea.lib.tokens.yubicotoken") module_list.add("privacyidea.lib.tokens.yubikeytoken") module_list.add("privacyidea.lib.tokens.radiustoken") module_list.add("privacyidea.lib.tokens.smstoken") module_list.add("privacyidea.lib.tokens.emailtoken") module_list.add("privacyidea.lib.tokens.registrationtoken") module_list.add("privacyidea.lib.tokens.certificatetoken") module_list.add("privacyidea.lib.tokens.foureyestoken") module_list.add("privacyidea.lib.tokens.tiqrtoken") module_list.add("privacyidea.lib.tokens.ocratoken") module_list.add("privacyidea.lib.tokens.u2ftoken") module_list.add("privacyidea.lib.tokens.papertoken") module_list.add("privacyidea.lib.tokens.questionnairetoken") module_list.add("privacyidea.lib.tokens.vascotoken") module_list.add("privacyidea.lib.tokens.tantoken") module_list.add("privacyidea.lib.tokens.pushtoken") module_list.add("privacyidea.lib.tokens.indexedsecrettoken") module_list.add("privacyidea.lib.tokens.webauthntoken") # Dynamic token modules dynamic_token_modules = get_app_config_value("PI_TOKEN_MODULES") if dynamic_token_modules: # In the pi.cfg you can specify a list or set of 3rd party token modules like # PI_TOKEN_MODULES = [ "myproj.tokens.tok1", "myproj.tokens.tok2" ] module_list.update(to_list(dynamic_token_modules)) return module_list
def actions(cls): """ This method returns a dictionary of allowed actions and possible options in this handler module. :return: dict with actions """ smtpserver_objs = get_smtpservers() smsgateway_dicts = get_smsgateway() smsgateways = [sms.identifier for sms in smsgateway_dicts] smtpservers = [s.config.identifier for s in smtpserver_objs] actions = {"sendmail": {"emailconfig": {"type": "str", "required": True, "description": _("Send notification " "email via this " "email server."), "value": smtpservers}, "mimetype": {"type": "str", "description": _("Either send " "email as plain text or HTML."), "value": ["plain", "html"]}, "subject": {"type": "str", "required": False, "description": _("The subject of " "the mail that " "is sent.")}, "reply_to": {"type": "str", "required": False, "description": _("The Reply-To " "header in the " "sent email.")}, "body": {"type": "text", "required": False, "description": _("The body of the " "mail that is " "sent.")}, "To": {"type": "str", "required": True, "description": _("Send notification to " "this user."), "value": [ NOTIFY_TYPE.TOKENOWNER, NOTIFY_TYPE.LOGGED_IN_USER, NOTIFY_TYPE.INTERNAL_ADMIN, NOTIFY_TYPE.ADMIN_REALM, NOTIFY_TYPE.EMAIL]}, "To "+NOTIFY_TYPE.ADMIN_REALM: { "type": "str", "value": get_app_config_value("SUPERUSER_REALM", []), "visibleIf": "To", "visibleValue": NOTIFY_TYPE.ADMIN_REALM}, "To "+NOTIFY_TYPE.INTERNAL_ADMIN: { "type": "str", "value": [a.username for a in get_db_admins()], "visibleIf": "To", "visibleValue": NOTIFY_TYPE.INTERNAL_ADMIN}, "To "+NOTIFY_TYPE.EMAIL: { "type": "str", "description": _("Any email address, to " "which the notification " "should be sent."), "visibleIf": "To", "visibleValue": NOTIFY_TYPE.EMAIL} }, "sendsms": {"smsconfig": {"type": "str", "required": True, "description": _("Send the user " "notification via a " "predefined SMS " "gateway."), "value": smsgateways}, "body": {"type": "text", "required": False, "description": _("The text of the " "SMS.")}, "To": {"type": "str", "required": True, "description": _("Send notification to " "this user."), "value": [NOTIFY_TYPE.TOKENOWNER]} } } return actions
def _reload_from_db(self): """ Read the timestamp from the database. If the timestamp is newer than the internal timestamp, then read the complete data :return: """ check_reload_config = get_app_config_value("PI_CHECK_RELOAD_CONFIG", 0) if not self.timestamp or \ self.timestamp + datetime.timedelta(seconds=check_reload_config) < datetime.datetime.now(): db_ts = Config.query.filter_by(Key=PRIVACYIDEA_TIMESTAMP).first() if reload_db(self.timestamp, db_ts): log.debug(u"Reloading shared config from database") config = {} resolverconfig = {} realmconfig = {} default_realm = None policies = [] events = [] # Load system configuration for sysconf in Config.query.all(): config[sysconf.Key] = { "Value": sysconf.Value, "Type": sysconf.Type, "Description": sysconf.Description } # Load resolver configuration for resolver in Resolver.query.all(): resolverdef = { "type": resolver.rtype, "resolvername": resolver.name, "censor_keys": [] } data = {} for rconf in resolver.config_list: if rconf.Type == "password": value = decryptPassword(rconf.Value) resolverdef["censor_keys"].append(rconf.Key) else: value = rconf.Value data[rconf.Key] = value resolverdef["data"] = data resolverconfig[resolver.name] = resolverdef # Load realm configuration for realm in Realm.query.all(): if realm.default: default_realm = realm.name realmdef = { "id": realm.id, "option": realm.option, "default": realm.default, "resolver": [] } for x in realm.resolver_list: realmdef["resolver"].append({ "priority": x.priority, "name": x.resolver.name, "type": x.resolver.rtype }) realmconfig[realm.name] = realmdef # Load all policies for pol in Policy.query.all(): policies.append(pol.get()) # Load all events for event in EventHandler.query.order_by( EventHandler.ordering): events.append(event.get()) # Finally, set the current timestamp timestamp = datetime.datetime.now() with self._config_lock: self.config = config self.resolver = resolverconfig self.realm = realmconfig self.default_realm = default_realm self.policies = policies self.events = events self.timestamp = timestamp
def test_email(config, recipient, subject, body, sender=None, reply_to=None, mimetype="plain"): """ Sends an email via the configuration. :param config: The email configuration :type config: dict :param recipient: The recipients of the email :type recipient: list :param subject: The subject of the email :type subject: basestring :param body: The body of the email :type body: basestring :param sender: An optional sender of the email. The SMTP database object has its own sender. This parameter can be used to override the internal sender. :type sender: basestring :param reply_to: The Reply-To parameter :type reply_to: basestring :param mimetype: The type of the email to send. Can by plain or html :return: True or False """ if type(recipient) != list: recipient = [recipient] mail_from = sender or config['sender'] reply_to = reply_to or mail_from msg = MIMEText(body.encode('utf-8'), mimetype, 'utf-8') msg['Subject'] = subject msg['From'] = mail_from msg['To'] = ",".join(recipient) msg['Date'] = strftime("%a, %d %b %Y %H:%M:%S +0000", gmtime()) msg['Reply-To'] = reply_to mail = smtplib.SMTP(config['server'], port=int(config['port']), timeout=config.get('timeout', TIMEOUT)) log.debug(u"submitting message to {0!s}".format(msg["To"])) log.debug("Saying EHLO to mailserver {0!s}".format(config['server'])) r = mail.ehlo() log.debug("mailserver responded with {0!s}".format(r)) # Start TLS if required if config.get('tls', False): log.debug("Trying to STARTTLS: {0!s}".format(config['tls'])) mail.starttls() # Authenticate, if a username is given. if config.get('username', ''): log.debug("Doing authentication with {0!s}".format(config['username'])) password = decryptPassword(config['password']) if password == FAILED_TO_DECRYPT_PASSWORD: password = config['password'] # Under Python 2, we pass passwords as bytestrings to get CRAM-MD5 to work. # We add a safeguard config option to disable the conversion. # Under Python 3, we pass passwords as unicode. if six.PY2 and get_app_config_value("PI_SMTP_PASSWORD_AS_BYTES", True): password = to_bytes(password) mail.login(config['username'], password) r = mail.sendmail(mail_from, recipient, msg.as_string()) log.info("Mail sent: {0!s}".format(r)) # r is a dictionary like {"*****@*****.**": (200, 'OK')} # we change this to True or False success = True for one_recipient in recipient: res_id, res_text = r.get(one_recipient, (200, "OK")) if res_id != 200 and res_text != "OK": success = False log.error("Failed to send email to {0!r}: {1!r}, {2!r}".format(one_recipient, res_id, res_text)) mail.quit() log.debug("I am done sending your email.") return success
def verify_with_pepper(passwordhash, password): # get the password pepper password = password or "" key = get_app_config_value("PI_PEPPER", "missing") success = passlib.hash.pbkdf2_sha512.verify(key + password, passwordhash) return success
def test_email(config, recipient, subject, body, sender=None, reply_to=None, mimetype="plain"): """ Sends an email via the configuration. :param config: The email configuration :type config: dict :param recipient: The recipients of the email :type recipient: list :param subject: The subject of the email :type subject: basestring :param body: The body of the email :type body: basestring :param sender: An optional sender of the email. The SMTP database object has its own sender. This parameter can be used to override the internal sender. :type sender: basestring :param reply_to: The Reply-To parameter :type reply_to: basestring :param mimetype: The type of the email to send. Can by plain or html :return: True or False """ if type(recipient) != list: recipient = [recipient] mail_from = sender or config['sender'] reply_to = reply_to or mail_from msg = MIMEText(body.encode('utf-8'), mimetype, 'utf-8') msg['Subject'] = subject msg['From'] = mail_from msg['To'] = ",".join(recipient) msg['Date'] = strftime("%a, %d %b %Y %H:%M:%S +0000", gmtime()) msg['Reply-To'] = reply_to mail = smtplib.SMTP(config['server'], port=int(config['port']), timeout=config.get('timeout', TIMEOUT)) log.debug(u"submitting message to {0!s}".format(msg["To"])) log.debug("Saying EHLO to mailserver {0!s}".format(config['server'])) r = mail.ehlo() log.debug("mailserver responded with {0!s}".format(r)) # Start TLS if required if config.get('tls', False): log.debug("Trying to STARTTLS: {0!s}".format(config['tls'])) mail.starttls() # Authenticate, if a username is given. if config.get('username', ''): log.debug("Doing authentication with {0!s}".format( config['username'])) password = decryptPassword(config['password']) if password == FAILED_TO_DECRYPT_PASSWORD: password = config['password'] # Under Python 2, we pass passwords as bytestrings to get CRAM-MD5 to work. # We add a safeguard config option to disable the conversion. # Under Python 3, we pass passwords as unicode. if six.PY2 and get_app_config_value("PI_SMTP_PASSWORD_AS_BYTES", True): password = to_bytes(password) mail.login(config['username'], password) r = mail.sendmail(mail_from, recipient, msg.as_string()) log.info("Mail sent: {0!s}".format(r)) # r is a dictionary like {"*****@*****.**": (200, 'OK')} # we change this to True or False success = True for one_recipient in recipient: res_id, res_text = r.get(one_recipient, (200, "OK")) if res_id != 200 and res_text != "OK": success = False log.error("Failed to send email to {0!r}: {1!r}, {2!r}".format( one_recipient, res_id, res_text)) mail.quit() log.debug("I am done sending your email.") return success
def verify_with_pepper(passwordhash, password): # get the password pepper password = password or "" key = get_app_config_value("PI_PEPPER", "missing") success = passlib.hash.pbkdf2_sha512.verify(key + password, passwordhash) return success
from ctypes import c_ulong from ctypes import c_char from ctypes import c_byte from privacyidea.lib.framework import get_app_config_value from privacyidea.lib.error import ParameterError __all__ = ["vasco_otp_check"] log = logging.getLogger(__name__) vasco_dll = None try: vasco_library_path = get_app_config_value("PI_VASCO_LIBRARY") if vasco_library_path is not None: # pragma: no cover log.info(u"Loading VASCO library from {!s} ...".format(vasco_library_path)) vasco_dll = CDLL(vasco_library_path) else: log.info(u"PI_VASCO_LIBRARY option is not set, functionality disabled") except Exception as exx: log.warning(u"Could not load VASCO library: {!r}".format(exx)) def check_vasco(fn): ''' This is a decorator: checks if vasco dll is defined, it then runs the function otherwise raises RuntimeError :param fn: function - the to be called function
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 = handler_options.get("attach_qrcode", False) 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 actions(cls): """ This method returns a dictionary of allowed actions and possible options in this handler module. :return: dict with actions """ smtpserver_objs = get_smtpservers() smsgateway_dicts = get_smsgateway() smsgateways = [sms.identifier for sms in smsgateway_dicts] smtpservers = [s.config.identifier for s in smtpserver_objs] actions = { "sendmail": { "emailconfig": { "type": "str", "required": True, "description": _("Send notification email via this email server."), "value": smtpservers }, "mimetype": { "type": "str", "description": _("Either send email as plain text or HTML."), "value": ["plain", "html"] }, "attach_qrcode": { "type": "bool", "description": _("Send QR-Code image as an attachment " "(cid URL: token_image)") }, "subject": { "type": "str", "required": False, "description": _("The subject of the mail that is sent.") }, "reply_to": { "type": "str", "required": False, "description": _("The Reply-To header in the sent email."), "value": [ NOTIFY_TYPE.NO_REPLY_TO, NOTIFY_TYPE.TOKENOWNER, NOTIFY_TYPE.LOGGED_IN_USER, NOTIFY_TYPE.INTERNAL_ADMIN, NOTIFY_TYPE.ADMIN_REALM, NOTIFY_TYPE.EMAIL ] }, "reply_to " + NOTIFY_TYPE.ADMIN_REALM: { "type": "str", "value": get_app_config_value("SUPERUSER_REALM", []), "visibleIf": "reply_to", "visibleValue": NOTIFY_TYPE.ADMIN_REALM }, "reply_to " + NOTIFY_TYPE.INTERNAL_ADMIN: { "type": "str", "value": [a.username for a in get_db_admins()], "visibleIf": "reply_to", "visibleValue": NOTIFY_TYPE.INTERNAL_ADMIN }, "reply_to " + NOTIFY_TYPE.EMAIL: { "type": "str", "description": _("Any email address, to which the notification " "should be sent."), "visibleIf": "reply_to", "visibleValue": NOTIFY_TYPE.EMAIL }, "body": { "type": "text", "required": False, "description": _("The body of the mail that is sent.") }, "To": { "type": "str", "required": True, "description": _("Send notification to this user."), "value": [ NOTIFY_TYPE.TOKENOWNER, NOTIFY_TYPE.LOGGED_IN_USER, NOTIFY_TYPE.INTERNAL_ADMIN, NOTIFY_TYPE.ADMIN_REALM, NOTIFY_TYPE.EMAIL ] }, "To " + NOTIFY_TYPE.ADMIN_REALM: { "type": "str", "value": get_app_config_value("SUPERUSER_REALM", []), "visibleIf": "To", "visibleValue": NOTIFY_TYPE.ADMIN_REALM }, "To " + NOTIFY_TYPE.INTERNAL_ADMIN: { "type": "str", "value": [a.username for a in get_db_admins()], "visibleIf": "To", "visibleValue": NOTIFY_TYPE.INTERNAL_ADMIN }, "To " + NOTIFY_TYPE.EMAIL: { "type": "str", "description": _("Any email address, to which the notification " "should be sent."), "visibleIf": "To", "visibleValue": NOTIFY_TYPE.EMAIL } }, "sendsms": { "smsconfig": { "type": "str", "required": True, "description": _("Send the user notification via a " "predefined SMS gateway."), "value": smsgateways }, "body": { "type": "text", "required": False, "description": _("The text of the SMS.") }, "To": { "type": "str", "required": True, "description": _("Send notification to this user."), "value": [NOTIFY_TYPE.TOKENOWNER] } }, "savefile": { "body": { "type": "text", "required": True, "description": _("This is the template content of " "the new file. Can contain the tags " "as specified in the documentation.") }, "filename": { "type": "str", "required": True, "description": _("The filename of the notification. Existing files " "are overwritten. The name can contain tags as specified " "in the documentation and can also contain the tag {random}." ) } } } return actions
def actions(cls): """ This method returns a dictionary of allowed actions and possible options in this handler module. :return: dict with actions """ smtpserver_objs = get_smtpservers() smsgateway_dicts = get_smsgateway() smsgateways = [sms.identifier for sms in smsgateway_dicts] smtpservers = [s.config.identifier for s in smtpserver_objs] actions = { "sendmail": { "emailconfig": { "type": "str", "required": True, "description": _("Send notification " "email via this " "email server."), "value": smtpservers }, "mimetype": { "type": "str", "description": _("Either send " "email as plain text or HTML."), "value": ["plain", "html"] }, "subject": { "type": "str", "required": False, "description": _("The subject of " "the mail that " "is sent.") }, "reply_to": { "type": "str", "required": False, "description": _("The Reply-To " "header in the " "sent email.") }, "body": { "type": "text", "required": False, "description": _("The body of the " "mail that is " "sent.") }, "To": { "type": "str", "required": True, "description": _("Send notification to " "this user."), "value": [ NOTIFY_TYPE.TOKENOWNER, NOTIFY_TYPE.LOGGED_IN_USER, NOTIFY_TYPE.INTERNAL_ADMIN, NOTIFY_TYPE.ADMIN_REALM, NOTIFY_TYPE.EMAIL ] }, "To " + NOTIFY_TYPE.ADMIN_REALM: { "type": "str", "value": get_app_config_value("SUPERUSER_REALM", []), "visibleIf": "To", "visibleValue": NOTIFY_TYPE.ADMIN_REALM }, "To " + NOTIFY_TYPE.INTERNAL_ADMIN: { "type": "str", "value": [a.username for a in get_db_admins()], "visibleIf": "To", "visibleValue": NOTIFY_TYPE.INTERNAL_ADMIN }, "To " + NOTIFY_TYPE.EMAIL: { "type": "str", "description": _("Any email address, to " "which the notification " "should be sent."), "visibleIf": "To", "visibleValue": NOTIFY_TYPE.EMAIL } }, "sendsms": { "smsconfig": { "type": "str", "required": True, "description": _("Send the user " "notification via a " "predefined SMS " "gateway."), "value": smsgateways }, "body": { "type": "text", "required": False, "description": _("The text of the " "SMS.") }, "To": { "type": "str", "required": True, "description": _("Send notification to " "this user."), "value": [NOTIFY_TYPE.TOKENOWNER] } } } return actions
from ctypes import byref from ctypes import c_ulong from ctypes import c_char from ctypes import c_byte from privacyidea.lib.framework import get_app_config_value from privacyidea.lib.error import ParameterError __all__ = ["vasco_otp_check"] log = logging.getLogger(__name__) vasco_dll = None try: vasco_library_path = get_app_config_value("PI_VASCO_LIBRARY") if vasco_library_path is not None: # pragma: no cover log.info( u"Loading VASCO library from {!s} ...".format(vasco_library_path)) vasco_dll = CDLL(vasco_library_path) else: log.info(u"PI_VASCO_LIBRARY option is not set, functionality disabled") except Exception as exx: log.warning(u"Could not load VASCO library: {!r}".format(exx)) def check_vasco(fn): ''' This is a decorator: checks if vasco dll is defined, it then runs the function otherwise raises RuntimeError