def test_13_parse_time_offset_from_now(self): td = parse_timedelta("+5s") self.assertEqual(td, timedelta(seconds=5)) td = parse_timedelta("-12m") self.assertEqual(td, timedelta(minutes=-12)) td = parse_timedelta("+123h") self.assertEqual(td, timedelta(hours=123)) td = parse_timedelta("+2d") self.assertEqual(td, timedelta(days=2)) # It is allowed to start without a +/- which would mean a + td = parse_timedelta("12d") self.assertEqual(td, timedelta(days=12)) # Does not contains numbers self.assertRaises(Exception, parse_timedelta, "+twod") s, td = parse_time_offset_from_now("Hello {now}+5d with 5 days.") self.assertEqual(s, "Hello {now} with 5 days.") self.assertEqual(td, timedelta(days=5)) s, td = parse_time_offset_from_now("Hello {current_time}+5m!") self.assertEqual(s, "Hello {current_time}!") self.assertEqual(td, timedelta(minutes=5)) s, td = parse_time_offset_from_now("Hello {current_time}-3habc") self.assertEqual(s, "Hello {current_time}abc") self.assertEqual(td, timedelta(hours=-3))
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
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: clientip = options.get("clientip") policy_object = g.policy_object auth_cache_dict = policy_object.get_action_values( action=ACTION.AUTH_CACHE, scope=SCOPE.AUTH, realm=user_object.realm, resolver=user_object.resolver, user=user_object.login, client=clientip, unique=True) if auth_cache_dict: # verify in cache and return an early success auth_times = list(auth_cache_dict)[0].split("/") # determine first_auth from policy! first_offset = parse_timedelta(auth_times[0]) if len(auth_times) == 2: # Determine last_auth from policy last_offset = parse_timedelta(auth_times[1]) else: # If there is no last_auth, it is equal to first_auth last_offset = first_offset first_auth = datetime.datetime.utcnow() - first_offset 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) 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
def auditlog_age(request=None, action=None): """ This pre condition checks for the policy auditlog_age and set the "timelimit" paramter of the audit search API. Check ACTION.AUDIT_AGE The decorator can wrap GET /audit/ :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 = request.User policy_object = g.policy_object role = g.logged_in_user.get("role") if role == ROLE.ADMIN: scope = SCOPE.ADMIN adminrealm = g.logged_in_user.get("realm") user = g.logged_in_user.get("username") realm = user_object.realm else: scope = SCOPE.USER adminrealm = None user = user_object.login realm = user_object.realm audit_age = policy_object.get_action_values(ACTION.AUDIT_AGE, scope=scope, adminrealm=adminrealm, realm=realm, user=user, client=g.client_ip, unique=True) timelimit = None timelimit_s = None for aa in audit_age: if not timelimit: timelimit_s = aa timelimit = parse_timedelta(timelimit_s) else: # We will use the longest allowed timelimit if parse_timedelta(aa) > timelimit: timelimit_s = aa timelimit = parse_timedelta(timelimit_s) log.debug("auditlog_age: {0!s}".format(timelimit_s)) request.all_data["timelimit"] = timelimit_s return True
def auditlog_age(request=None, action=None): """ This pre condition checks for the policy auditlog_age and set the "timelimit" parameter of the audit search API. Check ACTION.AUDIT_AGE The decorator can wrap GET /audit/ :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 = request.User policy_object = g.policy_object role = g.logged_in_user.get("role") if role == ROLE.ADMIN: scope = SCOPE.ADMIN adminrealm = g.logged_in_user.get("realm") user = g.logged_in_user.get("username") realm = user_object.realm else: scope = SCOPE.USER adminrealm = None user = user_object.login realm = user_object.realm audit_age = policy_object.get_action_values(ACTION.AUDIT_AGE, scope=scope, adminrealm=adminrealm, realm=realm, user=user, client=g.client_ip, unique=True) timelimit = None timelimit_s = None for aa in audit_age: if not timelimit: timelimit_s = aa timelimit = parse_timedelta(timelimit_s) else: # We will use the longest allowed timelimit if parse_timedelta(aa) > timelimit: timelimit_s = aa timelimit = parse_timedelta(timelimit_s) log.debug("auditlog_age: {0!s}".format(timelimit_s)) request.all_data["timelimit"] = timelimit_s return True
def test_02_timedelta(self): tdelta = parse_timedelta("123d") self.assertEqual(tdelta, timedelta(days=123)) tdelta = parse_timedelta("31h") self.assertEqual(tdelta, timedelta(hours=31)) tdelta = parse_timedelta(" 2y") self.assertEqual(tdelta, timedelta(days=2*365)) # A missing time specifier raises an Exception self.assertRaises(Exception, parse_timedelta, "7") # A non number raises an Exception self.assertRaises(Exception, parse_timedelta, "sevenm")
def search(config, param=None, user=None): """ Returns a list of audit entries, supports pagination :param config: The config entries from the file config :return: Audit dictionary with information about the previous and next pages. """ audit = getAudit(config) sortorder = "desc" page_size = 15 page = 1 timelimit = None hidden_columns = None # The filtering dictionary param = param or {} # special treatment for: # sortorder, page, pagesize if "sortorder" in param: sortorder = param["sortorder"] del param["sortorder"] if "page" in param: page = param["page"] del param["page"] if "page_size" in param: page_size = param["page_size"] del param["page_size"] if "timelimit" in param: timelimit = parse_timedelta(param["timelimit"]) del param["timelimit"] if "hidden_columns" in param: hidden_columns = param["hidden_columns"] del param["hidden_columns"] pagination = audit.search(param, sortorder=sortorder, page=page, page_size=page_size, timelimit=timelimit) # delete hidden columns from response if hidden_columns: for i in range(len(pagination.auditdata)): pagination.auditdata[i] = OrderedDict({ audit_col: value for audit_col, value in pagination.auditdata[i].items() if audit_col not in hidden_columns }) ret = { "auditdata": pagination.auditdata, "prev": pagination.prev, "next": pagination.next, "current": pagination.page, "count": pagination.total } return ret
def download_csv(csvfile=None): """ Download the audit entry as CSV file. Params can be passed as key-value-pairs. **Example request**: .. sourcecode:: http GET /audit/audit.csv?realm=realm1 HTTP/1.1 Host: example.com Accept: text/csv **Example response**: .. sourcecode:: http HTTP/1.1 200 OK Content-Type: text/csv { "id": 1, "jsonrpc": "2.0", "result": { "status": true, "value": [ { "serial": "....", "missing_line": "..." } ] }, "version": "privacyIDEA unknown" } """ audit = getAudit(current_app.config) g.audit_object.log({'success': True}) param = request.all_data if "timelimit" in param: timelimit = parse_timedelta(param["timelimit"]) del param["timelimit"] else: timelimit = None return current_app.response_class(stream_with_context( audit.csv_generator(param=param, timelimit=timelimit)), mimetype='text/csv', headers={ "Content-Disposition": ("attachment; " "filename=%s" % csvfile) })
def download_csv(csvfile=None): """ Download the audit entry as CSV file. Params can be passed as key-value-pairs. **Example request**: .. sourcecode:: http GET /audit/audit.csv?realm=realm1 HTTP/1.1 Host: example.com Accept: text/csv **Example response**: .. sourcecode:: http HTTP/1.1 200 OK Content-Type: text/csv { "id": 1, "jsonrpc": "2.0", "result": { "status": true, "value": [ { "serial": "....", "missing_line": "..." } ] }, "version": "privacyIDEA unknown" } """ audit = getAudit(current_app.config) g.audit_object.log({'success': True}) param = request.all_data if "timelimit" in param: timelimit = parse_timedelta(param["timelimit"]) del param["timelimit"] else: timelimit = None return Response(stream_with_context(audit.csv_generator(param=param, timelimit=timelimit)), mimetype='text/csv', headers={"Content-Disposition": ("attachment; " "filename=%s" % csvfile)})
def search(config, param=None, user=None): """ Returns a list of audit entries, supports pagination :param config: The config entries from the file config :return: Audit dictionary with information about the previous and next pages. """ audit = getAudit(config) sortorder = "desc" page_size = 15 page = 1 timelimit = None # The filtering dictionary param = param or {} # special treatment for: # sortorder, page, pagesize if "sortorder" in param: sortorder = param["sortorder"] del param["sortorder"] if "page" in param: page = param["page"] del param["page"] if "page_size" in param: page_size = param["page_size"] del param["page_size"] if "timelimit" in param: timelimit = parse_timedelta(param["timelimit"]) del param["timelimit"] pagination = audit.search(param, sortorder=sortorder, page=page, page_size=page_size, timelimit=timelimit) ret = { "auditdata": pagination.auditdata, "prev": pagination.prev, "next": pagination.next, "current": pagination.page, "count": pagination.total } return ret
def search(config, param=None, user=None): """ Returns a list of audit entries, supports pagination :param config: The config entries from the file config :return: Audit dictionary with information about the previous and next pages. """ audit = getAudit(config) sortorder = "desc" page_size = 15 page = 1 timelimit = None # The filtering dictionary param = param or {} # special treatment for: # sortorder, page, pagesize if "sortorder" in param: sortorder = param["sortorder"] del param["sortorder"] if "page" in param: page = param["page"] del param["page"] if "page_size" in param: page_size = param["page_size"] del param["page_size"] if "timelimit" in param: timelimit = parse_timedelta(param["timelimit"]) del param["timelimit"] pagination = audit.search(param, sortorder=sortorder, page=page, page_size=page_size, timelimit=timelimit) ret = {"auditdata": pagination.auditdata, "prev": pagination.prev, "next": pagination.next, "current": pagination.page, "count": pagination.total} return ret
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: clientip = options.get("clientip") policy_object = g.policy_object # in case of a serial: realm = None login = None serial = user_or_serial try: # Assume we have a user realm = user_or_serial.realm login = user_or_serial.login serial = reply_dict.get("serial") except Exception: # in case of a serial: realm = None login = 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 = policy_object.get_action_values( action=ACTION.LASTAUTH, scope=SCOPE.AUTHZ, realm=realm, user=login, client=clientip, unique=True) if len(last_auth) == 1: # The tdelta in the policy tdelta = parse_timedelta(last_auth[0]) # The last successful authentication of the token last_success_auth = token.get_tokeninfo(ACTION.LASTAUTH) if last_success_auth: log.debug("Compare the last successful authentication of " "token %s with policy " "tdelat %s: %s" % (serial, tdelta, last_success_auth)) # convert string of last_success_auth last_success_auth = datetime.datetime.strptime( last_success_auth, "%Y-%m-%d %H:%M:%S.%f") # The last auth is to far in the past if last_success_auth + tdelta < datetime.datetime.now(): res = False log.debug("The last successful authentication is too old.") reply_dict["message"] = "The last successful " \ "authentication was %s. It is to " \ "long ago." % last_success_auth # set the last successful authentication, if res still true if res: token.add_tokeninfo(ACTION.LASTAUTH, datetime.datetime.utcnow()) return res, reply_dict
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: clientip = options.get("clientip") policy_object = g.policy_object # in case of a serial: realm = None login = None serial = user_or_serial try: # Assume we have a user realm = user_or_serial.realm login = user_or_serial.login serial = reply_dict.get("serial") except Exception: # in case of a serial: realm = None login = 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 = policy_object.get_action_values(action=ACTION.LASTAUTH, scope=SCOPE.AUTHZ, realm=realm, user=login, client=clientip, unique=True) if len(last_auth) == 1: # The tdelta in the policy tdelta = parse_timedelta(last_auth[0]) # The last successful authentication of the token last_success_auth = token.get_tokeninfo(ACTION.LASTAUTH) if last_success_auth: log.debug("Compare the last successful authentication of " "token %s with policy " "tdelat %s: %s" % (serial, tdelta, last_success_auth)) # convert string of last_success_auth last_success_auth = datetime.datetime.strptime( last_success_auth, "%Y-%m-%d %H:%M:%S.%f") # The last auth is to far in the past if last_success_auth + tdelta < datetime.datetime.now(): res = False log.debug( "The last successful authentication is too old.") reply_dict["message"] = "The last successful " \ "authentication was %s. It is to " \ "long ago." % last_success_auth # set the last successful authentication, if res still true if res: token.add_tokeninfo(ACTION.LASTAUTH, datetime.datetime.utcnow()) return res, reply_dict