mirror of
https://github.com/cisagov/manage.get.gov.git
synced 2025-07-22 18:56:15 +02:00
Merge branch 'main' of https://github.com/cisagov/manage.get.gov into es/1975-fed-agency-update-script
This commit is contained in:
commit
e03fc95e44
26 changed files with 697 additions and 173 deletions
15
.github/workflows/deploy-development.yaml
vendored
15
.github/workflows/deploy-development.yaml
vendored
|
@ -22,9 +22,16 @@ jobs:
|
||||||
- name: Compile USWDS assets
|
- name: Compile USWDS assets
|
||||||
working-directory: ./src
|
working-directory: ./src
|
||||||
run: |
|
run: |
|
||||||
docker compose run node npm install &&
|
docker compose run node bash -c "\
|
||||||
docker compose run node npx gulp copyAssets &&
|
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh | bash && \
|
||||||
docker compose run node npx gulp compile
|
export NVM_DIR=\"\$HOME/.nvm\" && \
|
||||||
|
[ -s \"\$NVM_DIR/nvm.sh\" ] && \. \"\$NVM_DIR/nvm.sh\" && \
|
||||||
|
[ -s \"\$NVM_DIR/bash_completion\" ] && \. \"\$NVM_DIR/bash_completion\" && \
|
||||||
|
nvm install 21.7.3 && \
|
||||||
|
nvm use 21.7.3 && \
|
||||||
|
npm install && \
|
||||||
|
npx gulp copyAssets && \
|
||||||
|
npx gulp compile"
|
||||||
- name: Collect static assets
|
- name: Collect static assets
|
||||||
working-directory: ./src
|
working-directory: ./src
|
||||||
run: docker compose run app python manage.py collectstatic --no-input
|
run: docker compose run app python manage.py collectstatic --no-input
|
||||||
|
@ -45,4 +52,4 @@ jobs:
|
||||||
cf_password: ${{ secrets.CF_DEVELOPMENT_PASSWORD }}
|
cf_password: ${{ secrets.CF_DEVELOPMENT_PASSWORD }}
|
||||||
cf_org: cisa-dotgov
|
cf_org: cisa-dotgov
|
||||||
cf_space: development
|
cf_space: development
|
||||||
cf_command: "run-task getgov-development --command 'python manage.py migrate' --name migrate"
|
cf_command: "run-task getgov-development --command 'python manage.py migrate' --name migrate"
|
15
.github/workflows/deploy-sandbox.yaml
vendored
15
.github/workflows/deploy-sandbox.yaml
vendored
|
@ -42,9 +42,16 @@ jobs:
|
||||||
- name: Compile USWDS assets
|
- name: Compile USWDS assets
|
||||||
working-directory: ./src
|
working-directory: ./src
|
||||||
run: |
|
run: |
|
||||||
docker compose run node npm install &&
|
docker compose run node bash -c "\
|
||||||
docker compose run node npx gulp copyAssets &&
|
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh | bash && \
|
||||||
docker compose run node npx gulp compile
|
export NVM_DIR=\"\$HOME/.nvm\" && \
|
||||||
|
[ -s \"\$NVM_DIR/nvm.sh\" ] && \. \"\$NVM_DIR/nvm.sh\" && \
|
||||||
|
[ -s \"\$NVM_DIR/bash_completion\" ] && \. \"\$NVM_DIR/bash_completion\" && \
|
||||||
|
nvm install 21.7.3 && \
|
||||||
|
nvm use 21.7.3 && \
|
||||||
|
npm install && \
|
||||||
|
npx gulp copyAssets && \
|
||||||
|
npx gulp compile"
|
||||||
- name: Collect static assets
|
- name: Collect static assets
|
||||||
working-directory: ./src
|
working-directory: ./src
|
||||||
run: docker compose run app python manage.py collectstatic --no-input
|
run: docker compose run app python manage.py collectstatic --no-input
|
||||||
|
@ -75,4 +82,4 @@ jobs:
|
||||||
owner: context.repo.owner,
|
owner: context.repo.owner,
|
||||||
repo: context.repo.repo,
|
repo: context.repo.repo,
|
||||||
body: '🥳 Successfully deployed to developer sandbox **[${{ env.ENVIRONMENT }}](https://getgov-${{ env.ENVIRONMENT }}.app.cloud.gov/)**.'
|
body: '🥳 Successfully deployed to developer sandbox **[${{ env.ENVIRONMENT }}](https://getgov-${{ env.ENVIRONMENT }}.app.cloud.gov/)**.'
|
||||||
})
|
})
|
13
.github/workflows/deploy-stable.yaml
vendored
13
.github/workflows/deploy-stable.yaml
vendored
|
@ -23,9 +23,16 @@ jobs:
|
||||||
- name: Compile USWDS assets
|
- name: Compile USWDS assets
|
||||||
working-directory: ./src
|
working-directory: ./src
|
||||||
run: |
|
run: |
|
||||||
docker compose run node npm install &&
|
docker compose run node bash -c "\
|
||||||
docker compose run node npx gulp copyAssets &&
|
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh | bash && \
|
||||||
docker compose run node npx gulp compile
|
export NVM_DIR=\"\$HOME/.nvm\" && \
|
||||||
|
[ -s \"\$NVM_DIR/nvm.sh\" ] && \. \"\$NVM_DIR/nvm.sh\" && \
|
||||||
|
[ -s \"\$NVM_DIR/bash_completion\" ] && \. \"\$NVM_DIR/bash_completion\" && \
|
||||||
|
nvm install 21.7.3 && \
|
||||||
|
nvm use 21.7.3 && \
|
||||||
|
npm install && \
|
||||||
|
npx gulp copyAssets && \
|
||||||
|
npx gulp compile"
|
||||||
- name: Collect static assets
|
- name: Collect static assets
|
||||||
working-directory: ./src
|
working-directory: ./src
|
||||||
run: docker compose run app python manage.py collectstatic --no-input
|
run: docker compose run app python manage.py collectstatic --no-input
|
||||||
|
|
15
.github/workflows/deploy-staging.yaml
vendored
15
.github/workflows/deploy-staging.yaml
vendored
|
@ -23,9 +23,16 @@ jobs:
|
||||||
- name: Compile USWDS assets
|
- name: Compile USWDS assets
|
||||||
working-directory: ./src
|
working-directory: ./src
|
||||||
run: |
|
run: |
|
||||||
docker compose run node npm install &&
|
docker compose run node bash -c "\
|
||||||
docker compose run node npx gulp copyAssets &&
|
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh | bash && \
|
||||||
docker compose run node npx gulp compile
|
export NVM_DIR=\"\$HOME/.nvm\" && \
|
||||||
|
[ -s \"\$NVM_DIR/nvm.sh\" ] && \. \"\$NVM_DIR/nvm.sh\" && \
|
||||||
|
[ -s \"\$NVM_DIR/bash_completion\" ] && \. \"\$NVM_DIR/bash_completion\" && \
|
||||||
|
nvm install 21.7.3 && \
|
||||||
|
nvm use 21.7.3 && \
|
||||||
|
npm install && \
|
||||||
|
npx gulp copyAssets && \
|
||||||
|
npx gulp compile"
|
||||||
- name: Collect static assets
|
- name: Collect static assets
|
||||||
working-directory: ./src
|
working-directory: ./src
|
||||||
run: docker compose run app python manage.py collectstatic --no-input
|
run: docker compose run app python manage.py collectstatic --no-input
|
||||||
|
@ -44,4 +51,4 @@ jobs:
|
||||||
cf_password: ${{ secrets.CF_STAGING_PASSWORD }}
|
cf_password: ${{ secrets.CF_STAGING_PASSWORD }}
|
||||||
cf_org: cisa-dotgov
|
cf_org: cisa-dotgov
|
||||||
cf_space: staging
|
cf_space: staging
|
||||||
cf_command: "run-task getgov-staging --command 'python manage.py migrate' --name migrate"
|
cf_command: "run-task getgov-staging --command 'python manage.py migrate' --name migrate"
|
|
@ -602,18 +602,18 @@ That data are synthesized from the generic_org_type field and the is_election_bo
|
||||||
The latest domain_election_board csv can be found [here](https://drive.google.com/file/d/1aDeCqwHmBnXBl2arvoFCN0INoZmsEGsQ/view).
|
The latest domain_election_board csv can be found [here](https://drive.google.com/file/d/1aDeCqwHmBnXBl2arvoFCN0INoZmsEGsQ/view).
|
||||||
After downloading this file, place it in `src/migrationdata`
|
After downloading this file, place it in `src/migrationdata`
|
||||||
|
|
||||||
#### Step 2: Upload the domain_election_board file to your sandbox
|
#### Step 3: Upload the domain_election_board file to your sandbox
|
||||||
Follow [Step 1: Transfer data to sandboxes](#step-1-transfer-data-to-sandboxes) and [Step 2: Transfer uploaded files to the getgov directory](#step-2-transfer-uploaded-files-to-the-getgov-directory) from the [Set Up Migrations on Sandbox](#set-up-migrations-on-sandbox) portion of this doc.
|
Follow [Step 1: Transfer data to sandboxes](#step-1-transfer-data-to-sandboxes) and [Step 2: Transfer uploaded files to the getgov directory](#step-2-transfer-uploaded-files-to-the-getgov-directory) from the [Set Up Migrations on Sandbox](#set-up-migrations-on-sandbox) portion of this doc.
|
||||||
|
|
||||||
#### Step 2: SSH into your environment
|
#### Step 4: SSH into your environment
|
||||||
```cf ssh getgov-{space}```
|
```cf ssh getgov-{space}```
|
||||||
|
|
||||||
Example: `cf ssh getgov-za`
|
Example: `cf ssh getgov-za`
|
||||||
|
|
||||||
#### Step 3: Create a shell instance
|
#### Step 5: Create a shell instance
|
||||||
```/tmp/lifecycle/shell```
|
```/tmp/lifecycle/shell```
|
||||||
|
|
||||||
#### Step 4: Running the script
|
#### Step 6: Running the script
|
||||||
```./manage.py populate_organization_type {domain_election_board_filename}```
|
```./manage.py populate_organization_type {domain_election_board_filename}```
|
||||||
|
|
||||||
- The domain_election_board_filename file must adhere to this format:
|
- The domain_election_board_filename file must adhere to this format:
|
||||||
|
@ -642,3 +642,29 @@ Example (assuming that this is being ran from src/):
|
||||||
| | Parameter | Description |
|
| | Parameter | Description |
|
||||||
|:-:|:------------------------------------|:-------------------------------------------------------------------|
|
|:-:|:------------------------------------|:-------------------------------------------------------------------|
|
||||||
| 1 | **domain_election_board_filename** | A file containing every domain that is an election office.
|
| 1 | **domain_election_board_filename** | A file containing every domain that is an election office.
|
||||||
|
|
||||||
|
|
||||||
|
## Populate Verification Type
|
||||||
|
This section outlines how to run the `populate_verification_type` script.
|
||||||
|
The script is used to update the verification_type field on User when it is None.
|
||||||
|
|
||||||
|
### Running on sandboxes
|
||||||
|
|
||||||
|
#### Step 1: Login to CloudFoundry
|
||||||
|
```cf login -a api.fr.cloud.gov --sso```
|
||||||
|
|
||||||
|
#### Step 2: SSH into your environment
|
||||||
|
```cf ssh getgov-{space}```
|
||||||
|
|
||||||
|
Example: `cf ssh getgov-za`
|
||||||
|
|
||||||
|
#### Step 3: Create a shell instance
|
||||||
|
```/tmp/lifecycle/shell```
|
||||||
|
|
||||||
|
#### Step 4: Running the script
|
||||||
|
```./manage.py populate_verification_type```
|
||||||
|
|
||||||
|
### Running locally
|
||||||
|
|
||||||
|
#### Step 1: Running the script
|
||||||
|
```docker-compose exec app ./manage.py populate_verification_type```
|
||||||
|
|
|
@ -9,7 +9,7 @@ cfenv = "*"
|
||||||
django-cors-headers = "*"
|
django-cors-headers = "*"
|
||||||
pycryptodomex = "*"
|
pycryptodomex = "*"
|
||||||
django-allow-cidr = "*"
|
django-allow-cidr = "*"
|
||||||
django-auditlog = "2.3.0"
|
django-auditlog = "*"
|
||||||
django-csp = "*"
|
django-csp = "*"
|
||||||
environs = {extras=["django"]}
|
environs = {extras=["django"]}
|
||||||
Faker = "*"
|
Faker = "*"
|
||||||
|
|
218
src/Pipfile.lock
generated
218
src/Pipfile.lock
generated
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"_meta": {
|
"_meta": {
|
||||||
"hash": {
|
"hash": {
|
||||||
"sha256": "ce10883aef7e1ce10421d99b3ac35ebf419857a3fe468f0e2d93785f4323eaa8"
|
"sha256": "16a0db98015509322cf1d27f06fced5b7635057c4eb98921a9419d63d51925ab"
|
||||||
},
|
},
|
||||||
"pipfile-spec": 6,
|
"pipfile-spec": 6,
|
||||||
"requires": {},
|
"requires": {},
|
||||||
|
@ -32,20 +32,20 @@
|
||||||
},
|
},
|
||||||
"boto3": {
|
"boto3": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:168894499578a9d69d6f7deb5811952bf4171c51b95749a9aef32cf67bc71f87",
|
"sha256:2824e3dd18743ca50e5b10439d20e74647b1416e8a94509cb30beac92d27a18d",
|
||||||
"sha256:1bd4cef11b7c5f293cede50f3d33ca89fe3413c51f1864f40163c56a732dd6b3"
|
"sha256:b2e5cb5b95efcc881e25a3bc872d7a24e75ff4e76f368138e4baf7b9d6ee3422"
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"markers": "python_version >= '3.8'",
|
"markers": "python_version >= '3.8'",
|
||||||
"version": "==1.34.88"
|
"version": "==1.34.90"
|
||||||
},
|
},
|
||||||
"botocore": {
|
"botocore": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:36f2e9e8dfa856e55dbbe703aea601f134db3fddc3615f1020a755b27fd26a5e",
|
"sha256:113cd4c0cb63e13163ccbc2bb13d551be314ba7f8ba5bfab1c51a19ca01aa133",
|
||||||
"sha256:e87a660599ed3e14b2a770f4efc3df2f2f6d04f3c7bfd64ddbae186667864a7b"
|
"sha256:d48f152498e2c60b43ce25b579d26642346a327b6fb2c632d57219e0a4f63392"
|
||||||
],
|
],
|
||||||
"markers": "python_version >= '3.8'",
|
"markers": "python_version >= '3.8'",
|
||||||
"version": "==1.34.88"
|
"version": "==1.34.90"
|
||||||
},
|
},
|
||||||
"cachetools": {
|
"cachetools": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
|
@ -313,12 +313,12 @@
|
||||||
},
|
},
|
||||||
"django-auditlog": {
|
"django-auditlog": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:7bc2c87e4aff62dec9785d1b2359a2b27148f8c286f8a52b9114fc7876c5a9f7",
|
"sha256:92db1cf4a51ceca5c26b3ff46997d9e3305a02da1bd435e2efb5b8b6d300ce1f",
|
||||||
"sha256:b9d3acebb64f3f2785157efe3f2f802e0929aafc579d85bbfb9827db4adab532"
|
"sha256:9de49f80a4911135d136017123cd73461f869b4947eec14d5e76db4b88182f3f"
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"markers": "python_version >= '3.7'",
|
"markers": "python_version >= '3.8'",
|
||||||
"version": "==2.3.0"
|
"version": "==3.0.0"
|
||||||
},
|
},
|
||||||
"django-cache-url": {
|
"django-cache-url": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
|
@ -958,96 +958,96 @@
|
||||||
},
|
},
|
||||||
"pydantic": {
|
"pydantic": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:9dee74a271705f14f9a1567671d144a851c675b072736f0a7b2608fd9e495352",
|
"sha256:e029badca45266732a9a79898a15ae2e8b14840b1eabbb25844be28f0b33f3d5",
|
||||||
"sha256:b5ecdd42262ca2462e2624793551e80911a1e989f462910bb81aef974b4bb383"
|
"sha256:e9dbb5eada8abe4d9ae5f46b9939aead650cd2b68f249bb3a8139dbe125803cc"
|
||||||
],
|
],
|
||||||
"markers": "python_version >= '3.8'",
|
"markers": "python_version >= '3.8'",
|
||||||
"version": "==2.7.0"
|
"version": "==2.7.1"
|
||||||
},
|
},
|
||||||
"pydantic-core": {
|
"pydantic-core": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:030e4f9516f9947f38179249778709a460a3adb516bf39b5eb9066fcfe43d0e6",
|
"sha256:0098300eebb1c837271d3d1a2cd2911e7c11b396eac9661655ee524a7f10587b",
|
||||||
"sha256:09f03dfc0ef8c22622eaa8608caa4a1e189cfb83ce847045eca34f690895eccb",
|
"sha256:042473b6280246b1dbf530559246f6842b56119c2926d1e52b631bdc46075f2a",
|
||||||
"sha256:12a05db5013ec0ca4a32cc6433f53faa2a014ec364031408540ba858c2172bb0",
|
"sha256:05b7133a6e6aeb8df37d6f413f7705a37ab4031597f64ab56384c94d98fa0e90",
|
||||||
"sha256:14fe73881cf8e4cbdaded8ca0aa671635b597e42447fec7060d0868b52d074e6",
|
"sha256:0680b1f1f11fda801397de52c36ce38ef1c1dc841a0927a94f226dea29c3ae3d",
|
||||||
"sha256:1a0c3e718f4e064efde68092d9d974e39572c14e56726ecfaeebbe6544521f47",
|
"sha256:0d69b4c2f6bb3e130dba60d34c0845ba31b69babdd3f78f7c0c8fae5021a253e",
|
||||||
"sha256:1be91ad664fc9245404a789d60cba1e91c26b1454ba136d2a1bf0c2ac0c0505a",
|
"sha256:1404c69d6a676245199767ba4f633cce5f4ad4181f9d0ccb0577e1f66cf4c46d",
|
||||||
"sha256:201713f2f462e5c015b343e86e68bd8a530a4f76609b33d8f0ec65d2b921712a",
|
"sha256:182245ff6b0039e82b6bb585ed55a64d7c81c560715d1bad0cbad6dfa07b4027",
|
||||||
"sha256:2027493cc44c23b598cfaf200936110433d9caa84e2c6cf487a83999638a96ac",
|
"sha256:1a388a77e629b9ec814c1b1e6b3b595fe521d2cdc625fcca26fbc2d44c816804",
|
||||||
"sha256:250ae39445cb5475e483a36b1061af1bc233de3e9ad0f4f76a71b66231b07f88",
|
"sha256:1d90c3265ae107f91a4f279f4d6f6f1d4907ac76c6868b27dc7fb33688cfb347",
|
||||||
"sha256:2533ad2883f001efa72f3d0e733fb846710c3af6dcdd544fe5bf14fa5fe2d7db",
|
"sha256:20aca1e2298c56ececfd8ed159ae4dde2df0781988c97ef77d5c16ff4bd5b400",
|
||||||
"sha256:25595ac311f20e5324d1941909b0d12933f1fd2171075fcff763e90f43e92a0d",
|
"sha256:219da3f096d50a157f33645a1cf31c0ad1fe829a92181dd1311022f986e5fbe3",
|
||||||
"sha256:2684a94fdfd1b146ff10689c6e4e815f6a01141781c493b97342cdc5b06f4d5d",
|
"sha256:22057013c8c1e272eb8d0eebc796701167d8377441ec894a8fed1af64a0bf399",
|
||||||
"sha256:27f1009dc292f3b7ca77feb3571c537276b9aad5dd4efb471ac88a8bd09024e9",
|
"sha256:223ee893d77a310a0391dca6df00f70bbc2f36a71a895cecd9a0e762dc37b349",
|
||||||
"sha256:2adaeea59849ec0939af5c5d476935f2bab4b7f0335b0110f0f069a41024278e",
|
"sha256:224c421235f6102e8737032483f43c1a8cfb1d2f45740c44166219599358c2cd",
|
||||||
"sha256:2ae80f72bb7a3e397ab37b53a2b49c62cc5496412e71bc4f1277620a7ce3f52b",
|
"sha256:2334ce8c673ee93a1d6a65bd90327588387ba073c17e61bf19b4fd97d688d63c",
|
||||||
"sha256:2d5728e93d28a3c63ee513d9ffbac9c5989de8c76e049dbcb5bfe4b923a9739d",
|
"sha256:269322dcc3d8bdb69f054681edff86276b2ff972447863cf34c8b860f5188e2e",
|
||||||
"sha256:2e91711e36e229978d92642bfc3546333a9127ecebb3f2761372e096395fc649",
|
"sha256:2728b01246a3bba6de144f9e3115b532ee44bd6cf39795194fb75491824a1413",
|
||||||
"sha256:2fe0c1ce5b129455e43f941f7a46f61f3d3861e571f2905d55cdbb8b5c6f5e2c",
|
"sha256:2b8ed04b3582771764538f7ee7001b02e1170223cf9b75dff0bc698fadb00cf3",
|
||||||
"sha256:38a5024de321d672a132b1834a66eeb7931959c59964b777e8f32dbe9523f6b1",
|
"sha256:2e29d20810dfc3043ee13ac7d9e25105799817683348823f305ab3f349b9386e",
|
||||||
"sha256:3e352f0191d99fe617371096845070dee295444979efb8f27ad941227de6ad09",
|
"sha256:36789b70d613fbac0a25bb07ab3d9dba4d2e38af609c020cf4d888d165ee0bf3",
|
||||||
"sha256:48dd883db92e92519201f2b01cafa881e5f7125666141a49ffba8b9facc072b0",
|
"sha256:390193c770399861d8df9670fb0d1874f330c79caaca4642332df7c682bf6b91",
|
||||||
"sha256:54764c083bbe0264f0f746cefcded6cb08fbbaaf1ad1d78fb8a4c30cff999a90",
|
"sha256:3a6515ebc6e69d85502b4951d89131ca4e036078ea35533bb76327f8424531ce",
|
||||||
"sha256:54c7375c62190a7845091f521add19b0f026bcf6ae674bdb89f296972272e86d",
|
"sha256:3f9a801e7c8f1ef8718da265bba008fa121243dfe37c1cea17840b0944dfd72c",
|
||||||
"sha256:561cf62c8a3498406495cfc49eee086ed2bb186d08bcc65812b75fda42c38294",
|
"sha256:43f0f463cf89ace478de71a318b1b4f05ebc456a9b9300d027b4b57c1a2064fb",
|
||||||
"sha256:56823a92075780582d1ffd4489a2e61d56fd3ebb4b40b713d63f96dd92d28144",
|
"sha256:4456f2dca97c425231d7315737d45239b2b51a50dc2b6f0c2bb181fce6207664",
|
||||||
"sha256:582cf2cead97c9e382a7f4d3b744cf0ef1a6e815e44d3aa81af3ad98762f5a9b",
|
"sha256:470b94480bb5ee929f5acba6995251ada5e059a5ef3e0dfc63cca287283ebfa6",
|
||||||
"sha256:58aca931bef83217fca7a390e0486ae327c4af9c3e941adb75f8772f8eeb03a1",
|
"sha256:4774f3184d2ef3e14e8693194f661dea5a4d6ca4e3dc8e39786d33a94865cefd",
|
||||||
"sha256:5f7973c381283783cd1043a8c8f61ea5ce7a3a58b0369f0ee0ee975eaf2f2a1b",
|
"sha256:4b4356d3538c3649337df4074e81b85f0616b79731fe22dd11b99499b2ebbdf3",
|
||||||
"sha256:6395a4435fa26519fd96fdccb77e9d00ddae9dd6c742309bd0b5610609ad7fb2",
|
"sha256:553ef617b6836fc7e4df130bb851e32fe357ce36336d897fd6646d6058d980af",
|
||||||
"sha256:63d7523cd95d2fde0d28dc42968ac731b5bb1e516cc56b93a50ab293f4daeaad",
|
"sha256:6132dd3bd52838acddca05a72aafb6eab6536aa145e923bb50f45e78b7251043",
|
||||||
"sha256:641a018af4fe48be57a2b3d7a1f0f5dbca07c1d00951d3d7463f0ac9dac66622",
|
"sha256:6a46e22a707e7ad4484ac9ee9f290f9d501df45954184e23fc29408dfad61350",
|
||||||
"sha256:667880321e916a8920ef49f5d50e7983792cf59f3b6079f3c9dac2b88a311d17",
|
"sha256:6e5c584d357c4e2baf0ff7baf44f4994be121e16a2c88918a5817331fc7599d7",
|
||||||
"sha256:684d840d2c9ec5de9cb397fcb3f36d5ebb6fa0d94734f9886032dd796c1ead06",
|
"sha256:75250dbc5290e3f1a0f4618db35e51a165186f9034eff158f3d490b3fed9f8a0",
|
||||||
"sha256:68717c38a68e37af87c4da20e08f3e27d7e4212e99e96c3d875fbf3f4812abfc",
|
"sha256:75f7e9488238e920ab6204399ded280dc4c307d034f3924cd7f90a38b1829563",
|
||||||
"sha256:6b7bbb97d82659ac8b37450c60ff2e9f97e4eb0f8a8a3645a5568b9334b08b50",
|
"sha256:78363590ef93d5d226ba21a90a03ea89a20738ee5b7da83d771d283fd8a56761",
|
||||||
"sha256:72722ce529a76a4637a60be18bd789d8fb871e84472490ed7ddff62d5fed620d",
|
"sha256:7ca4ae5a27ad7a4ee5170aebce1574b375de390bc01284f87b18d43a3984df72",
|
||||||
"sha256:73c1bc8a86a5c9e8721a088df234265317692d0b5cd9e86e975ce3bc3db62a59",
|
"sha256:800d60565aec896f25bc3cfa56d2277d52d5182af08162f7954f938c06dc4ee3",
|
||||||
"sha256:76909849d1a6bffa5a07742294f3fa1d357dc917cb1fe7b470afbc3a7579d539",
|
"sha256:82d5d4d78e4448683cb467897fe24e2b74bb7b973a541ea1dcfec1d3cbce39fb",
|
||||||
"sha256:76b86e24039c35280ceee6dce7e62945eb93a5175d43689ba98360ab31eebc4a",
|
"sha256:852e966fbd035a6468fc0a3496589b45e2208ec7ca95c26470a54daed82a0788",
|
||||||
"sha256:7a5d83efc109ceddb99abd2c1316298ced2adb4570410defe766851a804fcd5b",
|
"sha256:868649da93e5a3d5eacc2b5b3b9235c98ccdbfd443832f31e075f54419e1b96b",
|
||||||
"sha256:80e0e57cc704a52fb1b48f16d5b2c8818da087dbee6f98d9bf19546930dc64b5",
|
"sha256:886eec03591b7cf058467a70a87733b35f44707bd86cf64a615584fd72488b7c",
|
||||||
"sha256:85233abb44bc18d16e72dc05bf13848a36f363f83757541f1a97db2f8d58cfd9",
|
"sha256:8b172601454f2d7701121bbec3425dd71efcb787a027edf49724c9cefc14c038",
|
||||||
"sha256:907a4d7720abfcb1c81619863efd47c8a85d26a257a2dbebdb87c3b847df0278",
|
"sha256:95b9d5e72481d3780ba3442eac863eae92ae43a5f3adb5b4d0a1de89d42bb250",
|
||||||
"sha256:9376d83d686ec62e8b19c0ac3bf8d28d8a5981d0df290196fb6ef24d8a26f0d6",
|
"sha256:98758d627ff397e752bc339272c14c98199c613f922d4a384ddc07526c86a2ec",
|
||||||
"sha256:94b9769ba435b598b547c762184bcfc4783d0d4c7771b04a3b45775c3589ca44",
|
"sha256:997abc4df705d1295a42f95b4eec4950a37ad8ae46d913caeee117b6b198811c",
|
||||||
"sha256:9a29726f91c6cb390b3c2338f0df5cd3e216ad7a938762d11c994bb37552edb0",
|
"sha256:9b5155ff768083cb1d62f3e143b49a8a3432e6789a3abee8acd005c3c7af1c74",
|
||||||
"sha256:9b6431559676a1079eac0f52d6d0721fb8e3c5ba43c37bc537c8c83724031feb",
|
"sha256:9e08e867b306f525802df7cd16c44ff5ebbe747ff0ca6cf3fde7f36c05a59a81",
|
||||||
"sha256:9ece8a49696669d483d206b4474c367852c44815fca23ac4e48b72b339807f80",
|
"sha256:9fdad8e35f278b2c3eb77cbdc5c0a49dada440657bf738d6905ce106dc1de439",
|
||||||
"sha256:a139fe9f298dc097349fb4f28c8b81cc7a202dbfba66af0e14be5cfca4ef7ce5",
|
"sha256:a1874c6dd4113308bd0eb568418e6114b252afe44319ead2b4081e9b9521fe75",
|
||||||
"sha256:a32204489259786a923e02990249c65b0f17235073149d0033efcebe80095570",
|
"sha256:a8309f67285bdfe65c372ea3722b7a5642680f3dba538566340a9d36e920b5f0",
|
||||||
"sha256:a3982b0a32d0a88b3907e4b0dc36809fda477f0757c59a505d4e9b455f384b8b",
|
"sha256:ae0a8a797a5e56c053610fa7be147993fe50960fa43609ff2a9552b0e07013e8",
|
||||||
"sha256:aad17e462f42ddbef5984d70c40bfc4146c322a2da79715932cd8976317054de",
|
"sha256:b14d82cdb934e99dda6d9d60dc84a24379820176cc4a0d123f88df319ae9c150",
|
||||||
"sha256:b560b72ed4816aee52783c66854d96157fd8175631f01ef58e894cc57c84f0f6",
|
"sha256:b1bd7e47b1558ea872bd16c8502c414f9e90dcf12f1395129d7bb42a09a95438",
|
||||||
"sha256:b6b0e4912030c6f28bcb72b9ebe4989d6dc2eebcd2a9cdc35fefc38052dd4fe8",
|
"sha256:b3ef08e20ec49e02d5c6717a91bb5af9b20f1805583cb0adfe9ba2c6b505b5ae",
|
||||||
"sha256:baf1c7b78cddb5af00971ad5294a4583188bda1495b13760d9f03c9483bb6203",
|
"sha256:b89ed9eb7d616ef5714e5590e6cf7f23b02d0d539767d33561e3675d6f9e3857",
|
||||||
"sha256:c0295d52b012cbe0d3059b1dba99159c3be55e632aae1999ab74ae2bd86a33d7",
|
"sha256:c4fcf5cd9c4b655ad666ca332b9a081112cd7a58a8b5a6ca7a3104bc950f2038",
|
||||||
"sha256:c562b49c96906b4029b5685075fe1ebd3b5cc2601dfa0b9e16c2c09d6cbce048",
|
"sha256:c6fdc8627910eed0c01aed6a390a252fe3ea6d472ee70fdde56273f198938374",
|
||||||
"sha256:c69567ddbac186e8c0aadc1f324a60a564cfe25e43ef2ce81bcc4b8c3abffbae",
|
"sha256:c9bd70772c720142be1020eac55f8143a34ec9f82d75a8e7a07852023e46617f",
|
||||||
"sha256:ca71d501629d1fa50ea7fa3b08ba884fe10cefc559f5c6c8dfe9036c16e8ae89",
|
"sha256:ca7b0c1f1c983e064caa85f3792dd2fe3526b3505378874afa84baf662e12241",
|
||||||
"sha256:ca976884ce34070799e4dfc6fbd68cb1d181db1eefe4a3a94798ddfb34b8867f",
|
"sha256:cbca948f2d14b09d20268cda7b0367723d79063f26c4ffc523af9042cad95592",
|
||||||
"sha256:d0491006a6ad20507aec2be72e7831a42efc93193d2402018007ff827dc62926",
|
"sha256:cc1cfd88a64e012b74e94cd00bbe0f9c6df57049c97f02bb07d39e9c852e19a4",
|
||||||
"sha256:d074b07a10c391fc5bbdcb37b2f16f20fcd9e51e10d01652ab298c0d07908ee2",
|
"sha256:ccdd111c03bfd3666bd2472b674c6899550e09e9f298954cfc896ab92b5b0e6d",
|
||||||
"sha256:d2ce426ee691319d4767748c8e0895cfc56593d725594e415f274059bcf3cb76",
|
"sha256:cfeecd1ac6cc1fb2692c3d5110781c965aabd4ec5d32799773ca7b1456ac636b",
|
||||||
"sha256:d4284c621f06a72ce2cb55f74ea3150113d926a6eb78ab38340c08f770eb9b4d",
|
"sha256:d4d938ec0adf5167cb335acb25a4ee69a8107e4984f8fbd2e897021d9e4ca21b",
|
||||||
"sha256:d5e6b7155b8197b329dc787356cfd2684c9d6a6b1a197f6bbf45f5555a98d411",
|
"sha256:d7d904828195733c183d20a54230c0df0eb46ec746ea1a666730787353e87182",
|
||||||
"sha256:d816f44a51ba5175394bc6c7879ca0bd2be560b2c9e9f3411ef3a4cbe644c2e9",
|
"sha256:d91cb5ea8b11607cc757675051f61b3d93f15eca3cefb3e6c704a5d6e8440f4e",
|
||||||
"sha256:dd3f79e17b56741b5177bcc36307750d50ea0698df6aa82f69c7db32d968c1c2",
|
"sha256:d9319e499827271b09b4e411905b24a426b8fb69464dfa1696258f53a3334641",
|
||||||
"sha256:dd63cec4e26e790b70544ae5cc48d11b515b09e05fdd5eff12e3195f54b8a586",
|
"sha256:e0e8b1be28239fc64a88a8189d1df7fad8be8c1ae47fcc33e43d4be15f99cc70",
|
||||||
"sha256:de9d3e8717560eb05e28739d1b35e4eac2e458553a52a301e51352a7ffc86a35",
|
"sha256:e18609ceaa6eed63753037fc06ebb16041d17d28199ae5aba0052c51449650a9",
|
||||||
"sha256:df4249b579e75094f7e9bb4bd28231acf55e308bf686b952f43100a5a0be394c",
|
"sha256:e1b395e58b10b73b07b7cf740d728dd4ff9365ac46c18751bf8b3d8cca8f625a",
|
||||||
"sha256:e178e5b66a06ec5bf51668ec0d4ac8cfb2bdcb553b2c207d58148340efd00143",
|
"sha256:e23ec367a948b6d812301afc1b13f8094ab7b2c280af66ef450efc357d2ae543",
|
||||||
"sha256:e60defc3c15defb70bb38dd605ff7e0fae5f6c9c7cbfe0ad7868582cb7e844a6",
|
"sha256:e25add29b8f3b233ae90ccef2d902d0ae0432eb0d45370fe315d1a5cf231004b",
|
||||||
"sha256:ee2794111c188548a4547eccc73a6a8527fe2af6cf25e1a4ebda2fd01cdd2e60",
|
"sha256:e6dac87ddb34aaec85f873d737e9d06a3555a1cc1a8e0c44b7f8d5daeb89d86f",
|
||||||
"sha256:ee7ccc7fb7e921d767f853b47814c3048c7de536663e82fbc37f5eb0d532224b",
|
"sha256:ef26c9e94a8c04a1b2924149a9cb081836913818e55681722d7f29af88fe7b38",
|
||||||
"sha256:ee9cf33e7fe14243f5ca6977658eb7d1042caaa66847daacbd2117adb258b226",
|
"sha256:eff2de745698eb46eeb51193a9f41d67d834d50e424aef27df2fcdee1b153845",
|
||||||
"sha256:f0f17814c505f07806e22b28856c59ac80cee7dd0fbb152aed273e116378f519",
|
"sha256:f0a21cbaa69900cbe1a2e7cad2aa74ac3cf21b10c3efb0fa0b80305274c0e8a2",
|
||||||
"sha256:f3202a429fe825b699c57892d4371c74cc3456d8d71b7f35d6028c96dfecad31",
|
"sha256:f459a5ce8434614dfd39bbebf1041952ae01da6bed9855008cb33b875cb024c0",
|
||||||
"sha256:f7054fdc556f5421f01e39cbb767d5ec5c1139ea98c3e5b350e02e62201740c7",
|
"sha256:f93a8a2e3938ff656a7c1bc57193b1319960ac015b6e87d76c76bf14fe0244b4",
|
||||||
"sha256:fd1a9edb9dd9d79fbeac1ea1f9a8dd527a6113b18d2e9bcc0d541d308dae639b"
|
"sha256:fb2bd7be70c0fe4dfd32c951bc813d9fe6ebcbfdd15a07527796c8204bd36242"
|
||||||
],
|
],
|
||||||
"markers": "python_version >= '3.8'",
|
"markers": "python_version >= '3.8'",
|
||||||
"version": "==2.18.1"
|
"version": "==2.18.2"
|
||||||
},
|
},
|
||||||
"pydantic-settings": {
|
"pydantic-settings": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
|
@ -1281,12 +1281,12 @@
|
||||||
},
|
},
|
||||||
"boto3": {
|
"boto3": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:168894499578a9d69d6f7deb5811952bf4171c51b95749a9aef32cf67bc71f87",
|
"sha256:2824e3dd18743ca50e5b10439d20e74647b1416e8a94509cb30beac92d27a18d",
|
||||||
"sha256:1bd4cef11b7c5f293cede50f3d33ca89fe3413c51f1864f40163c56a732dd6b3"
|
"sha256:b2e5cb5b95efcc881e25a3bc872d7a24e75ff4e76f368138e4baf7b9d6ee3422"
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"markers": "python_version >= '3.8'",
|
"markers": "python_version >= '3.8'",
|
||||||
"version": "==1.34.88"
|
"version": "==1.34.90"
|
||||||
},
|
},
|
||||||
"boto3-mocking": {
|
"boto3-mocking": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
|
@ -1299,28 +1299,28 @@
|
||||||
},
|
},
|
||||||
"boto3-stubs": {
|
"boto3-stubs": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:23ca9e0cd0d3e7702d6631a1e94a4208a26b39fa6b12c734427e68a7fa649477",
|
"sha256:7361f162523168ddcfb3e0cc70e5208e78f95b9f1f2553032036a2b67ab33355",
|
||||||
"sha256:8f472d1bf09743c3d33304ecc8830d70ebe3ca19ac9604ae8da9af55421b0fce"
|
"sha256:c82f3db8558e28f766361ba1eea7c77dff735f72fef2a0b9dffaa9c0d9ae76a3"
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"markers": "python_version >= '3.8'",
|
"markers": "python_version >= '3.8'",
|
||||||
"version": "==1.34.88"
|
"version": "==1.34.90"
|
||||||
},
|
},
|
||||||
"botocore": {
|
"botocore": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:36f2e9e8dfa856e55dbbe703aea601f134db3fddc3615f1020a755b27fd26a5e",
|
"sha256:113cd4c0cb63e13163ccbc2bb13d551be314ba7f8ba5bfab1c51a19ca01aa133",
|
||||||
"sha256:e87a660599ed3e14b2a770f4efc3df2f2f6d04f3c7bfd64ddbae186667864a7b"
|
"sha256:d48f152498e2c60b43ce25b579d26642346a327b6fb2c632d57219e0a4f63392"
|
||||||
],
|
],
|
||||||
"markers": "python_version >= '3.8'",
|
"markers": "python_version >= '3.8'",
|
||||||
"version": "==1.34.88"
|
"version": "==1.34.90"
|
||||||
},
|
},
|
||||||
"botocore-stubs": {
|
"botocore-stubs": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:656e966ea152a4f2828892aa7a9673bc91799998f5a8efd8e8fe390f61c2f4f1",
|
"sha256:b2d7416b524bce7325aa5fe09bb5e0b6bc9531d4136f4407fa39b6bc58507f34",
|
||||||
"sha256:f55b03ae2e1706bd56299fd2975bb048f96aa49012a866e931a040a74f85c3cc"
|
"sha256:d9b66542cbb8fbe28eef3c22caf941ae22d36cc1ef55b93fc0b52239457cab57"
|
||||||
],
|
],
|
||||||
"markers": "python_version >= '3.8' and python_version < '4.0'",
|
"markers": "python_version >= '3.8' and python_version < '4.0'",
|
||||||
"version": "==1.34.88"
|
"version": "==1.34.89"
|
||||||
},
|
},
|
||||||
"click": {
|
"click": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
|
@ -1497,11 +1497,11 @@
|
||||||
},
|
},
|
||||||
"platformdirs": {
|
"platformdirs": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:0614df2a2f37e1a662acbd8e2b25b92ccf8632929bc6d43467e17fe89c75e068",
|
"sha256:031cd18d4ec63ec53e82dceaac0417d218a6863f7745dfcc9efe7793b7039bdf",
|
||||||
"sha256:ef0cc731df711022c174543cb70a9b5bd22e5a9337c8624ef2c2ceb8ddad8768"
|
"sha256:17d5a1161b3fd67b390023cb2d3b026bbd40abde6fdb052dfbd3a29c3ba22ee1"
|
||||||
],
|
],
|
||||||
"markers": "python_version >= '3.8'",
|
"markers": "python_version >= '3.8'",
|
||||||
"version": "==4.2.0"
|
"version": "==4.2.1"
|
||||||
},
|
},
|
||||||
"pycodestyle": {
|
"pycodestyle": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
|
|
|
@ -4,8 +4,10 @@ from django.http import HttpResponse
|
||||||
from django.test import Client, TestCase, RequestFactory
|
from django.test import Client, TestCase, RequestFactory
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
|
||||||
|
from api.tests.common import less_console_noise_decorator
|
||||||
from djangooidc.exceptions import StateMismatch, InternalError
|
from djangooidc.exceptions import StateMismatch, InternalError
|
||||||
from ..views import login_callback
|
from ..views import login_callback
|
||||||
|
from registrar.models import User, Contact, VerifiedByStaff, DomainInvitation, TransitionDomain, Domain
|
||||||
|
|
||||||
from .common import less_console_noise
|
from .common import less_console_noise
|
||||||
|
|
||||||
|
@ -16,6 +18,14 @@ class ViewsTest(TestCase):
|
||||||
self.client = Client()
|
self.client = Client()
|
||||||
self.factory = RequestFactory()
|
self.factory = RequestFactory()
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
User.objects.all().delete()
|
||||||
|
Contact.objects.all().delete()
|
||||||
|
DomainInvitation.objects.all().delete()
|
||||||
|
VerifiedByStaff.objects.all().delete()
|
||||||
|
TransitionDomain.objects.all().delete()
|
||||||
|
Domain.objects.all().delete()
|
||||||
|
|
||||||
def say_hi(*args):
|
def say_hi(*args):
|
||||||
return HttpResponse("Hi")
|
return HttpResponse("Hi")
|
||||||
|
|
||||||
|
@ -229,6 +239,140 @@ class ViewsTest(TestCase):
|
||||||
self.assertEqual(response.status_code, 302)
|
self.assertEqual(response.status_code, 302)
|
||||||
self.assertEqual(response.url, "/")
|
self.assertEqual(response.url, "/")
|
||||||
|
|
||||||
|
@less_console_noise_decorator
|
||||||
|
def test_login_callback_sets_verification_type_regular(self, mock_client):
|
||||||
|
"""
|
||||||
|
Test that openid sets the verification type to regular on the returned user.
|
||||||
|
Regular, in this context, means that this user was "Verifed by Login.gov"
|
||||||
|
"""
|
||||||
|
# SETUP
|
||||||
|
session = self.client.session
|
||||||
|
session.save()
|
||||||
|
# MOCK
|
||||||
|
# mock that callback returns user_info; this is the expected behavior
|
||||||
|
mock_client.callback.side_effect = self.user_info
|
||||||
|
# patch that the request does not require step up auth
|
||||||
|
with patch("djangooidc.views._requires_step_up_auth", return_value=False), patch(
|
||||||
|
"djangooidc.views._initialize_client"
|
||||||
|
) as mock_init_client:
|
||||||
|
with patch("djangooidc.views._client_is_none", return_value=True):
|
||||||
|
# TEST
|
||||||
|
# test the login callback url
|
||||||
|
response = self.client.get(reverse("openid_login_callback"))
|
||||||
|
|
||||||
|
# assert that _initialize_client was called
|
||||||
|
mock_init_client.assert_called_once()
|
||||||
|
|
||||||
|
# Assert that we get a redirect
|
||||||
|
self.assertEqual(response.status_code, 302)
|
||||||
|
self.assertEqual(response.url, "/")
|
||||||
|
|
||||||
|
# Test the created user object
|
||||||
|
created_user = User.objects.get(email="test@example.com")
|
||||||
|
self.assertEqual(created_user.verification_type, User.VerificationTypeChoices.REGULAR)
|
||||||
|
|
||||||
|
@less_console_noise_decorator
|
||||||
|
def test_login_callback_sets_verification_type_invited(self, mock_client):
|
||||||
|
"""Test that openid sets the verification type to invited on the returned user
|
||||||
|
when they exist in the DomainInvitation table"""
|
||||||
|
# SETUP
|
||||||
|
session = self.client.session
|
||||||
|
session.save()
|
||||||
|
|
||||||
|
domain, _ = Domain.objects.get_or_create(name="test123.gov")
|
||||||
|
invitation, _ = DomainInvitation.objects.get_or_create(email="test@example.com", domain=domain)
|
||||||
|
# MOCK
|
||||||
|
# mock that callback returns user_info; this is the expected behavior
|
||||||
|
mock_client.callback.side_effect = self.user_info
|
||||||
|
# patch that the request does not require step up auth
|
||||||
|
with patch("djangooidc.views._requires_step_up_auth", return_value=False), patch(
|
||||||
|
"djangooidc.views._initialize_client"
|
||||||
|
) as mock_init_client:
|
||||||
|
with patch("djangooidc.views._client_is_none", return_value=True):
|
||||||
|
# TEST
|
||||||
|
# test the login callback url
|
||||||
|
response = self.client.get(reverse("openid_login_callback"))
|
||||||
|
|
||||||
|
# assert that _initialize_client was called
|
||||||
|
mock_init_client.assert_called_once()
|
||||||
|
|
||||||
|
# Assert that we get a redirect
|
||||||
|
self.assertEqual(response.status_code, 302)
|
||||||
|
self.assertEqual(response.url, "/")
|
||||||
|
|
||||||
|
# Test the created user object
|
||||||
|
created_user = User.objects.get(email="test@example.com")
|
||||||
|
self.assertEqual(created_user.email, invitation.email)
|
||||||
|
self.assertEqual(created_user.verification_type, User.VerificationTypeChoices.INVITED)
|
||||||
|
|
||||||
|
@less_console_noise_decorator
|
||||||
|
def test_login_callback_sets_verification_type_grandfathered(self, mock_client):
|
||||||
|
"""Test that openid sets the verification type to grandfathered
|
||||||
|
on a user which exists in our TransitionDomain table"""
|
||||||
|
# SETUP
|
||||||
|
session = self.client.session
|
||||||
|
session.save()
|
||||||
|
# MOCK
|
||||||
|
# mock that callback returns user_info; this is the expected behavior
|
||||||
|
mock_client.callback.side_effect = self.user_info
|
||||||
|
|
||||||
|
td, _ = TransitionDomain.objects.get_or_create(username="test@example.com", domain_name="test123.gov")
|
||||||
|
|
||||||
|
# patch that the request does not require step up auth
|
||||||
|
with patch("djangooidc.views._requires_step_up_auth", return_value=False), patch(
|
||||||
|
"djangooidc.views._initialize_client"
|
||||||
|
) as mock_init_client:
|
||||||
|
with patch("djangooidc.views._client_is_none", return_value=True):
|
||||||
|
# TEST
|
||||||
|
# test the login callback url
|
||||||
|
response = self.client.get(reverse("openid_login_callback"))
|
||||||
|
|
||||||
|
# assert that _initialize_client was called
|
||||||
|
mock_init_client.assert_called_once()
|
||||||
|
|
||||||
|
# Assert that we get a redirect
|
||||||
|
self.assertEqual(response.status_code, 302)
|
||||||
|
self.assertEqual(response.url, "/")
|
||||||
|
|
||||||
|
# Test the created user object
|
||||||
|
created_user = User.objects.get(email="test@example.com")
|
||||||
|
self.assertEqual(created_user.email, td.username)
|
||||||
|
self.assertEqual(created_user.verification_type, User.VerificationTypeChoices.GRANDFATHERED)
|
||||||
|
|
||||||
|
@less_console_noise_decorator
|
||||||
|
def test_login_callback_sets_verification_type_verified_by_staff(self, mock_client):
|
||||||
|
"""Test that openid sets the verification type to verified_by_staff
|
||||||
|
on a user which exists in our VerifiedByStaff table"""
|
||||||
|
# SETUP
|
||||||
|
session = self.client.session
|
||||||
|
session.save()
|
||||||
|
# MOCK
|
||||||
|
# mock that callback returns user_info; this is the expected behavior
|
||||||
|
mock_client.callback.side_effect = self.user_info
|
||||||
|
|
||||||
|
vip, _ = VerifiedByStaff.objects.get_or_create(email="test@example.com")
|
||||||
|
|
||||||
|
# patch that the request does not require step up auth
|
||||||
|
with patch("djangooidc.views._requires_step_up_auth", return_value=False), patch(
|
||||||
|
"djangooidc.views._initialize_client"
|
||||||
|
) as mock_init_client:
|
||||||
|
with patch("djangooidc.views._client_is_none", return_value=True):
|
||||||
|
# TEST
|
||||||
|
# test the login callback url
|
||||||
|
response = self.client.get(reverse("openid_login_callback"))
|
||||||
|
|
||||||
|
# assert that _initialize_client was called
|
||||||
|
mock_init_client.assert_called_once()
|
||||||
|
|
||||||
|
# Assert that we get a redirect
|
||||||
|
self.assertEqual(response.status_code, 302)
|
||||||
|
self.assertEqual(response.url, "/")
|
||||||
|
|
||||||
|
# Test the created user object
|
||||||
|
created_user = User.objects.get(email="test@example.com")
|
||||||
|
self.assertEqual(created_user.email, vip.email)
|
||||||
|
self.assertEqual(created_user.verification_type, User.VerificationTypeChoices.VERIFIED_BY_STAFF)
|
||||||
|
|
||||||
def test_login_callback_no_step_up_auth(self, mock_client):
|
def test_login_callback_no_step_up_auth(self, mock_client):
|
||||||
"""Walk through login_callback when _requires_step_up_auth returns False
|
"""Walk through login_callback when _requires_step_up_auth returns False
|
||||||
and assert that we have a redirect to /"""
|
and assert that we have a redirect to /"""
|
||||||
|
|
|
@ -99,8 +99,22 @@ def login_callback(request):
|
||||||
return CLIENT.create_authn_request(request.session)
|
return CLIENT.create_authn_request(request.session)
|
||||||
user = authenticate(request=request, **userinfo)
|
user = authenticate(request=request, **userinfo)
|
||||||
if user:
|
if user:
|
||||||
|
|
||||||
|
# Fixture users kind of exist in a superposition of verification types,
|
||||||
|
# because while the system "verified" them, if they login,
|
||||||
|
# we don't know how the user themselves was verified through login.gov until
|
||||||
|
# they actually try logging in. This edge-case only matters in non-production environments.
|
||||||
|
fixture_user = User.VerificationTypeChoices.FIXTURE_USER
|
||||||
|
is_fixture_user = user.verification_type and user.verification_type == fixture_user
|
||||||
|
|
||||||
|
# Set the verification type if it doesn't already exist or if its a fixture user
|
||||||
|
if not user.verification_type or is_fixture_user:
|
||||||
|
user.set_user_verification_type()
|
||||||
|
user.save()
|
||||||
|
|
||||||
login(request, user)
|
login(request, user)
|
||||||
logger.info("Successfully logged in user %s" % user)
|
logger.info("Successfully logged in user %s" % user)
|
||||||
|
|
||||||
# Clear the flag if the exception is not caught
|
# Clear the flag if the exception is not caught
|
||||||
request.session.pop("redirect_attempted", None)
|
request.session.pop("redirect_attempted", None)
|
||||||
return redirect(request.session.get("next", "/"))
|
return redirect(request.session.get("next", "/"))
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
FROM docker.io/cimg/node:current-browsers
|
FROM docker.io/cimg/node:current-browsers
|
||||||
|
FROM node:21.7.3
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
# Install app dependencies
|
# Install app dependencies
|
||||||
|
@ -7,4 +7,6 @@ WORKDIR /app
|
||||||
# where available (npm@5+)
|
# where available (npm@5+)
|
||||||
COPY --chown=circleci:circleci package*.json ./
|
COPY --chown=circleci:circleci package*.json ./
|
||||||
|
|
||||||
RUN npm install
|
|
||||||
|
RUN npm install -g npm@10.5.0
|
||||||
|
RUN npm install
|
4
src/package-lock.json
generated
4
src/package-lock.json
generated
|
@ -15,6 +15,10 @@
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@uswds/compile": "^1.0.0-beta.3"
|
"@uswds/compile": "^1.0.0-beta.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "21.7.3",
|
||||||
|
"npm": "10.5.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@gulp-sourcemaps/identity-map": {
|
"node_modules/@gulp-sourcemaps/identity-map": {
|
||||||
|
|
|
@ -3,6 +3,11 @@
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "========================",
|
"description": "========================",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
|
"engines": {
|
||||||
|
"node": "21.7.3",
|
||||||
|
"npm": "10.5.0"
|
||||||
|
},
|
||||||
|
"engineStrict": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"pa11y-ci": "pa11y-ci",
|
"pa11y-ci": "pa11y-ci",
|
||||||
"test": "echo \"Error: no test specified\" && exit 1"
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
|
@ -17,4 +22,4 @@
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@uswds/compile": "^1.0.0-beta.3"
|
"@uswds/compile": "^1.0.0-beta.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -537,7 +537,7 @@ class MyUserAdmin(BaseUserAdmin):
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(
|
(
|
||||||
None,
|
None,
|
||||||
{"fields": ("username", "password", "status")},
|
{"fields": ("username", "password", "status", "verification_type")},
|
||||||
),
|
),
|
||||||
("Personal Info", {"fields": ("first_name", "last_name", "email")}),
|
("Personal Info", {"fields": ("first_name", "last_name", "email")}),
|
||||||
(
|
(
|
||||||
|
@ -555,13 +555,20 @@ class MyUserAdmin(BaseUserAdmin):
|
||||||
("Important dates", {"fields": ("last_login", "date_joined")}),
|
("Important dates", {"fields": ("last_login", "date_joined")}),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
readonly_fields = ("verification_type",)
|
||||||
|
|
||||||
# Hide Username (uuid), Groups and Permissions
|
# Hide Username (uuid), Groups and Permissions
|
||||||
# Q: Now that we're using Groups and Permissions,
|
# Q: Now that we're using Groups and Permissions,
|
||||||
# do we expose those to analysts to view?
|
# do we expose those to analysts to view?
|
||||||
analyst_fieldsets = (
|
analyst_fieldsets = (
|
||||||
(
|
(
|
||||||
None,
|
None,
|
||||||
{"fields": ("status",)},
|
{
|
||||||
|
"fields": (
|
||||||
|
"status",
|
||||||
|
"verification_type",
|
||||||
|
)
|
||||||
|
},
|
||||||
),
|
),
|
||||||
("Personal Info", {"fields": ("first_name", "last_name", "email")}),
|
("Personal Info", {"fields": ("first_name", "last_name", "email")}),
|
||||||
(
|
(
|
||||||
|
@ -681,11 +688,14 @@ class MyUserAdmin(BaseUserAdmin):
|
||||||
return []
|
return []
|
||||||
|
|
||||||
def get_readonly_fields(self, request, obj=None):
|
def get_readonly_fields(self, request, obj=None):
|
||||||
|
readonly_fields = list(self.readonly_fields)
|
||||||
|
|
||||||
if request.user.has_perm("registrar.full_access_permission"):
|
if request.user.has_perm("registrar.full_access_permission"):
|
||||||
return () # No read-only fields for all access users
|
return readonly_fields
|
||||||
# Return restrictive Read-only fields for analysts and
|
else:
|
||||||
# users who might not belong to groups
|
# Return restrictive Read-only fields for analysts and
|
||||||
return self.analyst_readonly_fields
|
# users who might not belong to groups
|
||||||
|
return self.analyst_readonly_fields
|
||||||
|
|
||||||
|
|
||||||
class HostIPInline(admin.StackedInline):
|
class HostIPInline(admin.StackedInline):
|
||||||
|
|
|
@ -630,3 +630,8 @@ address.dja-address-contact-list {
|
||||||
.usa-button__small-text {
|
.usa-button__small-text {
|
||||||
font-size: small;
|
font-size: small;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get rid of padding on all help texts
|
||||||
|
form .aligned p.help, form .aligned div.help {
|
||||||
|
padding-left: 0px !important;
|
||||||
|
}
|
||||||
|
|
|
@ -780,3 +780,11 @@ if DEBUG:
|
||||||
# due to Docker, bypass Debug Toolbar's check on INTERNAL_IPS
|
# due to Docker, bypass Debug Toolbar's check on INTERNAL_IPS
|
||||||
"SHOW_TOOLBAR_CALLBACK": lambda _: True,
|
"SHOW_TOOLBAR_CALLBACK": lambda _: True,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# From https://django-auditlog.readthedocs.io/en/latest/upgrade.html
|
||||||
|
# Run:
|
||||||
|
# cf run-task getgov-<> --wait --command 'python manage.py auditlogmigratejson --traceback' --name auditlogmigratejson
|
||||||
|
# on our staging and stable, then remove these 2 variables or set to False
|
||||||
|
AUDITLOG_TWO_STEP_MIGRATION = True
|
||||||
|
|
||||||
|
AUDITLOG_USE_TEXT_CHANGES_IF_JSON_IS_NOT_PRESENT = True
|
||||||
|
|
|
@ -7,6 +7,7 @@ from registrar.models import (
|
||||||
UserGroup,
|
UserGroup,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
fake = Faker()
|
fake = Faker()
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -207,6 +208,10 @@ class UserFixture:
|
||||||
user.email = user_data["email"]
|
user.email = user_data["email"]
|
||||||
user.is_staff = True
|
user.is_staff = True
|
||||||
user.is_active = True
|
user.is_active = True
|
||||||
|
# This verification type will get reverted to "regular" (or whichever is applicables)
|
||||||
|
# once the user logs in for the first time (as they then got verified through different means).
|
||||||
|
# In the meantime, we can still describe how the user got here in the first place.
|
||||||
|
user.verification_type = User.VerificationTypeChoices.FIXTURE_USER
|
||||||
group = UserGroup.objects.get(name=group_name)
|
group = UserGroup.objects.get(name=group_name)
|
||||||
user.groups.add(group)
|
user.groups.add(group)
|
||||||
user.save()
|
user.save()
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
import logging
|
||||||
|
from django.core.management import BaseCommand
|
||||||
|
from registrar.management.commands.utility.terminal_helper import PopulateScriptTemplate, TerminalColors
|
||||||
|
from registrar.models import User
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand, PopulateScriptTemplate):
|
||||||
|
help = "Loops through each valid User object and updates its verification_type value"
|
||||||
|
|
||||||
|
def handle(self, **kwargs):
|
||||||
|
"""Loops through each valid User object and updates its verification_type value"""
|
||||||
|
filter_condition = {"verification_type__isnull": True}
|
||||||
|
self.mass_populate_field(User, filter_condition, ["verification_type"])
|
||||||
|
|
||||||
|
def populate_field(self, field_to_update):
|
||||||
|
"""Defines how we update the verification_type field"""
|
||||||
|
field_to_update.set_user_verification_type()
|
||||||
|
logger.info(
|
||||||
|
f"{TerminalColors.OKCYAN}Updating {field_to_update} => "
|
||||||
|
f"{field_to_update.verification_type}{TerminalColors.OKCYAN}"
|
||||||
|
)
|
|
@ -1,5 +1,6 @@
|
||||||
import logging
|
import logging
|
||||||
import sys
|
import sys
|
||||||
|
from abc import ABC, abstractmethod
|
||||||
from django.core.paginator import Paginator
|
from django.core.paginator import Paginator
|
||||||
from typing import List
|
from typing import List
|
||||||
from registrar.utility.enums import LogCode
|
from registrar.utility.enums import LogCode
|
||||||
|
@ -58,6 +59,55 @@ class ScriptDataHelper:
|
||||||
model_class.objects.bulk_update(page.object_list, fields_to_update)
|
model_class.objects.bulk_update(page.object_list, fields_to_update)
|
||||||
|
|
||||||
|
|
||||||
|
class PopulateScriptTemplate(ABC):
|
||||||
|
"""
|
||||||
|
Contains an ABC for generic populate scripts
|
||||||
|
"""
|
||||||
|
|
||||||
|
def mass_populate_field(self, sender, filter_conditions, fields_to_update):
|
||||||
|
"""Loops through each valid "sender" object - specified by filter_conditions - and
|
||||||
|
updates fields defined by fields_to_update using populate_function.
|
||||||
|
|
||||||
|
You must define populate_field before you can use this function.
|
||||||
|
"""
|
||||||
|
|
||||||
|
objects = sender.objects.filter(**filter_conditions)
|
||||||
|
|
||||||
|
# Code execution will stop here if the user prompts "N"
|
||||||
|
TerminalHelper.prompt_for_execution(
|
||||||
|
system_exit_on_terminate=True,
|
||||||
|
info_to_inspect=f"""
|
||||||
|
==Proposed Changes==
|
||||||
|
Number of {sender} objects to change: {len(objects)}
|
||||||
|
These fields will be updated on each record: {fields_to_update}
|
||||||
|
""",
|
||||||
|
prompt_title="Do you wish to patch this data?",
|
||||||
|
)
|
||||||
|
logger.info("Updating...")
|
||||||
|
|
||||||
|
to_update: List[sender] = []
|
||||||
|
failed_to_update: List[sender] = []
|
||||||
|
for updated_object in objects:
|
||||||
|
try:
|
||||||
|
self.populate_field(updated_object)
|
||||||
|
to_update.append(updated_object)
|
||||||
|
except Exception as err:
|
||||||
|
failed_to_update.append(updated_object)
|
||||||
|
logger.error(err)
|
||||||
|
logger.error(f"{TerminalColors.FAIL}" f"Failed to update {updated_object}" f"{TerminalColors.ENDC}")
|
||||||
|
|
||||||
|
# Do a bulk update on the first_ready field
|
||||||
|
ScriptDataHelper.bulk_update_fields(sender, to_update, fields_to_update)
|
||||||
|
|
||||||
|
# Log what happened
|
||||||
|
TerminalHelper.log_script_run_summary(to_update, failed_to_update, skipped=[], debug=True)
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def populate_field(self, field_to_update):
|
||||||
|
"""Defines how we update each field. Must be defined before using mass_populate_field."""
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
class TerminalHelper:
|
class TerminalHelper:
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def log_script_run_summary(to_update, failed_to_update, skipped, debug: bool, log_header=None):
|
def log_script_run_summary(to_update, failed_to_update, skipped, debug: bool, log_header=None):
|
||||||
|
|
29
src/registrar/migrations/0089_user_verification_type.py
Normal file
29
src/registrar/migrations/0089_user_verification_type.py
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
# Generated by Django 4.2.10 on 2024-04-26 14:03
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("registrar", "0088_domaininformation_cisa_representative_email_and_more"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="user",
|
||||||
|
name="verification_type",
|
||||||
|
field=models.CharField(
|
||||||
|
blank=True,
|
||||||
|
choices=[
|
||||||
|
("grandfathered", "Legacy user"),
|
||||||
|
("verified_by_staff", "Verified by staff"),
|
||||||
|
("regular", "Verified by Login.gov"),
|
||||||
|
("invited", "Invited by a domain manager"),
|
||||||
|
("fixture_user", "Created by fixtures"),
|
||||||
|
],
|
||||||
|
help_text="The means through which this user was verified",
|
||||||
|
null=True,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
|
@ -2,6 +2,7 @@ import logging
|
||||||
|
|
||||||
from django.contrib.auth.models import AbstractUser
|
from django.contrib.auth.models import AbstractUser
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
from django.db.models import Q
|
||||||
|
|
||||||
from registrar.models.user_domain_role import UserDomainRole
|
from registrar.models.user_domain_role import UserDomainRole
|
||||||
|
|
||||||
|
@ -23,6 +24,28 @@ class User(AbstractUser):
|
||||||
but can be customized later.
|
but can be customized later.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
class VerificationTypeChoices(models.TextChoices):
|
||||||
|
"""
|
||||||
|
Users achieve access to our system in a few different ways.
|
||||||
|
These choices reflect those pathways.
|
||||||
|
|
||||||
|
Overview of verification types:
|
||||||
|
- GRANDFATHERED: User exists in the `TransitionDomain` table
|
||||||
|
- VERIFIED_BY_STAFF: User exists in the `VerifiedByStaff` table
|
||||||
|
- INVITED: User exists in the `DomainInvitation` table
|
||||||
|
- REGULAR: User was verified through IAL2
|
||||||
|
- FIXTURE_USER: User was created by fixtures
|
||||||
|
"""
|
||||||
|
|
||||||
|
GRANDFATHERED = "grandfathered", "Legacy user"
|
||||||
|
VERIFIED_BY_STAFF = "verified_by_staff", "Verified by staff"
|
||||||
|
REGULAR = "regular", "Verified by Login.gov"
|
||||||
|
INVITED = "invited", "Invited by a domain manager"
|
||||||
|
# We need a type for fixture users (rather than using verified by staff)
|
||||||
|
# because those users still do get "verified" through normal means
|
||||||
|
# after they login.
|
||||||
|
FIXTURE_USER = "fixture_user", "Created by fixtures"
|
||||||
|
|
||||||
# #### Constants for choice fields ####
|
# #### Constants for choice fields ####
|
||||||
RESTRICTED = "restricted"
|
RESTRICTED = "restricted"
|
||||||
STATUS_CHOICES = ((RESTRICTED, RESTRICTED),)
|
STATUS_CHOICES = ((RESTRICTED, RESTRICTED),)
|
||||||
|
@ -50,6 +73,13 @@ class User(AbstractUser):
|
||||||
db_index=True,
|
db_index=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
verification_type = models.CharField(
|
||||||
|
choices=VerificationTypeChoices.choices,
|
||||||
|
null=True,
|
||||||
|
blank=True,
|
||||||
|
help_text="The means through which this user was verified",
|
||||||
|
)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
# this info is pulled from Login.gov
|
# this info is pulled from Login.gov
|
||||||
if self.first_name or self.last_name:
|
if self.first_name or self.last_name:
|
||||||
|
@ -114,23 +144,61 @@ class User(AbstractUser):
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
raise err
|
raise err
|
||||||
|
|
||||||
# A new incoming user who is a domain manager for one of the domains
|
# We can't set the verification type here because the user may not
|
||||||
# that we inputted from Verisign (that is, their email address appears
|
# always exist at this point. We do it down the line.
|
||||||
# in the username field of a TransitionDomain)
|
verification_type = cls.get_verification_type_from_email(email)
|
||||||
if TransitionDomain.objects.filter(username=email).exists():
|
|
||||||
return False
|
|
||||||
|
|
||||||
# New users flagged by Staff to bypass ial2
|
# Checks if the user needs verification.
|
||||||
if VerifiedByStaff.objects.filter(email=email).exists():
|
# The user needs identity verification if they don't meet
|
||||||
return False
|
# any special criteria, i.e. we are validating them "regularly"
|
||||||
|
return verification_type == cls.VerificationTypeChoices.REGULAR
|
||||||
|
|
||||||
# A new incoming user who is being invited to be a domain manager (that is,
|
def set_user_verification_type(self):
|
||||||
# their email address is in DomainInvitation for an invitation that is not yet "retrieved").
|
"""
|
||||||
invited = DomainInvitation.DomainInvitationStatus.INVITED
|
Given pre-existing data from TransitionDomain, VerifiedByStaff, and DomainInvitation,
|
||||||
if DomainInvitation.objects.filter(email=email, status=invited).exists():
|
set the verification "type" defined in VerificationTypeChoices.
|
||||||
return False
|
"""
|
||||||
|
email_or_username = self.email if self.email else self.username
|
||||||
|
retrieved = DomainInvitation.DomainInvitationStatus.RETRIEVED
|
||||||
|
verification_type = self.get_verification_type_from_email(email_or_username, invitation_status=retrieved)
|
||||||
|
|
||||||
return True
|
# An existing user may have been invited to a domain after they got verified.
|
||||||
|
# We need to check for this condition.
|
||||||
|
if verification_type == User.VerificationTypeChoices.INVITED:
|
||||||
|
invitation = (
|
||||||
|
DomainInvitation.objects.filter(email=email_or_username, status=retrieved)
|
||||||
|
.order_by("created_at")
|
||||||
|
.first()
|
||||||
|
)
|
||||||
|
|
||||||
|
# If you joined BEFORE the oldest invitation was created, then you were verified normally.
|
||||||
|
# (See logic in get_verification_type_from_email)
|
||||||
|
if not invitation and self.date_joined < invitation.created_at:
|
||||||
|
verification_type = User.VerificationTypeChoices.REGULAR
|
||||||
|
|
||||||
|
self.verification_type = verification_type
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_verification_type_from_email(cls, email, invitation_status=DomainInvitation.DomainInvitationStatus.INVITED):
|
||||||
|
"""Retrieves the verification type based off of a provided email address"""
|
||||||
|
|
||||||
|
verification_type = None
|
||||||
|
if TransitionDomain.objects.filter(Q(username=email) | Q(email=email)).exists():
|
||||||
|
# A new incoming user who is a domain manager for one of the domains
|
||||||
|
# that we inputted from Verisign (that is, their email address appears
|
||||||
|
# in the username field of a TransitionDomain)
|
||||||
|
verification_type = cls.VerificationTypeChoices.GRANDFATHERED
|
||||||
|
elif VerifiedByStaff.objects.filter(email=email).exists():
|
||||||
|
# New users flagged by Staff to bypass ial2
|
||||||
|
verification_type = cls.VerificationTypeChoices.VERIFIED_BY_STAFF
|
||||||
|
elif DomainInvitation.objects.filter(email=email, status=invitation_status).exists():
|
||||||
|
# A new incoming user who is being invited to be a domain manager (that is,
|
||||||
|
# their email address is in DomainInvitation for an invitation that is not yet "retrieved").
|
||||||
|
verification_type = cls.VerificationTypeChoices.INVITED
|
||||||
|
else:
|
||||||
|
verification_type = cls.VerificationTypeChoices.REGULAR
|
||||||
|
|
||||||
|
return verification_type
|
||||||
|
|
||||||
def check_domain_invitations_on_login(self):
|
def check_domain_invitations_on_login(self):
|
||||||
"""When a user first arrives on the site, we need to retrieve any domain
|
"""When a user first arrives on the site, we need to retrieve any domain
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
<address class="{% if no_title_top_padding %}margin-top-neg-1__detail-list{% endif %} {% if user.has_contact_info %}margin-bottom-1{% endif %} dja-address-contact-list">
|
<address class="{% if no_title_top_padding %}margin-top-neg-1__detail-list{% endif %} {% if user.has_contact_info %}margin-bottom-1{% endif %} dja-address-contact-list">
|
||||||
|
|
||||||
{% if show_formatted_name %}
|
{% if show_formatted_name %}
|
||||||
{% if contact.get_formatted_name %}
|
{% if user.get_formatted_name %}
|
||||||
<a href="{% url 'admin:registrar_contact_change' user.id %}">{{ user.get_formatted_name }}</a><br />
|
<a href="{% url 'admin:registrar_contact_change' user.id %}">{{ user.get_formatted_name }}</a><br />
|
||||||
{% else %}
|
{% else %}
|
||||||
None<br />
|
None<br />
|
||||||
|
@ -47,7 +47,12 @@
|
||||||
{% else %}
|
{% else %}
|
||||||
None<br>
|
None<br>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% else %}
|
{% else %}
|
||||||
No additional contact information found.
|
No additional contact information found.<br>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if user_verification_type %}
|
||||||
|
{{ user_verification_type }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</address>
|
</address>
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
{% comment %}
|
{% comment %}
|
||||||
This is using a custom implementation fieldset.html (see admin/fieldset.html)
|
This is using a custom implementation fieldset.html (see admin/fieldset.html)
|
||||||
{% endcomment %}
|
{% endcomment %}
|
||||||
|
|
||||||
{% block field_readonly %}
|
{% block field_readonly %}
|
||||||
{% with all_contacts=original_object.other_contacts.all %}
|
{% with all_contacts=original_object.other_contacts.all %}
|
||||||
{% if field.field.name == "other_contacts" %}
|
{% if field.field.name == "other_contacts" %}
|
||||||
|
@ -65,22 +66,15 @@ This is using a custom implementation fieldset.html (see admin/fieldset.html)
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
{% endblock field_readonly %}
|
{% endblock field_readonly %}
|
||||||
|
|
||||||
{% block help_text %}
|
|
||||||
<div class="help margin-bottom-1" {% if field.field.id_for_label %} id="{{ field.field.id_for_label }}_helptext"{% endif %}>
|
|
||||||
<div>{{ field.field.help_text|safe }}</div>
|
|
||||||
</div>
|
|
||||||
{% endblock help_text %}
|
|
||||||
|
|
||||||
|
|
||||||
{% block after_help_text %}
|
{% block after_help_text %}
|
||||||
{% if field.field.name == "creator" %}
|
{% if field.field.name == "creator" %}
|
||||||
<div class="flex-container tablet:margin-top-1">
|
<div class="flex-container tablet:margin-top-2">
|
||||||
<label aria-label="Creator contact details"></label>
|
<label aria-label="Creator contact details"></label>
|
||||||
{% include "django/admin/includes/contact_detail_list.html" with user=original_object.creator no_title_top_padding=field.is_readonly %}
|
{% include "django/admin/includes/contact_detail_list.html" with user=original_object.creator no_title_top_padding=field.is_readonly user_verification_type=original_object.creator.get_verification_type_display%}
|
||||||
</div>
|
</div>
|
||||||
{% include "django/admin/includes/user_detail_list.html" with user=original_object.creator no_title_top_padding=field.is_readonly %}
|
{% include "django/admin/includes/user_detail_list.html" with user=original_object.creator no_title_top_padding=field.is_readonly %}
|
||||||
{% elif field.field.name == "submitter" %}
|
{% elif field.field.name == "submitter" %}
|
||||||
<div class="flex-container">
|
<div class="flex-container tablet:margin-top-2">
|
||||||
<label aria-label="Submitter contact details"></label>
|
<label aria-label="Submitter contact details"></label>
|
||||||
{% include "django/admin/includes/contact_detail_list.html" with user=original_object.submitter no_title_top_padding=field.is_readonly %}
|
{% include "django/admin/includes/contact_detail_list.html" with user=original_object.submitter no_title_top_padding=field.is_readonly %}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -3065,7 +3065,15 @@ class TestMyUserAdmin(TestCase):
|
||||||
request.user = create_user()
|
request.user = create_user()
|
||||||
fieldsets = self.admin.get_fieldsets(request)
|
fieldsets = self.admin.get_fieldsets(request)
|
||||||
expected_fieldsets = (
|
expected_fieldsets = (
|
||||||
(None, {"fields": ("status",)}),
|
(
|
||||||
|
None,
|
||||||
|
{
|
||||||
|
"fields": (
|
||||||
|
"status",
|
||||||
|
"verification_type",
|
||||||
|
)
|
||||||
|
},
|
||||||
|
),
|
||||||
("Personal Info", {"fields": ("first_name", "last_name", "email")}),
|
("Personal Info", {"fields": ("first_name", "last_name", "email")}),
|
||||||
("Permissions", {"fields": ("is_active", "groups")}),
|
("Permissions", {"fields": ("is_active", "groups")}),
|
||||||
("Important dates", {"fields": ("last_login", "date_joined")}),
|
("Important dates", {"fields": ("last_login", "date_joined")}),
|
||||||
|
|
|
@ -14,8 +14,9 @@ from registrar.models import (
|
||||||
TransitionDomain,
|
TransitionDomain,
|
||||||
DomainInformation,
|
DomainInformation,
|
||||||
UserDomainRole,
|
UserDomainRole,
|
||||||
|
VerifiedByStaff,
|
||||||
|
PublicContact,
|
||||||
)
|
)
|
||||||
from registrar.models.public_contact import PublicContact
|
|
||||||
|
|
||||||
from django.core.management import call_command
|
from django.core.management import call_command
|
||||||
from unittest.mock import patch, call
|
from unittest.mock import patch, call
|
||||||
|
@ -25,6 +26,103 @@ from .common import MockEppLib, less_console_noise, completed_domain_request
|
||||||
from api.tests.common import less_console_noise_decorator
|
from api.tests.common import less_console_noise_decorator
|
||||||
|
|
||||||
|
|
||||||
|
class TestPopulateVerificationType(MockEppLib):
|
||||||
|
"""Tests for the populate_organization_type script"""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
"""Creates a fake domain object"""
|
||||||
|
super().setUp()
|
||||||
|
|
||||||
|
# Get the domain requests
|
||||||
|
self.domain_request_1 = completed_domain_request(
|
||||||
|
name="lasers.gov",
|
||||||
|
generic_org_type=DomainRequest.OrganizationChoices.FEDERAL,
|
||||||
|
is_election_board=True,
|
||||||
|
status=DomainRequest.DomainRequestStatus.IN_REVIEW,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Approve the request
|
||||||
|
self.domain_request_1.approve()
|
||||||
|
|
||||||
|
# Get the domains
|
||||||
|
self.domain_1 = Domain.objects.get(name="lasers.gov")
|
||||||
|
|
||||||
|
# Get users
|
||||||
|
self.regular_user, _ = User.objects.get_or_create(username="testuser@igormail.gov")
|
||||||
|
|
||||||
|
vip, _ = VerifiedByStaff.objects.get_or_create(email="vipuser@igormail.gov")
|
||||||
|
self.verified_by_staff_user, _ = User.objects.get_or_create(username="vipuser@igormail.gov")
|
||||||
|
|
||||||
|
grandfathered, _ = TransitionDomain.objects.get_or_create(
|
||||||
|
username="grandpa@igormail.gov", domain_name=self.domain_1.name
|
||||||
|
)
|
||||||
|
self.grandfathered_user, _ = User.objects.get_or_create(username="grandpa@igormail.gov")
|
||||||
|
|
||||||
|
invited, _ = DomainInvitation.objects.get_or_create(
|
||||||
|
email="invited@igormail.gov", domain=self.domain_1, status=DomainInvitation.DomainInvitationStatus.RETRIEVED
|
||||||
|
)
|
||||||
|
self.invited_user, _ = User.objects.get_or_create(username="invited@igormail.gov")
|
||||||
|
|
||||||
|
self.untouched_user, _ = User.objects.get_or_create(
|
||||||
|
username="iaminvincible@igormail.gov", verification_type=User.VerificationTypeChoices.GRANDFATHERED
|
||||||
|
)
|
||||||
|
|
||||||
|
# Fixture users should be untouched by the script. These will auto update once the
|
||||||
|
# user logs in / creates an account.
|
||||||
|
self.fixture_user, _ = User.objects.get_or_create(
|
||||||
|
username="fixture@igormail.gov", verification_type=User.VerificationTypeChoices.FIXTURE_USER
|
||||||
|
)
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
"""Deletes all DB objects related to migrations"""
|
||||||
|
super().tearDown()
|
||||||
|
|
||||||
|
# Delete domains and related information
|
||||||
|
Domain.objects.all().delete()
|
||||||
|
DomainInformation.objects.all().delete()
|
||||||
|
DomainRequest.objects.all().delete()
|
||||||
|
User.objects.all().delete()
|
||||||
|
Contact.objects.all().delete()
|
||||||
|
Website.objects.all().delete()
|
||||||
|
|
||||||
|
@less_console_noise_decorator
|
||||||
|
def run_populate_verification_type(self):
|
||||||
|
"""
|
||||||
|
This method executes the populate_organization_type command.
|
||||||
|
|
||||||
|
The 'call_command' function from Django's management framework is then used to
|
||||||
|
execute the populate_organization_type command with the specified arguments.
|
||||||
|
"""
|
||||||
|
with patch(
|
||||||
|
"registrar.management.commands.utility.terminal_helper.TerminalHelper.query_yes_no_exit", # noqa
|
||||||
|
return_value=True,
|
||||||
|
):
|
||||||
|
call_command("populate_verification_type")
|
||||||
|
|
||||||
|
@less_console_noise_decorator
|
||||||
|
def test_verification_type_script_populates_data(self):
|
||||||
|
"""Ensures that the verification type script actually populates data"""
|
||||||
|
|
||||||
|
# Run the script
|
||||||
|
self.run_populate_verification_type()
|
||||||
|
|
||||||
|
# Scripts don't work as we'd expect in our test environment, we need to manually
|
||||||
|
# trigger the refresh event
|
||||||
|
self.regular_user.refresh_from_db()
|
||||||
|
self.grandfathered_user.refresh_from_db()
|
||||||
|
self.invited_user.refresh_from_db()
|
||||||
|
self.verified_by_staff_user.refresh_from_db()
|
||||||
|
self.untouched_user.refresh_from_db()
|
||||||
|
|
||||||
|
# Test all users
|
||||||
|
self.assertEqual(self.regular_user.verification_type, User.VerificationTypeChoices.REGULAR)
|
||||||
|
self.assertEqual(self.grandfathered_user.verification_type, User.VerificationTypeChoices.GRANDFATHERED)
|
||||||
|
self.assertEqual(self.invited_user.verification_type, User.VerificationTypeChoices.INVITED)
|
||||||
|
self.assertEqual(self.verified_by_staff_user.verification_type, User.VerificationTypeChoices.VERIFIED_BY_STAFF)
|
||||||
|
self.assertEqual(self.untouched_user.verification_type, User.VerificationTypeChoices.GRANDFATHERED)
|
||||||
|
self.assertEqual(self.fixture_user.verification_type, User.VerificationTypeChoices.FIXTURE_USER)
|
||||||
|
|
||||||
|
|
||||||
class TestPopulateOrganizationType(MockEppLib):
|
class TestPopulateOrganizationType(MockEppLib):
|
||||||
"""Tests for the populate_organization_type script"""
|
"""Tests for the populate_organization_type script"""
|
||||||
|
|
||||||
|
|
|
@ -344,8 +344,6 @@ class TestDomainManagers(TestDomainOverview):
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
"""Ensure that the user has its original permissions"""
|
"""Ensure that the user has its original permissions"""
|
||||||
super().tearDown()
|
super().tearDown()
|
||||||
self.user.is_staff = False
|
|
||||||
self.user.save()
|
|
||||||
|
|
||||||
def test_domain_managers(self):
|
def test_domain_managers(self):
|
||||||
response = self.client.get(reverse("domain-users", kwargs={"pk": self.domain.id}))
|
response = self.client.get(reverse("domain-users", kwargs={"pk": self.domain.id}))
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
-i https://pypi.python.org/simple
|
-i https://pypi.python.org/simple
|
||||||
annotated-types==0.6.0; python_version >= '3.8'
|
annotated-types==0.6.0; python_version >= '3.8'
|
||||||
asgiref==3.8.1; python_version >= '3.8'
|
asgiref==3.8.1; python_version >= '3.8'
|
||||||
boto3==1.34.88; python_version >= '3.8'
|
boto3==1.34.90; python_version >= '3.8'
|
||||||
botocore==1.34.88; python_version >= '3.8'
|
botocore==1.34.90; python_version >= '3.8'
|
||||||
cachetools==5.3.3; python_version >= '3.7'
|
cachetools==5.3.3; python_version >= '3.7'
|
||||||
certifi==2024.2.2; python_version >= '3.6'
|
certifi==2024.2.2; python_version >= '3.6'
|
||||||
cfenv==0.5.3
|
cfenv==0.5.3
|
||||||
|
@ -15,7 +15,7 @@ dj-email-url==1.0.6
|
||||||
django==4.2.10; python_version >= '3.8'
|
django==4.2.10; python_version >= '3.8'
|
||||||
django-admin-multiple-choice-list-filter==0.1.1
|
django-admin-multiple-choice-list-filter==0.1.1
|
||||||
django-allow-cidr==0.7.1
|
django-allow-cidr==0.7.1
|
||||||
django-auditlog==2.3.0; python_version >= '3.7'
|
django-auditlog==3.0.0; python_version >= '3.8'
|
||||||
django-cache-url==3.4.5
|
django-cache-url==3.4.5
|
||||||
django-cors-headers==4.3.1; python_version >= '3.8'
|
django-cors-headers==4.3.1; python_version >= '3.8'
|
||||||
django-csp==3.8
|
django-csp==3.8
|
||||||
|
@ -44,8 +44,8 @@ phonenumberslite==8.13.35
|
||||||
psycopg2-binary==2.9.9; python_version >= '3.7'
|
psycopg2-binary==2.9.9; python_version >= '3.7'
|
||||||
pycparser==2.22; python_version >= '3.8'
|
pycparser==2.22; python_version >= '3.8'
|
||||||
pycryptodomex==3.20.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'
|
pycryptodomex==3.20.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'
|
||||||
pydantic==2.7.0; python_version >= '3.8'
|
pydantic==2.7.1; python_version >= '3.8'
|
||||||
pydantic-core==2.18.1; python_version >= '3.8'
|
pydantic-core==2.18.2; python_version >= '3.8'
|
||||||
pydantic-settings==2.2.1; python_version >= '3.8'
|
pydantic-settings==2.2.1; python_version >= '3.8'
|
||||||
pyjwkest==1.4.2
|
pyjwkest==1.4.2
|
||||||
python-dateutil==2.9.0.post0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'
|
python-dateutil==2.9.0.post0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue