def _getEmailProviderConfig(self): """ get the defined e-mail provider config definition :return: dict of the e-mail provider definition :rtype: dict """ LOG.debug('[getEmailProviderConfig] begin. load the e-mail ' + 'provider config definition') config = {} tConfig = getFromConfig("enclinotp.EmailProviderConfig", None) if tConfig is None: tConfig = getFromConfig("EmailProviderConfig", "{}") LOG.debug("[getEmailProviderConfig] provider config: %s" % tConfig) try: if tConfig is not None: config = loads(tConfig) except ValueError as exx: raise ValueError('Failed to load provider config:%r %r' % (tConfig, exx)) LOG.debug('[getEmailProviderConfig] e-mail provider config' ' found: config %r' % (config)) return config
def get_challenge_validity(self): ''' This method returns the token specific challenge validity :return: int - validity in seconds ''' validity = 120 try: validity = int(getFromConfig('DefaultChallengeValidityTime', 120)) # handle the token specific validity typ = self.getType() lookup_for = typ.capitalize() + 'ChallengeValidityTime' validity = int(getFromConfig(lookup_for, validity)) # instance specific timeout validity = int(self.getFromTokenInfo('challenge_validity_time', validity)) except ValueError: validity = 120 return validity
def check_encryption(self): """ check if a value, which got written into config, got encrypted :return: """ test_key = 'linotp.testkey' linotp_conf = LinOtpConfig() if test_key not in linotp_conf: storeConfig(test_key, '', typ='password', desc=None) old_value = getFromConfig(test_key, defVal=None) now = datetime.datetime.now() new_value_plain = unicode(now) storeConfig(test_key, new_value_plain, typ='password', desc=None) new_value_enc = getFromConfig(test_key, defVal=None) # if new_value_enc != old_value: something new was written into db # if new_value_enc != new_value_plain: the new value got encrypted if new_value_enc and new_value_plain != new_value_enc != old_value: return True return False
def loadLinOtpSMSProviderConfig(self): ''' load the defined sms provider config definition :return: dict of the sms provider definition :rtype: dict ''' log.debug('[loadLinOtpSMSProviderConfig] begin. load the sms ' + 'provider config definition') ## get User realm config = {} tConfig = getFromConfig("enclinotp.SMSProviderConfig", None) if tConfig is None: tConfig = getFromConfig("SMSProviderConfig", "{}") ## fix the handling of multiline config entries lconfig = [] lines = tConfig.splitlines() for line in lines: line = line.strip('\\') if len(line) > 0: lconfig.append(line) tConfig = " ".join(lconfig) log.debug("[loadLinOtpSMSProviderConfig] providerconfig: %s" % tConfig) if tConfig is not None: config = loads(tConfig) log.debug('[loadLinOtpSMSProviderConfig] sms provider config' + ' found: config %r' % (config)) return config
def loadLinOtpSMSProviderConfig(self): """ load the defined sms provider config definition :return: dict of the sms provider definition :rtype: dict """ log.debug("[loadLinOtpSMSProviderConfig] begin. load the sms " + "provider config definition") # # get User realm config = {} tConfig = getFromConfig("enclinotp.SMSProviderConfig", None) if tConfig is None: tConfig = getFromConfig("SMSProviderConfig", "{}") # # fix the handling of multiline config entries lconfig = [] lines = tConfig.splitlines() for line in lines: line = line.strip("\\") if len(line) > 0: lconfig.append(line) tConfig = " ".join(lconfig) log.debug("[loadLinOtpSMSProviderConfig] providerconfig: %s" % tConfig) try: if tConfig is not None: config = loads(tConfig) except ValueError as exx: raise ValueError("Failed to load provider config:%r %r" % (tConfig, exx)) log.debug("[loadLinOtpSMSProviderConfig] sms provider config" + " found: config %r" % (config)) return config
def autosync(self, hmac2Otp, anOtpVal): ''' auto - sync the token based on two otp values - internal method to realize the autosync within the checkOtp method :param hmac2Otp: the hmac object (with reference to the token secret) :type hmac2Otp: hmac object :param anOtpVal: the actual otp value :type anOtpVal: string :return: counter or -1 if otp does not exist :rtype: int ''' log.debug("[autosync] begin. Autosync the token, based on: hmac2Otp: %r, anOtpVal: %r" % (hmac2Otp, anOtpVal)) res = -1 autosync = False ## get autosync from config or use False as default async = getFromConfig("AutoResync", False) # TODO: nasty: # The SQLite database returns AutoResync as a boolean and not as a string. # So the boolean has no .lower() if isinstance(async, bool): autosync = async
def _initalGetRealms(): ''' initaly parse all config entries, and extract the realm definition :return : a dict with all realm definitions :rtype : dict of definitions ''' Realms = {} defRealmConf = "linotp.useridresolver" realmConf = "linotp.useridresolver.group." defaultRealmDef = "linotp.DefaultRealm" defaultRealm = None dc = getLinotpConfig() for entry in dc: if entry.startswith(realmConf): #the realm might contain dots "." # so take all after the 3rd dot for realm r = {} realm = entry.split(".", 3) theRealm = realm[3].lower() r["realmname"] = realm[3] r["entry"] = entry ##resids = env.config[entry] resids = getFromConfig(entry) # we adjust here the *ee resolvers from the config # so we only have to deal with the un-ee resolvers in the server # which match the available resolver classes resids = resids.replace("useridresolveree.", "useridresolver.") r["useridresolver"] = resids.split(",") Realms[theRealm] = r if entry == defRealmConf: r = {} theRealm = "_default_" r["realmname"] = theRealm r["entry"] = defRealmConf #resids = env.config[entry] resids = getFromConfig(entry) r["useridresolver"] = resids.split(",") defaultRealm = "_default_" Realms[theRealm] = r if entry == defaultRealmDef: defaultRealm = getFromConfig(defaultRealmDef) if defaultRealm is not None: _setDefaultRealm(Realms, defaultRealm) return Realms
def autosync(self, hmac2Otp, anOtpVal): ''' auto - sync the token based on two otp values - internal method to realize the autosync within the checkOtp method :param hmac2Otp: the hmac object (with reference to the token secret) :type hmac2Otp: hmac object :param anOtpVal: the actual otp value :type anOtpVal: string :return: counter or -1 if otp does not exist :rtype: int ''' log.debug("[autosync] begin. Autosync the token, based on: hmac2Otp: %r, anOtpVal: %r" % (hmac2Otp, anOtpVal)) res = -1 autosync = False try: async = getFromConfig("AutoResync") if async is None: autosync = False elif "true" == async.lower(): autosync = True
def __init__(self, aToken): ''' constructor - create a token object :param aToken: instance of the orm db object :type aToken: orm object ''' TokenClass.__init__(self, aToken) self.setType(u"TOTP") self.hKeyRequired = True ''' timeStep defines the granularity: ''' self.timeStep = getFromConfig("totp.timeStep", 30) or 30 ''' window size in seconds: 30 seconds with as step width of 30 seconds results in a window of 1 which is one attempt ''' self.timeWindow = getFromConfig("totp.timeWindow", 180) or 180 '''the time shift is specified in seconds - and could be positive and negative ''' self.timeShift = getFromConfig("totp.timeShift", 0) '''we support various hashlib methods, but only on create which is effectively set in the update ''' self.hashlibStr = getFromConfig("totp.hashlib", u'sha1') or 'sha1' return
def _initalGetRealms(): ''' initaly parse all config entries, and extract the realm definition :return : a dict with all realm definitions :rtype : dict of definitions ''' Realms = {} defRealmConf = "linotp.useridresolver" realmConf = "linotp.useridresolver.group." defaultRealmDef = "linotp.DefaultRealm" defaultRealm = None dc = getLinotpConfig() for entry in dc: if entry.startswith(realmConf): #the realm might contain dots "." # so take all after the 3rd dot for realm r = {} realm = entry.split(".", 3) theRealm = realm[3].lower() r["realmname"] = realm[3] r["entry"] = entry ##resids = env.config[entry] resids = getFromConfig(entry) r["useridresolver"] = resids.split(",") Realms[theRealm] = r if entry == defRealmConf: r = {} theRealm = "_default_" r["realmname"] = theRealm r["entry"] = defRealmConf #resids = env.config[entry] resids = getFromConfig(entry) r["useridresolver"] = resids.split(",") defaultRealm = "_default_" Realms[theRealm] = r if entry == defaultRealmDef: defaultRealm = getFromConfig(defaultRealmDef) if defaultRealm is not None: _setDefaultRealm(Realms, defaultRealm) return Realms
def setDefaults(self): # set the defaults self.token.LinOtpOtpLen = int( getFromConfig("DefaultOtpLen") or 6) self.token.LinOtpCountWindow = int( getFromConfig("DefaultCountWindow") or 10) self.token.LinOtpMaxFail = int( getFromConfig("DefaultMaxFailCount") or 10) self.token.LinOtpSyncWindow = int( getFromConfig("DefaultSyncWindow") or 1000) self.token.LinOtpTokenType = u'' + self.type return
def verify_duration(lic_dict, raiseException=False): """ verify that the license duration is not already expired :param lic_dict: the license info object :return: boolean, if expired or not """ if not (lic_dict.license_expiration and 'days' in lic_dict.license_expiration): return False date_format = "%d%m%y" # get the decrypted value from the config, if there is one duration = getFromConfig('enclinotp.license_duration', None) # no entry set by now, so this must be an error if not duration: log.error("License incorrectly installed!") return False # ok, we already have an entry else: # fetch config and split the signature and the expiration date _signature, _sep, date = duration.rpartition(':') expiration_date = datetime.datetime.strptime(date, date_format) now = datetime.datetime.now() if now > expiration_date + datetime.timedelta(days=1): return False return True
def test_local_admins_enable_command(app, runner, resolver, res_list): # Forcibly remove the resolver from the admin realm. admin_realm_name = app.config["ADMIN_REALM_NAME"].lower() admin_resolvers_key = f"useridresolver.group.{admin_realm_name}" set_config( key=admin_resolvers_key, value=res_list, typ="text", description="None", ) resolver.session.commit() # Try to re-add it using the enable command. result = runner.invoke(cli_main, ["local-admins", "enable"]) assert result.exit_code == 0 # See whether it is there now. admin_resolvers = getFromConfig(admin_resolvers_key, "") if admin_resolvers: first_resolver = admin_resolvers.split(",")[0].strip() assert ( first_resolver == "useridresolver.SQLIdResolver.IdResolver." + resolver.admin_resolver_name ) else: assert False, "still no resolvers in admin realm"
def get_config_items(self): """ iterator function, to return a config entry in the migration format it reads all config entries from the config table, which have the type password. The decrypted value is taken from the linotp config :return: dictionary with the config entry: key, type, description and the value, which is a dict with the encryption relevant data like: encrypted_data, iv, mac """ config_entries = model_config.query.filter_by(Type="password").all() for entry in config_entries: key = "enc%s" % entry.Key value = getFromConfig(key) # calculate encryption and add mac from mac_data enc_value = self.crypter.encrypt(input_data=value, just_mac=key + entry.Value) config_item = { "Key": entry.Key, "Value": enc_value, "Type": entry.Type, "Description": entry.Description, } yield config_item
def getOtp(self, curTime=None): ''' get the next OTP value :return: next otp value :rtype: string ''' log.debug("[getOtp] begin. Get the next OTP value for: curTime: %r" % (curTime)) try: otplen = int(self.token.LinOtpOtpLen) except ValueError as ex: log.error("[getOtp]: Could not convert otplen - value error %r " % (ex)) raise Exception(ex) self.hashlibStr = self.getFromTokenInfo("hashlib", 'sha1') secretHOtp = self.token.getHOtpKey() hmac2Otp = HmacOtp(secretHOtp, self.getOtpCount(), otplen, self.getHashlib(self.hashlibStr)) otpval = hmac2Otp.generate(inc_counter=False) pin = self.token.getPin() combined = "%s%s" % (otpval, pin) if getFromConfig("PrependPin") == "True" : combined = "%s%s" % (pin, otpval) log.debug("[getOtp] end. Return opt is: (pin: %r, otpval: %r, combined: %r) " % (pin, otpval, combined)) return (1, pin, otpval, combined)
def getOtp(self, curTime=None): ''' get the next OTP value :return: next otp value :rtype: string ''' log.debug("[getOtp] begin. Get the next OTP value for: curTime: %r" % (curTime)) try: otplen = int(self.token.LinOtpOtpLen) except ValueError as ex: log.error("[getOtp]: Could not convert otplen - value error %r " % (ex)) raise Exception(ex) self.hashlibStr = self.getFromTokenInfo("hashlib", 'sha1') secretHOtp = self.token.getHOtpKey() hmac2Otp = HmacOtp(secretHOtp, self.getOtpCount(), otplen, self.getHashlib(self.hashlibStr)) otpval = hmac2Otp.generate(inc_counter=False) pin = self.token.getPin() combined = "%s%s" % (otpval, pin) if getFromConfig("PrependPin") == "True": combined = "%s%s" % (pin, otpval) log.debug( "[getOtp] end. Return opt is: (pin: %r, otpval: %r, combined: %r) " % (pin, otpval, combined)) return (1, pin, otpval, combined)
def get_config_items(self): """ iterator function, to return a config entry in the migration format it reads all config entries from the config table, which have the type password. The decrypted value is taken from the linotp config :return: dictionary with the config entry: key, type, description and the value, which is a dict with the encryption relevant data like: encrypted_data, iv, mac """ config_entries = Session.query(model_config).\ filter(model_config.Type == 'password').all() for entry in config_entries: key = 'enc%s' % entry.Key value = getFromConfig(key) # calculate encryption and add mac from mac_data enc_value = self.crypter.encrypt(input_data=value, just_mac=key + entry.Value) config_item = { "Key": entry.Key, "Value": enc_value, "Type": entry.Type, "Description": entry.Description } yield config_item
def get_client(request): ''' This function returns the client. It first tries to get the client as it is passed as the HTTP Client via REMOTE_ADDR. If this client Address is in a list, that is allowed to overwrite its client address (like e.g. a FreeRADIUS server, which will always pass the FreeRADIUS address but not the address of the RADIUS client) it checks for the existance of the client parameter. ''' may_overwrite = [] over_client = getFromConfig("mayOverwriteClient", "") try: may_overwrite = [c.strip() for c in over_client.split(',')] except Exception as e: log.warning("evaluating config entry 'mayOverwriteClient': %r" % e) client = _get_client_from_request(request) if client in may_overwrite or client is None: log.debug("client %s may overwrite!" % client) client = get_request_param(request, "client") if client: log.debug("client overwritten to %s" % client) log.debug("returning client %s" % client) return client
def getOtp(self, curTime=None): ''' get the next OTP value :return: next otp value :rtype: string ''' try: otplen = int(self.token.LinOtpOtpLen) except ValueError as ex: log.exception( "[getOtp]: Could not convert otplen - value error %r " % (ex)) raise Exception(ex) self.hashlibStr = self.getFromTokenInfo("hashlib", 'sha1') secObj = self._get_secret_object() hmac2Otp = HmacOtp(secObj, self.getOtpCount(), otplen, self.getHashlib(self.hashlibStr)) otpval = hmac2Otp.generate(inc_counter=False) pin = self.getPin() combined = "%s%s" % (otpval, pin) if getFromConfig("PrependPin") == "True": combined = "%s%s" % (pin, otpval) return (1, pin, otpval, combined)
def getSupportLicenseInfo(): """ get the current support and subscription information :param validate: inform program to validate or not the license info :return: tuple of dict with the license information and signature in case of an error, the dict and the signature are empty """ refreshConfig() lic_dict = LicenseInfo() lic_sign = "" try: licString = getFromConfig("license", '') if licString: lic_text = binascii.unhexlify(licString.encode('utf-8')).decode() lic_dict, lic_sign = parseSupportLicense(lic_text) lic_dict['expire'] = get_expiration_date(lic_dict) except InvalidLicenseException as exx: log.info('Invalid license error: %r' % exx) return lic_dict, lic_sign
def _getEmailProviderClass(self): """ getEmailProviderClass(): helper method to load the EmailProvider class from config checks, if the submitMessage method exists if not an error is thrown """ LOG.debug('[getEmailProviderClass] begin. get the e-mail Provider ' 'class definition') email_provider = getFromConfig("EmailProvider", self.DEFAULT_EMAIL_PROVIDER) if not email_provider: raise Exception("No EmailProvider defined.") (email_provider_package, email_provider_class_name) = email_provider.rsplit(".", 1) if not email_provider_package or not email_provider_class_name: raise Exception( "Could not load e-mail provider class. Maybe EmailProvider is " "not set in the config file.") mod = __import__(email_provider_package, globals(), locals(), [email_provider_class_name]) # TODO Kay sagt hier soll das Modul global geladen werden (mit einem bisher nicht existierenden Hook) provider_class = getattr(mod, email_provider_class_name) if not hasattr(provider_class, "submitMessage"): raise NameError( "EmailProvider AttributeError: " + email_provider_package + "." + email_provider_class_name + " instance of EmailProvider has no method 'submitMessage'") return provider_class
def getOtp(self, curTime=None): ''' get the next OTP value :return: next otp value :rtype: string ''' res = (-1, 0, 0, 0) otplen = int(self.token.LinOtpOtpLen) secObj = self._get_secret_object() self.hashlibStr = self.getFromTokenInfo("hashlib", "sha1") or 'sha1' timeStepping = int(self.getFromTokenInfo("timeStep", 30) or 30) shift = int(self.getFromTokenInfo("timeShift", 0) or 0) hmac2Otp = HmacOtp(secObj, self.getOtpCount(), otplen, self.getHashlib(self.hashlibStr)) tCounter = self.time2float(datetime.datetime.utcnow()) if curTime: tCounter = self.time2float(curTime) counter = int((tCounter - shift) // timeStepping) otpval = hmac2Otp.generate(counter=counter, inc_counter=False) pin = self.getPin() combined = "%s%s" % (otpval, pin) if getFromConfig("PrependPin") == "True": combined = "%s%s" % (pin, otpval) return (1, pin, otpval, combined)
def getSupportLicenseInfo(): """ get the current support and subscription information :return: dict with the license informstion """ info = {} refreshConfig() licString = getFromConfig("license", None) if licString: try: lic_str = binascii.unhexlify(licString) except TypeError: lic_str = licString (info, _lic_sign, _lic_txt) = parseSupportLicense(lic_str) else: # if we have no licens in the config, we compose the # comuninity edition text info.update(support_info) version = get_version_number() info["version"] = "LinOTP %s" % version return info
def createChallenge(self, transactionid, options=None): """ This method creates a challenge, which is submitted to the user. The submitted challenge will be preserved in the challenge database. This method is called *after* the method :py:meth:`~linotp.tokens.base.TokenClass.initChallenge`. :param transactionid: the id of this challenge :param options: the request context parameters / data :type options: dict :return: tuple of (bool, message, data, attributes) The return tuple builds up like this: ``bool`` if submit was successfull; ``message`` which is displayed in the JSON response; ``data`` is preserved in the challenge; additional ``attributes``, which are displayed in the JSON response. """ message = getFromConfig(self.type.upper() + "_CHALLENGE_PROMPT", 'Otp: ') data = {'serial': self.getSerial()} attributes = None return (True, message, data, attributes)
def getOtp(self, curTime=None): ## kay: init value res = (-1, 0, 0, 0) try: otplen = int(self.token.LinOtpOtpLen) except ValueError: return res secObj = self._get_secret_object() dpw = dpwOtp(secObj, otplen) date_string = None if curTime: if type(curTime) == datetime.datetime: date_string = curTime.strftime("%d%m%y") elif type(curTime) == unicode: date_string = datetime.datetime.strptime( curTime, "%Y-%m-%d %H:%M:%S.%f").strftime("%d%m%y") else: log.error( "[getOtp] invalid curTime: %r. You need to specify a datetime.datetime" % type(curTime)) otpval = dpw.getOtp(date_string) pin = self.getPin() combined = "%s%s" % (otpval, pin) if getFromConfig("PrependPin") == "True": combined = "%s%s" % (pin, otpval) return (1, pin, otpval, combined)
def check_license_restrictions(): """ check if there are restrictions, which are caused by the license :return: boolean - True if there are restrictions """ license_str = getFromConfig('license') if not license_str: return False licString = binascii.unhexlify(license_str).decode() lic_dict, lic_sign = parseSupportLicense(licString) res, reason = verifyLicenseInfo( lic_dict, lic_sign, checkVolume=False, raiseException=False) if not res: log.info("license check: %r", reason) return True res, msg = verify_volume(lic_dict) if not res: log.info("License check: Too many tokens enrolled %r", msg) return True res, _msg = verify_expiration(lic_dict) if res is False: log.info("License check: License expired!") return True return False
def __init__(self, a_token): ''' constructor - create a token object :param aToken: instance of the orm db object :type aToken: orm object ''' log.debug("[init] begin. Create a token object with: a_token %r" % (a_token)) TokenClass.__init__(self, a_token) self.setType(u"HMAC") self.hKeyRequired = True # we support various hashlib methods, but only on create # which is effectively set in the update self.hashlibStr = u"sha1" try: self.hashlibStr = getFromConfig("hotp.hashlib", u'sha1') except Exception as ex: log.error('[init] Failed to get the hotp.hashlib (%r)' % (ex)) raise Exception(ex) log.debug("[init] end. Token object created") return
def get_challenges(serial=None, transid=None): ''' get_challenges - give all challenges for a given token :param serial: serial of the token :param transid: transaction id, if None, all will be retrieved :return: return a list of challenge dict ''' log.debug('[get_challenges] %r' % (serial)) challenges = [] if transid is None and serial is None: return challenges if transid is None: db_challenges = Session.query(Challenge)\ .filter(Challenge.tokenserial == u'' + serial)\ .order_by(desc(Challenge.id))\ .all() else: transid_len = int(getFromConfig('TransactionIdLength', 12)) if len(transid) == transid_len: db_challenges = Session.query(Challenge)\ .filter(Challenge.transid == transid)\ .all() else: db_challenges = Session.query(Challenge)\ .filter(Challenge.transid.startswith(transid))\ .all() challenges.extend(db_challenges) log.debug('[getTransactions4serial] %r' % challenges) return challenges
def test_getsearchfields_case_sensitive_resolver_names(fthsm, app): g.request_context["Config"] = getLinotpConfig() # This sucks. g.request_context["CacheManager"] = app.cache # This sucks even worse. admin_realm_name = app.config["ADMIN_REALM_NAME"] admin_resolvers_key = f"useridresolver.group.{admin_realm_name}" admin_resolvers = getFromConfig(admin_resolvers_key, "") _, _, aci = admin_resolvers.rpartition(".") user = User( login="******", realm=admin_realm_name, resolver_config_identifier=aci ) # If the user's resolver config identifier matches its resolver, then the # `getSearchFields` function should return a set of search fields. If there # is no match, the result should be empty. Hence if we tweak the RCI to be # the uppercase version, then if the comparison is case-sensitive the result # should be empty. user.resolver_config_identifier = user.resolver_config_identifier.upper() search_fields = getSearchFields(user) assert ( not search_fields ), "getSearchFields resolver name comparison is not case-sensitive"
def test_getuserlist_case_sensitive_resolver_names(fthsm, app, admin_res): g.request_context["Config"] = getLinotpConfig() # This sucks. g.request_context["CacheManager"] = app.cache # This sucks even worse. g.request_context["UserLookup"] = {} # Don't get me started. admin_realm_name = app.config["ADMIN_REALM_NAME"] admin_resolvers_key = f"useridresolver.group.{admin_realm_name}" admin_resolvers = getFromConfig(admin_resolvers_key, "") _, _, aci = admin_resolvers.rpartition(".") # As before, if we make the RCI uppercase and the `getUserList()` function # still returns a user list, then the comparison must have been # case-insensitive. With a case-sensitive comparison, the result should # be empty. search_user = User( login="******", realm=admin_realm_name, resolver_config_identifier=aci.upper(), ) user_list = getUserList({}, search_user) assert ( not user_list ), "getUserList resolver name comparison is not case-sensitive"
def getOtp(self, curTime=None): ''' get the next OTP value :return: next otp value :rtype: string ''' res = (-1, 0, 0, 0) otplen = int(self.token.LinOtpOtpLen) secObj = self._get_secret_object() self.hashlibStr = self.getFromTokenInfo("hashlib", "sha1") or 'sha1' timeStepping = int(self.getFromTokenInfo("timeStep", 30) or 30) shift = int(self.getFromTokenInfo("timeShift", 0) or 0) hmac2Otp = HmacOtp(secObj, self.getOtpCount(), otplen, self.getHashlib(self.hashlibStr)) tCounter = self.time2float(datetime.datetime.now()) if curTime: tCounter = self.time2float(curTime) ## we don't need to round here as we have alread float counter = int(((tCounter - shift) / timeStepping)) otpval = hmac2Otp.generate(counter=counter, inc_counter=False) pin = self.getPin() combined = "%s%s" % (otpval, pin) if getFromConfig("PrependPin") == "True": combined = "%s%s" % (pin, otpval) return (1, pin, otpval, combined)
def getSupportLicenseInfo(): """ get the current support and subscription information :return: dict with the license informstion """ info = {} refreshConfig() licString = getFromConfig("license", None) if licString: try: lic_str = binascii.unhexlify(licString) except TypeError: lic_str = licString (info, _lic_sign, _lic_txt) = parseSupportLicense(lic_str) else: # if we have no licens in the config, we compose the # comuninity edition text info.update(support_info) version = get_version_number() info['version'] = 'LinOTP %s' % version return info
def check_duration(expire, lic_info): """ check duration - check only for duration in days :param: the expiration string value :return: tuple of bool and the amount of days as string """ if 'days' not in expire: return False, 'no expiration days found!' lic_sign = lic_info.signature # if there is already a license with duration installed # check if it is still valid date_format = "%d%m%y" duration = getFromConfig('enclinotp.license_duration', None) if duration: signature, _sep, date = duration.rpartition(':') expiration_date = datetime.datetime.strptime(date, date_format) # only check the current license if base64.b64encode(lic_sign)[:500] == signature: now = datetime.datetime.now() expiration_date = datetime.datetime.strptime(date, date_format) # preserve the volatile expiration date lic_info["expire"] = expiration_date.strftime("%Y-%m-%d") if now > expiration_date + datetime.timedelta(days=1): return False, 'License expired' duration = int(expire.replace('days', '').strip()) return duration > 0, "%d days" % duration
def get_client(): ''' This function returns the client. It first tries to get the client as it is passed as the HTTP Client via REMOTE_ADDR. If this client Address is in a list, that is allowed to overwrite its client address (like e.g. a FreeRADIUS server, which will always pass the FreeRADIUS address but not the address of the RADIUS client) it checks for the existance of the client parameter. ''' may_overwrite = [] over_client = getFromConfig("mayOverwriteClient", "") log.debug("[get_client] config entry mayOverwriteClient: %s" % over_client) try: may_overwrite = [ c.strip() for c in over_client.split(',') ] except Exception as e: log.warning("[get_client] evaluating config entry 'mayOverwriteClient': %r" % e) client = get_client_from_request() log.debug("[get_client] got the original client %s" % client) if client in may_overwrite or client == None: log.debug("[get_client] client %s may overwrite!" % client) if get_client_from_param(): client = get_client_from_param() log.debug("[get_client] client overwritten to %s" % client) log.debug("[get_client] returning %s" % client) return client
def check_license_restrictions(): """ check if there are restrictions, which are caused by the license """ license_str = getFromConfig('license') if not license_str: return False licString = binascii.unhexlify(license_str) lic_dict, lic_sign = parseSupportLicense(licString) res, reason = verifyLicenseInfo(lic_dict, lic_sign, raiseException=False) if not (lic_dict.license_type and lic_dict.license_type == 'demo'): return False import linotp.lib.token installed_tokens = int(linotp.lib.token.getTokenNumResolver()) allowed_tokens = lic_dict.get('token-num', 'unlimited') try: allowed_tokens = int(allowed_tokens.strip()) if installed_tokens >= allowed_tokens: return True except ValueError as _val_err: # in case of no int we ignore this restriction as it could # be a string representation like 'unlimited' pass res, _msg = verify_expiration(lic_dict) if res is False: return True return False
def _getEmailProviderClass(self): """ getEmailProviderClass(): helper method to load the EmailProvider class from config checks, if the submitMessage method exists if not an error is thrown """ LOG.debug('[getEmailProviderClass] begin. get the e-mail Provider ' 'class definition') email_provider = getFromConfig("EmailProvider", self.DEFAULT_EMAIL_PROVIDER) if not email_provider: raise Exception("No EmailProvider defined.") (email_provider_package, email_provider_class_name) = \ email_provider.rsplit(".", 1) if not email_provider_package or not email_provider_class_name: raise Exception("Could not load e-mail provider class. Maybe " "EmailProvider is not set in the config file.") mod = __import__(email_provider_package, globals(), locals(), [email_provider_class_name]) # TODO Kay sagt hier soll das Modul global geladen werden (mit einem # bisher nicht existierenden Hook) provider_class = getattr(mod, email_provider_class_name) if not hasattr(provider_class, "submitMessage"): raise NameError("EmailProvider AttributeError: %s.%s instance of " "EmailProvider has no method 'submitMessage'" % (email_provider_package, email_provider_class_name)) return provider_class
def verify_duration(lic_dict, raiseException=False): """ verify that the license duration is not already expired :param lic_dict: the license info object :return: boolean, if expired or not """ if not (lic_dict.license_expiration and 'days' in lic_dict.license_expiration): return False date_format = "%d%m%y" # get the decrypted value from the config, if there is one duration = getFromConfig('enclinotp.license_duration', None) # no entry set by now, so this must be an error if not duration: log.error("license incorrectly installed!") return False # ok, we already have an entry else: # fetch config and split the signature and the expiration date _signature, _sep, date = duration.rpartition(':') expiration_date = datetime.datetime.strptime(date, date_format) now = datetime.datetime.now() if now > expiration_date + datetime.timedelta(days=1): return False return True
def getOtp(self, curTime=None): ## kay: init value res = (-1, 0, 0, 0) try: otplen = int(self.token.LinOtpOtpLen) except ValueError: return res secretHOtp = self.token.getHOtpKey() dpw = dpwOtp(secretHOtp, otplen) date_string = None if curTime: if type(curTime) == datetime.datetime: date_string = curTime.strftime("%d%m%y") elif type(curTime) == unicode: date_string = datetime.datetime.strptime(curTime, "%Y-%m-%d %H:%M:%S.%f").strftime("%d%m%y") else: log.error("[getOtp] invalid curTime: %r. You need to specify a datetime.datetime" % type(curTime)) otpval = dpw.getOtp(date_string) pin = self.token.getPin() combined = "%s%s" % (otpval, pin) if getFromConfig("PrependPin") == "True" : combined = "%s%s" % (pin, otpval) return (1, pin, otpval, combined)
def samlcheck(self): ''' This function is used to validate the username and the otp value/password in a SAML environment. If ``linotp.allowSamlAttributes = True`` then the attributes of the authenticated users are also contained in the response. method: validate/samlcheck arguments: * user: username / loginname * pass: the password that consists of a possible fixes password component and the OTP value * realm: optional realm to match the user to a useridresolver returns: JSON response ''' try: opt = None param = self.request_params (ok, opt) = self._check(param) attributes = {} if True == ok: allowSAML = False try: allowSAML = getFromConfig("allowSamlAttributes") except: log.warning("[samlcheck] Calling controller samlcheck. But allowSamlAttributes is False.") if "True" == allowSAML: ## Now we get the attributes of the user user = getUserFromParam(param) (uid, resId, resIdC) = getUserId(user) userInfo = getUserInfo(uid, resId, resIdC) log.debug("[samlcheck] getting attributes for: %s@%s" % (user.login, user.realm)) res = userInfo for key in ['username', 'surname', 'mobile', 'phone', 'givenname', 'email']: if key in res: attributes[key] = res[key] Session.commit() return sendResult(response, { 'auth': ok, 'attributes' : attributes } , 0, opt) except Exception as exx: log.exception("[samlcheck] validate/check failed: %r" % exx) Session.rollback() return sendResult(response, False, 0) finally: Session.close()
def samlcheck(self): ''' This function is used to validate the username and the otp value/password in a SAML environment. If ``linotp.allowSamlAttributes = True`` then the attributes of the authenticated users are also contained in the response. method: validate/samlcheck arguments: * user: username / loginname * pass: the password that consists of a possible fixes password component and the OTP value * realm: optional realm to match the user to a useridresolver returns: JSON response ''' try: opt = None param = request.params (ok, opt) = self._check(param) attributes = {} if True == ok: allowSAML = False try: allowSAML = getFromConfig("allowSamlAttributes") except: log.warning("[samlcheck] Calling controller samlcheck. But allowSamlAttributes is False.") if "True" == allowSAML: ## Now we get the attributes of the user user = getUserFromParam(param) (uid, resId, resIdC) = getUserId(user) userInfo = getUserInfo(uid, resId, resIdC) log.debug("[samlcheck] getting attributes for: %s@%s" % (user.login, user.realm)) res = userInfo for key in ['username', 'surname', 'mobile', 'phone', 'givenname', 'email']: if key in res: attributes[key] = res[key] Session.commit() return sendResult(response, { 'auth': ok, 'attributes' : attributes } , 0, opt) except Exception as exx: log.exception("[samlcheck] validate/check failed: %r" % exx) Session.rollback() return sendResult(response, False, 0) finally: Session.close()
def getSplitAtSign(): ''' returns the config value of splitAtSign. if True, the username should be split if there is an at sign. if False, the username will be taken unchanged for loginname. ''' splitAtSign = getFromConfig("splitAtSign", "true") or 'true' return "true" == splitAtSign.lower()
def tokenview(self): """ This is the template for the token TAB """ c.title = "LinOTP Management" c.tokenArray = [] c.getotp_active = boolean(getFromConfig("linotpGetotp.active", False)) return render("/manage/tokenview.mako")
def get_helper_params_post(cls, param, user=None): helper_param = {} tok_type = "ocra" # take the keysize from the ocrasuite ocrasuite = param.get("ocrasuite", None) activationcode = param.get("activationcode", None) sharedsecret = param.get("sharedsecret", None) serial = param.get("serial", None) genkey = param.get("genkey", None) if activationcode is not None: # dont create a new key genkey = None serial = getRolloutToken4User(user=user, serial=serial, tok_type=tok_type) if serial is None: raise Exception('no token found for user: %r or serial: %r' % (user, serial)) helper_param['serial'] = serial helper_param['activationcode'] = \ normalize_activation_code(activationcode) if ocrasuite is None: if sharedsecret is not None or activationcode is not None: ocrasuite = getFromConfig("QrOcraDefaultSuite", 'OCRA-1:HOTP-SHA256-6:C-QA64') else: ocrasuite = getFromConfig("OcraDefaultSuite", 'OCRA-1:HOTP-SHA256-8:C-QN08') helper_param['ocrasuite'] = ocrasuite if genkey is not None: if ocrasuite.find('-SHA256'): key_size = 32 elif ocrasuite.find('-SHA512'): key_size = 64 else: key_size = 20 helper_param['key_size'] = key_size return helper_param
def set_duration(lic_dict, raiseException=False): """ set the duration value in linotp config and thus in config database :param lic_dict: the license info object :param raiseException: switch to control if an exception should be thrown in case of a problem """ # if there is no expiration in the license we just can go on if not (lic_dict.license_expiration and 'days' in lic_dict.license_expiration): return True lic_sign = lic_dict.signature days = lic_dict.license_expiration.replace('days', '').strip() try: days = int(days) except ValueError as _val: raise Exception('Unable to interpret duration in' ' license description') # we have a timely limited version, so we have to check if there is # already a license like this installed by comparing the signatures date_format = "%d%m%y" # get the decrypted value from the config, if there is one expiration = getFromConfig('enclinotp.license_duration', None) if expiration: # fetch config and split the signature and the expiration date signature, _sep, _date_str = expiration.rpartition(':') # here we only verify that the license signature is not the same # - we only take a slice as the stored signature will be # stored in an encrypted way and then will become too long if base64.b64encode(lic_sign)[:500] == signature: error = _('License already installed!') if raiseException: raise Exception(error) else: log.error(error) return False # so we calculate the expiration and store this together # with the license signature expires = datetime.datetime.now() + datetime.timedelta(days=days) expires_str = expires.strftime(date_format) # we take only some bytes as it is encrypted afterwards signature = base64.b64encode(lic_sign)[:500] license_expire = "%s:%s" % (signature, expires_str) storeConfig("license_duration", license_expire, typ='password') log.info("license_expiration %s" % license_expire) return True
def set_duration(lic_dict, raiseException=False): """ set the duration value in linotp config and thus in config database :param lic_dict: the license info object :param raiseException: switch to control if an exception should be thrown in case of a problem """ _ = context['translate'] # if there is no expiration in the license we just can go on if not (lic_dict.license_expiration and 'days' in lic_dict.license_expiration): return True lic_sign = lic_dict.signature days = lic_dict.license_expiration.replace('days', '').strip() try: days = int(days) except ValueError as _val: raise Exception('Unable to interpret duration in' ' license description') # we have a timely limited version, so we have to check if there is # already a license like this installed by comparing the signatures date_format = "%d%m%y" # get the decrypted value from the config, if there is one expiration = getFromConfig('enclinotp.license_duration', None) if expiration: # fetch config and split the signature and the expiration date signature, _sep, _date_str = expiration.rpartition(':') # here we only verify that the license signature is not the same # - we only take a slice as the stored signature will be # stored in an encrypted way and then will become too long if base64.b64encode(lic_sign)[:500] == signature: error = _('License already installed!') if raiseException: raise Exception(error) else: log.error(error) return False # so we calculate the expiration and store this together # with the license signature expires = datetime.datetime.now() + datetime.timedelta(days=days) expires_str = expires.strftime(date_format) # we take only some bytes as it is encrypted afterwards signature = base64.b64encode(lic_sign)[:500] license_expire = "%s:%s" % (signature, expires_str) storeConfig("license_duration", license_expire, typ='password') log.info("license_expiration %s" % license_expire) return True
def maxChallengeJanitor(cls, transId=None, serial=None): ''' maxChallengeJanitor - remove for one token (serial) all challengens but the last ones :param transId: the current transaction, which provides a the lookup for the serial number :type transId: string :param serial: the serial number of the token :type serial: string :return: - nothing ''' maxChallDef = getFromConfig("OcraMaxChallenges", '3') try: ones = int(maxChallDef) except ValueError as ex: log.exception('Failed to convert OcraMaxChallenges value from ' 'config: %r :%r' % (maxChallDef, ex)) ones = 3 if ones <= 0: ones = 3 if transId is not None: challenges = Session.query(OcraChallenge).filter( OcraChallenge.transid == u'' + transId) if challenges is None: return for challenge in challenges: serial = challenge.tokenserial if serial is None: log.info('Ocra max challenge janitor: Failed to lookup result ' 'for transid %r or serial %r' % (transId, serial)) return challenges = Session.query(OcraChallenge).\ filter(OcraChallenge.tokenserial == u'' + serial)\ .order_by(desc(OcraChallenge.id)) lastIds = set() for challenge in challenges: if len(lastIds) < ones: lastIds.add(challenge.id) else: log.info( "Dropping ocra challenge %r (transaction id %r) " "for token %r", challenge.id, challenge.transid, challenge.tokenserial) Session.delete(challenge) return
def __init__(self, aToken): HmacTokenClass.__init__(self, aToken) self.setType(u"email") self.hKeyRequired = False # we support various hashlib methods, but only on create # which is effectively set in the update self.hashlibStr = getFromConfig("hotp.hashlib", "sha1") self.mode = ['challenge']
def __init__(self, aToken): HmacTokenClass.__init__(self, aToken) self.setType(u"sms") self.hKeyRequired = False # we support various hashlib methods, but only on create # which is effectively set in the update self.hashlibStr = getFromConfig("hotp.hashlib", "sha1") self.mode = ['challenge']
def do_nagging(lic_info, nag_days=7): """ do nagging - answer the question if nagging should be done :param lic_info: the license info :return: boolean - True if nagging should be displayed """ d_fmt = "%Y-%m-%d" # we start 7 days after download license was installed nag_offset = nag_days if not ( lic_info.license_type and ( lic_info.license_type == "download" or lic_info.license_type == "demo" ) ): return False # in case there is no duration definition in 'xx days' we do the nagging if not lic_info.license_expiration: log.error( "Download license format error: Missing expiration definition!" ) return True now_date = datetime.datetime.now().date() expire = get_expiration_date(lic_info) expire_date = datetime.datetime.strptime(expire, d_fmt).date() # calculate back, when the license was enrolled duration = int(lic_info.license_expiration.replace("days", "").strip()) lic_start_date = expire_date - datetime.timedelta(days=duration) # calulate the nagging start date with given nag_offset nag_start_date = lic_start_date + datetime.timedelta(days=nag_offset) if now_date <= nag_start_date: return False # ok, we are in the nagging time frame, so start nagging last_nagged = getFromConfig("last_nagged") if last_nagged: # nag only once a day: check, if we nagged already today last_nag_date = datetime.datetime.strptime(last_nagged, d_fmt).date() # check if we nagged already today if last_nag_date >= now_date: return False datum = now_date.strftime(d_fmt) storeConfig("last_nagged", datum, desc="last nagged") return True
def getUserFromParam(param, optionalOrRequired, optional=False, required=False): realm = "" conf = "" if optional: optionalOrRequired = True if required: optionalOrRequired = False log.debug("[getUserFromParam] entering function") user = getParam(param, "user", optionalOrRequired) log.debug("[getUserFromParam] got user <<%r>>" % user) if user is None: user = "" else: splitAtSign = getFromConfig("splitAtSign", "true") if splitAtSign.lower() == "true": (user, realm) = splitUser(user) if "realm" in param: realm = param["realm"] if user != "": if realm is None or realm == "": realm = getDefaultRealm() usr = User(user, realm, "") if "resConf" in param: conf = param["resConf"] # with the short resolvernames, we have to extract the # configuration name from the resolver spec if "(" in conf and ")" in conf: res_conf, resolver_typ = conf.split(" ") conf = res_conf usr.conf = conf else: if len(usr.login) > 0 or len(usr.realm) > 0 or len(usr.conf) > 0: res = getResolversOfUser(usr) usr.saveResolvers(res) if len(res) > 1: log.error("[getUserFromParam] user %r@%r in more than one " "resolver: %r" % (user, realm, res)) raise Exception("The user %s@%s is in more than one resolver:" " %s" % (user, realm, unicode(res))) log.debug("[getUserFromParam] creating user object %r,%r,%r" % (user, realm, conf)) log.debug("[getUserFromParam] created user object %r " % usr) return usr
def submitChallenge(self, options=None, pin=None): ''' submit the sms message - former method name was checkPin :param options: the request options context :return: tuple of success and message ''' log.debug("[submitChallenge] entering function") res = 0 user = None if options is None: options = {} message = options.get('challenge', None) # it is configurable, if sms should be triggered by a valid pin send_by_PIN = getFromConfig("sms.sendByPin") or True if self.isActive() == True and send_by_PIN == True: counter = self.getOtpCount() log.debug("[submitChallenge] counter=%r" % counter) self.incOtpCounter(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: if options is not None and type(options) == dict: user = options.get('user', None) # a given message overrules the policy defined message if user and not message: _sms_ret, message = get_auth_smstext( realm=user.realm) if not message: message = "<otp>" res, message = self.sendSMS(message=message, pin=pin) if res is None: res = False message = "failed to submitt sms" self.info['info'] = "SMS sent: %r" % res except Exception as e: # The PIN was correct, but the SMS could not be sent. self.info['info'] = unicode(e) info = ("The SMS could not be sent: %r" % e) log.warning("[submitChallenge] %s" % info) res = False message = info if len(message) == 0: pass return res, message
def autosync(self, ocraSuite, passw, challenge): ''' try to resync a token automaticaly, if a former and the current request failed :param ocraSuite: the ocraSuite of the current Token :type ocraSuite: ocra object :param passw: ''' log.debug('[OcraToken::autosync] %r : %r' % (passw, challenge)) res = -1 autosync = False try: async = getFromConfig("AutoResync") if async is None: autosync = False elif "true" == async.lower(): autosync = True
def getSyncTimeOut(self): ''' get the token sync timeout value :return: timeout value in seconds :rtype: int ''' timeOut = int(getFromConfig("AutoResyncTimeout", 5 * 60)) return timeOut
def submitChallenge(self, options=None): ''' submit the sms message - former method name was checkPin :param options: the request options context :return: tuple of success and message ''' res = 0 user = None if options is None: options = {} message = options.get('challenge', None) # it is configurable, if sms should be triggered by a valid pin send_by_PIN = getFromConfig("sms.sendByPin") or True if self.isActive() == True and send_by_PIN == True: counter = self.getOtpCount() log.debug("[submitChallenge] counter=%r" % counter) self.incOtpCounter(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: if options is not None and type(options) == dict: user = options.get('user', None) # a given message overrules the policy defined message if user and not message: _sms_ret, message = get_auth_smstext( realm=user.realm, context=self.context) if not message: message = "<otp>" res, message = self.sendSMS(message=message) if res is None: res = False message = "failed to submitt sms" self.info['info'] = "SMS sent: %r" % res except Exception as e: # The PIN was correct, but the SMS could not be sent. self.info['info'] = unicode(e) info = ("The SMS could not be sent: %r" % e) log.warning("[submitChallenge] %s" % info) res = False message = info if len(message) == 0: pass return res, message