diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 872a9382..31d85d88 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,6 +1,12 @@ Unreleased ========== +0.7.3 (2020-05-14) +================== + +- Add: check SOA serial limit before any operation +- Fix: reset serial counter if day changed + 0.7.2 (2020-05-13) ================== diff --git a/api/app/controllers/api/record.py b/api/app/controllers/api/record.py index c98bd099..6e817093 100644 --- a/api/app/controllers/api/record.py +++ b/api/app/controllers/api/record.py @@ -11,21 +11,49 @@ from app.vendors.rest import response -def update_serial(zone, increment="01"): +def get_serial_resource(zone): soa_record = record_model.get_soa_record(zone) rdata_record = model.get_one( table="rdata", field="record_id", value=soa_record["id"] ) rdatas = rdata_record["rdata"].split(" ") - serial = rdatas[2] # serial MUST be in this position + serial = rdatas[2] + # `serial_counter` is the last two digit of serial value (YYYYMMDDnn) + serial_counter = serial[-2:] + serial_date = serial[:-2] + + return { + "soa_record": soa_record, + "rdata_record": rdata_record, + "serial": serial, + "serial_counter": serial_counter, + "serial_date": serial_date, + } + + +def check_serial_limit(serial_resource): + serial_counter = serial_resource["serial_counter"] + serial_date = serial_resource["serial_date"] + today_date = helpers.soa_time_set() + + if int(serial_counter) > 97 and serial_date == today_date: + # knot maximum of nn is 99 + # 97 was chosen because serial + # increment can be twice at time + raise ValueError("Zone Change Limit Reached") + + +def update_serial(serial_resource, increment="01"): + serial = serial_resource["serial"] + soa_record = serial_resource["soa_record"] + rdata_record = serial_resource["rdata_record"] + new_serial = helpers.increment_serial(serial, increment) new_rdata = helpers.replace_serial(rdata_record["rdata"], new_serial) - content_data = { "where": {"record_id": soa_record["id"]}, "data": {"rdata": new_rdata, "record_id": soa_record["id"]}, } - model.update("rdata", data=content_data) @@ -109,6 +137,12 @@ def post(self): except Exception as e: return response(422, message=f"{e}") + try: + serial_resource = get_serial_resource(zone) + check_serial_limit(serial_resource) + except Exception as e: + return response(429, message=f"{e}") + try: data = { "owner": owner, @@ -126,10 +160,7 @@ def post(self): # increment serial after adding new record rtype = type_model.get_type_by_recordid(record_id) if rtype != "SOA": - try: - update_serial(zone) - except Exception as e: - return response(429, message=f"{e}") + update_serial(serial_resource) record = model.get_one(table="record", field="id", value=record_id) data = record_model.get_other_data(record) @@ -176,6 +207,12 @@ def put(self, record_id): except Exception as e: return response(422, message=f"{e}") + try: + serial_resource = get_serial_resource(zone) + check_serial_limit(serial_resource) + except Exception as e: + return response(429, message=f"{e}") + try: data = { "where": {"id": record_id}, @@ -201,10 +238,7 @@ def put(self, record_id): # increment serial after adding new record rtype = type_model.get_type_by_recordid(record_id) if rtype != "SOA": - try: - update_serial(zone, "02") - except Exception as e: - return response(429, message=f"{e}") + update_serial(serial_resource, "02") record = model.get_one(table="record", field="id", value=record_id) data_ = record_model.get_other_data(record) @@ -229,17 +263,21 @@ def delete(self, record_id): except Exception: return response(404) + zone = zone_model.get_zone_by_record(record_id) + zone_name = zone["zone"] + + try: + serial_resource = get_serial_resource(zone_name) + check_serial_limit(serial_resource) + except Exception as e: + return response(429, message=f"{e}") + try: rtype = type_model.get_type_by_recordid(record_id) if rtype == "SOA": return response(403, message=f"Can't Delete SOA Record") if rtype != "SOA": - zone = zone_model.get_zone_by_record(record_id) - zone_name = zone["zone"] - try: - update_serial(zone_name) - except Exception as e: - return response(429, message=f"{e}") + update_serial(serial_resource) command.set_zone(record_id, "zone-unset") diff --git a/api/app/helpers/helpers.py b/api/app/helpers/helpers.py index 7818f2a9..65a883d8 100755 --- a/api/app/helpers/helpers.py +++ b/api/app/helpers/helpers.py @@ -27,18 +27,17 @@ def increment_serial(serial, increment="01"): Keyword arguments: increment -- the increment value (default "01") """ - current_time = soa_time_set() - previous_time = serial[:-2] + today_date = soa_time_set() + record_date = serial[:-2] # The 10-digit serial (YYYYMMDDnn) is incremented, the first # 8 digits match the current iso-date nn = serial[-2:] - if int(nn) > 97 and previous_time == current_time: - # knot maximum of nn is 99 - # 97 was chosen because serial - # increment can be twice at time - raise ValueError("Zone Change Limit Reached") + if record_date != today_date: + # date changed, reset `nn` + nn = "01" + increment = add_str(nn, increment) - return f"{current_time}{increment}" + return f"{today_date}{increment}" def get_datetime(): diff --git a/api/tests/integration/test_record.py b/api/tests/integration/test_record.py index 6868dd4e..99774baa 100644 --- a/api/tests/integration/test_record.py +++ b/api/tests/integration/test_record.py @@ -1,6 +1,8 @@ import datetime import app.helpers.helpers +from app.controllers.api import record as record_api +from app.helpers import helpers class TestRecord: @@ -413,7 +415,17 @@ def test_edit_record_respect_zone_limit(self, client, monkeypatch, mocker): assert edit_record_data["code"] == 429 assert edit_record_data["message"] == "Zone Change Limit Reached" + # ensure correct serial + serial_resource = record_api.get_serial_resource("company.com") + today_date = helpers.soa_time_set() + + assert serial_resource["serial_counter"] == "98" + assert serial_resource["serial_date"] == today_date + assert serial_resource["serial"] == f"{today_date}98" + + # # if user waits until tomorrow + # def fake_soa_time_set(): tomorrow_date = datetime.datetime.now() + datetime.timedelta(days=1) return tomorrow_date.strftime("%Y%m%d") @@ -430,3 +442,11 @@ def fake_soa_time_set(): edit_record_data = res.get_json() assert edit_record_data["code"] == 200 + + # ensure correct serial + serial_resource = record_api.get_serial_resource("company.com") + today_date = helpers.soa_time_set() + + assert serial_resource["serial_counter"] == "03" + assert serial_resource["serial_date"] == today_date + assert serial_resource["serial"] == f"{today_date}03"