From be3d33b64cb362ca269a55235e9fbefe9cee1f16 Mon Sep 17 00:00:00 2001 From: SKairinos Date: Mon, 18 Sep 2023 12:41:31 +0100 Subject: [PATCH] feat: settings and auth_backends --- Pipfile | 2 + Pipfile.lock | 242 +++++++++++++++++- codeforlife/__init__.py | 8 +- codeforlife/settings/__init__.py | 19 ++ codeforlife/settings/custom.py | 26 ++ codeforlife/settings/django.py | 54 ++++ codeforlife/settings/third_party.py | 3 + codeforlife/user/admin.py | 120 ++++----- codeforlife/user/auth_backends/__init__.py | 5 + .../user/auth_backends/email_and_password.py | 35 +++ .../auth_backends/user_id_and_login_id.py | 39 +++ .../username_and_password_and_class_id.py | 39 +++ codeforlife/user/migrations/0001_initial.py | 130 ---------- codeforlife/user/models/__init__.py | 16 +- manage.py | 2 +- tests/test_package.py | 8 + 16 files changed, 538 insertions(+), 210 deletions(-) create mode 100644 codeforlife/settings/__init__.py create mode 100644 codeforlife/settings/custom.py create mode 100644 codeforlife/settings/django.py create mode 100644 codeforlife/settings/third_party.py create mode 100644 codeforlife/user/auth_backends/__init__.py create mode 100644 codeforlife/user/auth_backends/email_and_password.py create mode 100644 codeforlife/user/auth_backends/user_id_and_login_id.py create mode 100644 codeforlife/user/auth_backends/username_and_password_and_class_id.py delete mode 100644 codeforlife/user/migrations/0001_initial.py diff --git a/Pipfile b/Pipfile index a53c7660..a8b4909d 100644 --- a/Pipfile +++ b/Pipfile @@ -13,6 +13,8 @@ flask = "==2.2.3" importlib-metadata = "==4.13.0" # TODO: remove. needed by old portal django-formtools = "==2.2" # TODO: remove. needed by old portal django-otp = "==1.0.2" # TODO: remove. needed by old portal +# https://pypi.org/user/codeforlife/ +cfl-common = "==6.36.0" # TODO: remove [dev-packages] black = "==23.1.0" diff --git a/Pipfile.lock b/Pipfile.lock index 482bcc1a..91876cf8 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "5d108d463775ac6d17cd2de16bdf3ea58dcfa922eac8d9df3d54a393550e5886" + "sha256": "7155f5067fa14f3d0ca824f6c441181f69e987ab31c88cbf166c2303805d90a0" }, "pipfile-spec": 6, "requires": { @@ -24,6 +24,103 @@ "markers": "python_version >= '3.7'", "version": "==3.7.2" }, + "certifi": { + "hashes": [ + "sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082", + "sha256:92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9" + ], + "markers": "python_version >= '3.6'", + "version": "==2023.7.22" + }, + "cfl-common": { + "hashes": [ + "sha256:6eb1a349e770187c32e075b93b059df9eff0ae42e3abdd98b5fb6e05e9abb465", + "sha256:8ea2b387c91a14da0a03bf1808b5b002a76138c89f23723530911d1c0dc28730" + ], + "index": "pypi", + "version": "==6.36.0" + }, + "charset-normalizer": { + "hashes": [ + "sha256:04e57ab9fbf9607b77f7d057974694b4f6b142da9ed4a199859d9d4d5c63fe96", + "sha256:09393e1b2a9461950b1c9a45d5fd251dc7c6f228acab64da1c9c0165d9c7765c", + "sha256:0b87549028f680ca955556e3bd57013ab47474c3124dc069faa0b6545b6c9710", + "sha256:1000fba1057b92a65daec275aec30586c3de2401ccdcd41f8a5c1e2c87078706", + "sha256:1249cbbf3d3b04902ff081ffbb33ce3377fa6e4c7356f759f3cd076cc138d020", + "sha256:1920d4ff15ce893210c1f0c0e9d19bfbecb7983c76b33f046c13a8ffbd570252", + "sha256:193cbc708ea3aca45e7221ae58f0fd63f933753a9bfb498a3b474878f12caaad", + "sha256:1a100c6d595a7f316f1b6f01d20815d916e75ff98c27a01ae817439ea7726329", + "sha256:1f30b48dd7fa1474554b0b0f3fdfdd4c13b5c737a3c6284d3cdc424ec0ffff3a", + "sha256:203f0c8871d5a7987be20c72442488a0b8cfd0f43b7973771640fc593f56321f", + "sha256:246de67b99b6851627d945db38147d1b209a899311b1305dd84916f2b88526c6", + "sha256:2dee8e57f052ef5353cf608e0b4c871aee320dd1b87d351c28764fc0ca55f9f4", + "sha256:2efb1bd13885392adfda4614c33d3b68dee4921fd0ac1d3988f8cbb7d589e72a", + "sha256:2f4ac36d8e2b4cc1aa71df3dd84ff8efbe3bfb97ac41242fbcfc053c67434f46", + "sha256:3170c9399da12c9dc66366e9d14da8bf7147e1e9d9ea566067bbce7bb74bd9c2", + "sha256:3b1613dd5aee995ec6d4c69f00378bbd07614702a315a2cf6c1d21461fe17c23", + "sha256:3bb3d25a8e6c0aedd251753a79ae98a093c7e7b471faa3aa9a93a81431987ace", + "sha256:3bb7fda7260735efe66d5107fb7e6af6a7c04c7fce9b2514e04b7a74b06bf5dd", + "sha256:41b25eaa7d15909cf3ac4c96088c1f266a9a93ec44f87f1d13d4a0e86c81b982", + "sha256:45de3f87179c1823e6d9e32156fb14c1927fcc9aba21433f088fdfb555b77c10", + "sha256:46fb8c61d794b78ec7134a715a3e564aafc8f6b5e338417cb19fe9f57a5a9bf2", + "sha256:48021783bdf96e3d6de03a6e39a1171ed5bd7e8bb93fc84cc649d11490f87cea", + "sha256:4957669ef390f0e6719db3613ab3a7631e68424604a7b448f079bee145da6e09", + "sha256:5e86d77b090dbddbe78867a0275cb4df08ea195e660f1f7f13435a4649e954e5", + "sha256:6339d047dab2780cc6220f46306628e04d9750f02f983ddb37439ca47ced7149", + "sha256:681eb3d7e02e3c3655d1b16059fbfb605ac464c834a0c629048a30fad2b27489", + "sha256:6c409c0deba34f147f77efaa67b8e4bb83d2f11c8806405f76397ae5b8c0d1c9", + "sha256:7095f6fbfaa55defb6b733cfeb14efaae7a29f0b59d8cf213be4e7ca0b857b80", + "sha256:70c610f6cbe4b9fce272c407dd9d07e33e6bf7b4aa1b7ffb6f6ded8e634e3592", + "sha256:72814c01533f51d68702802d74f77ea026b5ec52793c791e2da806a3844a46c3", + "sha256:7a4826ad2bd6b07ca615c74ab91f32f6c96d08f6fcc3902ceeedaec8cdc3bcd6", + "sha256:7c70087bfee18a42b4040bb9ec1ca15a08242cf5867c58726530bdf3945672ed", + "sha256:855eafa5d5a2034b4621c74925d89c5efef61418570e5ef9b37717d9c796419c", + "sha256:8700f06d0ce6f128de3ccdbc1acaea1ee264d2caa9ca05daaf492fde7c2a7200", + "sha256:89f1b185a01fe560bc8ae5f619e924407efca2191b56ce749ec84982fc59a32a", + "sha256:8b2c760cfc7042b27ebdb4a43a4453bd829a5742503599144d54a032c5dc7e9e", + "sha256:8c2f5e83493748286002f9369f3e6607c565a6a90425a3a1fef5ae32a36d749d", + "sha256:8e098148dd37b4ce3baca71fb394c81dc5d9c7728c95df695d2dca218edf40e6", + "sha256:94aea8eff76ee6d1cdacb07dd2123a68283cb5569e0250feab1240058f53b623", + "sha256:95eb302ff792e12aba9a8b8f8474ab229a83c103d74a750ec0bd1c1eea32e669", + "sha256:9bd9b3b31adcb054116447ea22caa61a285d92e94d710aa5ec97992ff5eb7cf3", + "sha256:9e608aafdb55eb9f255034709e20d5a83b6d60c054df0802fa9c9883d0a937aa", + "sha256:a103b3a7069b62f5d4890ae1b8f0597618f628b286b03d4bc9195230b154bfa9", + "sha256:a386ebe437176aab38c041de1260cd3ea459c6ce5263594399880bbc398225b2", + "sha256:a38856a971c602f98472050165cea2cdc97709240373041b69030be15047691f", + "sha256:a401b4598e5d3f4a9a811f3daf42ee2291790c7f9d74b18d75d6e21dda98a1a1", + "sha256:a7647ebdfb9682b7bb97e2a5e7cb6ae735b1c25008a70b906aecca294ee96cf4", + "sha256:aaf63899c94de41fe3cf934601b0f7ccb6b428c6e4eeb80da72c58eab077b19a", + "sha256:b0dac0ff919ba34d4df1b6131f59ce95b08b9065233446be7e459f95554c0dc8", + "sha256:baacc6aee0b2ef6f3d308e197b5d7a81c0e70b06beae1f1fcacffdbd124fe0e3", + "sha256:bf420121d4c8dce6b889f0e8e4ec0ca34b7f40186203f06a946fa0276ba54029", + "sha256:c04a46716adde8d927adb9457bbe39cf473e1e2c2f5d0a16ceb837e5d841ad4f", + "sha256:c0b21078a4b56965e2b12f247467b234734491897e99c1d51cee628da9786959", + "sha256:c1c76a1743432b4b60ab3358c937a3fe1341c828ae6194108a94c69028247f22", + "sha256:c4983bf937209c57240cff65906b18bb35e64ae872da6a0db937d7b4af845dd7", + "sha256:c4fb39a81950ec280984b3a44f5bd12819953dc5fa3a7e6fa7a80db5ee853952", + "sha256:c57921cda3a80d0f2b8aec7e25c8aa14479ea92b5b51b6876d975d925a2ea346", + "sha256:c8063cf17b19661471ecbdb3df1c84f24ad2e389e326ccaf89e3fb2484d8dd7e", + "sha256:ccd16eb18a849fd8dcb23e23380e2f0a354e8daa0c984b8a732d9cfaba3a776d", + "sha256:cd6dbe0238f7743d0efe563ab46294f54f9bc8f4b9bcf57c3c666cc5bc9d1299", + "sha256:d62e51710986674142526ab9f78663ca2b0726066ae26b78b22e0f5e571238dd", + "sha256:db901e2ac34c931d73054d9797383d0f8009991e723dab15109740a63e7f902a", + "sha256:e03b8895a6990c9ab2cdcd0f2fe44088ca1c65ae592b8f795c3294af00a461c3", + "sha256:e1c8a2f4c69e08e89632defbfabec2feb8a8d99edc9f89ce33c4b9e36ab63037", + "sha256:e4b749b9cc6ee664a3300bb3a273c1ca8068c46be705b6c31cf5d276f8628a94", + "sha256:e6a5bf2cba5ae1bb80b154ed68a3cfa2fa00fde979a7f50d6598d3e17d9ac20c", + "sha256:e857a2232ba53ae940d3456f7533ce6ca98b81917d47adc3c7fd55dad8fab858", + "sha256:ee4006268ed33370957f55bf2e6f4d263eaf4dc3cfc473d1d90baff6ed36ce4a", + "sha256:eef9df1eefada2c09a5e7a40991b9fc6ac6ef20b1372abd48d2794a316dc0449", + "sha256:f058f6963fd82eb143c692cecdc89e075fa0828db2e5b291070485390b2f1c9c", + "sha256:f25c229a6ba38a35ae6e25ca1264621cc25d4d38dca2942a7fce0b67a4efe918", + "sha256:f2a1d0fd4242bd8643ce6f98927cf9c04540af6efa92323e9d3124f57727bfc1", + "sha256:f7560358a6811e52e9c4d142d497f1a6e10103d3a6881f18d04dbce3729c0e2c", + "sha256:f779d3ad205f108d14e99bb3859aa7dd8e9c68874617c72354d7ecaec2a054ac", + "sha256:f87f746ee241d30d6ed93969de31e5ffd09a2961a051e60ae6bddde9ec3583aa" + ], + "markers": "python_full_version >= '3.7.0'", + "version": "==3.2.0" + }, "click": { "hashes": [ "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28", @@ -96,6 +193,14 @@ "index": "pypi", "version": "==2.2.3" }, + "idna": { + "hashes": [ + "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4", + "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2" + ], + "markers": "python_version >= '3.5'", + "version": "==3.4" + }, "importlib-metadata": { "hashes": [ "sha256:8a8a81bcf996e74fee46f0d16bd3eaa382a7eb20fd82445c3ad11f4090334116", @@ -186,6 +291,79 @@ "markers": "python_version >= '3.7'", "version": "==2.1.3" }, + "numpy": { + "hashes": [ + "sha256:04640dab83f7c6c85abf9cd729c5b65f1ebd0ccf9de90b270cd61935eef0197f", + "sha256:1452241c290f3e2a312c137a9999cdbf63f78864d63c79039bda65ee86943f61", + "sha256:222e40d0e2548690405b0b3c7b21d1169117391c2e82c378467ef9ab4c8f0da7", + "sha256:2541312fbf09977f3b3ad449c4e5f4bb55d0dbf79226d7724211acc905049400", + "sha256:31f13e25b4e304632a4619d0e0777662c2ffea99fcae2029556b17d8ff958aef", + "sha256:4602244f345453db537be5314d3983dbf5834a9701b7723ec28923e2889e0bb2", + "sha256:4979217d7de511a8d57f4b4b5b2b965f707768440c17cb70fbf254c4b225238d", + "sha256:4c21decb6ea94057331e111a5bed9a79d335658c27ce2adb580fb4d54f2ad9bc", + "sha256:6620c0acd41dbcb368610bb2f4d83145674040025e5536954782467100aa8835", + "sha256:692f2e0f55794943c5bfff12b3f56f99af76f902fc47487bdfe97856de51a706", + "sha256:7215847ce88a85ce39baf9e89070cb860c98fdddacbaa6c0da3ffb31b3350bd5", + "sha256:79fc682a374c4a8ed08b331bef9c5f582585d1048fa6d80bc6c35bc384eee9b4", + "sha256:7ffe43c74893dbf38c2b0a1f5428760a1a9c98285553c89e12d70a96a7f3a4d6", + "sha256:80f5e3a4e498641401868df4208b74581206afbee7cf7b8329daae82676d9463", + "sha256:95f7ac6540e95bc440ad77f56e520da5bf877f87dca58bd095288dce8940532a", + "sha256:9667575fb6d13c95f1b36aca12c5ee3356bf001b714fc354eb5465ce1609e62f", + "sha256:a5425b114831d1e77e4b5d812b69d11d962e104095a5b9c3b641a218abcc050e", + "sha256:b4bea75e47d9586d31e892a7401f76e909712a0fd510f58f5337bea9572c571e", + "sha256:b7b1fc9864d7d39e28f41d089bfd6353cb5f27ecd9905348c24187a768c79694", + "sha256:befe2bf740fd8373cf56149a5c23a0f601e82869598d41f8e188a0e9869926f8", + "sha256:c0bfb52d2169d58c1cdb8cc1f16989101639b34c7d3ce60ed70b19c63eba0b64", + "sha256:d11efb4dbecbdf22508d55e48d9c8384db795e1b7b51ea735289ff96613ff74d", + "sha256:dd80e219fd4c71fc3699fc1dadac5dcf4fd882bfc6f7ec53d30fa197b8ee22dc", + "sha256:e2926dac25b313635e4d6cf4dc4e51c8c0ebfed60b801c799ffc4c32bf3d1254", + "sha256:e98f220aa76ca2a977fe435f5b04d7b3470c0a2e6312907b37ba6068f26787f2", + "sha256:ed094d4f0c177b1b8e7aa9cba7d6ceed51c0e569a5318ac0ca9a090680a6a1b1", + "sha256:f136bab9c2cfd8da131132c2cf6cc27331dd6fae65f95f69dcd4ae3c3639c810", + "sha256:f3a86ed21e4f87050382c7bc96571755193c4c1392490744ac73d660e8f564a9" + ], + "markers": "python_version >= '3.8'", + "version": "==1.24.4" + }, + "pandas": { + "hashes": [ + "sha256:04dbdbaf2e4d46ca8da896e1805bc04eb85caa9a82e259e8eed00254d5e0c682", + "sha256:1168574b036cd8b93abc746171c9b4f1b83467438a5e45909fed645cf8692dbc", + "sha256:1994c789bf12a7c5098277fb43836ce090f1073858c10f9220998ac74f37c69b", + "sha256:258d3624b3ae734490e4d63c430256e716f488c4fcb7c8e9bde2d3aa46c29089", + "sha256:32fca2ee1b0d93dd71d979726b12b61faa06aeb93cf77468776287f41ff8fdc5", + "sha256:37673e3bdf1551b95bf5d4ce372b37770f9529743d2498032439371fc7b7eb26", + "sha256:3ef285093b4fe5058eefd756100a367f27029913760773c8bf1d2d8bebe5d210", + "sha256:5247fb1ba347c1261cbbf0fcfba4a3121fbb4029d95d9ef4dc45406620b25c8b", + "sha256:5ec591c48e29226bcbb316e0c1e9423622bc7a4eaf1ef7c3c9fa1a3981f89641", + "sha256:694888a81198786f0e164ee3a581df7d505024fbb1f15202fc7db88a71d84ebd", + "sha256:69d7f3884c95da3a31ef82b7618af5710dba95bb885ffab339aad925c3e8ce78", + "sha256:6a21ab5c89dcbd57f78d0ae16630b090eec626360085a4148693def5452d8a6b", + "sha256:81af086f4543c9d8bb128328b5d32e9986e0c84d3ee673a2ac6fb57fd14f755e", + "sha256:9e4da0d45e7f34c069fe4d522359df7d23badf83abc1d1cef398895822d11061", + "sha256:9eae3dc34fa1aa7772dd3fc60270d13ced7346fcbcfee017d3132ec625e23bb0", + "sha256:9ee1a69328d5c36c98d8e74db06f4ad518a1840e8ccb94a4ba86920986bb617e", + "sha256:b084b91d8d66ab19f5bb3256cbd5ea661848338301940e17f4492b2ce0801fe8", + "sha256:b9cb1e14fdb546396b7e1b923ffaeeac24e4cedd14266c3497216dd4448e4f2d", + "sha256:ba619e410a21d8c387a1ea6e8a0e49bb42216474436245718d7f2e88a2f8d7c0", + "sha256:c02f372a88e0d17f36d3093a644c73cfc1788e876a7c4bcb4020a77512e2043c", + "sha256:ce0c6f76a0f1ba361551f3e6dceaff06bde7514a374aa43e33b588ec10420183", + "sha256:d9cd88488cceb7635aebb84809d087468eb33551097d600c6dad13602029c2df", + "sha256:e4c7c9f27a4185304c7caf96dc7d91bc60bc162221152de697c98eb0b2648dd8", + "sha256:f167beed68918d62bffb6ec64f2e1d8a7d297a038f86d4aed056b9493fca407f", + "sha256:f3421a7afb1a43f7e38e82e844e2bca9a6d793d66c1a7f9f0ff39a795bbc5e02" + ], + "markers": "python_version >= '3.8'", + "version": "==2.0.3" + }, + "pgeocode": { + "hashes": [ + "sha256:07995d4cd2d7fec1f82afb14d6025e83bbc156b6f225fa3e0b3417da2ec020c8", + "sha256:60fc2bad60aa161c3cf46ace4fde607b77e016b1e2a25470534163305499e55e" + ], + "markers": "python_version >= '3.8'", + "version": "==0.4.0" + }, "pydantic": { "hashes": [ "sha256:01aea3a42c13f2602b7ecbbea484a98169fb568ebd9e247593ea05f01b884b2e", @@ -228,6 +406,14 @@ "index": "pypi", "version": "==1.10.7" }, + "pyjwt": { + "hashes": [ + "sha256:69285c7e31fc44f68a1feb309e948e0df53259d579295e6cfe2b1792329f05fd", + "sha256:d83c3d892a77bbb74d3e1a2cfa90afaadb60945205d1095d9221f04466f64c14" + ], + "markers": "python_version >= '3.7'", + "version": "==2.6.0" + }, "pypng": { "hashes": [ "sha256:4a43e969b8f5aaafb2a415536c1a8ec7e341cd6a3f957fd5b5f32a4cfeed902c", @@ -235,6 +421,14 @@ ], "version": "==0.20220715.0" }, + "python-dateutil": { + "hashes": [ + "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86", + "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==2.8.2" + }, "pytz": { "hashes": [ "sha256:7b4fddbeb94a1eba4b557da24f19fdf9db575192544270a9101d8509f9f43d7b", @@ -250,6 +444,22 @@ "markers": "python_version >= '3.7'", "version": "==7.4.2" }, + "requests": { + "hashes": [ + "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f", + "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1" + ], + "markers": "python_version >= '3.7'", + "version": "==2.31.0" + }, + "six": { + "hashes": [ + "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", + "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==1.16.0" + }, "sqlparse": { "hashes": [ "sha256:5430a4fe2ac7d0f93e66f1efc6e1338a41884b7ddf2a350cedd20ccc4d9d28f3", @@ -260,11 +470,27 @@ }, "typing-extensions": { "hashes": [ - "sha256:440d5dd3af93b060174bf433bccd69b0babc3b15b1a8dca43789fd7f61514b36", - "sha256:b75ddc264f0ba5615db7ba217daeb99701ad295353c45f9e95963337ceeeffb2" + "sha256:8f92fc8806f9a6b641eaa5318da32b44d401efaac0f6678c9bc448ba3605faa0", + "sha256:df8e4339e9cb77357558cbdbceca33c303714cf861d1eef15e1070055ae8b7ef" + ], + "markers": "python_version >= '3.8'", + "version": "==4.8.0" + }, + "tzdata": { + "hashes": [ + "sha256:11ef1e08e54acb0d4f95bdb1be05da659673de4acbd21bf9c69e94cc5e907a3a", + "sha256:7e65763eef3120314099b6939b5546db7adce1e7d6f2e179e3df563c70511eda" + ], + "markers": "python_version >= '2'", + "version": "==2023.3" + }, + "urllib3": { + "hashes": [ + "sha256:8d22f86aae8ef5e410d4f539fde9ce6b2113a001bb4d189e0aed70642d602b11", + "sha256:de7df1803967d2c2a98e4b11bb7d6bd9210474c46e8a0401514e3a42a75ebde4" ], "markers": "python_version >= '3.7'", - "version": "==4.7.1" + "version": "==2.0.4" }, "werkzeug": { "hashes": [ @@ -468,11 +694,11 @@ }, "typing-extensions": { "hashes": [ - "sha256:440d5dd3af93b060174bf433bccd69b0babc3b15b1a8dca43789fd7f61514b36", - "sha256:b75ddc264f0ba5615db7ba217daeb99701ad295353c45f9e95963337ceeeffb2" + "sha256:8f92fc8806f9a6b641eaa5318da32b44d401efaac0f6678c9bc448ba3605faa0", + "sha256:df8e4339e9cb77357558cbdbceca33c303714cf861d1eef15e1070055ae8b7ef" ], - "markers": "python_version >= '3.7'", - "version": "==4.7.1" + "markers": "python_version >= '3.8'", + "version": "==4.8.0" } } } diff --git a/codeforlife/__init__.py b/codeforlife/__init__.py index 09278b86..1d8216ae 100644 --- a/codeforlife/__init__.py +++ b/codeforlife/__init__.py @@ -7,6 +7,8 @@ from .version import __version__ -from . import kurono -from . import service -from . import user +from . import ( + kurono, + service, + user, +) diff --git a/codeforlife/settings/__init__.py b/codeforlife/settings/__init__.py new file mode 100644 index 00000000..afe83a8d --- /dev/null +++ b/codeforlife/settings/__init__.py @@ -0,0 +1,19 @@ +""" +These are the common settings that need to be imported in all django projects +throughout our system. Place the following at the END of every settings.py file. + +`from codeforlife.settings import *` + +If you need to reference a specific CFL setting in a project's setting: +` +# Place this at the top of your file. +from codeforlife import settings as cfl_settings + +# Do something with EXAMPLE_SETTING from codeforlife's settings. +cfl_settings.EXAMPLE_SETTING +` +""" + +from .custom import * +from .django import * +from .third_party import * diff --git a/codeforlife/settings/custom.py b/codeforlife/settings/custom.py new file mode 100644 index 00000000..d3bd0e99 --- /dev/null +++ b/codeforlife/settings/custom.py @@ -0,0 +1,26 @@ +""" +This file contains all of our custom settings we define for our own purposes. +""" + +import os + +# The name of the current service. +SERVICE_NAME = os.environ["SERVICE_NAME"] # *required + +# If the current service the root service. This will only be true for portal. +SERVICE_IS_ROOT = bool(int(os.getenv("SERVICE_IS_ROOT", "0"))) + +# The protocol, domain and port of the current service. +SERVICE_PROTOCOL = os.getenv("SITE_PROTOCOL", "http") +SERVICE_DOMAIN = os.getenv("SITE_DOMAIN", "localhost") +SERVICE_PORT = int(os.getenv("SITE_PORT", "8000")) + +# The base url of the current service. +# The root service does not need its name included in the base url. +SERVICE_BASE_URL = f"{SERVICE_PROTOCOL}://{SERVICE_DOMAIN}:{SERVICE_PORT}" +if not SERVICE_IS_ROOT: + SERVICE_BASE_URL += f"/{SERVICE_NAME}" + + +# The api url of the current service. +SERVICE_API_URL = f"{SERVICE_BASE_URL}/api" diff --git a/codeforlife/settings/django.py b/codeforlife/settings/django.py new file mode 100644 index 00000000..7e880dd2 --- /dev/null +++ b/codeforlife/settings/django.py @@ -0,0 +1,54 @@ +""" +This file contains all of the settings Django supports out of the box. +https://docs.djangoproject.com/en/3.2/ref/settings/ +""" + +import os + +from .custom import SERVICE_NAME + +# SECURITY WARNING: don't run with debug turned on in production! +DEBUG = bool(int(os.getenv("DEBUG", "1"))) + +# SECURITY WARNING: keep the secret key used in production secret! +SECRET_KEY = os.getenv("SECRET_KEY", "replace-me") + +# Authentication backends +# https://docs.djangoproject.com/en/3.2/ref/settings/#authentication-backends + +AUTHENTICATION_BACKENDS = [ + "user.auth_backends.EmailAndPasswordBackend", + "user.auth_backends.UserIdAndLoginIdBackend", + "user.auth_backends.UsernameAndPasswordAndClassIdBackend", +] + +# Sessions +# https://docs.djangoproject.com/en/3.2/topics/http/sessions/ + +SESSION_COOKIE_AGE = 60 * 60 +SESSION_SAVE_EVERY_REQUEST = True +SESSION_EXPIRE_AT_BROWSER_CLOSE = True +SESSION_COOKIE_SECURE = True +SESSION_COOKIE_SAMESITE = "None" +SESSION_COOKIE_DOMAIN = "localhost" if DEBUG else "codeforlife.education" + +# Internationalization +# https://docs.djangoproject.com/en/3.2/topics/i18n/ + +LANGUAGE_CODE = "en-us" +TIME_ZONE = "UTC" +USE_I18N = True +USE_L10N = True +USE_TZ = True + +# Default primary key field type +# https://docs.djangoproject.com/en/3.2/ref/settings/#default-auto-field + +DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" + +# CSRF +# https://docs.djangoproject.com/en/3.2/ref/csrf/ + +CSRF_COOKIE_NAME = f"{SERVICE_NAME}_csrftoken" +CSRF_COOKIE_SAMESITE = "None" +CSRF_COOKIE_SECURE = True diff --git a/codeforlife/settings/third_party.py b/codeforlife/settings/third_party.py new file mode 100644 index 00000000..a4257ac5 --- /dev/null +++ b/codeforlife/settings/third_party.py @@ -0,0 +1,3 @@ +""" +This file contains custom settings defined by third party extensions. +""" diff --git a/codeforlife/user/admin.py b/codeforlife/user/admin.py index c3738007..565c5f03 100644 --- a/codeforlife/user/admin.py +++ b/codeforlife/user/admin.py @@ -1,60 +1,60 @@ -from django.contrib import admin -from django.contrib.auth.admin import UserAdmin - -from .models import User - - -class CustomUserAdmin(UserAdmin): - model = User - - list_display = ( - "username", - "email", - "is_active", - "is_staff", - "is_superuser", - "last_login", - ) - - list_filter = ("is_active", "is_staff", "is_superuser") - - fieldsets = ( - (None, {"fields": ("username", "email", "password")}), - ( - "Permissions", - { - "fields": ( - "is_staff", - "is_active", - "is_superuser", - "groups", - "user_permissions", - ) - }, - ), - ("Dates", {"fields": ("last_login", "date_joined")}), - ) - - add_fieldsets = ( - ( - None, - { - "classes": ("wide",), - "fields": ( - "username", - "email", - "password1", - "password2", - "is_staff", - "is_active", - ), - }, - ), - ) - - search_fields = ("email",) - - ordering = ("email",) - - -admin.site.register(User, CustomUserAdmin) +# from django.contrib import admin +# from django.contrib.auth.admin import UserAdmin + +# from .models import User + + +# class CustomUserAdmin(UserAdmin): +# model = User + +# list_display = ( +# "username", +# "email", +# "is_active", +# "is_staff", +# "is_superuser", +# "last_login", +# ) + +# list_filter = ("is_active", "is_staff", "is_superuser") + +# fieldsets = ( +# (None, {"fields": ("username", "email", "password")}), +# ( +# "Permissions", +# { +# "fields": ( +# "is_staff", +# "is_active", +# "is_superuser", +# "groups", +# "user_permissions", +# ) +# }, +# ), +# ("Dates", {"fields": ("last_login", "date_joined")}), +# ) + +# add_fieldsets = ( +# ( +# None, +# { +# "classes": ("wide",), +# "fields": ( +# "username", +# "email", +# "password1", +# "password2", +# "is_staff", +# "is_active", +# ), +# }, +# ), +# ) + +# search_fields = ("email",) + +# ordering = ("email",) + + +# admin.site.register(User, CustomUserAdmin) diff --git a/codeforlife/user/auth_backends/__init__.py b/codeforlife/user/auth_backends/__init__.py new file mode 100644 index 00000000..5fef85c7 --- /dev/null +++ b/codeforlife/user/auth_backends/__init__.py @@ -0,0 +1,5 @@ +from .email_and_password import EmailAndPasswordBackend +from .user_id_and_login_id import UserIdAndLoginIdBackend +from .username_and_password_and_class_id import ( + UsernameAndPasswordAndClassIdBackend, +) diff --git a/codeforlife/user/auth_backends/email_and_password.py b/codeforlife/user/auth_backends/email_and_password.py new file mode 100644 index 00000000..d2c53118 --- /dev/null +++ b/codeforlife/user/auth_backends/email_and_password.py @@ -0,0 +1,35 @@ +import typing as t + +from django.contrib.auth import get_user_model +from django.contrib.auth.backends import BaseBackend +from django.contrib.auth.base_user import AbstractBaseUser +from django.core.handlers.wsgi import WSGIRequest + +User = get_user_model() + + +class EmailAndPasswordBackend(BaseBackend): + def authenticate( + self, + request: WSGIRequest, + email: t.Optional[str] = None, + password: t.Optional[str] = None, + **kwargs + ) -> t.Optional[AbstractBaseUser]: + if email is None or password is None: + return + + try: + user = User.objects.get(email=email) + if getattr(user, "is_active", True) and user.check_password( + password + ): + return user + except User.DoesNotExist: + return + + def get_user(self, user_id: int) -> t.Optional[AbstractBaseUser]: + try: + return User.objects.get(id=user_id) + except User.DoesNotExist: + return diff --git a/codeforlife/user/auth_backends/user_id_and_login_id.py b/codeforlife/user/auth_backends/user_id_and_login_id.py new file mode 100644 index 00000000..9048bf0b --- /dev/null +++ b/codeforlife/user/auth_backends/user_id_and_login_id.py @@ -0,0 +1,39 @@ +import typing as t + +from common.helpers.generators import get_hashed_login_id +from common.models import Student +from django.contrib.auth import get_user_model +from django.contrib.auth.backends import BaseBackend +from django.contrib.auth.base_user import AbstractBaseUser +from django.core.handlers.wsgi import WSGIRequest + +User = get_user_model() + + +class UserIdAndLoginIdBackend(BaseBackend): + def authenticate( + self, + request: WSGIRequest, + user_id: t.Optional[int] = None, + login_id: t.Optional[str] = None, + **kwargs + ) -> t.Optional[AbstractBaseUser]: + if user_id is None or login_id is None: + return + + user = self.get_user(user_id) + if user and getattr(user, "is_active", True): + # Check the url against the student's stored hash. + student = Student.objects.get(new_user=user) + if ( + student.login_id + # TODO: refactor this + and get_hashed_login_id(login_id) == student.login_id + ): + return user + + def get_user(self, user_id: int) -> t.Optional[AbstractBaseUser]: + try: + return User.objects.get(id=user_id) + except User.DoesNotExist: + return diff --git a/codeforlife/user/auth_backends/username_and_password_and_class_id.py b/codeforlife/user/auth_backends/username_and_password_and_class_id.py new file mode 100644 index 00000000..0162f6c4 --- /dev/null +++ b/codeforlife/user/auth_backends/username_and_password_and_class_id.py @@ -0,0 +1,39 @@ +import typing as t + +from django.contrib.auth import get_user_model +from django.contrib.auth.backends import BaseBackend +from django.contrib.auth.base_user import AbstractBaseUser +from django.core.handlers.wsgi import WSGIRequest + +User = get_user_model() + + +class UsernameAndPasswordAndClassIdBackend(BaseBackend): + def authenticate( + self, + request: WSGIRequest, + username: t.Optional[str] = None, + password: t.Optional[str] = None, + class_id: t.Optional[str] = None, + **kwargs + ) -> t.Optional[AbstractBaseUser]: + if username is None or password is None or class_id is None: + return + + try: + user = User.objects.get( + username=username, + new_student__class_field__access_code=class_id, + ) + if getattr(user, "is_active", True) and user.check_password( + password + ): + return user + except User.DoesNotExist: + return + + def get_user(self, user_id: int) -> t.Optional[AbstractBaseUser]: + try: + return User.objects.get(id=user_id) + except User.DoesNotExist: + return diff --git a/codeforlife/user/migrations/0001_initial.py b/codeforlife/user/migrations/0001_initial.py deleted file mode 100644 index 4fffd359..00000000 --- a/codeforlife/user/migrations/0001_initial.py +++ /dev/null @@ -1,130 +0,0 @@ -# Generated by Django 3.2.18 on 2023-04-03 12:52 - -import codeforlife.user.models.user -from django.conf import settings -import django.contrib.auth.validators -from django.db import migrations, models -import django.db.models.deletion -import django.utils.timezone -import django_countries.fields - - -class Migration(migrations.Migration): - - initial = True - - dependencies = [ - ('auth', '0012_alter_user_first_name_max_length'), - ] - - operations = [ - migrations.CreateModel( - name='User', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('password', models.CharField(max_length=128, verbose_name='password')), - ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')), - ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')), - ('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')), - ('first_name', models.CharField(blank=True, max_length=150, verbose_name='first name')), - ('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')), - ('email', models.EmailField(blank=True, max_length=254, verbose_name='email address')), - ('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')), - ('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')), - ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')), - ('developer', models.BooleanField(default=False)), - ('is_verified', models.BooleanField(default=False)), - ('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.Group', verbose_name='groups')), - ('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.Permission', verbose_name='user permissions')), - ], - options={ - 'verbose_name': 'user', - 'verbose_name_plural': 'users', - 'abstract': False, - }, - managers=[ - ('objects', codeforlife.user.models.user.UserManager()), - ], - ), - migrations.CreateModel( - name='Class', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=200)), - ('access_code', models.CharField(max_length=5, null=True)), - ('classmates_data_viewable', models.BooleanField(default=False)), - ('always_accept_requests', models.BooleanField(default=False)), - ('accept_requests_until', models.DateTimeField(null=True)), - ('creation_time', models.DateTimeField(default=django.utils.timezone.now, null=True)), - ('is_active', models.BooleanField(default=True)), - ], - options={ - 'verbose_name_plural': 'classes', - }, - ), - migrations.CreateModel( - name='School', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=200)), - ('postcode', models.CharField(max_length=10, null=True)), - ('country', django_countries.fields.CountryField(max_length=2)), - ('creation_time', models.DateTimeField(default=django.utils.timezone.now, null=True)), - ('is_active', models.BooleanField(default=True)), - ], - ), - migrations.CreateModel( - name='UserSession', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('login_time', models.DateTimeField(default=django.utils.timezone.now)), - ('login_type', models.CharField(max_length=100, null=True)), - ('class_field', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='user.class')), - ('school', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='user.school')), - ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), - ], - ), - migrations.CreateModel( - name='Teacher', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('is_admin', models.BooleanField(default=False)), - ('blocked_time', models.DateTimeField(blank=True, null=True)), - ('invited_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='invited_teachers', to='user.teacher')), - ('school', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='school_teacher', to='user.school')), - ('user', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='teacher', to=settings.AUTH_USER_MODEL)), - ], - ), - migrations.CreateModel( - name='Student', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('login_id', models.CharField(max_length=64, null=True)), - ('blocked_time', models.DateTimeField(blank=True, null=True)), - ('class_field', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='students', to='user.class')), - ('pending_class_request', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='class_request', to='user.class')), - ('user', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='student', to=settings.AUTH_USER_MODEL)), - ], - ), - migrations.CreateModel( - name='SchoolTeacherInvitation', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('token', models.CharField(max_length=32)), - ('creation_time', models.DateTimeField(default=django.utils.timezone.now, null=True)), - ('is_active', models.BooleanField(default=True)), - ('from_teacher', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='school_invitations', to='user.teacher')), - ('school', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='teacher_invitations', to='user.school')), - ], - ), - migrations.AddField( - model_name='class', - name='created_by', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='created_classes', to='user.teacher'), - ), - migrations.AddField( - model_name='class', - name='teacher', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='class_teacher', to='user.teacher'), - ), - ] diff --git a/codeforlife/user/models/__init__.py b/codeforlife/user/models/__init__.py index 58d5d0d8..e3f1351e 100644 --- a/codeforlife/user/models/__init__.py +++ b/codeforlife/user/models/__init__.py @@ -1,9 +1,9 @@ -from .classroom import Class +# from .classroom import Class -# from .other import * -from .school import School -from .session import UserSession -from .student import Student -from .teacher_invitation import SchoolTeacherInvitation -from .teacher import Teacher -from .user import User +# # from .other import * +# from .school import School +# from .session import UserSession +# from .student import Student +# from .teacher_invitation import SchoolTeacherInvitation +# from .teacher import Teacher +# from .user import User diff --git a/manage.py b/manage.py index 61dd1ab7..f12f9295 100644 --- a/manage.py +++ b/manage.py @@ -93,7 +93,7 @@ }, ] -AUTH_USER_MODEL = "user.User" +# AUTH_USER_MODEL = "user.User" # Internationalization # https://docs.djangoproject.com/en/3.2/topics/i18n/ diff --git a/tests/test_package.py b/tests/test_package.py index 7321cf68..680b9a66 100644 --- a/tests/test_package.py +++ b/tests/test_package.py @@ -1,3 +1,11 @@ +import os + + def test_import(): """Basic test to ensure importing the package does not raise an error.""" import codeforlife + + +def test_import_settings(): + os.environ["SERVICE_NAME"] = "example" + from codeforlife import settings