def test_12_resolver_priority(self): # Test the priority of resolvers. # we create resolvers with the same user in it. Depending on the # priority we either get the one or the other user. save_resolver({"resolver": "double1", "type": "passwdresolver", "fileName": PWFILE}) save_resolver({"resolver": "double2", "type": "passwdresolver", "fileName": PWFILE}) save_resolver({"resolver": "double3", "type": "passwdresolver", "fileName": PWFILE}) (added, failed) = set_realm("double", ["double1", "double2", "double3"], priority={"double1": 2, "double2": 1, "double3": 3}) self.assertEqual(len(failed), 0) self.assertEqual(len(added), 3) user = get_user_from_param({"user": "******", "realm": "double"}) self.assertEqual(user.resolver, "double2") (added, failed) = set_realm("double", ["double1", "double2", "double3"], priority={"double1": 3, "double2": 2, "double3": 1}) self.assertEqual(len(failed), 0) self.assertEqual(len(added), 3) user = get_user_from_param({"user": "******", "realm": "double"}) self.assertEqual(user.resolver, "double3")
def test_09_get_user_from_param(self): user = get_user_from_param({"user": "******"}) self.assertTrue(user.realm == self.realm1, user) self.assertTrue(user.resolver == self.resolvername1, user) user = get_user_from_param({"realm": self.realm1}) self.assertTrue(user.realm == self.realm1, user) self.assertTrue(user.login == "", user) self.assertTrue(user.resolver == "", user.resolver) user = get_user_from_param({"user": "******", "resolver": self.resolvername1}) self.assertTrue(user.realm == self.realm1, user) # create a realm, where cornelius is in two resolvers! rid = save_resolver({"resolver": self.resolvername3, "type": "passwdresolver", "fileName": PWFILE2}) self.assertTrue(rid > 0, rid) (added, failed) = set_realm(self.realm2, [self.resolvername1, self.resolvername3]) self.assertTrue(len(failed) == 0) self.assertTrue(len(added) == 2) # get user cornelius, who is in two resolvers! param = {"user": "******", "realm": self.realm2} user = get_user_from_param(param) self.assertEqual("{0!s}".format(user), "<cornelius.resolver1@realm2>")
def update(self, param): """ This method is called during the initialization process. :param param: parameters from the token init :type param: dict :return: None """ TokenClass.update(self, param) request = getParam(param, "request", optional) spkac = getParam(param, "spkac", optional) certificate = getParam(param, "certificate", optional) generate = getParam(param, "genkey", optional) if request or generate: # If we do not upload a user certificate, then we need a CA do # sign the uploaded request or generated certificate. ca = getParam(param, "ca", required) self.add_tokeninfo("CA", ca) cacon = get_caconnector_object(ca) if request: # During the initialization process, we need to create the # certificate x509object = cacon.sign_request(request, options={"spkac": spkac}) certificate = crypto.dump_certificate(crypto.FILETYPE_PEM, x509object) elif generate: # Create the certificate on behalf of another user. # Now we need to create the key pair, # the request # and the certificate # We need the user for whom the certificate should be created user = get_user_from_param(param, optionalOrRequired=required) keysize = getParam(param, "keysize", optional, 2048) key = crypto.PKey() key.generate_key(crypto.TYPE_RSA, keysize) req = crypto.X509Req() req.get_subject().CN = user.login # Add email to subject if user.info.get("email"): req.get_subject().emailAddress = user.info.get("email") req.get_subject().organizationalUnitName = user.realm # TODO: Add Country, Organization, Email # req.get_subject().countryName = 'xxx' # req.get_subject().stateOrProvinceName = 'xxx' # req.get_subject().localityName = 'xxx' # req.get_subject().organizationName = 'xxx' req.set_pubkey(key) req.sign(key, "sha256") x509object = cacon.sign_request(crypto.dump_certificate_request( crypto.FILETYPE_PEM, req)) certificate = crypto.dump_certificate(crypto.FILETYPE_PEM, x509object) # Save the private key to the encrypted key field of the token s = crypto.dump_privatekey(crypto.FILETYPE_PEM, key) self.add_tokeninfo("privatekey", s, value_type="password") if certificate: self.add_tokeninfo("certificate", certificate)
def check_max_token_user(request=None, action=None): """ Pre Policy This checks the maximum token per user policy. Check ACTION.MAXTOKENUSER This decorator can wrap: /token/init (with a realm and user) /token/assign :param req: :param action: :return: True otherwise raises an Exception """ ERROR = "The number of tokens for this user is limited!" params = request.all_data user_object = get_user_from_param(params) if user_object.login: policy_object = g.policy_object limit_list = policy_object.get_action_values(ACTION.MAXTOKENUSER, scope=SCOPE.ENROLL, realm=user_object.realm, user=user_object.login, client=request.remote_addr) if len(limit_list) > 0: # we need to check how many tokens the user already has assigned! tokenobject_list = get_tokens(user=user_object) already_assigned_tokens = len(tokenobject_list) if already_assigned_tokens >= int(max(limit_list)): raise PolicyError(ERROR) return True
def init_tokenlabel(request=None, action=None): """ This policy function is to be used as a decorator in the API init function. It adds the tokenlabel definition to the params like this: params : { "tokenlabel": "<u>@<r>" } It uses the policy SCOPE.ENROLL, ACTION.TOKENLABEL to set the tokenlabel of Smartphone tokens during enrollment and this fill the details of the response. """ params = request.all_data policy_object = g.policy_object user_object = get_user_from_param(params) # get the serials from a policy definition label_pols = policy_object.get_action_values(action=ACTION.TOKENLABEL, scope=SCOPE.ENROLL, user=user_object.login, realm=user_object.realm, client=request.remote_addr, unique=True) if len(label_pols) == 1: # The policy was set, so we need to set the tokenlabel in the request. request.all_data["tokenlabel"] = label_pols[0] return True
def encrypt_pin(request=None, action=None): """ This policy function is to be used as a decorator for several API functions. E.g. token/assign, token/setpin, token/init If the policy is set to define the PIN to be encrypted, the request.all_data is modified like this: encryptpin = True It uses the policy SCOPE.ENROLL, ACTION.ENCRYPTPIN """ params = request.all_data policy_object = g.policy_object user_object = get_user_from_param(params) # get the length of the random PIN from the policies pin_pols = policy_object.get_policies(action=ACTION.ENCRYPTPIN, scope=SCOPE.ENROLL, user=user_object.login, realm=user_object.realm, client=request.remote_addr, active=True) if len(pin_pols) > 0: request.all_data["encryptpin"] = "True" else: if "encryptpin" in request.all_data: del request.all_data["encryptpin"] return True
def check_anonymous_user(request=None, action=None): """ This decorator function takes the request and verifies the given action for the SCOPE USER without an authenticated user but the user from the parameters. This is used with password_reset :param request: :param action: :return: True otherwise raises an Exception """ ERROR = "User actions are defined, but this action is not allowed!" params = request.all_data policy_object = g.policy_object scope = SCOPE.USER user_obj = get_user_from_param(params) username = user_obj.login realm = user_obj.realm action = policy_object.get_policies(action=action, user=username, realm=realm, scope=scope, client=request.remote_addr, adminrealm=None, active=True) action_at_all = policy_object.get_policies(scope=scope, active=True) if action_at_all and len(action) == 0: raise PolicyError(ERROR) return True
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 autoassign(request, response): """ This decorator decorates the function /validate/check. Depending on ACTION.AUTOASSIGN it checks if the user has no token and if the given OTP-value matches a token in the users realm, that is not yet assigned to any user. If a token can be found, it assigns the token to the user also taking into account ACTION.MAXTOKENUSER and ACTION.MAXTOKENREALM. :return: """ content = json.loads(response.data) # check, if the authentication was successful, then we need to do nothing if content.get("result").get("value") is False: user_obj = get_user_from_param(request.all_data) password = request.all_data.get("pass", "") if user_obj.login and user_obj.realm: # If there is no user in the request (because it is a serial # authentication request) we immediately bail out # check if the policy is defined policy_object = g.policy_object autoassign_bool = policy_object.get_policies( action=ACTION.AUTOASSIGN, scope=SCOPE.ENROLL, user=user_obj.login, realm=user_obj.realm, client=request.remote_addr, active=True, ) if len(autoassign_bool) >= 1: # check if the user has no token if get_tokens(user=user_obj, count=True) == 0: # Check is the token would match # get all unassigned tokens in the realm and look for a matching OTP: realm_tokens = get_tokens(realm=user_obj.realm, assigned=False) for token_obj in realm_tokens: (res, pin, otp) = token_obj.split_pin_pass(password) # TODO: What do we want to do with the PIN? # Check it against userstore? if token_obj.check_otp(otp) >= 0: # we found a matching token # check MAXTOKENUSER and MAXTOKENREALM check_max_token_user(request=request) check_max_token_realm(request=request) # Assign token assign_token(serial=token_obj.token.serial, user=user_obj, pin=pin) # Set the response to true content.get("result")["value"] = True # Set the serial number if not content.get("detail"): content["detail"] = {} content.get("detail")["serial"] = token_obj.token.serial content.get("detail")["type"] = token_obj.type content.get("detail")["message"] = "Token " "assigned to " "user via " "Autoassignment" response.data = json.dumps(content) break return response
def token(ttype=None): """ This is a special token function. Each token type can define an additional API call, that does not need authentication on the REST API level. :return: Token Type dependent """ tokenc = get_token_class(ttype) res = tokenc.api_endpoint(request, g) serial = getParam(request.all_data, "serial") user = get_user_from_param(request.all_data) g.audit_object.log({"success": 1, "user": user.login, "realm": user.realm, "serial": serial, "token_type": ttype}) if res[0] == "json": return jsonify(res[1]) elif res[0] in ["html", "plain"]: return Response(res[1], mimetype="text/{0!s}".format(res[0])) elif len(res) == 2: return Response(json.dumps(res[1]), mimetype="application/{0!s}".format(res[0])) else: return Response(res[1], mimetype="application/octet-binary", headers=res[2])
def twostep_enrollment_parameters(request=None, action=None): """ If the ``2stepinit`` parameter is set to true, this policy function reads additional configuration from policies and adds it to ``request.all_data``, that is: * ``{type}_2step_serversize`` is written to ``2step_serversize`` * ``{type}_2step_clientsize`` is written to ``2step_clientsize` * ``{type}_2step_difficulty`` is written to ``2step_difficulty`` If no policy matches, the value passed by the user is kept. This policy function is used to decorate the ``/token/init`` endpoint. """ policy_object = g.policy_object user_object = get_user_from_param(request.all_data) serial = getParam(request.all_data, "serial", optional) token_type = getParam(request.all_data, "type", optional, "hotp") if serial: tokensobject_list = get_tokens(serial=serial) if len(tokensobject_list) == 1: token_type = tokensobject_list[0].token.tokentype token_type = token_type.lower() role = g.logged_in_user.get("role") # Differentiate between an admin enrolling a token for the # user and a user self-enrolling a token. if role == ROLE.ADMIN: adminrealm = g.logged_in_user.get("realm") else: adminrealm = None realm = user_object.realm # In any case, the policy's user attribute is matched against the # currently logged-in user (which may be the admin or the # self-enrolling user). user = g.logged_in_user.get("username") # Tokentypes have separate twostep actions if is_true(getParam(request.all_data, "2stepinit", optional)): parameters = ("2step_serversize", "2step_clientsize", "2step_difficulty") for parameter in parameters: action = u"{}_{}".format(token_type, parameter) action_values = policy_object.get_action_values(action=action, scope=SCOPE.ENROLL, unique=True, user=user, realm=realm, client=g.client_ip, adminrealm=adminrealm) if action_values: request.all_data[parameter] = action_values[0]
def get_recover_code(): """ This method requests a recover code for a user. The recover code it sent via email to the user. :queryparam user: username of the user :queryparam realm: realm of the user :queryparam email: email of the user :return: JSON with value=True or value=False """ param = request.all_data user_obj = get_user_from_param(param, required) email = getParam(param, "email", required) r = create_recoverycode(user_obj, email, base_url=request.base_url) g.audit_object.log({"success": r, "info": u"{0!s}".format(user_obj)}) return send_result(r)
def do(self, action, options=None): """ This method executes the defined action in the given event. :param action: :param environment: :param options: :return: """ ret = True g = options.get("g") request = options.get("request") logged_in_user = g.logged_in_user user = get_user_from_param(request.all_data) if action.lower() == "sendmail" and logged_in_user.get("role") == \ ROLE.ADMIN and not user.is_empty() and user.login: emailconfig = options.get("emailconfig") if not emailconfig: log.error("Missing parameter 'emailconfig'") raise ParameterError("Missing parameter 'emailconfig'") useremail = user.info.get("email") subject = options.get("subject") or "An action was performed on " \ "your token." body = options.get("body") or DEFAULT_BODY body = body.format( admin=logged_in_user.get("username"), realm=logged_in_user.get("realm"), action=request.path, serial=g.audit_object.audit_data.get("serial"), url=request.url_root, user=user.info.get("givenname") ) try: ret = send_email_identifier(emailconfig, recipient=useremail, subject=subject, body=body) except Exception as exx: log.error("Failed to send email: {0!s}".format(exx)) ret = False if ret: log.info("Sent a notification email to user {0}".format(user)) else: log.warning("Failed to send a notification email to user " "{0}".format(user)) return ret
def token(ttype=None): """ This is a special token function. Each token type can define an additional API call, that does not need authentication on the REST API level. :return: Token Type dependent """ tokenc = get_token_class(ttype) res = tokenc.api_endpoint(request.all_data) serial = getParam(request.all_data, "serial") user = get_user_from_param(request.all_data) g.audit_object.log({"success": 1, "user": user, "serial": serial, "tokentype": ttype}) if res[0] == "json": return jsonify(res[1]) elif res[0] == "text": return Response(res[1])
def init_random_pin(request=None, action=None): """ This policy function is to be used as a decorator in the API init function. If the policy is set accordingly it adds a random PIN to the request.all_data like. It uses the policy SCOPE.ENROLL, ACTION.OTPPINRANDOM to set a random OTP PIN during Token enrollment """ params = request.all_data policy_object = g.policy_object user_object = get_user_from_param(params) # get the length of the random PIN from the policies pin_pols = policy_object.get_action_values(action=ACTION.OTPPINRANDOM, scope=SCOPE.ENROLL, user=user_object.login, realm=user_object.realm, client=request.remote_addr, unique=True) if len(pin_pols) == 1: log.debug("Creating random OTP PIN with length %s" % pin_pols[0]) request.all_data["pin"] = generate_password(size=int(pin_pols[0])) # handle the PIN handle_pols = policy_object.get_action_values( action=ACTION.PINHANDLING, scope=SCOPE.ENROLL, user=user_object.login, realm=user_object.realm, client=request.remote_addr) # We can have more than one pin handler policy. So we can process the # PIN in several ways! for handle_pol in handle_pols: log.debug("Handle the random PIN with the class %s" % handle_pol) packageName = ".".join(handle_pol.split(".")[:-1]) className = handle_pol.split(".")[-1:][0] mod = __import__(packageName, globals(), locals(), [className]) pin_handler_class = getattr(mod, className) pin_handler = pin_handler_class() # Send the PIN pin_handler.send(request.all_data["pin"], request.all_data.get("serial", "N/A"), user_object, tokentype=request.all_data.get("type", "hotp"), logged_in_user=g.logged_in_user) return True
def set_realm(request=None, action=None): """ Pre Policy This pre condition gets the current realm and verifies if the realm should be rewritten due to the policy definition. I takes the realm from the request and - if a policy matches - replaces this realm with the realm defined in the policy Check ACTION.SETREALM This decorator should wrap /validate/check :param request: The request that is intercepted during the API call :type request: Request Object :param action: An optional Action :type action: basestring :returns: Always true. Modified the parameter request """ user_object = get_user_from_param(request.all_data) # At the moment a realm parameter with no user parameter returns a user # object like "@realm". If this is changed one day, we need to also fetch # the realm if user_object: realm = user_object.realm else: # pragma: no cover realm = request.all_data.get("realm") policy_object = g.policy_object new_realm = policy_object.get_action_values(ACTION.SETREALM, scope=SCOPE.AUTHZ, realm=realm, client=request.remote_addr) # reduce the entries to unique entries new_realm = list(set(new_realm)) if len(new_realm) > 1: raise PolicyError("I do not know, to which realm I should set the " "new realm. Conflicting policies exist.") elif len(new_realm) == 1: # There is one specific realm, which we set in the request request.all_data["realm"] = new_realm[0] return True
def api_key_required(request=None, action=None): """ This is a decorator for check_user_pass and check_serial_pass. It checks, if a policy scope=auth, action=apikeyrequired is set. If so, the validate request will only performed, if a JWT token is passed with role=validate. """ ERROR = "The policy requires an API key to authenticate, " \ "but no key was passed." params = request.all_data policy_object = g.policy_object user_object = get_user_from_param(params) # Get the policies action = policy_object.get_policies(action=ACTION.APIKEY, user=user_object.login, realm=user_object.realm, scope=SCOPE.AUTHZ, client=request.remote_addr, active=True) # Do we have a policy? if len(action) > 0: # check if we were passed a correct JWT # Get the Authorization token from the header auth_token = request.headers.get('PI-Authorization', None) if not auth_token: auth_token = request.headers.get('Authorization', None) try: r = jwt.decode(auth_token, current_app.secret_key) g.logged_in_user = {"username": r.get("username", ""), "realm": r.get("realm", ""), "role": r.get("role", "")} except AttributeError: raise PolicyError("No valid API key was passed.") role = g.logged_in_user.get("role") if role != ROLE.VALIDATE: raise PolicyError("A correct JWT was passed, but it was no API " "key.") # If everything went fine, we call the original function return True
def mangle(request=None, action=None): """ This pre condition checks if either of the parameters pass, user or realm in a validate/check request should be rewritten based on an authentication policy with action "mangle". See :ref:`policy_mangle` for an example. Check ACTION.MANGLE This decorator should wrap /validate/check :param request: The request that is intercepted during the API call :type request: Request Object :param action: An optional Action :type action: basestring :returns: Always true. Modified the parameter request """ user_object = get_user_from_param(request.all_data) policy_object = g.policy_object mangle_pols = policy_object.get_action_values(ACTION.MANGLE, scope=SCOPE.AUTH, realm=user_object.realm, user=user_object.login, client=request.remote_addr) # reduce the entries to unique entries mangle_pols = list(set(mangle_pols)) # We can have several mangle policies! One for user, one for realm and # one for pass. So we do no checking here. for mangle_pol_action in mangle_pols: # mangle_pol_action looks like this: # keyword/search/replace/. Where "keyword" can be "user", "pass" or # "realm". mangle_key, search, replace, _rest = mangle_pol_action.split("/", 3) mangle_value = request.all_data.get(mangle_key) if mangle_value: log.debug("mangling authentication data: %s" % mangle_key) request.all_data[mangle_key] = re.sub(search, replace, mangle_value) return True
def reset_password(): """ reset the password with a given recovery code. The recovery code was sent by get_recover_code and is bound to a certain user. :jsonparam recoverycode: The recoverycode sent the the user :jsonparam password: The new password of the user :return: a json result with a boolean "result": true """ r = False user_obj = get_user_from_param(request.all_data, required) recoverycode = getParam(request.all_data, "recoverycode", required) password = getParam(request.all_data, "password", required) if check_recoverycode(user_obj, recoverycode): # set password r = user_obj.update_user_info({"password": password}) g.audit_object.log({"success": r, "info": u"{0!s}".format(user_obj)}) return send_result(r)
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 update(self, param): """ This method is called during the initialization process. :param param: parameters from the token init :type param: dict :return: None """ user_object = get_user_from_param(param, optional) if user_object: self.set_user(user_object) ocrasuite = getParam(param, "ocrasuite", default=OCRA_DEFAULT_SUITE) OCRASuite(ocrasuite) self.add_tokeninfo("ocrasuite", ocrasuite) TokenClass.update(self, param) if user_object: # 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 check_max_token_realm(request=None, action=None): """ Pre Policy This checks the maximum token per realm. Check ACTION.MAXTOKENREALM This decorator can wrap: /token/init (with a realm and user) /token/assign /token/tokenrealms :param req: The request that is intercepted during the API call :type req: Request Object :param action: An optional Action :type action: basestring :return: True otherwise raises an Exception """ ERROR = "The number of tokens in this realm is limited!" params = request.all_data user_object = get_user_from_param(params) if user_object: realm = user_object.realm else: # pragma: no cover realm = params.get("realm") if realm: policy_object = g.policy_object limit_list = policy_object.get_action_values(ACTION.MAXTOKENREALM, scope=SCOPE.ENROLL, realm=realm, client=request.remote_addr) if len(limit_list) > 0: # we need to check how many tokens the user already has assigned! tokenobject_list = get_tokens(realm=realm) already_assigned_tokens = len(tokenobject_list) if already_assigned_tokens >= int(max(limit_list)): raise PolicyError(ERROR) return True
def update(self, param): """ This method is called during the initialization process. :param param: parameters from the token init :type param: dict :return: None """ TokenClass.update(self, param) request = getParam(param, "request", optional) spkac = getParam(param, "spkac", optional) certificate = getParam(param, "certificate", optional) generate = getParam(param, "genkey", optional) template_name = getParam(param, "template", optional) if request or generate: # If we do not upload a user certificate, then we need a CA do # sign the uploaded request or generated certificate. ca = getParam(param, "ca", required) self.add_tokeninfo("CA", ca) cacon = get_caconnector_object(ca) if request: # During the initialization process, we need to create the # certificate x509object = cacon.sign_request(request, options={ "spkac": spkac, "template": template_name }) certificate = crypto.dump_certificate(crypto.FILETYPE_PEM, x509object) elif generate: # Create the certificate on behalf of another user. # Now we need to create the key pair, # the request # and the certificate # We need the user for whom the certificate should be created user = get_user_from_param(param, optionalOrRequired=required) keysize = getParam(param, "keysize", optional, 2048) key = crypto.PKey() key.generate_key(crypto.TYPE_RSA, keysize) req = crypto.X509Req() req.get_subject().CN = user.login # Add email to subject if user.info.get("email"): req.get_subject().emailAddress = user.info.get("email") req.get_subject().organizationalUnitName = user.realm # TODO: Add Country, Organization, Email # req.get_subject().countryName = 'xxx' # req.get_subject().stateOrProvinceName = 'xxx' # req.get_subject().localityName = 'xxx' # req.get_subject().organizationName = 'xxx' req.set_pubkey(key) req.sign(key, "sha256") csr = to_unicode( crypto.dump_certificate_request(crypto.FILETYPE_PEM, req)) x509object = cacon.sign_request( csr, options={"template": template_name}) certificate = crypto.dump_certificate(crypto.FILETYPE_PEM, x509object) # Save the private key to the encrypted key field of the token s = crypto.dump_privatekey(crypto.FILETYPE_PEM, key) self.add_tokeninfo("privatekey", s, value_type="password") if "pin" in param: self.set_pin(param.get("pin"), encrypt=True) if certificate: self.add_tokeninfo("certificate", certificate)
def samlcheck(): """ Authenticate the user and return the SAML user information. :param user: The loginname/username of the user, who tries to authenticate. :param realm: The realm of the user, who tries to authenticate. If the realm is omitted, the user is looked up in the default realm. :param pass: The password, that consists of the OTP PIN and the OTP value. :return: a json result with a boolean "result": true **Example response** for a successful authentication: .. sourcecode:: http HTTP/1.1 200 OK Content-Type: application/json { "detail": { "message": "matching 1 tokens", "serial": "PISP0000AB00", "type": "spass" }, "id": 1, "jsonrpc": "2.0", "result": { "status": true, "value": {"attributes": { "username": "******", "realm": "themis", "mobile": null, "phone": null, "myOwn": "/data/file/home/koelbel", "resolver": "themis", "surname": "Kölbel", "givenname": "Cornelius", "email": null}, "auth": true} }, "version": "privacyIDEA unknown" } The response in value->attributes can contain additional attributes (like "myOwn") which you can define in the LDAP resolver in the attribute mapping. """ user = get_user_from_param(request.all_data) password = getParam(request.all_data, "pass", required) options = {"g": g, "clientip": g.client_ip} # Add all params to the options for key, value in request.all_data.items(): if value and key not in ["g", "clientip"]: options[key] = value auth, details = check_user_pass(user, password, options=options) ui = user.info result_obj = {"auth": auth, "attributes": {}} if return_saml_attributes(): if auth or return_saml_attributes_on_fail(): # privacyIDEA's own attribute map result_obj["attributes"] = {"username": ui.get("username"), "realm": user.realm, "resolver": user.resolver, "email": ui.get("email"), "surname": ui.get("surname"), "givenname": ui.get("givenname"), "mobile": ui.get("mobile"), "phone": ui.get("phone") } # additional attributes for k, v in ui.items(): result_obj["attributes"][k] = v g.audit_object.log({"info": details.get("message"), "success": auth, "serial": details.get("serial"), "tokentype": details.get("type"), "user": user.login, "resolver": user.resolver, "realm": user.realm}) return send_result(result_obj, details=details)
def autoassign(request, response): """ This decorator decorates the function /validate/check. Depending on ACTION.AUTOASSIGN it checks if the user has no token and if the given OTP-value matches a token in the users realm, that is not yet assigned to any user. If a token can be found, it assigns the token to the user also taking into account ACTION.MAXTOKENUSER and ACTION.MAXTOKENREALM. :return: """ content = json.loads(response.data) # check, if the authentication was successful, then we need to do nothing if content.get("result").get("value") is False: user_obj = get_user_from_param(request.all_data) password = request.all_data.get("pass", "") if user_obj.login and user_obj.realm: # If there is no user in the request (because it is a serial # authentication request) we immediately bail out # check if the policy is defined policy_object = g.policy_object autoassign_values = policy_object.\ get_action_values(action=ACTION.AUTOASSIGN, scope=SCOPE.ENROLL, user=user_obj.login, realm=user_obj.realm, client=g.client_ip) if len(autoassign_values) > 1: raise PolicyError("Contradicting Autoassign policies.") # check if the user has no token if autoassign_values and get_tokens(user=user_obj, count=True) == 0: # Check is the token would match # get all unassigned tokens in the realm and look for # a matching OTP: realm_tokens = get_tokens(realm=user_obj.realm, assigned=False) for token_obj in realm_tokens: (res, pin, otp) = token_obj.split_pin_pass(password) if res: pin_check = True if autoassign_values[0] == \ AUTOASSIGNVALUE.USERSTORE: # If the autoassign policy is set to userstore, # we need to check against the userstore. pin_check = user_obj.check_password(pin) if pin_check: otp_check = token_obj.check_otp(otp) if otp_check >= 0: # we found a matching token # check MAXTOKENUSER and MAXTOKENREALM check_max_token_user(request=request) check_max_token_realm(request=request) # Assign token assign_token(serial=token_obj.token.serial, user=user_obj, pin=pin) # Set the response to true content.get("result")["value"] = True # Set the serial number if not content.get("detail"): content["detail"] = {} content.get("detail")["serial"] = \ token_obj.token.serial content.get("detail")["otplen"] = \ token_obj.token.otplen content.get("detail")["type"] = token_obj.type content.get("detail")["message"] = "Token " \ "assigned to " \ "user via " \ "Autoassignment" response.data = json.dumps(content) g.audit_object.log( {"success": True, "action_info": "Token assigned via auto assignment", "serial": token_obj.token.serial}) break return response
def samlcheck(): """ Authenticate the user and return the SAML user information. :param user: The loginname/username of the user, who tries to authenticate. :param realm: The realm of the user, who tries to authenticate. If the realm is omitted, the user is looked up in the default realm. :param pass: The password, that consists of the OTP PIN and the OTP value. :return: a json result with a boolean "result": true **Example response** for a successful authentication: .. sourcecode:: http HTTP/1.1 200 OK Content-Type: application/json { "detail": { "message": "matching 1 tokens", "serial": "PISP0000AB00", "type": "spass" }, "id": 1, "jsonrpc": "2.0", "result": { "status": true, "value": {"auth": true, "username: <loginname>, "realm": ...., "surname": ...., "givenname": ....., "mobile": ...., "phone": ...., "email": .... } }, "version": "privacyIDEA unknown" } """ user = get_user_from_param(request.all_data) password = getParam(request.all_data, "pass", required) options = {"g": g, "clientip": request.remote_addr} # Add all params to the options for key, value in request.all_data.items(): if value and key not in ["g", "clientip"]: options[key] = value auth, details = check_user_pass(user, password, options=options) ui = user.get_user_info() result_obj = {"auth": auth, "attributes": {"username": ui.get("username"), "realm": user.realm, "resolver": user.resolver, "email": ui.get("email"), "surname": ui.get("surname"), "givenname": ui.get("givenname"), "mobile": ui.get("mobile"), "phone": ui.get("phone") } } g.audit_object.log({"info": details.get("message"), "success": auth, "serial": details.get("serial"), "tokentype": details.get("type"), "user": user.login, "realm": user.realm}) return send_result(result_obj, details=details)
def check(): """ check the authentication for a user or a serial number. Either a ``serial`` or a ``user`` is required to authenticate. The PIN and OTP value is sent in the parameter ``pass``. :param serial: The serial number of the token, that tries to authenticate. :param user: The loginname/username of the user, who tries to authenticate. :param realm: The realm of the user, who tries to authenticate. If the realm is omitted, the user is looked up in the default realm. :param pass: The password, that consists of the OTP PIN and the OTP value. :param transaction_id: The transaction ID for a response to a challenge request :param state: The state ID for a response to a challenge request :return: a json result with a boolean "result": true **Example response** for a successful authentication: .. sourcecode:: http HTTP/1.1 200 OK Content-Type: application/json { "detail": { "message": "matching 1 tokens", "serial": "PISP0000AB00", "type": "spass" }, "id": 1, "jsonrpc": "2.0", "result": { "status": true, "value": true }, "version": "privacyIDEA unknown" } """ user = get_user_from_param(request.all_data) serial = getParam(request.all_data, "serial") password = getParam(request.all_data, "pass", required) options = {"g": g, "clientip": request.remote_addr} # Add all params to the options for key, value in request.all_data.items(): if value and key not in ["g", "clientip"]: options[key] = value g.audit_object.log({"user": user.login, "realm": user.realm}) if serial: result, details = check_serial_pass(serial, password, options=options) else: result, details = check_user_pass(user, password, options=options) g.audit_object.log({"info": details.get("message"), "success": result, "serial": serial or details.get("serial"), "tokentype": details.get("type")}) return send_result(result, details=details)
def do(self, action, options=None): """ This method executes the defined action in the given event. :param action: :param options: :return: """ ret = True g = options.get("g") request = options.get("request") try: logged_in_user = g.logged_in_user except Exception: logged_in_user = {} user = get_user_from_param(request.all_data) serial = request.all_data.get("serial") if user.is_empty() and serial: # maybe the user is empty, but a serial was passed. # Then we determine the user by the serial user = get_token_owner(serial) if not user.is_empty() and user.login and logged_in_user.get("role") ==\ ROLE.ADMIN: body = options.get("body") or DEFAULT_BODY body = body.format( admin=logged_in_user.get("username"), realm=logged_in_user.get("realm"), action=request.path, serial=g.audit_object.audit_data.get("serial"), url=request.url_root, user=user.info.get("givenname") ) if action.lower() == "sendmail": emailconfig = options.get("emailconfig") useremail = user.info.get("email") subject = options.get("subject") or "An action was performed on " \ "your token." try: ret = send_email_identifier(emailconfig, recipient=useremail, subject=subject, body=body) except Exception as exx: log.error("Failed to send email: {0!s}".format(exx)) ret = False if ret: log.info("Sent a notification email to user {0}".format(user)) else: log.warning("Failed to send a notification email to user " "{0}".format(user)) elif action.lower() == "sendsms": smsconfig = options.get("smsconfig") userphone = user.info.get("mobile") try: ret = send_sms_identifier(smsconfig, userphone, body) except Exception as exx: log.error("Failed to send sms: {0!s}".format(exx)) ret = False if ret: log.info("Sent a notification sms to user {0}".format(user)) else: log.warning("Failed to send a notification email to user " "{0}".format(user)) return ret
def check_otp_pin(request=None, action=None): """ This policy function checks if the OTP PIN that is about to be set follows the OTP PIN policies ACTION.OTPPINMAXLEN, ACTION.OTPPINMINLEN and ACTION.OTPPINCONTENTS in the SCOPE.USER. It is used to decorate the API functions. The pin is investigated in the params as pin = params.get("pin") In case the given OTP PIN does not match the requirements an exception is raised. """ # This policy is only used for USER roles at the moment: if g.logged_in_user.get("role") == "user": params = request.all_data pin = params.get("otppin", "") or params.get("pin", "") serial = params.get("serial") if serial: # if this is a token, that does not use a pin, we ignore this check # And immediately return true tokensobject_list = get_tokens(serial=serial) if len(tokensobject_list) == 1: if tokensobject_list[0].using_pin is False: return True policy_object = g.policy_object user_object = get_user_from_param(params) # get the policies for minimum length, maximum length and PIN contents pol_minlen = policy_object.get_action_values(action=ACTION.OTPPINMINLEN, scope=SCOPE.USER, user=user_object.login, realm=user_object.realm, client=request.remote_addr, unique=True) pol_maxlen = policy_object.get_action_values(action=ACTION.OTPPINMAXLEN, scope=SCOPE.USER, user=user_object.login, realm=user_object.realm, client=request.remote_addr, unique=True) pol_contents = policy_object.get_action_values(action=ACTION.OTPPINCONTENTS, scope=SCOPE.USER, user=user_object.login, realm=user_object.realm, client=request.remote_addr, unique=True) if len(pol_minlen) == 1: # check the minimum length requirement if len(pin) < int(pol_minlen[0]): raise PolicyError("The minimum OTP PIN length is %s" % pol_minlen[0]) if len(pol_maxlen) == 1: # check the maximum length requirement if len(pin) > int(pol_maxlen[0]): raise PolicyError("The maximum OTP PIN length is %s" % pol_minlen[0]) if len(pol_contents) == 1: # check the contents requirement chars = "[a-zA-Z]" # c digits = "[0-9]" # n special = "[.:,;-_<>+*!/()=?$§%&#~\^]" # s no_others = False grouping = False if pol_contents[0] == "-": no_others = True pol_contents = pol_contents[1:] elif pol_contents[0] == "+": grouping = True pol_contents = pol_contents[1:] # TODO implement grouping and substraction if "c" in pol_contents[0] and not re.search(chars, pin): raise PolicyError("Missing character in PIN: %s" % chars) if "n" in pol_contents[0] and not re.search(digits, pin): raise PolicyError("Missing character in PIN: %s" % digits) if "s" in pol_contents[0] and not re.search(special, pin): raise PolicyError("Missing character in PIN: %s" % special) return True
def twostep_enrollment_activation(request=None, action=None): """ This policy function enables the two-step enrollment process according to the configured policies. It is used to decorate the ``/token/init`` endpoint. If a ``<type>_2step`` policy matches, the ``2stepinit`` parameter is handled according to the policy. If no policy matches, the ``2stepinit`` parameter is removed from the request data. """ policy_object = g.policy_object user_object = get_user_from_param(request.all_data) serial = getParam(request.all_data, "serial", optional) token_type = getParam(request.all_data, "type", optional, "hotp") token_exists = False if serial: tokensobject_list = get_tokens(serial=serial) if len(tokensobject_list) == 1: token_type = tokensobject_list[0].token.tokentype token_exists = True token_type = token_type.lower() role = g.logged_in_user.get("role") # Differentiate between an admin enrolling a token for the # user and a user self-enrolling a token. if role == ROLE.ADMIN: scope = SCOPE.ADMIN adminrealm = g.logged_in_user.get("realm") else: scope = SCOPE.USER adminrealm = None realm = user_object.realm # In any case, the policy's user attribute is matched against the # currently logged-in user (which may be the admin or the # self-enrolling user). user = g.logged_in_user.get("username") # Tokentypes have separate twostep actions action = "{}_2step".format(token_type) twostep_enabled_pols = policy_object.get_action_values(action=action, scope=scope, unique=True, user=user, realm=realm, client=g.client_ip, adminrealm=adminrealm) if twostep_enabled_pols: enabled_setting = twostep_enabled_pols[0] if enabled_setting == "allow": # The user is allowed to pass 2stepinit=1 pass elif enabled_setting == "force": # We force 2stepinit to be 1 (if the token does not exist yet) if not token_exists: request.all_data["2stepinit"] = 1 else: raise PolicyError("Unknown 2step policy setting: {}".format(enabled_setting)) else: # If no policy matches, the user is not allowed # to pass 2stepinit # Force two-step initialization to be None if "2stepinit" in request.all_data: del request.all_data["2stepinit"] return True