diff --git a/greg/tests/test_national_id.py b/greg/tests/test_national_id.py new file mode 100644 index 0000000000000000000000000000000000000000..62815de91b7445394a222b87129e6df4e3bf7936 --- /dev/null +++ b/greg/tests/test_national_id.py @@ -0,0 +1,47 @@ +import pytest + +from greg.utils import is_valid_norwegian_national_id_number + +test_data = [ + # Mostly random numbers from http://www.fnrinfo.no/Verktoy/FinnLovlige_Tilfeldig.aspx + ("13063626672", False, True), + ("09095114412", False, True), + ("02048538757", False, True), + ("12042335418", False, True), + ("16061739592", False, True), + ("05087648003", False, True), + ("22051602882", False, True), + ("11120618212", False, True), + ("19045838326", False, True), + ("17041579641", False, True), + ("20064632362", False, True), + ("12118913939", False, True), + ("04062141242", False, True), + ("10075049345", False, True), + ("17093749006", False, True), + ("07049226653", False, True), + ("25110899915", False, True), + ("01014000719", False, True), + ("19035205023", False, True), + ("12345678912", False, False), + ("410185", True, False), + # Test cases from https://github.com/navikt/fnrvalidator/blob/master/tests/fnr.spec.js + ("13097248022", False, True), + ("29029648784", False, True), + ("29020075838", False, True), + ("29020112345", False, False), + ("15021951940", False, True), + ("1234567890", False, False), + ("123456789101", False, False), + ("1234567891A", False, False), + ("13097248032", False, False), + ("13097248023", False, False), + ("32127248022", False, False), + ("13137248022", False, False), + ("53097248016", True, True), +] + + +@pytest.mark.parametrize("test_number, is_dnumber, valid", test_data) +def test_register_guest(test_number, is_dnumber, valid): + assert is_valid_norwegian_national_id_number(test_number, is_dnumber) == valid diff --git a/greg/utils.py b/greg/utils.py index 8fd90ff1d89d8b025c1693dc07b94b0f2ed9273c..8408087a224f8877f6be4c619434eae081bccdce 100644 --- a/greg/utils.py +++ b/greg/utils.py @@ -1,6 +1,92 @@ import re +from datetime import date def camel_to_snake(s: str) -> str: """Turns `FooBar` into `foo_bar`.""" return re.sub("([A-Z])", "_\\1", s).lower().lstrip("_") + + +def is_valid_norwegian_national_id_number(input_digits: str, is_dnumber: bool) -> bool: + """ + Checks whether input_digits is a valid national ID number of D-number. + + It is based on the code found here: + https://github.com/navikt/fnrvalidator/blob/master/src/validator.js + """ + return ( + _check_correct_number_of_digits(input_digits) + and _compute_checksum(input_digits) + and _check_birthdate(input_digits, is_dnumber) + ) + + +def _check_correct_number_of_digits(input_digits: str) -> bool: + return re.search("^\\d{11}$", input_digits) is not None + + +def _check_birthdate(digits: str, is_dnumber: bool) -> bool: + if is_dnumber: + # First number has been increased by 4, look here for an explanation of what the + # numbers in the D-number mean: + # https://www.skatteetaten.no/person/utenlandsk/norsk-identitetsnummer/d-nummer/ + digits = str((int(digits[0:1]) - 4)) + digits[1:] + + day = int(digits[0:2]) + month = int(digits[2:4]) + year = int(digits[4:6]) + + # Year 0 should mean 2000, not 1900 + if year == 0: + year = 2000 + + try: + # Try to create a date object, it will fail is the date is not valid + date(year, month, day) + except ValueError: + # Not a valid date + return False + + return True + + +def _compute_checksum(input_digits: str) -> bool: + d = [int(s) for s in input_digits] + k1 = 11 - ( + ( + 3 * d[0] + + 7 * d[1] + + 6 * d[2] + + 1 * d[3] + + 8 * d[4] + + 9 * d[5] + + 4 * d[6] + + 5 * d[7] + + 2 * d[8] + ) + % 11 + ) + + k2 = 11 - ( + ( + 5 * d[0] + + 4 * d[1] + + 3 * d[2] + + 2 * d[3] + + 7 * d[4] + + 6 * d[5] + + 5 * d[6] + + 4 * d[7] + + 3 * d[8] + + 2 * k1 + ) + % 11 + ) + + if k1 == 11: + k1 = 0 + + if k2 == 11: + k2 = 0 + + return k1 < 10 and k2 < 10 and k1 == d[9] and k2 == d[10]