def update(self, param, reset_failcount=True): """ This is called during initialzaton of the token to add additional attributes to the token object. :param param: dict of initialization parameters :type param: dict :return: nothing """ HotpTokenClass.update(self, param, reset_failcount=reset_failcount) timeStep = param.get("timeStep", get_from_config("totp.timeStep") or 30) timeWindow = param.get("timeWindow", get_from_config("totp.timeWindow") or 180) timeShift = param.get("timeShift", get_from_config("totp.timeShift") or 0) # we support various hashlib methods, but only on create # which is effectively set in the update hashlibStr = param.get("totp.hashlib", get_from_config("totp.hashlib", u'sha1')) self.add_tokeninfo("timeWindow", timeWindow) self.add_tokeninfo("timeShift", timeShift) self.add_tokeninfo("timeStep", timeStep) self.add_tokeninfo("hashlib", hashlibStr)
def create_challenge(self, transactionid=None, options=None): """ This method creates a challenge, which is submitted to the user. The submitted challenge will be preserved in the challenge database. If no transaction id is given, the system will create a transaction id and return it, so that the response can refer to this transaction. :param transactionid: the id of this challenge :param options: the request context parameters / data :type options: dict :return: tuple of (bool, message, transactionid, attributes) :rtype: tuple The return tuple builds up like this: ``bool`` if submit was successful; ``message`` which is displayed in the JSON response; additional ``attributes``, which are displayed in the JSON response. """ options = options or {} message = 'Please scan the QR Code' # Get ValidityTime=120s. Maybe there is a TIQRChallengeValidityTime... validity = int(get_from_config('DefaultChallengeValidityTime', 120)) tokentype = self.get_tokentype().lower() lookup_for = tokentype.capitalize() + 'ChallengeValidityTime' validity = int(get_from_config(lookup_for, validity)) # We need to set the user ID user_identifier, user_displayname = self.get_user_displayname() service_identifier = get_from_config("tiqr.serviceIdentifier") or \ "org.privacyidea" # Get the OCRASUITE from the token information ocrasuite = self.get_tokeninfo("ocrasuite") or OCRA_DEFAULT_SUITE # Depending on the OCRA-SUITE we create the challenge os = OCRASuite(ocrasuite) challenge = os.create_challenge() # Create the challenge in the database db_challenge = Challenge(self.token.serial, transaction_id=None, challenge=challenge, data=None, session=options.get("session"), validitytime=validity) db_challenge.save() authurl = "tiqrauth://%s@%s/%s/%s" % (user_identifier, service_identifier, db_challenge.transaction_id, challenge) attributes = {"img": create_img(authurl, width=250), "value": authurl, "poll": True, "hideResponseInput": True} return True, message, db_challenge.transaction_id, attributes
def create_challenge(self, transactionid=None, options=None): """ This method creates a challenge, which is submitted to the user. The submitted challenge will be preserved in the challenge database. If no transaction id is given, the system will create a transaction id and return it, so that the response can refer to this transaction. :param transactionid: the id of this challenge :param options: the request context parameters / data :type options: dict :return: tuple of (bool, message, transactionid, attributes) :rtype: tuple The return tuple builds up like this: ``bool`` if submit was successful; ``message`` which is displayed in the JSON response; additional ``attributes``, which are displayed in the JSON response. """ options = options or {} message = get_action_values_from_options(SCOPE.AUTH, "{0!s}_{1!s}".format(self.get_class_type(), ACTION.CHALLENGETEXT), options)or _(u'Please confirm with your U2F token ({0!s})').format( self.token.description) validity = int(get_from_config('DefaultChallengeValidityTime', 120)) tokentype = self.get_tokentype().lower() lookup_for = tokentype.capitalize() + 'ChallengeValidityTime' validity = int(get_from_config(lookup_for, validity)) challenge = geturandom(32) # Create the challenge in the database db_challenge = Challenge(self.token.serial, transaction_id=transactionid, challenge=hexlify_and_unicode(challenge), data=None, session=options.get("session"), validitytime=validity) db_challenge.save() sec_object = self.token.get_otpkey() key_handle_hex = sec_object.getKey() key_handle_bin = binascii.unhexlify(key_handle_hex) key_handle_url = url_encode(key_handle_bin) challenge_url = url_encode(challenge) u2f_sign_request = {"appId": self.get_tokeninfo("appId"), "version": U2F_Version, "challenge": challenge_url, "keyHandle": key_handle_url} image_url = IMAGES.get(self.token.description.lower().split()[0], "") response_details = {"u2fSignRequest": u2f_sign_request, "hideResponseInput": True, "img": image_url} return True, message, db_challenge.transaction_id, response_details
def test_04_store_encrypted_values(self): r = set_privacyidea_config("mySecretData", "soho", typ="password", desc="Very important") self.assertTrue(r == "insert", r) r = get_from_config("mySecretData") self.assertTrue(r == "soho", r) r = get_from_config() self.assertTrue(r.get("mySecretData") == "soho", r.get("mySecretData"))
def create_challenge(self, transactionid=None, options=None): """ This method creates a challenge, which is submitted to the user. The submitted challenge will be preserved in the challenge database. The challenge is a randomly selected question of the available questions for this token. If no transaction id is given, the system will create a transaction id and return it, so that the response can refer to this transaction. :param transactionid: the id of this challenge :param options: the request context parameters / data :type options: dict :return: tuple of (bool, message, transactionid, attributes) :rtype: tuple The return tuple builds up like this: ``bool`` if submit was successful; ``message`` which is displayed in the JSON response; additional ``attributes``, which are displayed in the JSON response. """ options = options or {} # Get a random question questions = [] tinfo = self.get_tokeninfo() for question, answer in tinfo.iteritems(): if question.endswith(".type") and answer == "password": # This is "Question1?.type" of type "password" # So this is actually a question and we add the question to # the list questions.append(question.strip(".type")) message = random.choice(questions) attributes = None validity = int(get_from_config('DefaultChallengeValidityTime', 120)) tokentype = self.get_tokentype().lower() # Maybe there is a QUESTIONChallengeValidityTime... lookup_for = tokentype.capitalize() + 'ChallengeValidityTime' validity = int(get_from_config(lookup_for, validity)) # Create the challenge in the database db_challenge = Challenge(self.token.serial, transaction_id=transactionid, challenge=message, validitytime=validity) db_challenge.save() self.challenge_janitor() return True, message, db_challenge.transaction_id, attributes
def request(config, user, password): """ Perform a RADIUS request to a RADIUS server. The RADIUS configuration contains the IP address, the port and the secret of the RADIUS server. * config.server * config.port * config.secret :param config: The RADIUS configuration :type config: RADIUSServer Database Model :param user: the radius username :param password: the radius password :return: True or False. If any error occurs, an exception is raised. """ success = False nas_identifier = get_from_config("radius.nas_identifier", "privacyIDEA") r_dict = config.dictionary or get_from_config("radius.dictfile", "/etc/privacyidea/" "dictionary") log.debug("NAS Identifier: %r, " "Dictionary: %r" % (nas_identifier, r_dict)) log.debug("constructing client object " "with server: %r, port: %r, secret: %r" % (config.server, config.port, config.secret)) srv = Client(server=config.server, authport=config.port, secret=decryptPassword(config.secret), dict=Dictionary(r_dict)) req = srv.CreateAuthPacket(code=pyrad.packet.AccessRequest, User_Name=user.encode('ascii'), NAS_Identifier=nas_identifier.encode('ascii')) req["User-Password"] = req.PwCrypt(password) response = srv.SendPacket(req) if response.code == pyrad.packet.AccessAccept: log.info("Radiusserver %s granted " "access to user %s." % (config.server, user)) success = True else: log.warning("Radiusserver %s" "rejected access to user %s." % (config.server, user)) return success
def get_init_detail(self, params=None, user=None): """ At the end of the initialization we ask the user to press the button """ response_detail = {} if self.init_step == 1: # This is the first step of the init request app_id = get_from_config("u2f.appId", "").strip("/") from privacyidea.lib.error import TokenAdminError if not app_id: raise TokenAdminError(_("You need to define the appId in the " "token config!")) nonce = urlsafe_b64encode_and_unicode(geturandom(32)) response_detail = TokenClass.get_init_detail(self, params, user) register_request = {"version": U2F_Version, "challenge": nonce, "appId": app_id} response_detail["u2fRegisterRequest"] = register_request self.add_tokeninfo("appId", app_id) elif self.init_step == 2: # This is the second step of the init request response_detail["u2fRegisterResponse"] = {"subject": self.token.description} return response_detail
def api_endpoint(request, g): """ This provides a function to be plugged into the API endpoint /ttype/u2f The u2f token can return the facet list at this URL. :param request: The Flask request :param g: The Flask global object g :return: Flask Response or text """ app_id = get_from_config("u2f.appId").strip("/") # Read the facets from the policies pol_facets = g.policy_object.get_action_values(U2FACTION.FACETS, scope=SCOPE.AUTH, client=request.remote_addr) facet_list = ["https://%s" % x for x in pol_facets] facet_list.append(app_id) log.debug("Sending facets lists for appId %s: %s" % (app_id, facet_list)) res = {"trustedFacets": [{"version": {"major": 1, "minor": 0}, "ids": facet_list } ] } return "json", res
def check_otp(self, anOtpVal, counter=None, window=None, options=None): """ check the otpval of a token against a given counter and the window :param passw: the to be verified passw/pin :type passw: string :return: counter if found, -1 if not found :rtype: int """ options = options or {} ret = HotpTokenClass.check_otp(self, anOtpVal, counter, window, options) if ret < 0 and is_true(get_from_config("email.concurrent_challenges")): if options.get("data") == anOtpVal: # We authenticate from the saved challenge ret = 1 if ret >= 0 and self._get_auto_email(options): message, mimetype = self._get_email_text_or_subject(options) subject, _ = self._get_email_text_or_subject(options, action=EMAILACTION.EMAILSUBJECT, default="Your OTP") self.inc_otp_counter(ret, reset=False) success, message = self._compose_email(message=message, subject=subject, mimetype=mimetype) log.debug("AutoEmail: send new SMS: {0!s}".format(success)) log.debug("AutoEmail: {0!r}".format(message)) return ret
def __init__(self, aToken): HotpTokenClass.__init__(self, aToken) self.set_type(u"email") self.mode = ['challenge'] # we support various hashlib methods, but only on create # which is effectively set in the update self.hashlibStr = get_from_config("hotp.hashlib", "sha1")
def is_remote_user_allowed(req): """ Checks if the REMOTE_USER server variable is allowed to be used. .. note:: This is not used as a decorator! :param req: The flask request, containing the remote user and the client IP :return: """ res = False if req.remote_user: loginname, realm = split_user(req.remote_user) realm = realm or get_default_realm() # Check if the remote user is allowed if "client_ip" not in g: g.client_ip = get_client_ip(req, get_from_config(SYSCONF.OVERRIDECLIENT)) if "policy_object" not in g: g.policy_object = PolicyClass() ruser_active = g.policy_object.get_action_values(ACTION.REMOTE_USER, scope=SCOPE.WEBUI, user=loginname, realm=realm, client=g.client_ip) res = ruser_active return res
def check_otp_exist(self, otp, window=None, options=None, symetric=True, inc_counter=True): """ checks if the given OTP value is/are values of this very token at all. This is used to autoassign and to determine the serial number of a token. In fact it is a check_otp with an enhanced window. :param otp: the to be verified otp value :type otp: string :param window: the lookahead window for the counter in seconds!!! :type window: int :return: counter or -1 if otp does not exist :rtype: int """ options = options or {} timeStepping = int(self.get_tokeninfo("timeStep") or get_from_config("totp.timeStep") or 30) window = window or (self.get_sync_window() * timeStepping) res = self.check_otp(otp, window=window, options=options) if inc_counter and res >= 0: # As usually the counter is increased in lib.token.checkUserPass, # we need to do this manually here: self.inc_otp_counter(res) return res
def get_otp(self, current_time=None): """ return the next otp value :param curTime: Not Used in HOTP :return: next otp value and PIN if possible :rtype: tuple """ otplen = int(self.token.otplen) secretHOtp = self.token.get_otpkey() hmac2Otp = HmacOtp(secretHOtp, self.token.count, otplen, self.get_hashlib(self.hashlib)) otpval = hmac2Otp.generate(inc_counter=False) pin = self.token.get_pin() if get_from_config("PrependPin") == "True": combined = u"{0!s}{1!s}".format(pin, otpval) else: combined = u"{0!s}{1!s}".format(otpval, pin) return 1, pin, otpval, combined
def before_request(): """ This is executed before the request. user_required checks if there is a logged in admin or user The checks for ONLY admin are preformed in api/system.py """ # remove session from param and gather all parameters, either # from the Form data or from JSON in the request body. request.all_data = get_all_params(request.values, request.data) g.policy_object = PolicyClass() g.audit_object = getAudit(current_app.config) g.event_config = EventConfiguration() # access_route contains the ip adresses of all clients, hops and proxies. g.client_ip = get_client_ip(request, get_from_config(SYSCONF.OVERRIDECLIENT)) privacyidea_server = current_app.config.get("PI_AUDIT_SERVERNAME") or \ request.host # Already get some typical parameters to log serial = getParam(request.all_data, "serial") realm = getParam(request.all_data, "realm") user_loginname = "" if "token_blueprint" in request.endpoint: # In case of token endpoint we evaluate the user in the request. # Note: In policy-endpoint "user" is part of the policy configuration # and will cause an exception user = get_user_from_param(request.all_data) user_loginname = user.login realm = user.realm or realm g.audit_object.log({"success": False, "serial": serial, "user": user_loginname, "realm": realm, "client": g.client_ip, "client_user_agent": request.user_agent.browser, "privacyidea_server": privacyidea_server, "action": "{0!s} {1!s}".format(request.method, request.url_rule), "action_detail": "", "info": ""}) if g.logged_in_user.get("role") == "user": # A user is calling this API # In case the token API is called by the user and not by the admin we # need to restrict the token view. CurrentUser = get_user_from_param({"user": g.logged_in_user.get( "username"), "realm": g.logged_in_user.get( "realm")}) request.all_data["user"] = CurrentUser.login request.all_data["resolver"] = CurrentUser.resolver request.all_data["realm"] = CurrentUser.realm g.audit_object.log({"user": CurrentUser.login, "realm": CurrentUser.realm}) else: # An administrator is calling this API g.audit_object.log({"administrator": g.logged_in_user.get("username")})
def before_request(): """ This is executed before the request """ update_config_object() request.all_data = get_all_params(request.values, request.data) request.User = get_user_from_param(request.all_data) privacyidea_server = current_app.config.get("PI_AUDIT_SERVERNAME") or \ request.host # Create a policy_object, that reads the database audit settings # and contains the complete policy definition during the request. # This audit_object can be used in the postpolicy and prepolicy and it # can be passed to the innerpolicies. g.policy_object = PolicyClass() g.audit_object = getAudit(current_app.config) g.event_config = EventConfiguration() # access_route contains the ip addresses of all clients, hops and proxies. g.client_ip = get_client_ip(request, get_from_config(SYSCONF.OVERRIDECLIENT)) g.audit_object.log({"success": False, "action_detail": "", "client": g.client_ip, "client_user_agent": request.user_agent.browser, "privacyidea_server": privacyidea_server, "action": "{0!s} {1!s}".format(request.method, request.url_rule), "info": ""})
def api_endpoint(cls, request, g): """ This provides a function to be plugged into the API endpoint /ttype/u2f The u2f token can return the facet list at this URL. :param request: The Flask request :param g: The Flask global object g :return: Flask Response or text """ configured_app_id = get_from_config("u2f.appId") if configured_app_id is None: raise ParameterError("u2f is not configured") app_id = configured_app_id.strip("/") # Read the facets from the policies pol_facets = g.policy_object.get_action_values(U2FACTION.FACETS, scope=SCOPE.AUTH, client=g.client_ip, audit_data=g.audit_object.audit_data) facet_list = ["https://{0!s}".format(x) for x in pol_facets] facet_list.append(app_id) log.debug("Sending facets lists for appId {0!s}: {1!s}".format(app_id, facet_list)) res = {"trustedFacets": [{"version": {"major": 1, "minor": 0}, "ids": facet_list } ] } return "fido.trusted-apps+json", res
def get_otp(self, current_time=None, do_truncation=True, time_seconds=None, challenge=None): """ get the next OTP value :param current_time: the current time, for which the OTP value should be calculated for. :type current_time: datetime object :param time_seconds: the current time, for which the OTP value should be calculated for (date +%s) :type: time_seconds: int, unix system time seconds :return: next otp value, and PIN, if possible :rtype: tuple """ otplen = int(self.token.otplen) secretHOtp = self.token.get_otpkey() hmac2Otp = HmacOtp(secretHOtp, self.get_otp_count(), otplen, self.get_hashlib(self.hashlib)) if time_seconds is None: time_seconds = self._time2float(datetime.datetime.now()) if current_time: time_seconds = self._time2float(current_time) # we don't need to round here as we have already float counter = int(((time_seconds - self.timeshift) / self.timestep)) otpval = hmac2Otp.generate(counter=counter, inc_counter=False, do_truncation=do_truncation, challenge=challenge) pin = self.token.get_pin() combined = "%s%s" % (otpval, pin) if get_from_config("PrependPin") == "True": combined = "%s%s" % (pin, otpval) return 1, pin, otpval, combined
def _autosync(self, hmac2Otp, anOtpVal): """ synchronize the token based on two otp values automatically. If the OTP is invalid, that OTP counter is stored. If an old OTP counter is stored, it is checked, if the new OTP value is the next value after this counter. 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 """ res = -1 autosync = get_from_config("AutoResync", False, return_bool=True) # if _autosync is not enabled: do nothing if autosync is False: return res info = self.get_tokeninfo() syncWindow = self.get_sync_window() # check if the otpval is valid in the sync scope res = hmac2Otp.checkOtp(anOtpVal, syncWindow, symetric=True) log.debug("found otpval {0!r} in syncwindow ({1!r}): {2!r}".format(anOtpVal, syncWindow, res)) if res != -1: # if former is defined if "otp1c" in info: # check if this is consecutive otp1c = int(info.get("otp1c")) otp2c = res log.debug("otp1c: {0!r}, otp2c: {1!r}".format(otp1c, otp2c)) diff = math.fabs(otp2c - otp1c) if diff > self.resyncDiffLimit: res = -1 else: server_time = time.time() counter = int((server_time / self.timestep) + 0.5) shift = otp2c - counter info["timeShift"] = shift self.set_tokeninfo(info) # now clean the resync data del info["otp1c"] self.set_tokeninfo(info) else: log.debug("setting otp1c: {0!s}".format(res)) info["otp1c"] = res self.set_tokeninfo(info) res = -1 return res
def test_06_public_and_admin(self): # This tests the new public available config set_privacyidea_config("publicInfo1", "info1", typ="public") set_privacyidea_config("publicInfo2", "info2", typ="public") set_privacyidea_config("secretInfo1", "info1") # Get administrators info a = get_from_config() self.assertTrue("secretInfo1" in a) self.assertTrue("publicInfo1" in a) a = get_from_config("publicInfo1") self.assertEqual(a, "info1") a = get_from_config("secretInfo1") self.assertEqual(a, "info1") # Get public info as user a = get_from_config() self.assertTrue("publicInfo1" in a) a = get_from_config("publicInfo1") self.assertEqual(a, "info1") # Not able to get private info as user a = get_from_config(role="public") self.assertTrue("secretInfo1" not in a) a = get_from_config("secretInfo1", role="public") self.assertEqual(a, None)
def _get_api_key(api_id): """ Return the symmetric key for the given apiId. :param apiId: The base64 encoded API ID :return: the base64 encoded API Key or None """ api_key = get_from_config("yubikey.apiid.%s" % api_id) return api_key
def _get_sms_provider_config(): """ load the defined sms provider config definition :return: dict of the sms provider definition :rtype: dict """ tConfig = get_from_config("sms.providerConfig", "{}") config = loads(tConfig) return config
def _send_sms(self, message="<otp>"): """ send sms :param message: the sms submit message - could contain placeholders like <otp> or <serial> :type message: string :return: submitted message :rtype: string """ ret = None phone = self.get_tokeninfo("phone") otp = self.get_otp()[2] serial = self.get_serial() message = message.replace("<otp>", otp) message = message.replace("<serial>", serial) log.debug("sending SMS to phone number {0!s} ".format(phone)) # First we try to get the new SMS gateway config style sms_gateway_identifier = get_from_config("sms.identifier") if sms_gateway_identifier: # New style sms = create_sms_instance(sms_gateway_identifier) else: # Old style (SMSProvider, SMSProviderClass) = self._get_sms_provider() log.debug("smsprovider: {0!s}, class: {1!s}".format(SMSProvider, SMSProviderClass)) try: sms = get_sms_provider_class(SMSProvider, SMSProviderClass)() except Exception as exc: log.error("Failed to load SMSProvider: {0!r}".format(exc)) log.debug("{0!s}".format(traceback.format_exc())) raise exc try: # now we need the config from the env log.debug("loading SMS configuration for class {0!s}".format(sms)) config = self._get_sms_provider_config() log.debug("config: {0!r}".format(config)) sms.load_config(config) except Exception as exc: log.error("Failed to load sms.providerConfig: {0!r}".format(exc)) log.debug("{0!s}".format(traceback.format_exc())) raise Exception("Failed to load sms.providerConfig: {0!r}".format(exc)) log.debug("submitMessage: {0!r}, to phone {1!r}".format(message, phone)) ret = sms.submit_message(phone, message) return ret, message
def create_challenge(self, transactionid=None, options=None): """ create a challenge, which is submitted to the user :param transactionid: the id of this challenge :param options: the request context parameters / data :return: tuple of (bool, message and data) bool, if submit was successful message is submitted to the user data is preserved in the challenge attributes - additional attributes, which are displayed in the output """ success = False options = options or {} return_message = "Enter the OTP from the Email:" attributes = {'state': transactionid} if self.is_active() is True: counter = self.get_otp_count() log.debug("counter={0!r}".format(counter)) self.inc_otp_counter(counter, reset=False) # At this point we must not bail out in case of an # Gateway error, since checkPIN is successful. A bail # out would cancel the checking of the other tokens try: message_template = self._get_email_text_or_subject(options) subject_template = self._get_email_text_or_subject(options, EMAILACTION.EMAILSUBJECT, "Your OTP") validity = int(get_from_config("email.validtime", 120)) # Create the challenge in the database db_challenge = Challenge(self.token.serial, transaction_id=transactionid, challenge=options.get("challenge"), session=options.get("session"), validitytime=validity) db_challenge.save() transactionid = transactionid or db_challenge.transaction_id # We send the email after creating the challenge for testing. success, sent_message = self._compose_email( message=message_template, subject=subject_template) except Exception as e: info = ("The PIN was correct, but the " "EMail could not be sent: %r" % e) log.warning(info) log.debug("{0!s}".format(traceback.format_exc(e))) return_message = info return success, return_message, transactionid, attributes
def get_cache_time(): """ :return: UserCacheExpiration config value as a timedelta :rtype: timedelta """ seconds = 0 try: seconds = int(get_from_config(EXPIRATION_SECONDS, '0')) except ValueError: log.info(u"Non-Integer value stored in system config {0!s}".format(EXPIRATION_SECONDS)) return datetime.timedelta(seconds=seconds)
def get_subscription(): """ Returns the license from the Config database """ DEFAULT_SUB = """{'systemid': 'unknown', 'customername': 'Not registered', 'subscription': -1, 'supportlevel': 'No Support', 'expires': 'never', 'signature': None}""" subscription = yaml.load(get_from_config("subscription", DEFAULT_SUB)) return subscription
def _get_sms_provider(): """ get the SMS Provider class definition :return: tuple of SMSProvider and Provider Class as string :rtype: tuple of (string, string) """ smsProvider = get_from_config("sms.provider", default="privacyidea.lib.smsprovider." "HttpSMSProvider.HttpSMSProvider") (SMSProvider, SMSProviderClass) = smsProvider.rsplit(".", 1) return SMSProvider, SMSProviderClass
def test_00_get_config(self): # set the config set_privacyidea_config(key="Hallo", value="What?", typ="string", desc="Some dumb value") # get the complete config conf = get_from_config() self.assertTrue("Hallo" in conf, conf) conf = get_from_config("Hallo") self.assertTrue(conf == "What?", conf) conf = get_from_config("Hello", "Does not exist") self.assertTrue(conf == "Does not exist", conf) conf = get_privacyidea_config() self.assertTrue("Hallo" in conf, conf) # delete privacyidea config delete_privacyidea_config("Hallo") conf = get_from_config("Hallo") self.assertFalse(conf == "What?", conf) # set more values to create a timestamp and overwrite set_privacyidea_config(key="k1", value="v1") set_privacyidea_config(key="k2", value="v2") set_privacyidea_config(key="k3", value="v3") conf = get_from_config("k3") self.assertTrue(conf == "v3", conf) set_privacyidea_config(key="k3", value="new", typ="string", desc="n") conf = get_from_config("k3") self.assertTrue(conf == "new", conf)
def _get_sms_timeout(): """ get the challenge time is in the specified range :return: the defined validation timeout in seconds :rtype: int """ try: timeout = int(get_from_config("sms.providerTimeout", 5 * 60)) except Exception as ex: # pragma: no cover log.warning("SMSProviderTimeout: value error {0!r} - reset to 5*60".format((ex))) timeout = 5 * 60 return timeout
def update(self, param, reset_failcount=True): """ process the initialization parameters Do we really always need an otpkey? the otpKey is handled in the parent class :param param: dict of initialization parameters :type param: dict :return: nothing """ # In case am Immutable MultiDict: upd_param = {} for k, v in param.items(): upd_param[k] = v val = getParam(upd_param, "hashlib", optional) if val is not None: hashlibStr = val else: hashlibStr = 'sha1' # check if the key_size id provided # if not, we could derive it from the hashlib key_size = getParam(upd_param, 'key_size', optional) if key_size is None: upd_param['key_size'] = keylen.get(hashlibStr) otpKey = '' if self.hKeyRequired is True: genkey = int(getParam(upd_param, "genkey", optional) or 0) if 1 == genkey: # if hashlibStr not in keylen dict, this will # raise an Exception otpKey = generate_otpkey(upd_param['key_size']) del upd_param['genkey'] else: # genkey not set: check otpkey is given # this will raise an exception if otpkey is not present otpKey = getParam(upd_param, "otpkey", required) # finally set the values for the update upd_param['otpkey'] = otpKey upd_param['hashlib'] = hashlibStr self.add_tokeninfo("hashlib", hashlibStr) val = getParam(upd_param, "otplen", optional) if val is not None: self.set_otplen(int(val)) else: self.set_otplen(get_from_config("DefaultOtpLen", 6)) TokenClass.update(self, upd_param, reset_failcount)
def _autosync(self, hmac2Otp, anOtpVal): """ synchronize the token based on two otp values automatically. If the OTP is invalid, that OTP counter is stored. If an old OTP counter is stored, it is checked, if the new OTP value is the next value after this counter. 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 """ res = -1 autosync = False async = get_from_config("AutoResync") if async is None: autosync = False
def update(self, param): """ This method is called during the initialization process. :param param: parameters from the token init :type param: dict :return: None """ # We should only initialize such a token, when the user is # immediately given in the init process, since the token on the # smartphone needs to contain a userId. if not self.user: # The user and realms should have already been set in init_token() raise ParameterError("Missing parameter: {0!r}".format("user"), id=905) ocrasuite = get_from_config("tiqr.ocrasuite") or OCRA_DEFAULT_SUITE OCRASuite(ocrasuite) self.add_tokeninfo("ocrasuite", ocrasuite) TokenClass.update(self, param)
def before_request(): """ This is executed before the request """ update_config_object() request.all_data = get_all_params(request.values, request.data) privacyidea_server = current_app.config.get("PI_AUDIT_SERVERNAME") or \ request.host g.policy_object = PolicyClass() g.audit_object = getAudit(current_app.config) # access_route contains the ip adresses of all clients, hops and proxies. g.client_ip = get_client_ip(request, get_from_config(SYSCONF.OVERRIDECLIENT)) g.audit_object.log({"success": False, "client": g.client_ip, "client_user_agent": request.user_agent.browser, "privacyidea_server": privacyidea_server, "action": "{0!s} {1!s}".format(request.method, request.url_rule), "action_detail": "", "info": ""})
def get_otp(self, current_time=None, do_truncation=True, time_seconds=None, challenge=None): """ get the next OTP value :param current_time: the current time, for which the OTP value should be calculated for. :type current_time: datetime object :param time_seconds: the current time, for which the OTP value should be calculated for (date +%s) :type: time_seconds: int, unix system time seconds :return: next otp value, and PIN, if possible :rtype: tuple """ otplen = int(self.token.otplen) secretHOtp = self.token.get_otpkey() hmac2Otp = HmacOtp(secretHOtp, self.get_otp_count(), otplen, self.get_hashlib(self.hashlib)) if time_seconds is None: time_seconds = self._time2float(datetime.datetime.now()) if current_time: time_seconds = self._time2float(current_time) # we don't need to round here as we have already float counter = int(((time_seconds - self.timeshift) / self.timestep)) otpval = hmac2Otp.generate(counter=counter, inc_counter=False, do_truncation=do_truncation, challenge=challenge) pin = self.token.get_pin() combined = "{0!s}{1!s}".format(otpval, pin) if get_from_config("PrependPin") == "True": combined = "{0!s}{1!s}".format(pin, otpval) return 1, pin, otpval, combined
def _compose_email(self, message="<otp>", subject="Your OTP"): """ send email :param message: the email submit message - could contain placeholders like <otp> or <serial> :type message: string :return: submitted message :rtype: string """ ret = None recipient = self._email_address otp = self.get_otp()[2] serial = self.get_serial() message = message.replace("<otp>", otp) message = message.replace("<serial>", serial) subject = subject.replace("<otp>", otp) subject = subject.replace("<serial>", serial) log.debug("sending Email to %s " % recipient) identifier = get_from_config("email.identifier") if identifier: # New way to send email ret = send_email_identifier(identifier, recipient, subject, message) else: # old way to send email / DEPRECATED mailserver = get_from_config("email.mailserver", "localhost") port = int(get_from_config("email.port", 25)) username = get_from_config("email.username") password = get_from_config("email.password") mail_from = get_from_config("email.mailfrom", "privacyidea@localhost") email_tls = get_from_config("email.tls") ret = send_email_data(mailserver, subject, message, mail_from, recipient, username, password, port, email_tls) return ret, message
def update(self, param): """ This method is called during the initialization process. :param param: parameters from the token init :type param: dict :return: None """ # We should only initialize such a token, when the user is # immediately given in the init process, since the token on the # smartphone needs to contain a userId. user_object = get_user_from_param(param, required) self.set_user(user_object) ocrasuite = get_from_config("tiqr.ocrasuite") or OCRA_DEFAULT_SUITE OCRASuite(ocrasuite) self.add_tokeninfo("ocrasuite", ocrasuite) TokenClass.update(self, param) # We have to set the realms here, since the token DB object does not # have an ID before TokenClass.update. self.set_realms([user_object.realm])
def before_request(): """ This is executed before the request """ ensure_no_config_object() request.all_data = get_all_params(request) request.User = get_user_from_param(request.all_data) privacyidea_server = current_app.config.get("PI_AUDIT_SERVERNAME") or \ request.host # Create a policy_object, that reads the database audit settings # and contains the complete policy definition during the request. # This audit_object can be used in the postpolicy and prepolicy and it # can be passed to the innerpolicies. g.policy_object = PolicyClass() g.audit_object = getAudit(current_app.config, g.startdate) g.event_config = EventConfiguration() # access_route contains the ip addresses of all clients, hops and proxies. g.client_ip = get_client_ip(request, get_from_config(SYSCONF.OVERRIDECLIENT)) # Save the HTTP header in the localproxy object g.request_headers = request.headers g.serial = getParam(request.all_data, "serial", default=None) g.audit_object.log({ "success": False, "action_detail": "", "client": g.client_ip, "client_user_agent": request.user_agent.browser, "privacyidea_server": privacyidea_server, "action": "{0!s} {1!s}".format(request.method, request.url_rule), "info": "" })
def get_otp(self, current_time=None): """ return the next otp value :param curTime: Not Used in HOTP :return: next otp value and PIN if possible :rtype: tuple """ otplen = int(self.token.otplen) secretHOtp = self.token.get_otpkey() hmac2Otp = HmacOtp(secretHOtp, self.token.count, otplen, self.get_hashlib(self.hashlib)) otpval = hmac2Otp.generate(inc_counter=False) pin = self.token.get_pin() combined = "{0!s}{1!s}".format(otpval, pin) if get_from_config("PrependPin") == "True": combined = "{0!s}{1!s}".format(pin, otpval) return 1, pin, otpval, combined
def get_init_detail(self, params=None, user=None): """ At the end of the initialization we return the URL for the TiQR App. """ response_detail = TokenClass.get_init_detail(self, params, user) params = params or {} enroll_url = get_from_config("tiqr.regServer") log.info("using tiqr.regServer for enrollment: %s" % enroll_url) serial = self.token.serial session = generate_otpkey() # save the session in the token self.add_tokeninfo("session", session) tiqrenroll = "tiqrenroll://%s?action=%s&session=%s&serial=%s" % ( enroll_url, API_ACTIONS.METADATA, session, serial) response_detail["tiqrenroll"] = { "description": _("URL for TiQR " "enrollment"), "value": tiqrenroll, "img": create_img(tiqrenroll, width=250) } return response_detail
def api_endpoint(cls, request, g): """ This provides a function to be plugged into the API endpoint /ttype/u2f The u2f token can return the facet list at this URL. :param request: The Flask request :param g: The Flask global object g :return: Flask Response or text """ configured_app_id = get_from_config("u2f.appId") if configured_app_id is None: raise ParameterError("u2f is not configured") app_id = configured_app_id.strip("/") # Read the facets from the policies pol_facets = g.policy_object.get_action_values( U2FACTION.FACETS, scope=SCOPE.AUTH, client=g.client_ip, audit_data=g.audit_object.audit_data) facet_list = ["https://{0!s}".format(x) for x in pol_facets] facet_list.append(app_id) log.debug("Sending facets lists for appId {0!s}: {1!s}".format( app_id, facet_list)) res = { "trustedFacets": [{ "version": { "major": 1, "minor": 0 }, "ids": facet_list }] } return "fido.trusted-apps+json", res
def before_request(): """ This is executed before the request """ ensure_no_config_object() request.all_data = get_all_params(request.values, request.data) privacyidea_server = current_app.config.get("PI_AUDIT_SERVERNAME") or \ request.host g.policy_object = PolicyClass() g.audit_object = getAudit(current_app.config) g.event_config = EventConfiguration() # access_route contains the ip adresses of all clients, hops and proxies. g.client_ip = get_client_ip(request, get_from_config(SYSCONF.OVERRIDECLIENT)) g.serial = getParam(request.all_data, "serial", default=None) g.audit_object.log({"success": False, "client": g.client_ip, "client_user_agent": request.user_agent.browser, "privacyidea_server": privacyidea_server, "action": "{0!s} {1!s}".format(request.method, request.url_rule), "action_detail": "", "info": ""}) username = getParam(request.all_data, "username") if username: # We only fill request.User, if we really have a username. # On endpoints like /auth/rights, this is not available loginname, realm = split_user(username) # overwrite the split realm if we have a realm parameter. Default back to default_realm realm = getParam(request.all_data, "realm", default=realm) or realm or get_default_realm() # Prefill the request.User. This is used by some pre-event handlers try: request.User = User(loginname, realm) except Exception as e: request.User = None log.warning(u"Problem resolving user {0!s} in realm {1!s}: {2!s}.".format(loginname, realm, e)) log.debug(u"{0!s}".format(traceback.format_exc()))
def before_request(): """ This is executed before the request """ g.config_object = ConfigClass() request.all_data = get_all_params(request.values, request.data) privacyidea_server = current_app.config.get("PI_AUDIT_SERVERNAME") or \ request.host # Create a policy_object, that reads the database audit settings # and contains the complete policy definition during the request. # This audit_object can be used in the postpolicy and prepolicy and it # can be passed to the innerpolicies. g.policy_object = PolicyClass() g.audit_object = getAudit(current_app.config) # access_route contains the ip adresses of all clients, hops and proxies. g.client_ip = get_client_ip(request, get_from_config(SYSCONF.OVERRIDECLIENT)) g.audit_object.log({"success": False, "action_detail": "", "client": g.client_ip, "client_user_agent": request.user_agent.browser, "privacyidea_server": privacyidea_server, "action": "{0!s} {1!s}".format(request.method, request.url_rule), "info": ""})
def update(self, param): """ This method is called during the initialization process. :param param: parameters from the token init :type param: dict :return: None """ j_questions = getParam(param, "questions", required) try: # If we have a string, we load the json format questions = json.loads(j_questions) except TypeError: # Obviously we have a dict... questions = j_questions num_answers = get_from_config("question.num_answers", DEFAULT_NUM_ANSWERS) if len(questions) < int(num_answers): raise TokenAdminError(_("You need to provide at least %s " "answers.") % num_answers) # Save all questions and answers and encrypt them for question, answer in questions.items(): self.add_tokeninfo(question, answer, value_type="password") TokenClass.update(self, param)
def _send_sms(self, message="<otp>"): """ send sms :param message: the sms submit message - could contain placeholders like <otp> or <serial> :type message: string :return: submitted message :rtype: string """ if is_true(self.get_tokeninfo("dynamic_phone")): phone = self.user.get_user_phone("mobile") if type(phone) == list and phone: # if there is a non-empty list, we use the first entry phone = phone[0] else: phone = self.get_tokeninfo("phone") if not phone: # pragma: no cover log.warning("Token {0!s} does not have a phone number!".format( self.token.serial)) otp = self.get_otp()[2] serial = self.get_serial() message = message.replace("<otp>", otp) message = message.replace("<serial>", serial) log.debug("sending SMS to phone number {0!s} ".format(phone)) # First we try to get the new SMS gateway config style # The token specific identifier has priority over the system wide identifier sms_gateway_identifier = self.get_tokeninfo( "sms.identifier") or get_from_config("sms.identifier") if sms_gateway_identifier: # New style sms = create_sms_instance(sms_gateway_identifier) else: # Old style (SMSProvider, SMSProviderClass) = self._get_sms_provider() log.debug("smsprovider: {0!s}, class: {1!s}".format( SMSProvider, SMSProviderClass)) try: sms = get_sms_provider_class(SMSProvider, SMSProviderClass)() except Exception as exc: log.error("Failed to load SMSProvider: {0!r}".format(exc)) log.debug("{0!s}".format(traceback.format_exc())) raise exc try: # now we need the config from the env log.debug( "loading SMS configuration for class {0!s}".format(sms)) config = self._get_sms_provider_config() log.debug("config: {0!r}".format(config)) sms.load_config(config) except Exception as exc: log.error( "Failed to load sms.providerConfig: {0!r}".format(exc)) log.debug("{0!s}".format(traceback.format_exc())) raise Exception( "Failed to load sms.providerConfig: {0!r}".format(exc)) log.debug("submitMessage: {0!r}, to phone {1!r}".format( message, phone)) ret = sms.submit_message(phone, message) return ret, message
def check_otp(self, otpval, counter=None, window=None, options=None): """ run the http request against the remote host :param otpval: the OTP value :param counter: The counter for counter based otp values :type counter: int :param window: a counter window :type counter: int :param options: additional token specific options :type options: dict :return: counter of the matching OTP value. :rtype: int """ otp_count = -1 otpval = otpval.encode("utf-8") remoteServer = self.get_tokeninfo("remote.server") or "" remoteServer = remoteServer.encode("utf-8") # in preparation of the ability to relocate privacyidea urls, # we introduce the remote url path remotePath = self.get_tokeninfo("remote.path") or "" remotePath = remotePath.strip().encode('utf-8') remoteSerial = self.get_tokeninfo("remote.serial") or "" remoteSerial = remoteSerial.encode('utf-8') remoteUser = self.get_tokeninfo("remote.user") or "" remoteUser = remoteUser.encode('utf-8') remoteRealm = self.get_tokeninfo("remote.realm") or "" remoteRealm = remoteRealm.encode('utf-8') remoteResolver = self.get_tokeninfo("remote.resolver") or "" remoteResolver = remoteResolver.encode('utf-8') ssl_verify = get_from_config( "remote.verify_ssl_certificate", False, return_bool=True) or False if type(ssl_verify) in [str, unicode]: if ssl_verify.lower() in ["true", "1"]: ssl_verify = True else: ssl_verify = False # here we also need to check for remote.user and so on.... log.debug("checking OTP len:%r remotely on server: %r," " serial: %r, user: %r" % (len(otpval), remoteServer, remoteSerial, remoteUser)) params = {} remotePath = remotePath or "/validate/check" if remoteSerial: params['serial'] = remoteSerial elif remoteUser: params['user'] = remoteUser params['realm'] = remoteRealm params['resolver'] = remoteResolver else: log.warning("The remote token does neither contain a " "remote.serial nor a remote.user.") return otp_count params['pass'] = otpval request_url = "{0!s}{1!s}".format(remoteServer, remotePath) try: r = requests.post(request_url, data=params, verify=ssl_verify) if r.status_code == requests.codes.ok: response = r.json() result = response.get("result") if result.get("value"): otp_count = 1 except Exception as exx: # pragma: no cover log.error("Error getting response from " "remote Server (%r): %r" % (request_url, exx)) log.debug("{0!s}".format(traceback.format_exc())) return otp_count
def _compose_email(self, message="<otp>", subject="Your OTP", mimetype="plain"): """ send email :param message: the email submit message - could contain placeholders like <otp> or <serial> :type message: string :param mimetype: the message MIME type - one of "plain", "html" :type mimetype: basestring :return: submitted message :rtype: string """ ret = None recipient = self._email_address otp = self.get_otp()[2] serial = self.get_serial() message = message.replace("<otp>", otp) message = message.replace("<serial>", serial) tags = create_tag_dict( serial=serial, tokenowner=self.user, tokentype=self.get_tokentype(), recipient={ "givenname": self.user.info.get("givenname") if self.user else "", "surname": self.user.info.get("surname") if self.user else "" }, escape_html=mimetype.lower() == "html") message = message.format(otp=otp, **tags) subject = subject.replace("<otp>", otp) subject = subject.replace("<serial>", serial) subject = subject.format(otp=otp, **tags) log.debug("sending Email to {0!r}".format(recipient)) # The token specific identifier has priority over the system wide identifier identifier = self.get_tokeninfo("email.identifier") or get_from_config( "email.identifier") if identifier: # New way to send email ret = send_email_identifier(identifier, recipient, subject, message, mimetype=mimetype) else: # old way to send email / DEPRECATED mailserver = get_from_config("email.mailserver", "localhost") port = int(get_from_config("email.port", 25)) username = get_from_config("email.username") password = get_from_config("email.password") mail_from = get_from_config("email.mailfrom", "privacyidea@localhost") email_tls = get_from_config("email.tls", default=False, return_bool=True) ret = send_email_data(mailserver, subject, message, mail_from, recipient, username, password, port, email_tls) return ret, message
def create_challenge(self, transactionid=None, options=None): """ create a challenge, which is submitted to the user :param transactionid: the id of this challenge :param options: the request context parameters / data You can pass ``exception=1`` to raise an exception, if the SMS could not be sent. Otherwise the message is contained in the response. :return: tuple of (success, message, transactionid, attributes) * success: if submit was successful * message: the text submitted to the user * transactionid: the given or generated transactionid * reply_dict: additional dictionary, which is added to the response :rtype: tuple(bool, str, str, dict) """ success = False options = options or {} return_message = get_action_values_from_options( SCOPE.AUTH, "{0!s}_{1!s}".format(self.get_class_type(), ACTION.CHALLENGETEXT), options) or _("Enter the OTP from the Email:") reply_dict = {'attributes': {'state': transactionid}} validity = int(get_from_config("email.validtime", 120)) if self.is_active() is True: counter = self.get_otp_count() log.debug("counter={0!r}".format(counter)) self.inc_otp_counter(counter, reset=False) # At this point we must not bail out in case of an # Gateway error, since checkPIN is successful. A bail # out would cancel the checking of the other tokens try: message_template, mimetype = self._get_email_text_or_subject( options) subject_template, _n = self._get_email_text_or_subject( options, EMAILACTION.EMAILSUBJECT, "Your OTP") # Create the challenge in the database if is_true(get_from_config("email.concurrent_challenges")): data = self.get_otp()[2] else: data = None db_challenge = Challenge(self.token.serial, transaction_id=transactionid, challenge=options.get("challenge"), data=data, session=options.get("session"), validitytime=validity) db_challenge.save() transactionid = transactionid or db_challenge.transaction_id # We send the email after creating the challenge for testing. success, sent_message = self._compose_email( message=message_template, subject=subject_template, mimetype=mimetype) except Exception as e: info = ("The PIN was correct, but the " "EMail could not be sent: %r" % e) log.warning(info) log.debug(u"{0!s}".format(traceback.format_exc())) return_message = info if is_true(options.get("exception")): raise Exception(info) expiry_date = datetime.datetime.now() + \ datetime.timedelta(seconds=validity) reply_dict['attributes']['valid_until'] = "{0!s}".format(expiry_date) return success, return_message, transactionid, reply_dict
def create_challenge(self, transactionid=None, options=None): """ This method creates a challenge, which is submitted to the user. The submitted challenge will be preserved in the challenge database. If no transaction id is given, the system will create a transaction id and return it, so that the response can refer to this transaction. :param transactionid: the id of this challenge :param options: the request context parameters / data :type options: dict :return: tuple of (bool, message, transactionid, attributes) :rtype: tuple The return tuple builds up like this: ``bool`` if submit was successful; ``message`` which is displayed in the JSON response; additional ``attributes``, which are displayed in the JSON response. """ options = options or {} message = get_action_values_from_options( SCOPE.AUTH, ACTION.CHALLENGETEXT, options) or DEFAULT_CHALLENGE_TEXT attributes = None data = None fb_identifier = self.get_tokeninfo(PUSH_ACTION.FIREBASE_CONFIG) if fb_identifier: challenge = b32encode_and_unicode(geturandom()) fb_gateway = create_sms_instance(fb_identifier) pem_privkey = self.get_tokeninfo(PRIVATE_KEY_SERVER) smartphone_data = _build_smartphone_data(self.token.serial, challenge, fb_gateway, pem_privkey, options) res = fb_gateway.submit_message( self.get_tokeninfo("firebase_token"), smartphone_data) if not res: raise ValidateError( "Failed to submit message to firebase service.") else: log.warning(u"The token {0!s} has no tokeninfo {1!s}. " u"The message could not be sent.".format( self.token.serial, PUSH_ACTION.FIREBASE_CONFIG)) raise ValidateError( "The token has no tokeninfo. Can not send via firebase service." ) validity = int(get_from_config('DefaultChallengeValidityTime', 120)) tokentype = self.get_tokentype().lower() # Maybe there is a PushChallengeValidityTime... lookup_for = tokentype.capitalize() + 'ChallengeValidityTime' validity = int(get_from_config(lookup_for, validity)) # Create the challenge in the database db_challenge = Challenge(self.token.serial, transaction_id=transactionid, challenge=challenge, data=data, session=options.get("session"), validitytime=validity) db_challenge.save() self.challenge_janitor() return True, message, db_challenge.transaction_id, attributes
def create_challenge(self, transactionid=None, options=None): """ This method creates a challenge, which is submitted to the user. The submitted challenge will be preserved in the challenge database. If no transaction id is given, the system will create a transaction id and return it, so that the response can refer to this transaction. :param transactionid: the id of this challenge :param options: the request context parameters / data :type options: dict :return: tuple of (bool, message, transactionid, attributes) :rtype: tuple The return tuple builds up like this: ``bool`` if submit was successful; ``message`` which is displayed in the JSON response; additional ``attributes``, which are displayed in the JSON response. """ options = options or {} message = get_action_values_from_options( SCOPE.AUTH, "{0!s}_{1!s}".format( self.get_class_type(), ACTION.CHALLENGETEXT), options) or _( u'Please confirm with your U2F token ({0!s})').format( self.token.description) validity = int(get_from_config('DefaultChallengeValidityTime', 120)) tokentype = self.get_tokentype().lower() lookup_for = tokentype.capitalize() + 'ChallengeValidityTime' validity = int(get_from_config(lookup_for, validity)) challenge = geturandom(32) # Create the challenge in the database db_challenge = Challenge(self.token.serial, transaction_id=transactionid, challenge=hexlify_and_unicode(challenge), data=None, session=options.get("session"), validitytime=validity) db_challenge.save() sec_object = self.token.get_otpkey() key_handle_hex = sec_object.getKey() key_handle_bin = binascii.unhexlify(key_handle_hex) key_handle_url = url_encode(key_handle_bin) challenge_url = url_encode(challenge) u2f_sign_request = { "appId": self.get_tokeninfo("appId"), "version": U2F_Version, "challenge": challenge_url, "keyHandle": key_handle_url } image_url = IMAGES.get(self.token.description.lower().split()[0], "") response_details = { "u2fSignRequest": u2f_sign_request, "hideResponseInput": True, "img": image_url } return True, message, db_challenge.transaction_id, response_details
def _autosync(self, hmac2Otp, anOtpVal): """ automatically sync the token based on two otp values internal method to implement 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 """ res = -1 # get _autosync from config or use False as default autosync = get_from_config("AutoResync", False, return_bool=True) # if _autosync is not enabled if autosync is False: log.debug("end. _autosync is not enabled : res {0!r}".format((res))) return res info = self.get_tokeninfo() syncWindow = self.get_sync_window() # check if the otpval is valid in the sync scope res = hmac2Otp.checkOtp(anOtpVal, syncWindow) # If the otpval is valid in the big sync scope, we # either store the value in the tokeninfo # or see if already another value exists. if res != -1: # if former is defined if "otp1c" in info: # check if this is consecutive otp1c = int(info.get("otp1c")) otp2c = res if (otp1c + 1) != otp2c: res = -1 if "dueDate" in info: dueDate = int(info.get("dueDate")) now = int(time.time()) if dueDate <= now: res = -1 else: # if by any reason the dueDate is missing! res = -1 # pragma: no cover # now clean the resync data self.del_tokeninfo("dueDate") self.del_tokeninfo("otp1c") else: self.add_tokeninfo("otp1c", res) self.add_tokeninfo("dueDate", int(time.time()) + self.get_sync_timeout()) res = -1 return res
def hashlib(self): hashlibStr = self.get_tokeninfo("hashlib") or \ get_from_config("hotp.hashlib", u'sha1') return hashlibStr
def __init__(self, aToken): HotpTokenClass.__init__(self, aToken) self.set_type(u"email") # we support various hashlib methods, but only on create # which is effectively set in the update self.hashlibStr = get_from_config("hotp.hashlib", "sha1")
def timewindow(self): window = int( self.get_tokeninfo("timeWindow") or get_from_config("totp.timeWindow") or 180) return window
def timestep(self): timeStepping = int( self.get_tokeninfo("timeStep") or get_from_config("totp.timeStep") or 30) return timeStepping
def get_auth_token(): """ This call verifies the credentials of the user and issues an authentication token, that is used for the later API calls. The authentication token has a validity, that is usually 1 hour. :jsonparam username: The username of the user who wants to authenticate to the API. :jsonparam password: The password/credentials of the user who wants to authenticate to the API. :jsonparam realm: The realm where the user will be searched. :return: A json response with an authentication token, that needs to be used in any further request. :status 200: in case of success :status 401: if authentication fails **Example Authentication Request**: .. sourcecode:: http POST /auth HTTP/1.1 Host: example.com Accept: application/json username=admin password=topsecret **Example Authentication Response**: .. sourcecode:: http HTTP/1.0 200 OK Content-Length: 354 Content-Type: application/json { "id": 1, "jsonrpc": "2.0", "result": { "status": true, "value": { "token": "eyJhbGciOiJIUz....jdpn9kIjuGRnGejmbFbM" } }, "version": "privacyIDEA unknown" } **Response for failed authentication**: .. sourcecode:: http HTTP/1.1 401 UNAUTHORIZED Content-Type: application/json Content-Length: 203 { "id": 1, "jsonrpc": "2.0", "result": { "error": { "code": -401, "message": "missing Authorization header" }, "status": false }, "version": "privacyIDEA unknown", "config": { "logout_time": 30 } } """ validity = timedelta(hours=1) username = getParam(request.all_data, "username") password = getParam(request.all_data, "password") realm_param = getParam(request.all_data, "realm") details = {} realm = '' # the realm parameter has precedence! Check if it exists if realm_param and not realm_is_defined(realm_param): raise AuthError(_( "Authentication failure. Unknown realm: {0!s}.".format( realm_param)), id=ERROR.AUTHENTICATE_WRONG_CREDENTIALS) if username is None: raise AuthError(_("Authentication failure. Missing Username"), id=ERROR.AUTHENTICATE_MISSING_USERNAME) loginname = username split_at_sign = get_from_config(SYSCONF.SPLITATSIGN, return_bool=True) if split_at_sign: (loginname, realm) = split_user(username) # overwrite the splitted realm if we have a realm parameter if realm_param: realm = realm_param # and finally check if there is a realm realm = realm or get_default_realm() # Failsafe to have the user attempt in the log, whatever happens # This can be overwritten later g.audit_object.log({"user": username, "realm": realm}) secret = current_app.secret_key superuser_realms = current_app.config.get("SUPERUSER_REALM", []) # This is the default role for the logged in user. # The role privileges may be risen to "admin" role = ROLE.USER # The way the user authenticated. This could be # "password" = The admin user DB or the user store # "pi" = The admin or the user is authenticated against privacyIDEA # "remote_user" = authenticated by webserver authtype = "password" # Verify the password admin_auth = False user_auth = False user_obj = User() # Check if the remote user is allowed if (request.remote_user == username) and is_remote_user_allowed(request): # Authenticated by the Web Server # Check if the username exists # 1. in local admins # 2. in a realm # 2a. is an admin realm authtype = "remote_user " if db_admin_exist(username): role = ROLE.ADMIN admin_auth = True g.audit_object.log({ "success": True, "user": "", "administrator": username, "info": "internal admin" }) else: # check, if the user exists user_obj = User(loginname, realm) g.audit_object.log({ "user": user_obj.login, "realm": user_obj.realm, "info": log_used_user(user_obj) }) if user_obj.exist(): user_auth = True if user_obj.realm in superuser_realms: role = ROLE.ADMIN admin_auth = True elif verify_db_admin(username, password): role = ROLE.ADMIN admin_auth = True # This admin is not in the default realm! realm = "" g.audit_object.log({ "success": True, "user": "", "administrator": username, "info": "internal admin" }) else: # The user could not be identified against the admin database, # so we do the rest of the check options = {"g": g, "clientip": g.client_ip} for key, value in request.all_data.items(): if value and key not in ["g", "clientip"]: options[key] = value user_obj = User(loginname, realm) user_auth, role, details = check_webui_user( user_obj, password, options=options, superuser_realms=superuser_realms) details = details or {} if role == ROLE.ADMIN: g.audit_object.log({ "user": "", "administrator": user_obj.login, "realm": user_obj.realm, "resolver": user_obj.resolver, "serial": details.get('serial', None), "info": u"{0!s}|loginmode={1!s}".format(log_used_user(user_obj), details.get("loginmode")) }) else: g.audit_object.log({ "user": user_obj.login, "realm": user_obj.realm, "resolver": user_obj.resolver, "serial": details.get('serial', None), "info": u"{0!s}|loginmode={1!s}".format(log_used_user(user_obj), details.get("loginmode")) }) if not admin_auth and not user_auth: raise AuthError(_("Authentication failure. Wrong credentials"), id=ERROR.AUTHENTICATE_WRONG_CREDENTIALS, details=details or {}) else: g.audit_object.log({"success": True}) request.User = user_obj # If the HSM is not ready, we need to create the nonce in another way! hsm = init_hsm() if hsm.is_ready: nonce = geturandom(hex=True) # Add the role to the JWT, so that we can verify it internally # Add the authtype to the JWT, so that we could use it for access # definitions rights = g.policy_object.ui_get_rights(role, realm, loginname, g.client_ip) menus = g.policy_object.ui_get_main_menus( { "username": loginname, "role": role, "realm": realm }, g.client_ip) else: import os nonce = hexlify_and_unicode(os.urandom(20)) rights = [] menus = [] # What is the log level? log_level = current_app.config.get("PI_LOGLEVEL", 30) token = jwt.encode( { "username": loginname, "realm": realm, "nonce": nonce, "role": role, "authtype": authtype, "exp": datetime.utcnow() + validity, "rights": rights }, secret, algorithm='HS256').decode('utf8') # Add the role to the response, so that the WebUI can make decisions # based on this (only show selfservice, not the admin part) return send_result( { "token": token, "role": role, "username": loginname, "realm": realm, "log_level": log_level, "rights": rights, "menus": menus }, details=details)
class ScriptEventHandler(BaseEventHandler): """ An Eventhandler needs to return a list of actions, which it can handle. It also returns a list of allowed action and conditions It returns an identifier, which can be used in the eventhandlig definitions """ identifier = "Script" description = "This event handler can trigger external scripts." try: script_directory = get_from_config("PI_SCRIPT_HANDLER_DIRECTORY", "/etc/privacyidea/scripts") except RuntimeError: # In case of the tests we are outside of the application context script_directory = "tests/testdata/scripts" @property def actions(cls): """ This method returns a dictionary of allowed actions and possible options in this handler module. :return: dict with actions """ scripts = os.listdir(cls.script_directory) actions = {} for script in scripts: actions[script] = { "serial": { "type": "bool", "description": _("Add '--serial <serial number>' as script " "parameter.") }, "user": { "type": "bool", "description": _("Add '--user <username>' as script " "parameter.") }, "realm": { "type": "bool", "description": _("Add '--realm <realm>' as script " "parameter.") }, "logged_in_user": { "type": "bool", "description": _("Add the username of the logged in user " "as script parameter like " "'--logged_in_user <username>'.") }, "logged_in_role": { "type": "bool", "description": _("Add the role (either admin or user) of " "the logged in user as script parameter " "like '--logged_in_role <role>'.") } } return actions def do(self, action, options=None): """ This method executes the defined action in the given event. :param action: :param options: Contains the flask parameters g, request, response and the handler_def configuration :type options: dict :return: """ ret = True script_name = self.script_directory + "/" + action proc_args = [script_name] g = options.get("g") request = options.get("request") response = options.get("response") content = json.loads(response.data) handler_def = options.get("handler_def") handler_options = handler_def.get("options", {}) if hasattr(g, "logged_in_user"): logged_in_user = g.logged_in_user else: logged_in_user = {"username": "******", "realm": "none", "role": "none"} serial = request.all_data.get("serial") or \ content.get("detail", {}).get("serial") or \ g.audit_object.audit_data.get("serial") if handler_options.get("serial"): proc_args.append("--serial") proc_args.append(serial or "none") if handler_options.get("user"): proc_args.append("--user") proc_args.append(request.User.login or "none") if handler_options.get("realm"): proc_args.append("--realm") proc_args.append(request.User.realm or "none") if handler_options.get("logged_in_user"): proc_args.append("--logged_in_user") proc_args.append("{username}@{realm}".format( **logged_in_user)) if handler_options.get("logged_in_role"): proc_args.append("--logged_in_role") proc_args.append(logged_in_user.get("role", "none")) try: p = subprocess.Popen(proc_args, cwd=self.script_directory) log.info("Started script {script!r}:" " {process!r}".format(script=script_name, process=p)) except Exception as e: log.warning("Failed to execute script {0!r}: {1!r}".format( script_name, e)) log.warning(traceback.format_exc()) return ret
def create_challenge(self, transactionid=None, options=None): """ This method creates a challenge, which is submitted to the user. The submitted challenge will be preserved in the challenge database. If no transaction id is given, the system will create a transaction id and return it, so that the response can refer to this transaction. :param transactionid: the id of this challenge :param options: the request context parameters / data :type options: dict :return: tuple of (bool, message, transactionid, attributes) :rtype: tuple The return tuple builds up like this: ``bool`` if submit was successful; ``message`` which is displayed in the JSON response; additional challenge ``reply_dict``, which are displayed in the JSON challenges response. """ options = options or {} message = get_action_values_from_options( SCOPE.AUTH, ACTION.CHALLENGETEXT, options) or DEFAULT_CHALLENGE_TEXT reply_dict = {} data = None # Initially we assume there is no error from Firebase res = True fb_identifier = self.get_tokeninfo(PUSH_ACTION.FIREBASE_CONFIG) if fb_identifier: challenge = b32encode_and_unicode(geturandom()) if fb_identifier != POLL_ONLY: # We only push to Firebase if this tokens does NOT POLL_ONLY. fb_gateway = create_sms_instance(fb_identifier) registration_url = get_action_values_from_options( SCOPE.ENROLL, PUSH_ACTION.REGISTRATION_URL, options=options) pem_privkey = self.get_tokeninfo(PRIVATE_KEY_SERVER) smartphone_data = _build_smartphone_data( self.token.serial, challenge, registration_url, pem_privkey, options) res = fb_gateway.submit_message( self.get_tokeninfo("firebase_token"), smartphone_data) # Create the challenge in the challenge table if either the message # was successfully submitted to the Firebase API or if polling is # allowed in general or for this specific token. allow_polling = get_action_values_from_options( SCOPE.AUTH, PUSH_ACTION.ALLOW_POLLING, options=options) or PushAllowPolling.ALLOW if ((allow_polling == PushAllowPolling.ALLOW or (allow_polling == PushAllowPolling.TOKEN and is_true( self.get_tokeninfo(POLLING_ALLOWED, default='True')))) or res): validity = int( get_from_config('DefaultChallengeValidityTime', 120)) tokentype = self.get_tokentype().lower() # Maybe there is a PushChallengeValidityTime... lookup_for = tokentype.capitalize() + 'ChallengeValidityTime' validity = int(get_from_config(lookup_for, validity)) # Create the challenge in the database db_challenge = Challenge(self.token.serial, transaction_id=transactionid, challenge=challenge, data=data, session=options.get("session"), validitytime=validity) db_challenge.save() self.challenge_janitor() # If sending the Push message failed, we still raise an error and a warning. if not res: log.warning( u"Failed to submit message to Firebase service for token {0!s}." .format(self.token.serial)) raise ValidateError( "Failed to submit message to Firebase service.") else: log.warning(u"The token {0!s} has no tokeninfo {1!s}. " u"The message could not be sent.".format( self.token.serial, PUSH_ACTION.FIREBASE_CONFIG)) raise ValidateError( "The token has no tokeninfo. Can not send via Firebase service." ) return True, message, db_challenge.transaction_id, reply_dict
def create_challenge(self, transactionid=None, options=None): """ This method creates a challenge, which is submitted to the user. The submitted challenge will be preserved in the challenge database. If no transaction id is given, the system will create a transaction id and return it, so that the response can refer to this transaction. :param transactionid: the id of this challenge :param options: the request context parameters / data :type options: dict :return: tuple of (bool, message, transactionid, reply_dict) :rtype: tuple The return tuple builds up like this: ``bool`` if submit was successful; ``message`` which is displayed in the JSON response; additional challenge ``reply_dict``, which are displayed in the JSON challenges response. """ options = options or {} message = 'Please answer the challenge' attributes = {} # Get ValidityTime=120s. Maybe there is a OCRAChallengeValidityTime... validity = int(get_from_config('DefaultChallengeValidityTime', 120)) tokentype = self.get_tokentype().lower() lookup_for = tokentype.capitalize() + 'ChallengeValidityTime' validity = int(get_from_config(lookup_for, validity)) # Get the OCRASUITE from the token information ocrasuite = self.get_tokeninfo("ocrasuite") or OCRA_DEFAULT_SUITE challenge = options.get("challenge") # TODO: we could add an additional parameter to hash the challenge # cleartext -> sha1 if not challenge: # If no challenge is given in the Request, we create a random # challenge based on the OCRA-SUITE os = OCRASuite(ocrasuite) challenge = os.create_challenge() else: # Add a random challenge if options.get("addrandomchallenge"): challenge += get_alphanum_str( int(options.get("addrandomchallenge"))) attributes["original_challenge"] = challenge attributes["qrcode"] = create_img(challenge) if options.get("hashchallenge", "").lower() == "sha256": challenge = hexlify_and_unicode( hashlib.sha256(to_bytes(challenge)).digest()) elif options.get("hashchallenge", "").lower() == "sha512": challenge = hexlify_and_unicode( hashlib.sha512(to_bytes(challenge)).digest()) elif options.get("hashchallenge"): challenge = hexlify_and_unicode( hashlib.sha1(to_bytes(challenge)).digest()) # Create the challenge in the database db_challenge = Challenge(self.token.serial, transaction_id=None, challenge=challenge, data=None, session=None, validitytime=validity) db_challenge.save() attributes["challenge"] = challenge reply_dict = {"attributes": attributes} return True, message, db_challenge.transaction_id, reply_dict
def create_challenge(self, transactionid=None, options=None): """ This method creates a challenge, which is submitted to the user. The submitted challenge will be preserved in the challenge database. If no transaction id is given, the system will create a transaction id and return it, so that the response can refer to this transaction. :param transactionid: the id of this challenge :param options: the request context parameters / data :type options: dict :return: tuple of (bool, message, transactionid, attributes) :rtype: tuple The return tuple builds up like this: ``bool`` if submit was successful; ``message`` which is displayed in the JSON response; additional ``attributes``, which are displayed in the JSON response. """ res = False options = options or {} message = get_action_values_from_options( SCOPE.AUTH, ACTION.CHALLENGETEXT, options) or DEFAULT_CHALLENGE_TEXT sslverify = get_action_values_from_options( SCOPE.AUTH, PUSH_ACTION.SSL_VERIFY, options) or "1" sslverify = getParam({"sslverify": sslverify}, "sslverify", allowed_values=["0", "1"], default="1") attributes = None data = None challenge = b32encode_and_unicode(geturandom()) fb_identifier = self.get_tokeninfo(PUSH_ACTION.FIREBASE_CONFIG) if fb_identifier: # We send the challenge to the Firebase service fb_gateway = create_sms_instance(fb_identifier) url = fb_gateway.smsgateway.option_dict.get( FIREBASE_CONFIG.REGISTRATION_URL) message_on_mobile = get_action_values_from_options( SCOPE.AUTH, PUSH_ACTION.MOBILE_TEXT, options) or DEFAULT_MOBILE_TEXT title = get_action_values_from_options( SCOPE.AUTH, PUSH_ACTION.MOBILE_TITLE, options) or "privacyIDEA" smartphone_data = { "nonce": challenge, "question": message_on_mobile, "serial": self.token.serial, "title": title, "sslverify": sslverify, "url": url } # Create the signature. # value to string sign_string = u"{nonce}|{url}|{serial}|{question}|{title}|{sslverify}".format( **smartphone_data) pem_privkey = self.get_tokeninfo(PRIVATE_KEY_SERVER) privkey_obj = serialization.load_pem_private_key( to_bytes(pem_privkey), None, default_backend()) # Sign the data with PKCS1 padding. Not all Androids support PSS padding. signature = privkey_obj.sign(sign_string.encode("utf8"), padding.PKCS1v15(), hashes.SHA256()) smartphone_data["signature"] = b32encode_and_unicode(signature) res = fb_gateway.submit_message( self.get_tokeninfo("firebase_token"), smartphone_data) if not res: raise ValidateError( "Failed to submit message to firebase service.") else: log.warning(u"The token {0!s} has no tokeninfo {1!s}. " u"The message could not be sent.".format( self.token.serial, PUSH_ACTION.FIREBASE_CONFIG)) raise ValidateError( "The token has no tokeninfo. Can not send via firebase service." ) validity = int(get_from_config('DefaultChallengeValidityTime', 120)) tokentype = self.get_tokentype().lower() # Maybe there is a PushChallengeValidityTime... lookup_for = tokentype.capitalize() + 'ChallengeValidityTime' validity = int(get_from_config(lookup_for, validity)) # Create the challenge in the database db_challenge = Challenge(self.token.serial, transaction_id=transactionid, challenge=challenge, data=data, session=options.get("session"), validitytime=validity) db_challenge.save() self.challenge_janitor() return True, message, db_challenge.transaction_id, attributes
def before_request(): """ This is executed before the request. user_required checks if there is a logged in admin or user The checks for ONLY admin are preformed in api/system.py """ # remove session from param and gather all parameters, either # from the Form data or from JSON in the request body. ensure_no_config_object() request.all_data = get_all_params(request.values, request.data) if g.logged_in_user.get("role") == "user": # A user is calling this API. First thing we do is restricting the user parameter. # ...to restrict token view, audit view or token actions. request.all_data["user"] = g.logged_in_user.get("username") request.all_data["realm"] = g.logged_in_user.get("realm") try: request.User = get_user_from_param(request.all_data) # overwrite or set the resolver parameter in case of a logged in user if g.logged_in_user.get("role") == "user": request.all_data["resolver"] = request.User.resolver except AttributeError: # Some endpoints do not need users OR e.g. the setPolicy endpoint # takes a list as the userobject request.User = None except UserError: # In cases like the policy API, the parameter "user" is part of the # policy and will not resolve to a user object request.User = User() g.policy_object = PolicyClass() g.audit_object = getAudit(current_app.config) g.event_config = EventConfiguration() # access_route contains the ip adresses of all clients, hops and proxies. g.client_ip = get_client_ip(request, get_from_config(SYSCONF.OVERRIDECLIENT)) privacyidea_server = current_app.config.get("PI_AUDIT_SERVERNAME") or \ request.host # Already get some typical parameters to log serial = getParam(request.all_data, "serial") if serial: tokentype = get_token_type(serial) else: tokentype = None if request.User: audit_username = request.User.login audit_realm = request.User.realm audit_resolver = request.User.resolver else: audit_realm = getParam(request.all_data, "realm") audit_resolver = getParam(request.all_data, "resolver") audit_username = getParam(request.all_data, "user") g.audit_object.log({ "success": False, "serial": serial, "user": audit_username, "realm": audit_realm, "resolver": audit_resolver, "token_type": tokentype, "client": g.client_ip, "client_user_agent": request.user_agent.browser, "privacyidea_server": privacyidea_server, "action": "{0!s} {1!s}".format(request.method, request.url_rule), "action_detail": "", "info": "" }) if g.logged_in_user.get("role") == "admin": # An administrator is calling this API g.audit_object.log({"administrator": g.logged_in_user.get("username")})
def create_challenge(self, transactionid=None, options=None): """ create a challenge, which is submitted to the user :param transactionid: the id of this challenge :param options: the request context parameters / data You can pass exception=1 to raise an exception, if the SMS could not be sent. Otherwise the message is contained in the response. :return: tuple of (bool, message and data) bool, if submit was successful message is submitted to the user data is preserved in the challenge attributes - additional attributes, which are displayed in the output """ success = False options = options or {} return_message = get_action_values_from_options( SCOPE.AUTH, "{0!s}_{1!s}".format(self.get_class_type(), ACTION.CHALLENGETEXT), options) or _("Enter the OTP from the SMS:") attributes = {'state': transactionid} validity = self._get_sms_timeout() if self.is_active() is True: counter = self.get_otp_count() log.debug("counter={0!r}".format(counter)) self.inc_otp_counter(counter, reset=False) # At this point we must not bail out in case of an # Gateway error, since checkPIN is successful. A bail # out would cancel the checking of the other tokens try: message_template = self._get_sms_text(options) success, sent_message = self._send_sms( message=message_template) # Create the challenge in the database if is_true(get_from_config("sms.concurrent_challenges")): data = self.get_otp()[2] else: data = None db_challenge = Challenge(self.token.serial, transaction_id=transactionid, challenge=options.get("challenge"), data=data, session=options.get("session"), validitytime=validity) db_challenge.save() transactionid = transactionid or db_challenge.transaction_id except Exception as e: info = ("The PIN was correct, but the " "SMS could not be sent: %r" % e) log.warning(info) log.debug("{0!s}".format(traceback.format_exc())) return_message = info if is_true(options.get("exception")): raise Exception(info) expiry_date = datetime.datetime.now() + \ datetime.timedelta(seconds=validity) attributes['valid_until'] = "{0!s}".format(expiry_date) return success, return_message, transactionid, attributes