Esempio n. 1
0
def save_pin_change(request, response, serial=None):
    """
    This policy function checks if the next_pin_change date should be
    stored in the tokeninfo table.

    1. Check scope:enrollment and
       ACTION.CHANGE_PIN_FIRST_USE.
       This action is used, when the administrator enrolls a token or sets a PIN

    2. Check scope:enrollment and
       ACTION.CHANGE_PIN_EVERY is used, if the user changes the PIN.

    This function decorates /token/init and /token/setpin. The parameter
    "pin" and "otppin" is investigated.

    :param request:
    :param action:
    :return:
    """
    policy_object = g.policy_object
    serial = serial or request.all_data.get("serial")
    if not serial:
        # No serial in request, so we look into the response
        serial = response.json.get("detail", {}).get("serial")
    if not serial:
        log.error("Can not determine serial number. Have no idea of any "
                  "realm!")
    else:
        # Determine the realm by the serial
        realm = get_realms_of_token(serial, only_first_realm=True)
        realm = realm or get_default_realm()

        if g.logged_in_user.get("role") == ROLE.ADMIN:
            pinpol = Match.realm(g, scope=SCOPE.ENROLL, action=ACTION.CHANGE_PIN_FIRST_USE,
                                 realm=realm).policies()
            if pinpol:
                token = get_one_token(serial=serial)
                token.set_next_pin_change(diff="0d")

        elif g.logged_in_user.get("role") == ROLE.USER:
            # Check for parameter "pin" or "otppin".
            otppin = request.all_data.get("otppin")
            pin = request.all_data.get("pin")
            # The user sets a pin or enrolls a token. -> delete the pin_change
            if otppin or pin:
                token = get_one_token(serial=serial)
                token.del_tokeninfo("next_pin_change")

                # If there is a change_pin_every policy, we need to set the PIN
                # anew.
                pinpol = Match.realm(g, scope=SCOPE.ENROLL, action=ACTION.CHANGE_PIN_EVERY,
                                     realm=realm).action_values(unique=True)
                if pinpol:
                    token = get_one_token(serial=serial)
                    token.set_next_pin_change(diff=list(pinpol)[0])

    # we do not modify the response!
    return response
Esempio n. 2
0
    def get_default_settings(cls, g, params):
        """
        This method returns a dictionary with default settings for token
        enrollment.
        These default settings are defined in SCOPE.USER or SCOPE.ADMIN and are
        totp_hashlib, totp_timestep and totp_otplen.
        If these are set, the user or admin will only be able to enroll tokens
        with these values.

        The returned dictionary is added to the parameters of the API call.
        :param g: context object, see documentation of ``Match``
        :param params: The call parameters
        :type params: dict
        :return: default parameters
        """
        ret = {}
        if not g.logged_in_user:
            return ret
        (role, username, userrealm, adminuser,
         adminrealm) = determine_logged_in_userparams(g.logged_in_user, params)
        hashlib_pol = Match.generic(
            g,
            scope=role,
            action="totp_hashlib",
            user=username,
            realm=userrealm,
            adminuser=adminuser,
            adminrealm=adminrealm).action_values(unique=True)
        if hashlib_pol:
            ret["hashlib"] = list(hashlib_pol)[0]

        timestep_pol = Match.generic(
            g,
            scope=role,
            action="totp_timestep",
            user=username,
            realm=userrealm,
            adminuser=adminuser,
            adminrealm=adminrealm).action_values(unique=True)
        if timestep_pol:
            ret["timeStep"] = list(timestep_pol)[0]

        otplen_pol = Match.generic(
            g,
            scope=role,
            action="totp_otplen",
            user=username,
            realm=userrealm,
            adminuser=adminuser,
            adminrealm=adminrealm).action_values(unique=True)
        if otplen_pol:
            ret["otplen"] = list(otplen_pol)[0]

        return ret
Esempio n. 3
0
def mangle_challenge_response(request, response):
    """
    This policy decorator is used in the AUTH scope to
    decorate the /validate/check endpoint.
    It can modify the contents of the response "detail"->"message"
    to allow a better readability for a challenge response text.

    :param request:
    :param response:
    :return:
    """
    if not response.is_json:
        # This can happen with the validate/radiuscheck endpoint
        return response
    content = response.json
    user_obj = request.User

    header_pol = Match.user(g,
                            scope=SCOPE.AUTH,
                            action=ACTION.CHALLENGETEXT_HEADER,
                            user_object=user_obj).action_values(
                                unique=True, allow_white_space_in_action=True)
    footer_pol = Match.user(g,
                            scope=SCOPE.AUTH,
                            action=ACTION.CHALLENGETEXT_FOOTER,
                            user_object=user_obj).action_values(
                                unique=True, allow_white_space_in_action=True)
    if header_pol:
        multi_challenge = content.get("detail", {}).get("multi_challenge")
        if multi_challenge:
            message = list(header_pol)[0]
            footer = ""
            if footer_pol:
                footer = list(footer_pol)[0]
            # We actually have challenge response
            messages = content.get("detail", {}).get("messages") or []
            messages = sorted(set(messages))
            if message[-4:].lower() in ["<ol>", "<ul>"]:
                for m in messages:
                    message += u"<li>{0!s}</li>\n".format(m)
            else:
                message += "\n"
                message += ", ".join(messages)
                message += "\n"
            # Add the footer
            message += footer

            content["detail"]["message"] = message
            response.set_data(json.dumps(content))

    return response
Esempio n. 4
0
def auth_user_does_not_exist(wrapped_function, user_object, passw,
                               options=None):
    """
    This decorator checks, if the user does exist at all.
    If the user does exist, the wrapped function is called.

    The wrapped function is usually token.check_user_pass, which takes the
    arguments (user, passw, options={})

    :param wrapped_function:
    :param user_object:
    :param passw:
    :param options: Dict containing values for "g" and "clientip"
    :return: Tuple of True/False and reply-dictionary
    """
    options = options or {}
    g = options.get("g")
    if g:
        pass_no_user = Match.user(g, scope=SCOPE.AUTH, action=ACTION.PASSNOUSER,
                                  user_object=user_object).policies(write_to_audit_log=False)
        if pass_no_user:
            # Check if user object exists
            if not user_object.exist():
                g.audit_object.add_policy([p.get("name") for p in pass_no_user])
                return True, {"message": u"user does not exist, accepted due to '{!s}'".format(
                    pass_no_user[0].get("name"))}

    # If nothing else returned, we return the wrapped function
    return wrapped_function(user_object, passw, options)
Esempio n. 5
0
def auth_user_has_no_token(wrapped_function, user_object, passw,
                           options=None):
    """
    This decorator checks if the user has a token at all.
    If the user has a token, the wrapped function is called.

    The wrapped function is usually token.check_user_pass, which takes the
    arguments (user, passw, options={})

    :param wrapped_function:
    :param user_object:
    :param passw:
    :param options: Dict containing values for "g" and "clientip"
    :return: Tuple of True/False and reply-dictionary
    """
    from privacyidea.lib.token import get_tokens
    options = options or {}
    g = options.get("g")
    if g:
        pass_no_token = Match.user(g, scope=SCOPE.AUTH, action=ACTION.PASSNOTOKEN,
                                   user_object=user_object).policies(write_to_audit_log=False)
        if pass_no_token:
            # Now we need to check, if the user really has no token.
            tokencount = get_tokens(user=user_object, count=True)
            if tokencount == 0:
                g.audit_object.add_policy([p.get("name") for p in pass_no_token])
                return True, {"message": u"user has no token, accepted due to '{!s}'".format(
                    pass_no_token[0].get("name"))}

    # If nothing else returned, we return the wrapped function
    return wrapped_function(user_object, passw, options)
Esempio n. 6
0
def login_mode(wrapped_function, *args, **kwds):
    """
    Decorator to decorate the lib.auth.check_webui_user function.
    Depending on ACTION.LOGINMODE it sets the check_otp parameter, to signal
    that the authentication should be performed against privacyIDEA.

    :param wrapped_function: Usually the function check_webui_user
    :param `*args`: arguments user_obj and password
    :param `**kwds`: keyword arguments like options and !check_otp!
        kwds["options"] contains the flask g
    :return: calls the original function with the modified "check_otp" argument
    """
    # if tokenclass.check_pin is called in any other way, options may be None
    #  or it might have no element "g".
    options = kwds.get("options") or {}
    g = options.get("g")
    if g:
        # We need the user but we do not need the password
        user_object = args[0]
        # get the policy
        login_mode_dict = Match.user(g, scope=SCOPE.WEBUI, action=ACTION.LOGINMODE,
                                     user_object=user_object).action_values(unique=True)
        if login_mode_dict:
            # There is a login mode policy
            if list(login_mode_dict)[0] == LOGINMODE.PRIVACYIDEA:
                # The original function should check against privacyidea!
                kwds["check_otp"] = True

            if list(login_mode_dict)[0] == LOGINMODE.DISABLE:
                # The login to the webui is disabled
                raise PolicyError("The login for this user is disabled.")

    return wrapped_function(*args, **kwds)
Esempio n. 7
0
def no_detail_on_fail(request, response):
    """
    This policy function is used with the AUTHZ scope.
    If the boolean value no_detail_on_fail is set,
    the details will be stripped if
    the authentication request failed.

    :param request:
    :param response:
    :return:
    """
    content = response.json

    # get the serials from a policy definition
    detailPol = Match.action_only(
        g, scope=SCOPE.AUTHZ,
        action=ACTION.NODETAILFAIL).policies(write_to_audit_log=False)
    if detailPol and content.get("result", {}).get("value") is False:
        # The policy was set, we need to strip the details, if the
        # authentication was successful. (value=true)
        del content["detail"]
        response.set_data(json.dumps(content))
        g.audit_object.add_policy([p.get("name") for p in detailPol])

    return response
    def get_default_settings(cls, g, params):
        """
        This method returns a dictionary with additional settings for token
        enrollment.
        The settings that are evaluated are
        SCOPE.ADMIN|SCOPE.USER, action=trusted_Assertion_CA_path
        It sets a list of configured paths.

        The returned dictionary is added to the parameters of the API call.
        :param g: context object, see documentation of ``Match``
        :param params: The call parameters
        :type params: dict
        :return: default parameters
        """
        ret = {ACTION.TRUSTED_CA_PATH: DEFAULT_CA_PATH}
        (role, username, userrealm, adminuser,
         adminrealm) = determine_logged_in_userparams(g.logged_in_user, params)
        # Now we fetch CA-pathes from the policies
        paths = Match.generic(g,
                              scope=role,
                              action=ACTION.TRUSTED_CA_PATH,
                              user=username,
                              realm=userrealm,
                              adminuser=adminuser,
                              adminrealm=adminrealm).action_values(
                                  unique=False,
                                  allow_white_space_in_action=True)
        if paths:
            ret[ACTION.TRUSTED_CA_PATH] = list(paths)

        return ret
Esempio n. 9
0
    def challenge_response_wrapper(*args, **kwds):
        options = kwds.get("options", {})
        g = options.get("g")
        token = args[0]
        user_object = kwds.get("user") or User()
        if g:
            allowed_tokentypes_dict = Match.user(g, scope=SCOPE.AUTH,
                                                 action=ACTION.CHALLENGERESPONSE, user_object=user_object)\
                .action_values(unique=False, write_to_audit_log=False)
            log.debug("Found these allowed tokentypes: {0!s}".format(
                list(allowed_tokentypes_dict)))

            # allowed_tokentypes_dict.keys() is a list of actions from several policies. I
            # could look like this:
            # ["tiqr hotp totp", "tiqr motp"]
            chal_resp_found = False
            for toks in allowed_tokentypes_dict:
                if token.get_tokentype().upper() in [
                        x.upper() for x in toks.split(" ")
                ]:
                    # This token is allowed to to chal resp
                    chal_resp_found = True
                    g.audit_object.add_policy(
                        allowed_tokentypes_dict.get(toks))

            if not chal_resp_found:
                # No policy to allow this token to do challenge response
                return False

        f_result = func(*args, **kwds)
        return f_result
Esempio n. 10
0
    def _get_sms_text(options):
        """
        This returns the SMSTEXT from the policy "smstext"
        
        options contains data like clientip, g, user and also the Request 
        parameters like "challenge" or "pass".

        :param options: contains user and g object.
        :type options: dict
        :return: Message template
        :rtype: basestring
        """
        message = "<otp>"
        g = options.get("g")
        user_object = options.get("user")
        if g:
            messages = Match.user(g,
                                  scope=SCOPE.AUTH,
                                  action=SMSACTION.SMSTEXT,
                                  user_object=user_object if user_object else
                                  None).action_values(unique=True)
            if len(messages) == 1:
                message = list(messages)[0]

        # Replace the {challenge}:
        message = message.format(challenge=options.get("challenge"))
        return message
Esempio n. 11
0
def reset_all_user_tokens(wrapped_function, *args, **kwds):
    """
    Resets all tokens if the corresponding policy is set.

    :param token: The successful token, the tokenowner is used to find policies.
    :param tokenobject_list: The list of all the tokens of the user
    :param options: options dictionary containing g.
    :return: None
    """
    tokenobject_list = args[0]
    options = kwds.get("options") or {}
    g = options.get("g")
    allow_reset = kwds.get("allow_reset_all_tokens")

    r = wrapped_function(*args, **kwds)

    # A successful authentication was done
    if r[0] and g and allow_reset:
        token_owner = tokenobject_list[0].user
        reset_all = Match.user(
            g,
            scope=SCOPE.AUTH,
            action=ACTION.RESETALLTOKENS,
            user_object=token_owner if token_owner else None).policies()
        if reset_all:
            log.debug(
                "Reset failcounter of all tokens of {0!s}".format(token_owner))
            for tok_obj_reset in tokenobject_list:
                try:
                    tok_obj_reset.reset()
                except Exception:
                    log.debug("registration token does not exist anymore and "
                              "cannot be reset.")

    return r
Esempio n. 12
0
def check_tokentype(request, response):
    """
    This policy function is to be used in a decorator of an API function.
    It checks, if the token, that was used in the API call is of a type that
    is allowed to be used.

    If not, a PolicyException is raised.

    :param response: The response of the decorated function
    :type response: Response object
    :return: A new (maybe modified) response
    """
    tokentype = response.json.get("detail", {}).get("type")
    user_object = request.User
    allowed_tokentypes = Match.user(
        g, scope=SCOPE.AUTHZ, action=ACTION.TOKENTYPE,
        user_object=user_object).action_values(unique=False)
    if tokentype and allowed_tokentypes and tokentype not in allowed_tokentypes:
        # If we have tokentype policies, but
        # the tokentype is not allowed, we raise an exception
        g.audit_object.log({
            "success":
            False,
            'action_detail':
            "Tokentype {0!r} not allowed for "
            "authentication".format(tokentype)
        })
        raise PolicyError("Tokentype not allowed for authentication!")
    return response
Esempio n. 13
0
def check_serial(request, response):
    """
    This policy function is to be used in a decorator of an API function.
    It checks, if the token, that was used in the API call has a serial
    number that is allowed to be used.

    If not, a PolicyException is raised.

    :param response: The response of the decorated function
    :type response: Response object
    :return: A new (maybe modified) response
    """
    serial = response.json.get("detail", {}).get("serial")
    # get the serials from a policy definition
    allowed_serials = Match.action_only(g, scope=SCOPE.AUTHZ, action=ACTION.SERIAL).action_values(unique=False)

    # If we can compare a serial and if we do serial matching!
    if serial and allowed_serials:
        serial_matches = False
        for allowed_serial in allowed_serials:
            if re.search(allowed_serial, serial):
                serial_matches = True
                break
        if serial_matches is False:
            g.audit_object.log({"action_detail": "Serial is not allowed for "
                                                 "authentication!"})
            raise PolicyError("Serial is not allowed for authentication!")
    return response
Esempio n. 14
0
def no_detail_on_success(request, response):
    """
    This policy function is used with the AUTHZ scope.
    If the boolean value no_detail_on_success is set,
    the details will be stripped if
    the authentication request was successful.

    :param request:
    :param response:
    :return:
    """
    content = response.json

    # get the serials from a policy definition
    detailPol = Match.action_only(g, scope=SCOPE.AUTHZ, action=ACTION.NODETAILSUCCESS)\
        .policies(write_to_audit_log=False)
    if detailPol and content.get("result", {}).get("value"):
        # The policy was set, we need to strip the details, if the
        # authentication was successful. (value=true)
        # None assures that we do not get an error, if "detail" does not exist.
        content.pop("detail", None)
        response.set_data(json.dumps(content))
        g.audit_object.add_policy([p.get("name") for p in detailPol])

    return response
Esempio n. 15
0
def is_authorized(request, response):
    """
    This policy decorator is used in the AUTHZ scope to
    decorate the /validate/check and /validate/triggerchallenge endpoint.
    I will cause authentication to fail, if the policy
    authorized=deny_access is set.

    :param request:
    :param response:
    :return:
    """
    if not response.is_json:
        # This can happen with the validate/radiuscheck endpoint
        return response

    authorized_pol = Match.user(g,
                                scope=SCOPE.AUTHZ,
                                action=ACTION.AUTHORIZED,
                                user_object=request.User).action_values(
                                    unique=True,
                                    allow_white_space_in_action=True)

    if authorized_pol:
        if list(authorized_pol)[0] == AUTHORIZED.DENY:
            raise ValidateError(
                "User is not authorized to authenticate under these conditions."
            )

    return response
Esempio n. 16
0
    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 = Match.action_only(
            g, scope=SCOPE.AUTH,
            action=U2FACTION.FACETS).action_values(unique=False)
        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
Esempio n. 17
0
def config_lost_token(wrapped_function, *args, **kwds):
    """
    Decorator to decorate the lib.token.lost_token function.
    Depending on ACTION.LOSTTOKENVALID, ACTION.LOSTTOKENPWCONTENTS,
    ACTION.LOSTTOKENPWLEN it sets the check_otp parameter, to signal
    how the lostToken should be generated.

    :param wrapped_function: Usually the function lost_token()
    :param args: argument "serial" as the old serial number
    :param kwds: keyword arguments like "validity", "contents", "pw_len"
    kwds["options"] contains the flask g

    :return: calls the original function with the modified "validity",
    "contents" and "pw_len" argument
    """
    # if called in any other way, options may be None
    #  or it might have no element "g".
    from privacyidea.lib.token import get_tokens
    options = kwds.get("options") or {}
    g = options.get("g")
    if g:
        # We need the old serial number, to determine the user - if it exist.
        serial = args[0]
        toks = get_tokens(serial=serial)
        if len(toks) == 1:
            user_object = toks[0].user
            # get the policy
            contents_dict = Match.user(g, scope=SCOPE.ENROLL, action=ACTION.LOSTTOKENPWCONTENTS,
                                       user_object=user_object if user_object else None)\
                .action_values(unique=True)
            validity_dict = Match.user(g, scope=SCOPE.ENROLL, action=ACTION.LOSTTOKENVALID,
                                       user_object=user_object if user_object else None)\
                .action_values(unique=True)
            pw_len_dict = Match.user(g, scope=SCOPE.ENROLL, action=ACTION.LOSTTOKENPWLEN,
                                     user_object=user_object if user_object else None)\
                .action_values(unique=True)

            if contents_dict:
                kwds["contents"] = list(contents_dict)[0]

            if validity_dict:
                kwds["validity"] = int(list(validity_dict)[0])

            if pw_len_dict:
                kwds["pw_len"] = int(list(pw_len_dict)[0])

    return wrapped_function(*args, **kwds)
Esempio n. 18
0
def auth_lastauth(wrapped_function, user_or_serial, passw, options=None):
    """
    This decorator checks the policy settings of ACTION.LASTAUTH
    If the last authentication stored in tokeninfo last_auth_success of a
    token is exceeded, the authentication is denied.

    The wrapped function is usually token.check_user_pass, which takes the
    arguments (user, passw, options={}) OR
    token.check_serial_pass with the arguments (user, passw, options={})

    :param wrapped_function: either check_user_pass or check_serial_pass
    :param user_or_serial: either the User user_or_serial or a serial
    :param passw:
    :param options: Dict containing values for "g" and "clientip"
    :return: Tuple of True/False and reply-dictionary
    """
    # First we call the wrapped function
    res, reply_dict = wrapped_function(user_or_serial, passw, options)

    options = options or {}
    g = options.get("g")
    if g and res:
        # in case of a serial:
        if isinstance(user_or_serial, User):
            user_object = user_or_serial
            serial = reply_dict.get("serial")
        else:
            # in case of a serial:
            user_object = None
            serial = user_or_serial

        # In case of a passthru policy we have no serial in the response
        # So we may only continue, if we have a serial.
        if serial:
            from privacyidea.lib.token import get_tokens
            try:
                token = get_tokens(serial=serial)[0]
            except IndexError:
                # In the special case of a registration token,
                # the token does not exist anymore. So we immediately return
                return res, reply_dict

            last_auth_dict = Match.user(g, scope=SCOPE.AUTHZ, action=ACTION.LASTAUTH,
                                        user_object=user_object).action_values(unique=True, write_to_audit_log=False)
            if len(last_auth_dict) == 1:
                res = token.check_last_auth_newer(list(last_auth_dict)[0])
                if not res:
                    reply_dict["message"] = "The last successful " \
                                            "authentication was %s. " \
                                            "It is to long ago." % \
                                            token.get_tokeninfo(ACTION.LASTAUTH)
                    g.audit_object.add_policy(next(iter(last_auth_dict.values())))

            # set the last successful authentication, if res still true
            if res:
                token.add_tokeninfo(ACTION.LASTAUTH,
                                    datetime.datetime.now(tzlocal()))

    return res, reply_dict
Esempio n. 19
0
def auth_cache(wrapped_function, user_object, passw, options=None):
    """
    Decorate lib.token:check_user_pass. Verify, if the authentication can 
    be found in the auth_cache.
    
    :param wrapped_function: usually "check_user_pass"
    :param user_object: User who tries to authenticate
    :param passw: The PIN and OTP
    :param options: Dict containing values for "g" and "clientip".
    :return: Tuple of True/False and reply-dictionary
    """
    options = options or {}
    g = options.get("g")
    auth_cache_dict = None

    if g:
        auth_cache_dict = Match.user(g,
                                     scope=SCOPE.AUTH,
                                     action=ACTION.AUTH_CACHE,
                                     user_object=user_object).action_values(
                                         unique=True, write_to_audit_log=False)
        if auth_cache_dict:
            auth_times = list(auth_cache_dict)[0].split("/")

            # determine first_auth from policy!
            first_offset = parse_timedelta(auth_times[0])
            first_auth = datetime.datetime.utcnow() - first_offset
            last_auth = first_auth  # Default if no last auth exists
            max_auths = 0  # Default value, 0 has no effect on verification

            # Use auth cache when number of allowed authentications is defined
            if len(auth_times) == 2:
                if re.match(r"^\d+$", auth_times[1]):
                    max_auths = int(auth_times[1])
                else:
                    # Determine last_auth delta from policy
                    last_offset = parse_timedelta(auth_times[1])
                    last_auth = datetime.datetime.utcnow() - last_offset

            result = verify_in_cache(user_object.login,
                                     user_object.realm,
                                     user_object.resolver,
                                     passw,
                                     first_auth=first_auth,
                                     last_auth=last_auth,
                                     max_auths=max_auths)

            if result:
                g.audit_object.add_policy(next(iter(auth_cache_dict.values())))
                return True, {"message": "Authenticated by AuthCache."}

    # If nothing else returned, call the wrapped function
    res, reply_dict = wrapped_function(user_object, passw, options)
    if auth_cache_dict and res:
        # If authentication is successful, we store the password in auth_cache
        add_to_cache(user_object.login, user_object.realm,
                     user_object.resolver, passw)
    return res, reply_dict
Esempio n. 20
0
def auth_otppin(wrapped_function, *args, **kwds):
    """
    Decorator to decorate the tokenclass.check_pin function.

    Depending on the ACTION.OTPPIN it
     * either simply accepts an empty pin
     * checks the pin against the userstore
     * or passes the request to the wrapped_function

    :param wrapped_function: In this case the wrapped function should be
        :py:func:`privacyidea.lib.tokenclass.TokenClass.check_pin`
    :param `*args`: args[1] is the pin
    :param `**kwds`: kwds["options"] contains the flask g
    :return: True or False
    """
    # if tokenclass.check_pin is called in any other way, options may be None
    #  or it might have no element "g".
    options = kwds.get("options") or {}
    g = options.get("g")
    if g:
        token = args[0]
        pin = args[1]
        clientip = options.get("clientip")
        user_object = kwds.get("user")
        if not user_object:
            # No user in the parameters, so we need to determine the owner of
            #  the token
            user_object = token.user
            realms = token.get_realms()
            if not user_object and len(realms):
                # if the token has not owner, we take a realm.
                user_object = User("", realm=realms[0])
        if not user_object:
            # If we still have no user and no tokenrealm, we create an empty
            # user object.
            user_object = User("", realm="")
        # get the policy
        otppin_dict = Match.user(
            g, scope=SCOPE.AUTH, action=ACTION.OTPPIN,
            user_object=user_object).action_values(unique=True)
        if otppin_dict:
            if list(otppin_dict)[0] == ACTIONVALUE.NONE:
                if pin == "":
                    # No PIN checking, we expect an empty PIN!
                    return True
                else:
                    return False

            if list(otppin_dict)[0] == ACTIONVALUE.USERSTORE:
                rv = user_object.check_password(pin)
                return rv is not None

    # call and return the original check_pin function
    return wrapped_function(*args, **kwds)
Esempio n. 21
0
def register_status():
    """
    This endpoint returns the information if registration is allowed or not.
    This is used by the UI to either display the registration button or not.

    :return: JSON with value=True or value=False
    """
    resolvername = Match.action_only(g, scope=SCOPE.REGISTER, action=ACTION.RESOLVER)\
        .action_values(unique=True)
    result = bool(resolvername)
    g.audit_object.log({"info": result, "success": True})
    return send_result(result)
Esempio n. 22
0
def check_verify_enrollment(request, response):
    """
    This policy decorator is used in the ENROLL scope to
    decorate the /token/init
    If will check for action=verify_enrollment and ask the user
    in a 2nd step to provide information to verify, that the token was successfully enrolled.

    :param request:
    :param response:
    :return:
    """
    serial = response.json.get("detail").get("serial")
    verify = request.all_data.get("verify")
    if verify:
        # In case we are in a 2nd step verification, we must early exit
        return response
    tokenobj_list = get_tokens(serial=serial)
    if len(tokenobj_list) == 1:
        tokenobj = tokenobj_list[0]
        # check if this token type can do verify enrollment
        if tokenobj.can_verify_enrollment:
            # Get policies
            verify_pol_dict = Match.user(
                g,
                scope=SCOPE.ENROLL,
                action=ACTION.VERIFY_ENROLLMENT,
                user_object=request.User).action_values(
                    unique=False,
                    allow_white_space_in_action=True,
                    write_to_audit_log=False)
            # verify_pol_dict.keys() is a list of actions from several policies. It
            # could look like this:
            # ["hotp totp", "hotp email"]
            do_verify_enrollment = False
            for toks in verify_pol_dict:
                if tokenobj.get_tokentype().upper() in [
                        x.upper() for x in toks.split(" ")
                ]:
                    # This token is supposed to do verify enrollment
                    do_verify_enrollment = True
                    g.audit_object.add_policy(verify_pol_dict.get(toks))
            if do_verify_enrollment:
                content = response.json
                content["detail"][
                    "verify"] = tokenobj.prepare_verify_enrollment()
                content["detail"]["rollout_state"] = ROLLOUTSTATE.VERIFYPENDING
                tokenobj.token.rollout_state = ROLLOUTSTATE.VERIFYPENDING
                tokenobj.token.save()
                response.set_data(json.dumps(content))
    else:
        log.warning("No distinct token object found in enrollment response!")

    return response
Esempio n. 23
0
def add_user_detail_to_response(request, response):
    """
    This policy decorated is used in the AUTHZ scope.
    If the boolean value add_user_in_response is set,
    the details will contain a dictionary "user" with all user details.

    :param request:
    :param response:
    :return:
    """
    content = response.json

    # Check for ADD USER IN RESPONSE
    detail_pol = Match.user(g, scope=SCOPE.AUTHZ, action=ACTION.ADDUSERINRESPONSE, user_object=request.User)\
        .policies(write_to_audit_log=False)
    if detail_pol and content.get("result", {}).get("value") and request.User:
        # The policy was set, we need to add the user
        #  details
        ui = request.User.info.copy()
        ui["password"] = ""
        for key, value in ui.items():
            if type(value) == datetime.datetime:
                ui[key] = str(value)
        content.setdefault("detail", {})["user"] = ui
        g.audit_object.add_policy([p.get("name") for p in detail_pol])

    # Check for ADD RESOLVER IN RESPONSE
    detail_pol = Match.user(g, scope=SCOPE.AUTHZ, action=ACTION.ADDRESOLVERINRESPONSE, user_object=request.User)\
        .policies(write_to_audit_log=False)
    if detail_pol and content.get("result", {}).get("value") and request.User:
        # The policy was set, we need to add the resolver and the realm
        content.setdefault("detail",
                           {})["user-resolver"] = request.User.resolver
        content["detail"]["user-realm"] = request.User.realm
        g.audit_object.add_policy([p.get("name") for p in detail_pol])

    response.set_data(json.dumps(content))
    return response
Esempio n. 24
0
def is_password_reset(g):
    """
    Check if password reset is allowed.

    We need to check, if a user policy with password_reset exists AND if an
    editable resolver exists. Otherwise password_reset does not make any sense.

    :return: True or False
    """
    rlist = get_resolver_list(editable=True)
    log.debug("Number of editable resolvers: {0!s}".format(len(rlist)))
    pwreset = Match.generic(
        g, scope=SCOPE.USER,
        action=ACTION.PASSWORDRESET).allowed(write_to_audit_log=False)
    log.debug("Password reset allowed via policies: {0!s}".format(pwreset))
    return bool(rlist and pwreset)
Esempio n. 25
0
    def _get_auto_email(options):
        """
        This returns the AUTOEMAIL setting.

        :param options: contains user and g object.
        :optins type: dict
        :return: True if an SMS should be sent automatically
        :rtype: bool
        """
        autosms = False
        g = options.get("g")
        user_object = options.get("user")
        if g:
            autoemailpol = Match.user(g, scope=SCOPE.AUTH, action=EMAILACTION.EMAILAUTO, user_object=user_object).policies()
            autosms = len(autoemailpol) >= 1

        return autosms
Esempio n. 26
0
def check_tokeninfo(request, response):
    """
    This policy function is used as a decorator for the validate API.
    It checks after a successful authentication if the token has a matching
    tokeninfo field. If it does not match, authorization is denied. Then
    a PolicyException is raised.

    :param response: The response of the decorated function
    :type response: Response object
    :return: A new modified response
    """
    serial = response.json.get("detail", {}).get("serial")

    if serial:
        tokeninfos_pol = Match.action_only(g, scope=SCOPE.AUTHZ, action=ACTION.TOKENINFO)\
            .action_values(unique=False, allow_white_space_in_action=True)
        if tokeninfos_pol:
            tokens = get_tokens(serial=serial)
            if len(tokens) == 1:
                token_obj = tokens[0]
                for tokeninfo_pol in tokeninfos_pol:
                    try:
                        key, regex, _r = tokeninfo_pol.split("/")
                        value = token_obj.get_tokeninfo(key, "")
                        if re.search(regex, value):
                            log.debug(
                                u"Regular expression {0!s} "
                                u"matches the tokeninfo field {1!s}.".format(
                                    regex, key))
                        else:
                            log.info(
                                u"Tokeninfo field {0!s} with contents {1!s} "
                                u"does not match {2!s}".format(
                                    key, value, regex))
                            raise PolicyError(
                                "Tokeninfo field {0!s} with contents does not"
                                " match regular expression.".format(key))
                    except ValueError:
                        log.warning(u"invalid tokeinfo policy: {0!s}".format(
                            tokeninfo_pol))

    return response
def generic_challenge_response_resync(wrapped_function, *args, **kwds):
    """
    Check if the authentication request results in an autosync

    Conditions: To do so we check for "otp1c" in the tokeninfo data.

    Policies: A policy defines that the token resync should be allowed this way.
    Note: The general config "autoresync" needs to be set anyways.

    args are:
    :param tokenobject_list: The list of all the tokens of the user, that will be checked
    :param passw: The password presented in the authentication.

    kwds are:
    :param options: options dictionary containing g
    :param user: The user_obj
    """
    success, reply_dict = wrapped_function(*args, **kwds)

    options = kwds.get("options") or {}
    # After a failed authentication, we check if the token has an otp1c
    if not success and reply_dict.get("serial"):
        # FIXME: Only works if the one token has a unique PIN
        serial = reply_dict.get("serial")
        g = options.get("g")
        # The tokenlist can contain more than one token. So we get the matching token object
        token_obj = next(t for t in args[0] if t.token.serial == serial)
        if token_obj and token_obj.get_tokeninfo("otp1c"):
            # We have an entry for resync
            if g and Match.token(g,
                                 scope=SCOPE.AUTH,
                                 action=ACTION.RESYNC_VIA_MULTICHALLENGE,
                                 token_obj=token_obj).any():
                reply_dict = _create_challenge(
                    token_obj, CHALLENGE_TYPE.RESYNC,
                    _("To resync your token, please enter the next OTP value"))

    return success, reply_dict
Esempio n. 28
0
    def _get_email_text_or_subject(options,
                                   action=EMAILACTION.EMAILTEXT,
                                   default="<otp>"):
        """
        This returns the EMAILTEXT or EMAILSUBJECT from the policy
        "emailtext" or "emailsubject

        :param options: contains user and g object.
        :type options: dict
        :param action: The action - either emailtext or emailsubject
        :param default: If no policy can be found, this is the default text
        :return: Message template, MIME type (one of "plain", "html")
        :rtype: (basestring, basestring)
        """
        message = default
        mimetype = "plain"
        g = options.get("g")
        user_object = options.get("user")
        if g:
            messages = Match.user(g, scope=SCOPE.AUTH, action=action, user_object=user_object if user_object else None)\
                .action_values(unique=True, allow_white_space_in_action=True)
            if len(messages) == 1:
                message = list(messages)[0]

        message = message.format(challenge=options.get("challenge"))
        if message.startswith("file:"):
            # We read the template from the file.
            try:
                with open(message[5:], "r") as f:
                    message = f.read()
                    mimetype = "html"
            except Exception as e:  # pragma: no cover
                message = default
                log.warning(u"Failed to read email template: {0!r}".format(e))
                log.debug(u"{0!s}".format(traceback.format_exc()))

        return message, mimetype
Esempio n. 29
0
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 = response.json
    # check, if the authentication was successful, then we need to do nothing
    if content.get("result").get("value") is False:
        user_obj = request.User
        #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
            autoassign_values = Match.user(g, scope=SCOPE.ENROLL, action=ACTION.AUTOASSIGN,
                                           user_object=user_obj).action_values(unique=True, write_to_audit_log=False)
            # check if the user has no token
            if autoassign_values and get_tokens(user=user_obj, count=True) == 0:
                # Check if 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 list(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
                                detail = content.setdefault("detail", {})
                                detail["serial"] = token_obj.token.serial
                                detail["otplen"] = token_obj.token.otplen
                                detail["type"] = token_obj.type
                                detail["message"] = "Token assigned to user via Autoassignment"
                                response.set_data(json.dumps(content))

                                g.audit_object.log(
                                    {"success": True,
                                     "info":
                                         "Token assigned via auto assignment",
                                     "serial": token_obj.token.serial})
                                # The token was assigned by autoassign. We save the first policy name
                                g.audit_object.add_policy(next(iter(autoassign_values.values())))
                                break

    return response
Esempio n. 30
0
def get_webui_settings(request, response):
    """
    This decorator is used in the /auth API to add configuration information
    like the logout_time or the policy_template_url to the response.
    :param request: flask request object
    :param response: flask response object
    :return: the response
    """
    content = response.json
    # check, if the authentication was successful, then we need to do nothing
    if content.get("result").get("status") is True:
        role = content.get("result").get("value").get("role")
        loginname = content.get("result").get("value").get("username")
        realm = content.get("result").get("value").get("realm") or get_default_realm()

        # At this point the logged in user is not necessarily a user object. It can
        # also be a local admin.
        logout_time_pol = Match.generic(g, scope=SCOPE.WEBUI, action=ACTION.LOGOUTTIME,
                                        user=loginname, realm=realm).action_values(unique=True)
        timeout_action_pol = Match.generic(g, scope=SCOPE.WEBUI, action=ACTION.TIMEOUT_ACTION,
                                           user=loginname, realm=realm).action_values(unique=True)
        token_page_size_pol = Match.generic(g, scope=SCOPE.WEBUI, action=ACTION.TOKENPAGESIZE,
                                            user=loginname, realm=realm).action_values(unique=True)
        user_page_size_pol = Match.generic(g, scope=SCOPE.WEBUI, action=ACTION.USERPAGESIZE,
                                           user=loginname, realm=realm).action_values(unique=True)
        token_wizard_2nd = bool(role == ROLE.USER
                                and Match.generic(g, scope=SCOPE.WEBUI, action=ACTION.TOKENWIZARD2ND,
                                                  user=loginname, realm=realm).policies())
        admin_dashboard = (role == ROLE.ADMIN
                           and Match.generic(g, scope=SCOPE.WEBUI, action=ACTION.ADMIN_DASHBOARD,
                                         user=loginname, realm=realm).any())
        token_wizard = False
        dialog_no_token = False
        if role == ROLE.USER:
            user_obj = User(loginname, realm)
            user_token_num = get_tokens(user=user_obj, count=True)
            token_wizard_pol = Match.user(g, scope=SCOPE.WEBUI, action=ACTION.TOKENWIZARD, user_object=user_obj).any()
            # We also need to check, if the user has not tokens assigned.
            # If the user has no tokens, we run the wizard. If the user
            # already has tokens, we do not run the wizard.
            token_wizard = token_wizard_pol and (user_token_num == 0)

            dialog_no_token_pol = Match.user(g, scope=SCOPE.WEBUI, action=ACTION.DIALOG_NO_TOKEN,
                                             user_object=user_obj).any()
            dialog_no_token = dialog_no_token_pol and (user_token_num == 0)
        user_details_pol = Match.generic(g, scope=SCOPE.WEBUI, action=ACTION.USERDETAILS,
                                         user=loginname, realm=realm).policies()
        search_on_enter = Match.generic(g, scope=SCOPE.WEBUI, action=ACTION.SEARCH_ON_ENTER,
                                        user=loginname, realm=realm).policies()
        hide_welcome = Match.generic(g, scope=SCOPE.WEBUI, action=ACTION.HIDE_WELCOME,
                                     user=loginname, realm=realm).any()
        hide_buttons = Match.generic(g, scope=SCOPE.WEBUI, action=ACTION.HIDE_BUTTONS,
                                     user=loginname, realm=realm).any()
        default_tokentype_pol = Match.generic(g, scope=SCOPE.WEBUI, action=ACTION.DEFAULT_TOKENTYPE,
                                              user=loginname, realm=realm).action_values(unique=True)
        show_seed = Match.generic(g, scope=SCOPE.WEBUI, action=ACTION.SHOW_SEED,
                                  user=loginname, realm=realm).any()
        show_node = Match.generic(g, scope=SCOPE.WEBUI, action=ACTION.SHOW_NODE, realm=realm).any()
        qr_ios_authenticator = Match.generic(g, scope=SCOPE.WEBUI, action=ACTION.SHOW_IOS_AUTHENTICATOR,
                                             user=loginname, realm=realm).any()
        qr_android_authenticator = Match.generic(g, scope=SCOPE.WEBUI, action=ACTION.SHOW_ANDROID_AUTHENTICATOR,
                                                 user=loginname, realm=realm).any()
        qr_custom_authenticator_url = Match.generic(g, scope=SCOPE.WEBUI, action=ACTION.SHOW_CUSTOM_AUTHENTICATOR,
                                                    user=loginname, realm=realm).action_values(unique=True)

        qr_image_android = create_img(DEFAULT_ANDROID_APP_URL) if qr_android_authenticator else None
        qr_image_ios = create_img(DEFAULT_IOS_APP_URL) if qr_ios_authenticator else None
        qr_image_custom = create_img(list(qr_custom_authenticator_url)[0]) if qr_custom_authenticator_url else None
        token_page_size = DEFAULT_PAGE_SIZE
        user_page_size = DEFAULT_PAGE_SIZE
        default_tokentype = DEFAULT_TOKENTYPE
        if len(token_page_size_pol) == 1:
            token_page_size = int(list(token_page_size_pol)[0])
        if len(user_page_size_pol) == 1:
            user_page_size = int(list(user_page_size_pol)[0])
        if len(default_tokentype_pol) == 1:
            default_tokentype = list(default_tokentype_pol)[0]

        logout_time = DEFAULT_LOGOUT_TIME
        if len(logout_time_pol) == 1:
            logout_time = int(list(logout_time_pol)[0])

        timeout_action = DEFAULT_TIMEOUT_ACTION
        if len(timeout_action_pol) == 1:
            timeout_action = list(timeout_action_pol)[0]

        policy_template_url_pol = Match.action_only(g, scope=SCOPE.WEBUI,
                                                    action=ACTION.POLICYTEMPLATEURL).action_values(unique=True)
        policy_template_url = DEFAULT_POLICY_TEMPLATE_URL
        if len(policy_template_url_pol) == 1:
            policy_template_url = list(policy_template_url_pol)[0]

        indexed_preset_attribute = Match.realm(g, scope=SCOPE.WEBUI, action="indexedsecret_preset_attribute",
                                               realm=realm).action_values(unique=True)
        if len(indexed_preset_attribute) == 1:
            content["result"]["value"]["indexedsecret_preset_attribute"] = list(indexed_preset_attribute)[0]

        # This only works for users, because the value of the policy does not change while logged in.
        if role == ROLE.USER and \
                Match.user(g, SCOPE.USER, "indexedsecret_force_attribute", user_obj).action_values(unique=False):
            content["result"]["value"]["indexedsecret_force_attribute"] = 1

        content["result"]["value"]["logout_time"] = logout_time
        content["result"]["value"]["token_page_size"] = token_page_size
        content["result"]["value"]["user_page_size"] = user_page_size
        content["result"]["value"]["policy_template_url"] = policy_template_url
        content["result"]["value"]["default_tokentype"] = default_tokentype
        content["result"]["value"]["user_details"] = len(user_details_pol) > 0
        content["result"]["value"]["token_wizard"] = token_wizard
        content["result"]["value"]["token_wizard_2nd"] = token_wizard_2nd
        content["result"]["value"]["admin_dashboard"] = admin_dashboard
        content["result"]["value"]["dialog_no_token"] = dialog_no_token
        content["result"]["value"]["search_on_enter"] = len(search_on_enter) > 0
        content["result"]["value"]["timeout_action"] = timeout_action
        content["result"]["value"]["hide_welcome"] = hide_welcome
        content["result"]["value"]["hide_buttons"] = hide_buttons
        content["result"]["value"]["show_seed"] = show_seed
        content["result"]["value"]["show_node"] = get_privacyidea_node() if show_node else ""
        content["result"]["value"]["subscription_status"] = subscription_status()
        content["result"]["value"]["qr_image_android"] = qr_image_android
        content["result"]["value"]["qr_image_ios"] = qr_image_ios
        content["result"]["value"]["qr_image_custom"] = qr_image_custom
        response.set_data(json.dumps(content))
    return response