mirror of
https://github.com/GSA/notifications-admin.git
synced 2026-08-26 01:04:00 -04:00
Pull main
This commit is contained in:
14
.ds.baseline
14
.ds.baseline
@@ -169,7 +169,7 @@
|
|||||||
"filename": "app/config.py",
|
"filename": "app/config.py",
|
||||||
"hashed_secret": "577a4c667e4af8682ca431857214b3a920883efc",
|
"hashed_secret": "577a4c667e4af8682ca431857214b3a920883efc",
|
||||||
"is_verified": false,
|
"is_verified": false,
|
||||||
"line_number": 111,
|
"line_number": 117,
|
||||||
"is_secret": false
|
"is_secret": false
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@@ -586,20 +586,12 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"tests/app/main/views/test_sign_in.py": [
|
"tests/app/main/views/test_sign_in.py": [
|
||||||
{
|
|
||||||
"type": "Private Key",
|
|
||||||
"filename": "tests/app/main/views/test_sign_in.py",
|
|
||||||
"hashed_secret": "1348b145fa1a555461c1b790a2f66614781091e9",
|
|
||||||
"is_verified": false,
|
|
||||||
"line_number": 31,
|
|
||||||
"is_secret": false
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"type": "Secret Keyword",
|
"type": "Secret Keyword",
|
||||||
"filename": "tests/app/main/views/test_sign_in.py",
|
"filename": "tests/app/main/views/test_sign_in.py",
|
||||||
"hashed_secret": "8b8b69116ee882b5e987e330f55db81aba0636f9",
|
"hashed_secret": "8b8b69116ee882b5e987e330f55db81aba0636f9",
|
||||||
"is_verified": false,
|
"is_verified": false,
|
||||||
"line_number": 104,
|
"line_number": 90,
|
||||||
"is_secret": false
|
"is_secret": false
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@@ -710,5 +702,5 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"generated_at": "2024-06-05T22:01:56Z"
|
"generated_at": "2024-06-11T22:26:18Z"
|
||||||
}
|
}
|
||||||
|
|||||||
4
.github/actions/setup-project/action.yml
vendored
4
.github/actions/setup-project/action.yml
vendored
@@ -9,10 +9,10 @@ runs:
|
|||||||
sudo apt-get update \
|
sudo apt-get update \
|
||||||
&& sudo apt-get install -y --no-install-recommends \
|
&& sudo apt-get install -y --no-install-recommends \
|
||||||
libcurl4-openssl-dev
|
libcurl4-openssl-dev
|
||||||
- name: Set up Python 3.12
|
- name: Set up Python 3.12.3
|
||||||
uses: actions/setup-python@v4
|
uses: actions/setup-python@v4
|
||||||
with:
|
with:
|
||||||
python-version: "3.12"
|
python-version: "3.12.3"
|
||||||
- name: Install poetry
|
- name: Install poetry
|
||||||
shell: bash
|
shell: bash
|
||||||
run: pip install poetry
|
run: pip install poetry
|
||||||
|
|||||||
8
.github/workflows/deploy-demo.yml
vendored
8
.github/workflows/deploy-demo.yml
vendored
@@ -18,11 +18,11 @@ jobs:
|
|||||||
|
|
||||||
- name: Check for changes to Terraform
|
- name: Check for changes to Terraform
|
||||||
id: changed-terraform-files
|
id: changed-terraform-files
|
||||||
uses: tj-actions/changed-files@v41.0.0
|
uses: tj-actions/changed-files@v44
|
||||||
with:
|
with:
|
||||||
files: |
|
files: |
|
||||||
terraform/demo
|
terraform/demo/**
|
||||||
terraform/shared
|
terraform/shared/**
|
||||||
.github/workflows/deploy-demo.yml
|
.github/workflows/deploy-demo.yml
|
||||||
- name: Terraform init
|
- name: Terraform init
|
||||||
if: steps.changed-terraform-files.outputs.any_changed == 'true'
|
if: steps.changed-terraform-files.outputs.any_changed == 'true'
|
||||||
@@ -88,7 +88,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Check for changes to egress config
|
- name: Check for changes to egress config
|
||||||
id: changed-egress-config
|
id: changed-egress-config
|
||||||
uses: tj-actions/changed-files@v41.0.0
|
uses: tj-actions/changed-files@v44
|
||||||
with:
|
with:
|
||||||
files: |
|
files: |
|
||||||
deploy-config/egress_proxy/notify-admin-demo.*.acl
|
deploy-config/egress_proxy/notify-admin-demo.*.acl
|
||||||
|
|||||||
8
.github/workflows/deploy-prod.yml
vendored
8
.github/workflows/deploy-prod.yml
vendored
@@ -18,11 +18,11 @@ jobs:
|
|||||||
|
|
||||||
- name: Check for changes to Terraform
|
- name: Check for changes to Terraform
|
||||||
id: changed-terraform-files
|
id: changed-terraform-files
|
||||||
uses: tj-actions/changed-files@v41.0.0
|
uses: tj-actions/changed-files@v44
|
||||||
with:
|
with:
|
||||||
files: |
|
files: |
|
||||||
terraform/production
|
terraform/production/**
|
||||||
terraform/shared
|
terraform/shared/**
|
||||||
.github/workflows/deploy-prod.yml
|
.github/workflows/deploy-prod.yml
|
||||||
- name: Terraform init
|
- name: Terraform init
|
||||||
if: steps.changed-terraform-files.outputs.any_changed == 'true'
|
if: steps.changed-terraform-files.outputs.any_changed == 'true'
|
||||||
@@ -88,7 +88,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Check for changes to egress config
|
- name: Check for changes to egress config
|
||||||
id: changed-egress-config
|
id: changed-egress-config
|
||||||
uses: tj-actions/changed-files@v41.0.0
|
uses: tj-actions/changed-files@v44
|
||||||
with:
|
with:
|
||||||
files: |
|
files: |
|
||||||
deploy-config/egress_proxy/notify-admin-production.*.acl
|
deploy-config/egress_proxy/notify-admin-production.*.acl
|
||||||
|
|||||||
166
.github/workflows/deploy.yml
vendored
166
.github/workflows/deploy.yml
vendored
@@ -17,96 +17,96 @@ jobs:
|
|||||||
|
|
||||||
environment: staging
|
environment: staging
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 2
|
fetch-depth: 2
|
||||||
|
|
||||||
- name: Check for changes to Terraform
|
- name: Check for changes to Terraform
|
||||||
id: changed-terraform-files
|
id: changed-terraform-files
|
||||||
uses: tj-actions/changed-files@v41.0.0
|
uses: tj-actions/changed-files@v44
|
||||||
with:
|
with:
|
||||||
files: |
|
files: |
|
||||||
terraform/staging
|
terraform/staging/**
|
||||||
terraform/shared
|
terraform/shared/**
|
||||||
.github/workflows/deploy.yml
|
.github/workflows/deploy.yml
|
||||||
- name: Terraform init
|
- name: Terraform init
|
||||||
if: steps.changed-terraform-files.outputs.any_changed == 'true'
|
if: steps.changed-terraform-files.outputs.any_changed == 'true'
|
||||||
working-directory: terraform/staging
|
working-directory: terraform/staging
|
||||||
env:
|
env:
|
||||||
AWS_ACCESS_KEY_ID: ${{ secrets.TERRAFORM_STATE_ACCESS_KEY }}
|
AWS_ACCESS_KEY_ID: ${{ secrets.TERRAFORM_STATE_ACCESS_KEY }}
|
||||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.TERRAFORM_STATE_SECRET_ACCESS_KEY }}
|
AWS_SECRET_ACCESS_KEY: ${{ secrets.TERRAFORM_STATE_SECRET_ACCESS_KEY }}
|
||||||
run: terraform init
|
run: terraform init
|
||||||
- name: Terraform apply
|
- name: Terraform apply
|
||||||
if: steps.changed-terraform-files.outputs.any_changed == 'true'
|
if: steps.changed-terraform-files.outputs.any_changed == 'true'
|
||||||
working-directory: terraform/staging
|
working-directory: terraform/staging
|
||||||
env:
|
env:
|
||||||
AWS_ACCESS_KEY_ID: ${{ secrets.TERRAFORM_STATE_ACCESS_KEY }}
|
AWS_ACCESS_KEY_ID: ${{ secrets.TERRAFORM_STATE_ACCESS_KEY }}
|
||||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.TERRAFORM_STATE_SECRET_ACCESS_KEY }}
|
AWS_SECRET_ACCESS_KEY: ${{ secrets.TERRAFORM_STATE_SECRET_ACCESS_KEY }}
|
||||||
TF_VAR_cf_user: ${{ secrets.CLOUDGOV_USERNAME }}
|
TF_VAR_cf_user: ${{ secrets.CLOUDGOV_USERNAME }}
|
||||||
TF_VAR_cf_password: ${{ secrets.CLOUDGOV_PASSWORD }}
|
TF_VAR_cf_password: ${{ secrets.CLOUDGOV_PASSWORD }}
|
||||||
run: terraform apply -auto-approve -input=false
|
run: terraform apply -auto-approve -input=false
|
||||||
|
|
||||||
- uses: ./.github/actions/setup-project
|
- uses: ./.github/actions/setup-project
|
||||||
|
|
||||||
- name: Create requirements.txt
|
- name: Create requirements.txt
|
||||||
run: poetry export --without-hashes --format=requirements.txt > requirements.txt
|
run: poetry export --without-hashes --format=requirements.txt > requirements.txt
|
||||||
|
|
||||||
|
|
||||||
- name: Deploy to cloud.gov
|
- name: Deploy to cloud.gov
|
||||||
uses: 18f/cg-deploy-action@main
|
uses: 18f/cg-deploy-action@main
|
||||||
env:
|
env:
|
||||||
DANGEROUS_SALT: ${{ secrets.DANGEROUS_SALT }}
|
DANGEROUS_SALT: ${{ secrets.DANGEROUS_SALT }}
|
||||||
SECRET_KEY: ${{ secrets.SECRET_KEY }}
|
SECRET_KEY: ${{ secrets.SECRET_KEY }}
|
||||||
ADMIN_CLIENT_SECRET: ${{ secrets.ADMIN_CLIENT_SECRET }}
|
ADMIN_CLIENT_SECRET: ${{ secrets.ADMIN_CLIENT_SECRET }}
|
||||||
NEW_RELIC_LICENSE_KEY: ${{ secrets.NEW_RELIC_LICENSE_KEY }}
|
NEW_RELIC_LICENSE_KEY: ${{ secrets.NEW_RELIC_LICENSE_KEY }}
|
||||||
NR_BROWSER_KEY: ${{ secrets.NR_BROWSER_KEY }}
|
NR_BROWSER_KEY: ${{ secrets.NR_BROWSER_KEY }}
|
||||||
COMMIT_HASH: ${{ github.sha }}
|
COMMIT_HASH: ${{ github.sha }}
|
||||||
LOGIN_PEM: ${{ secrets.LOGIN_PEM }}
|
LOGIN_PEM: ${{ secrets.LOGIN_PEM }}
|
||||||
LOGIN_DOT_GOV_CLIENT_ID: "urn:gov:gsa:openidconnect.profiles:sp:sso:gsa:notify-gov"
|
LOGIN_DOT_GOV_CLIENT_ID: "urn:gov:gsa:openidconnect.profiles:sp:sso:gsa:notify-gov"
|
||||||
LOGIN_DOT_GOV_USER_INFO_URL: "https://secure.login.gov/api/openid_connect/userinfo"
|
LOGIN_DOT_GOV_USER_INFO_URL: "https://secure.login.gov/api/openid_connect/userinfo"
|
||||||
LOGIN_DOT_GOV_ACCESS_TOKEN_URL: "https://secure.login.gov/api/openid_connect/token"
|
LOGIN_DOT_GOV_ACCESS_TOKEN_URL: "https://secure.login.gov/api/openid_connect/token"
|
||||||
LOGIN_DOT_GOV_LOGOUT_URL: "https://secure.login.gov/openid_connect/logout?client_id=urn:gov:gsa:openidconnect.profiles:sp:sso:gsa:notify-gov&post_logout_redirect_uri=https://notify-staging.app.cloud.gov/sign-out"
|
LOGIN_DOT_GOV_LOGOUT_URL: "https://secure.login.gov/openid_connect/logout?client_id=urn:gov:gsa:openidconnect.profiles:sp:sso:gsa:notify-gov&post_logout_redirect_uri=https://notify-staging.app.cloud.gov/sign-out"
|
||||||
LOGIN_DOT_GOV_BASE_LOGOUT_URL: "https://secure.login.gov/openid_connect/logout?"
|
LOGIN_DOT_GOV_BASE_LOGOUT_URL: "https://secure.login.gov/openid_connect/logout?"
|
||||||
LOGIN_DOT_GOV_SIGNOUT_REDIRECT: "https://notify-staging.app.cloud.gov/sign-out"
|
LOGIN_DOT_GOV_SIGNOUT_REDIRECT: "https://notify-staging.app.cloud.gov/sign-out"
|
||||||
LOGIN_DOT_GOV_INITIAL_SIGNIN_URL: "https://secure.login.gov/openid_connect/authorize?acr_values=http%3A%2F%2Fidmanagement.gov%2Fns%2Fassurance%2Fial%2F1&client_id=urn:gov:gsa:openidconnect.profiles:sp:sso:gsa:notify-gov&nonce=NONCE&prompt=select_account&redirect_uri=https://notify-staging.app.cloud.gov/sign-in&response_type=code&scope=openid+email&state=STATEE"
|
LOGIN_DOT_GOV_INITIAL_SIGNIN_URL: "https://secure.login.gov/openid_connect/authorize?acr_values=http%3A%2F%2Fidmanagement.gov%2Fns%2Fassurance%2Fial%2F1&client_id=urn:gov:gsa:openidconnect.profiles:sp:sso:gsa:notify-gov&nonce=NONCE&prompt=select_account&redirect_uri=https://notify-staging.app.cloud.gov/sign-in&response_type=code&scope=openid+email&state=STATEE"
|
||||||
with:
|
with:
|
||||||
cf_username: ${{ secrets.CLOUDGOV_USERNAME }}
|
cf_username: ${{ secrets.CLOUDGOV_USERNAME }}
|
||||||
cf_password: ${{ secrets.CLOUDGOV_PASSWORD }}
|
cf_password: ${{ secrets.CLOUDGOV_PASSWORD }}
|
||||||
cf_org: gsa-tts-benefits-studio
|
cf_org: gsa-tts-benefits-studio
|
||||||
cf_space: notify-staging
|
cf_space: notify-staging
|
||||||
push_arguments: >-
|
push_arguments: >-
|
||||||
--vars-file deploy-config/staging.yml
|
--vars-file deploy-config/staging.yml
|
||||||
--var DANGEROUS_SALT="$DANGEROUS_SALT"
|
--var DANGEROUS_SALT="$DANGEROUS_SALT"
|
||||||
--var SECRET_KEY="$SECRET_KEY"
|
--var SECRET_KEY="$SECRET_KEY"
|
||||||
--var ADMIN_CLIENT_USERNAME="notify-admin"
|
--var ADMIN_CLIENT_USERNAME="notify-admin"
|
||||||
--var ADMIN_CLIENT_SECRET="$ADMIN_CLIENT_SECRET"
|
--var ADMIN_CLIENT_SECRET="$ADMIN_CLIENT_SECRET"
|
||||||
--var NEW_RELIC_LICENSE_KEY="$NEW_RELIC_LICENSE_KEY"
|
--var NEW_RELIC_LICENSE_KEY="$NEW_RELIC_LICENSE_KEY"
|
||||||
--var NR_BROWSER_KEY="$NR_BROWSER_KEY"
|
--var NR_BROWSER_KEY="$NR_BROWSER_KEY"
|
||||||
--var COMMIT_HASH="$COMMIT_HASH"
|
--var COMMIT_HASH="$COMMIT_HASH"
|
||||||
--var LOGIN_PEM="$LOGIN_PEM"
|
--var LOGIN_PEM="$LOGIN_PEM"
|
||||||
--var LOGIN_DOT_GOV_CLIENT_ID="$LOGIN_DOT_GOV_CLIENT_ID"
|
--var LOGIN_DOT_GOV_CLIENT_ID="$LOGIN_DOT_GOV_CLIENT_ID"
|
||||||
--var LOGIN_DOT_GOV_USER_INFO_URL="$LOGIN_DOT_GOV_USER_INFO_URL"
|
--var LOGIN_DOT_GOV_USER_INFO_URL="$LOGIN_DOT_GOV_USER_INFO_URL"
|
||||||
--var LOGIN_DOT_GOV_ACCESS_TOKEN_URL="$LOGIN_DOT_GOV_ACCESS_TOKEN_URL"
|
--var LOGIN_DOT_GOV_ACCESS_TOKEN_URL="$LOGIN_DOT_GOV_ACCESS_TOKEN_URL"
|
||||||
--var LOGIN_DOT_GOV_LOGOUT_URL="$LOGIN_DOT_GOV_LOGOUT_URL"
|
--var LOGIN_DOT_GOV_LOGOUT_URL="$LOGIN_DOT_GOV_LOGOUT_URL"
|
||||||
--var LOGIN_DOT_GOV_BASE_LOGOUT_URL="$LOGIN_DOT_GOV_BASE_LOGOUT_URL"
|
--var LOGIN_DOT_GOV_BASE_LOGOUT_URL="$LOGIN_DOT_GOV_BASE_LOGOUT_URL"
|
||||||
--var LOGIN_DOT_GOV_SIGNOUT_REDIRECT="$LOGIN_DOT_GOV_SIGNOUT_REDIRECT"
|
--var LOGIN_DOT_GOV_SIGNOUT_REDIRECT="$LOGIN_DOT_GOV_SIGNOUT_REDIRECT"
|
||||||
--var LOGIN_DOT_GOV_INITIAL_SIGNIN_URL="$LOGIN_DOT_GOV_INITIAL_SIGNIN_URL"
|
--var LOGIN_DOT_GOV_INITIAL_SIGNIN_URL="$LOGIN_DOT_GOV_INITIAL_SIGNIN_URL"
|
||||||
|
|
||||||
|
|
||||||
- name: Check for changes to egress config
|
- name: Check for changes to egress config
|
||||||
id: changed-egress-config
|
id: changed-egress-config
|
||||||
uses: tj-actions/changed-files@v41.0.0
|
uses: tj-actions/changed-files@v44
|
||||||
with:
|
with:
|
||||||
files: |
|
files: |
|
||||||
deploy-config/egress_proxy/notify-admin-staging.*.acl
|
deploy-config/egress_proxy/notify-admin-staging.*.acl
|
||||||
.github/actions/deploy-proxy/action.yml
|
.github/actions/deploy-proxy/action.yml
|
||||||
.github/workflows/deploy.yml
|
.github/workflows/deploy.yml
|
||||||
- name: Deploy egress proxy
|
- name: Deploy egress proxy
|
||||||
if: steps.changed-egress-config.outputs.any_changed == 'true'
|
if: steps.changed-egress-config.outputs.any_changed == 'true'
|
||||||
uses: ./.github/actions/deploy-proxy
|
uses: ./.github/actions/deploy-proxy
|
||||||
with:
|
with:
|
||||||
cf_space: notify-staging
|
cf_space: notify-staging
|
||||||
app: notify-admin-staging
|
app: notify-admin-staging
|
||||||
|
|
||||||
bail:
|
bail:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|||||||
7
Makefile
7
Makefile
@@ -21,7 +21,8 @@ bootstrap: generate-version-file ## Set up everything to run the app
|
|||||||
poetry install --sync --no-root
|
poetry install --sync --no-root
|
||||||
poetry run playwright install --with-deps
|
poetry run playwright install --with-deps
|
||||||
poetry run pre-commit install
|
poetry run pre-commit install
|
||||||
source $(NVMSH) --no-use && nvm install && npm ci --no-audit
|
source $(NVMSH) --no-use && nvm install && npm install
|
||||||
|
source $(NVMSH) && npm ci --no-audit
|
||||||
source $(NVMSH) && npm run build
|
source $(NVMSH) && npm run build
|
||||||
|
|
||||||
.PHONY: watch-frontend
|
.PHONY: watch-frontend
|
||||||
@@ -40,6 +41,10 @@ run-flask-bare: ## Run flask without invoking poetry so we can override ENV var
|
|||||||
npm-audit: ## Check for vulnerabilities in NPM packages
|
npm-audit: ## Check for vulnerabilities in NPM packages
|
||||||
source $(NVMSH) && npm run audit
|
source $(NVMSH) && npm run audit
|
||||||
|
|
||||||
|
.PHONY: npm-audit-fix
|
||||||
|
npm-audit-fix: ## Fix vulnerabilities that do not require attentino (according to npm)
|
||||||
|
source $(NVMSH) && npm audit fix
|
||||||
|
|
||||||
.PHONY: help
|
.PHONY: help
|
||||||
help:
|
help:
|
||||||
@cat $(MAKEFILE_LIST) | grep -E '^[a-zA-Z_-]+:.*?## .*$$' | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
|
@cat $(MAKEFILE_LIST) | grep -E '^[a-zA-Z_-]+:.*?## .*$$' | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
|
||||||
|
|||||||
28
README.md
28
README.md
@@ -40,7 +40,7 @@ You will need the following items:
|
|||||||
[Follow the instructions here to set up the Notify.gov API.](https://github.com/GSA/notifications-api#before-you-start)
|
[Follow the instructions here to set up the Notify.gov API.](https://github.com/GSA/notifications-api#before-you-start)
|
||||||
|
|
||||||
The Notify.gov API is required in order for the Notify.gov Admin UI to run, and
|
The Notify.gov API is required in order for the Notify.gov Admin UI to run, and
|
||||||
it will also take care of many of the steps that are listed here. The sections
|
it will also take care of many of the steps that are listed here. The sections
|
||||||
that are a repeat from the API setup are flagged with an **[API Step]** label
|
that are a repeat from the API setup are flagged with an **[API Step]** label
|
||||||
in front of them.
|
in front of them.
|
||||||
|
|
||||||
@@ -83,11 +83,13 @@ Your system `$PATH` environment variable is likely set in one of these
|
|||||||
locations:
|
locations:
|
||||||
|
|
||||||
For BASH shells:
|
For BASH shells:
|
||||||
|
|
||||||
- `~/.bashrc`
|
- `~/.bashrc`
|
||||||
- `~/.bash_profile`
|
- `~/.bash_profile`
|
||||||
- `~/.profile`
|
- `~/.profile`
|
||||||
|
|
||||||
For ZSH shells:
|
For ZSH shells:
|
||||||
|
|
||||||
- `~/.zshrc`
|
- `~/.zshrc`
|
||||||
- `~/.zprofile`
|
- `~/.zprofile`
|
||||||
|
|
||||||
@@ -97,7 +99,7 @@ environments.
|
|||||||
Which file you need to modify depends on whether or not you are running an
|
Which file you need to modify depends on whether or not you are running an
|
||||||
interactive shell or a login shell
|
interactive shell or a login shell
|
||||||
(see [this Stack Overflow post](https://stackoverflow.com/questions/18186929/what-are-the-differences-between-a-login-shell-and-interactive-shell)
|
(see [this Stack Overflow post](https://stackoverflow.com/questions/18186929/what-are-the-differences-between-a-login-shell-and-interactive-shell)
|
||||||
for an explanation of the differences). If you're still not sure, please ask
|
for an explanation of the differences). If you're still not sure, please ask
|
||||||
the team for help!
|
the team for help!
|
||||||
|
|
||||||
Once you determine which file you'll need to modify, add these lines before any
|
Once you determine which file you'll need to modify, add these lines before any
|
||||||
@@ -158,7 +160,7 @@ _NOTE: This project currently uses the latest `1.4.x release of Terraform._
|
|||||||
#### [API Step] Python Installation
|
#### [API Step] Python Installation
|
||||||
|
|
||||||
Now we're going to install a tool to help us manage Python versions and
|
Now we're going to install a tool to help us manage Python versions and
|
||||||
virtual environments on our system. First, we'll install
|
virtual environments on our system. First, we'll install
|
||||||
[pyenv](https://github.com/pyenv/pyenv) and one of its plugins,
|
[pyenv](https://github.com/pyenv/pyenv) and one of its plugins,
|
||||||
[pyenv-virtualenv](https://github.com/pyenv/pyenv-virtualenv), with Homebrew:
|
[pyenv-virtualenv](https://github.com/pyenv/pyenv-virtualenv), with Homebrew:
|
||||||
|
|
||||||
@@ -285,7 +287,7 @@ we'll use `3.12` in our example here since we recently upgraded to this version:
|
|||||||
pyenv install 3.12
|
pyenv install 3.12
|
||||||
```
|
```
|
||||||
|
|
||||||
Next, delete the virtual environment you previously had set up. If you followed
|
Next, delete the virtual environment you previously had set up. If you followed
|
||||||
the instructions above with the first-time set up, you can do this with `pyenv`:
|
the instructions above with the first-time set up, you can do this with `pyenv`:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
@@ -306,6 +308,20 @@ you'll be set with an upgraded version of Python.
|
|||||||
|
|
||||||
_If you're not sure about the details of your current virtual environment, you can run `poetry env info` to get more information. If you've been using `pyenv` for everything, you can also see all available virtual environments with `pyenv virtualenvs`._
|
_If you're not sure about the details of your current virtual environment, you can run `poetry env info` to get more information. If you've been using `pyenv` for everything, you can also see all available virtual environments with `pyenv virtualenvs`._
|
||||||
|
|
||||||
|
#### Updating the .env file for Login.gov
|
||||||
|
|
||||||
|
To configure the application for Login.gov, you will need to update the following environment variables in the .env file:
|
||||||
|
|
||||||
|
```
|
||||||
|
COMMIT_HASH=”--------”
|
||||||
|
```
|
||||||
|
|
||||||
|
Reach out to someone on the team to get the most recent Login.gov key.
|
||||||
|
|
||||||
|
```
|
||||||
|
LOGIN_PEM="INSERT_LOGIN_GOV_KEY_HERE"
|
||||||
|
```
|
||||||
|
|
||||||
#### Updating the .env file for E2E tests
|
#### Updating the .env file for E2E tests
|
||||||
|
|
||||||
With the newly created `.env` file in place, you'll need to make one more
|
With the newly created `.env` file in place, you'll need to make one more
|
||||||
@@ -353,7 +369,7 @@ API is running as well!
|
|||||||
## Creating a 'First User' in the database
|
## Creating a 'First User' in the database
|
||||||
|
|
||||||
After you have completed all setup steps, you will be unable to log in, because there
|
After you have completed all setup steps, you will be unable to log in, because there
|
||||||
will not be a user in the database to link to the login.gov account you are using. So
|
will not be a user in the database to link to the login.gov account you are using. So
|
||||||
you will need to create that user in your database using the 'create-test-user' command.
|
you will need to create that user in your database using the 'create-test-user' command.
|
||||||
|
|
||||||
Open two terminals pointing to the api project and then run these commands in the
|
Open two terminals pointing to the api project and then run these commands in the
|
||||||
@@ -372,8 +388,6 @@ is the same one you are using in login.gov and make sure your phone number is in
|
|||||||
If for any reason in the course of development it is necessary for your to delete your db
|
If for any reason in the course of development it is necessary for your to delete your db
|
||||||
via the `dropdb` command, you will need to repeat these steps when you recreate your db.
|
via the `dropdb` command, you will need to repeat these steps when you recreate your db.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Git Hooks
|
## Git Hooks
|
||||||
|
|
||||||
We're using [`pre-commit`](https://pre-commit.com/) to manage hooks in order to
|
We're using [`pre-commit`](https://pre-commit.com/) to manage hooks in order to
|
||||||
|
|||||||
@@ -233,6 +233,24 @@ def create_app(application):
|
|||||||
)
|
)
|
||||||
logging.init_app(application)
|
logging.init_app(application)
|
||||||
|
|
||||||
|
# Hopefully will help identify if there is a race condition causing the CSRF errors
|
||||||
|
# that we have occasionally seen in our environments.
|
||||||
|
for key in ("SECRET_KEY", "DANGEROUS_SALT"):
|
||||||
|
try:
|
||||||
|
value = application.config[key]
|
||||||
|
except KeyError:
|
||||||
|
application.logger.error(f"Env Var {key} doesn't exist.")
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
data_len = len(value.strip())
|
||||||
|
except (TypeError, AttributeError):
|
||||||
|
application.logger.error(f"Env Var {key} invalid type: {type(value)}")
|
||||||
|
else:
|
||||||
|
if data_len:
|
||||||
|
application.logger.info(f"Env Var {key} is a non-zero length.")
|
||||||
|
else:
|
||||||
|
application.logger.error(f"Env Var {key} is empty.")
|
||||||
|
|
||||||
login_manager.login_view = "main.sign_in"
|
login_manager.login_view = "main.sign_in"
|
||||||
login_manager.login_message_category = "default"
|
login_manager.login_message_category = "default"
|
||||||
login_manager.session_protection = None
|
login_manager.session_protection = None
|
||||||
|
|||||||
1
app/assets/images/logo-login.svg
Normal file
1
app/assets/images/logo-login.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 292.1 39.47"><path fill="#fff" d="M7.44 6.89h17.2v22.06H7.44z"/><path fill="#112e51" d="M53.45 5.28h-6.16v27.31h17.87v-5.38H53.45V5.28zm30.76-.19c-8.35 0-14.67 5.89-14.67 13.85s6.32 13.93 14.67 13.93 14.67-6 14.67-13.93S92.56 5.09 84.21 5.09zm.08 22.39a8.49 8.49 0 0 1-8.43-8.54 8.4 8.4 0 0 1 8.43-8.47 8.29 8.29 0 0 1 8.27 8.47 8.36 8.36 0 0 1-8.27 8.54zm35.36-17.05a11.43 11.43 0 0 1 7.49 3l3.47-4.41a17.21 17.21 0 0 0-11-4c-8.51 0-14.9 6-14.9 13.85s6.29 13.99 14.58 13.99a20.21 20.21 0 0 0 11.08-3.55V17.57h-10.11v4.82H125V26a12.42 12.42 0 0 1-5.34 1.48 8.52 8.52 0 1 1 0-17zm19.61-5.15h6.17v27.31h-6.17zm34.53 17.56L160.52 5.28h-5.61v27.31h5.89V15.07l13.22 17.52h5.62V5.28h-5.85v17.56zm17.47 2.85a3.39 3.39 0 0 0-3.47 3.56 3.47 3.47 0 1 0 6.94 0 3.36 3.36 0 0 0-3.47-3.56zm23.15-15.26a11.43 11.43 0 0 1 7.49 3l3.47-4.41a17.21 17.21 0 0 0-11-4c-8.51 0-14.9 6-14.9 13.85s6.28 13.93 14.55 13.93a20.21 20.21 0 0 0 11.08-3.55V17.57H215v4.82h4.77V26a12.42 12.42 0 0 1-5.34 1.48 8.52 8.52 0 1 1 0-17zm32.23-5.34C238.29 5.09 232 11 232 18.94s6.32 13.93 14.67 13.93 14.67-6 14.67-13.93S255 5.09 246.64 5.09zm.08 22.39a8.49 8.49 0 0 1-8.43-8.54 8.4 8.4 0 0 1 8.43-8.47 8.29 8.29 0 0 1 8.28 8.47 8.36 8.36 0 0 1-8.28 8.54zm39.02-22.2-7.49 20.6-7.45-20.6h-6.63l10.73 27.31h6.36L292.1 5.28h-6.36z"/><path fill="#e21d3e" d="m11.38 25.34 1.45-8.28a4.39 4.39 0 0 1 2.71-7.86V0H4.34A4.34 4.34 0 0 0 0 4.34v21.28a2.62 2.62 0 0 0 .26 1.16c.94 1.9 4.65 8 15.27 12.69V25.7a24.3 24.3 0 0 1-4.15-.36z"/><path fill="#b51e23" d="M26.72 0H15.53v9.19A4.39 4.39 0 0 1 18.28 17l1.45 8.32a24.29 24.29 0 0 1-4.2.36v13.79C26.14 34.8 29.86 28.68 30.8 26.79a2.62 2.62 0 0 0 .26-1.16V4.34A4.34 4.34 0 0 0 26.72 0z"/></svg>
|
||||||
|
After Width: | Height: | Size: 1.7 KiB |
@@ -157,6 +157,18 @@ td.table-empty-message {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.usa-button img {
|
||||||
|
margin-left: .5rem;
|
||||||
|
height: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.usa-button.login-button.login-button--primary,.login-button.login-button--primary:hover{
|
||||||
|
color:#112e51;background-color:#fff;
|
||||||
|
border:1px solid #767676;
|
||||||
|
display: inline-flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
.user-list-edit-link:active:before,
|
.user-list-edit-link:active:before,
|
||||||
.user-list-edit-link:focus:before {
|
.user-list-edit-link:focus:before {
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
|
|||||||
@@ -53,7 +53,13 @@ class Config(object):
|
|||||||
PERMANENT_SESSION_LIFETIME = 1800 # 30 Minutes
|
PERMANENT_SESSION_LIFETIME = 1800 # 30 Minutes
|
||||||
SEND_FILE_MAX_AGE_DEFAULT = 365 * 24 * 60 * 60 # 1 year
|
SEND_FILE_MAX_AGE_DEFAULT = 365 * 24 * 60 * 60 # 1 year
|
||||||
REPLY_TO_EMAIL_ADDRESS_VALIDATION_TIMEOUT = 45
|
REPLY_TO_EMAIL_ADDRESS_VALIDATION_TIMEOUT = 45
|
||||||
ACTIVITY_STATS_LIMIT_DAYS = 7
|
ACTIVITY_STATS_LIMIT_DAYS = {
|
||||||
|
"today": 0,
|
||||||
|
"one_day": 1,
|
||||||
|
"three_day": 3,
|
||||||
|
"five_day": 5,
|
||||||
|
"seven_day": 7,
|
||||||
|
}
|
||||||
SESSION_COOKIE_HTTPONLY = True
|
SESSION_COOKIE_HTTPONLY = True
|
||||||
SESSION_COOKIE_NAME = "notify_admin_session"
|
SESSION_COOKIE_NAME = "notify_admin_session"
|
||||||
SESSION_COOKIE_SECURE = True
|
SESSION_COOKIE_SECURE = True
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import os
|
import os
|
||||||
|
|
||||||
from flask import abort, redirect, render_template, request, url_for
|
from flask import abort, current_app, redirect, render_template, request, url_for
|
||||||
from flask_login import current_user
|
from flask_login import current_user
|
||||||
|
|
||||||
from app import status_api_client
|
from app import status_api_client
|
||||||
@@ -9,20 +9,28 @@ from app.main import main
|
|||||||
from app.main.views.pricing import CURRENT_SMS_RATE
|
from app.main.views.pricing import CURRENT_SMS_RATE
|
||||||
from app.main.views.sub_navigation_dictionaries import features_nav, using_notify_nav
|
from app.main.views.sub_navigation_dictionaries import features_nav, using_notify_nav
|
||||||
from app.utils.user import user_is_logged_in
|
from app.utils.user import user_is_logged_in
|
||||||
|
from notifications_utils.url_safe_token import generate_token
|
||||||
login_dot_gov_url = os.getenv("LOGIN_DOT_GOV_INITIAL_SIGNIN_URL")
|
|
||||||
|
|
||||||
|
|
||||||
@main.route("/")
|
@main.route("/")
|
||||||
def index():
|
def index():
|
||||||
if current_user and current_user.is_authenticated:
|
if current_user and current_user.is_authenticated:
|
||||||
return redirect(url_for("main.choose_account"))
|
return redirect(url_for("main.choose_account"))
|
||||||
|
token = generate_token(
|
||||||
|
str(request.remote_addr),
|
||||||
|
current_app.config["SECRET_KEY"],
|
||||||
|
current_app.config["DANGEROUS_SALT"],
|
||||||
|
)
|
||||||
|
url = os.getenv("LOGIN_DOT_GOV_INITIAL_SIGNIN_URL")
|
||||||
|
# handle unit tests
|
||||||
|
if url is not None:
|
||||||
|
url = url.replace("NONCE", token)
|
||||||
|
url = url.replace("STATE", token)
|
||||||
return render_template(
|
return render_template(
|
||||||
"views/signedout.html",
|
"views/signedout.html",
|
||||||
sms_rate=CURRENT_SMS_RATE,
|
sms_rate=CURRENT_SMS_RATE,
|
||||||
counts=status_api_client.get_count_of_live_services_and_organizations(),
|
counts=status_api_client.get_count_of_live_services_and_organizations(),
|
||||||
login_dot_gov_url=login_dot_gov_url,
|
initial_signin_url=url,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -143,11 +143,40 @@ def view_notifications(service_id, message_type=None):
|
|||||||
True: ["reference"],
|
True: ["reference"],
|
||||||
False: [],
|
False: [],
|
||||||
}.get(bool(current_service.api_keys)),
|
}.get(bool(current_service.api_keys)),
|
||||||
download_link=url_for(
|
download_link_one_day=url_for(
|
||||||
".download_notifications_csv",
|
".download_notifications_csv",
|
||||||
service_id=current_service.id,
|
service_id=current_service.id,
|
||||||
message_type=message_type,
|
message_type=message_type,
|
||||||
status=request.args.get("status"),
|
status=request.args.get("status"),
|
||||||
|
number_of_days="one_day",
|
||||||
|
),
|
||||||
|
download_link_today=url_for(
|
||||||
|
".download_notifications_csv",
|
||||||
|
service_id=current_service.id,
|
||||||
|
message_type=message_type,
|
||||||
|
status=request.args.get("status"),
|
||||||
|
number_of_days="today",
|
||||||
|
),
|
||||||
|
download_link_three_day=url_for(
|
||||||
|
".download_notifications_csv",
|
||||||
|
service_id=current_service.id,
|
||||||
|
message_type=message_type,
|
||||||
|
status=request.args.get("status"),
|
||||||
|
number_of_days="three_day",
|
||||||
|
),
|
||||||
|
download_link_five_day=url_for(
|
||||||
|
".download_notifications_csv",
|
||||||
|
service_id=current_service.id,
|
||||||
|
message_type=message_type,
|
||||||
|
status=request.args.get("status"),
|
||||||
|
number_of_days="five_day",
|
||||||
|
),
|
||||||
|
download_link_seven_day=url_for(
|
||||||
|
".download_notifications_csv",
|
||||||
|
service_id=current_service.id,
|
||||||
|
message_type=message_type,
|
||||||
|
status=request.args.get("status"),
|
||||||
|
number_of_days="seven_day",
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -183,10 +212,9 @@ def get_notifications(service_id, message_type, status_override=None): # noqa
|
|||||||
filter_args["status"] = set_status_filters(filter_args)
|
filter_args["status"] = set_status_filters(filter_args)
|
||||||
service_data_retention_days = None
|
service_data_retention_days = None
|
||||||
search_term = request.form.get("to", "")
|
search_term = request.form.get("to", "")
|
||||||
|
|
||||||
if message_type is not None:
|
if message_type is not None:
|
||||||
service_data_retention_days = current_service.get_days_of_retention(
|
service_data_retention_days = current_service.get_days_of_retention(
|
||||||
message_type
|
message_type, number_of_days="seven_day"
|
||||||
)
|
)
|
||||||
|
|
||||||
if request.path.endswith("csv") and current_user.has_permissions("view_activity"):
|
if request.path.endswith("csv") and current_user.has_permissions("view_activity"):
|
||||||
@@ -212,7 +240,6 @@ def get_notifications(service_id, message_type, status_override=None): # noqa
|
|||||||
)
|
)
|
||||||
url_args = {"message_type": message_type, "status": request.args.get("status")}
|
url_args = {"message_type": message_type, "status": request.args.get("status")}
|
||||||
prev_page = None
|
prev_page = None
|
||||||
|
|
||||||
if "links" in notifications and notifications["links"].get("prev", None):
|
if "links" in notifications and notifications["links"].get("prev", None):
|
||||||
prev_page = generate_previous_dict(
|
prev_page = generate_previous_dict(
|
||||||
"main.view_notifications", service_id, page, url_args=url_args
|
"main.view_notifications", service_id, page, url_args=url_args
|
||||||
@@ -233,7 +260,6 @@ def get_notifications(service_id, message_type, status_override=None): # noqa
|
|||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
download_link = None
|
download_link = None
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"service_data_retention_days": service_data_retention_days,
|
"service_data_retention_days": service_data_retention_days,
|
||||||
"counts": render_template(
|
"counts": render_template(
|
||||||
@@ -362,6 +388,7 @@ def get_job_partials(job):
|
|||||||
filter_args = parse_filter_args(request.args)
|
filter_args = parse_filter_args(request.args)
|
||||||
filter_args["status"] = set_status_filters(filter_args)
|
filter_args["status"] = set_status_filters(filter_args)
|
||||||
notifications = job.get_notifications(status=filter_args["status"])
|
notifications = job.get_notifications(status=filter_args["status"])
|
||||||
|
number_of_days = "seven_day"
|
||||||
counts = render_template(
|
counts = render_template(
|
||||||
"partials/count.html",
|
"partials/count.html",
|
||||||
counts=_get_job_counts(job),
|
counts=_get_job_counts(job),
|
||||||
@@ -371,7 +398,7 @@ def get_job_partials(job):
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
service_data_retention_days = current_service.get_days_of_retention(
|
service_data_retention_days = current_service.get_days_of_retention(
|
||||||
job.template_type
|
job.template_type, number_of_days
|
||||||
)
|
)
|
||||||
|
|
||||||
if request.referrer is not None:
|
if request.referrer is not None:
|
||||||
|
|||||||
@@ -137,9 +137,9 @@ def get_all_personalisation_from_notification(notification):
|
|||||||
def download_notifications_csv(service_id):
|
def download_notifications_csv(service_id):
|
||||||
filter_args = parse_filter_args(request.args)
|
filter_args = parse_filter_args(request.args)
|
||||||
filter_args["status"] = set_status_filters(filter_args)
|
filter_args["status"] = set_status_filters(filter_args)
|
||||||
|
number_of_days = request.args["number_of_days"]
|
||||||
service_data_retention_days = current_service.get_days_of_retention(
|
service_data_retention_days = current_service.get_days_of_retention(
|
||||||
filter_args.get("message_type")[0]
|
filter_args.get("message_type")[0], number_of_days
|
||||||
)
|
)
|
||||||
file_time = datetime.now().strftime("%Y-%m-%d %I:%M:%S %p")
|
file_time = datetime.now().strftime("%Y-%m-%d %I:%M:%S %p")
|
||||||
file_time = f"{file_time} {get_user_preferred_timezone()}"
|
file_time = f"{file_time} {get_user_preferred_timezone()}"
|
||||||
|
|||||||
@@ -116,10 +116,10 @@ def registration_continue():
|
|||||||
|
|
||||||
def get_invite_data_from_redis(state):
|
def get_invite_data_from_redis(state):
|
||||||
|
|
||||||
invite_data = json.loads(redis_client.raw_get(f"invitedata-{state}"))
|
invite_data = json.loads(redis_client.get(f"invitedata-{state}"))
|
||||||
user_email = redis_client.raw_get(f"user_email-{state}").decode("utf8")
|
user_email = redis_client.get(f"user_email-{state}").decode("utf8")
|
||||||
user_uuid = redis_client.raw_get(f"user_uuid-{state}").decode("utf8")
|
user_uuid = redis_client.get(f"user_uuid-{state}").decode("utf8")
|
||||||
invited_user_email_address = redis_client.raw_get(
|
invited_user_email_address = redis_client.get(
|
||||||
f"invited_user_email_address-{state}"
|
f"invited_user_email_address-{state}"
|
||||||
).decode("utf8")
|
).decode("utf8")
|
||||||
return invite_data, user_email, user_uuid, invited_user_email_address
|
return invite_data, user_email, user_uuid, invited_user_email_address
|
||||||
@@ -130,10 +130,10 @@ def put_invite_data_in_redis(
|
|||||||
):
|
):
|
||||||
ttl = 60 * 15 # 15 minutes
|
ttl = 60 * 15 # 15 minutes
|
||||||
|
|
||||||
redis_client.raw_set(f"invitedata-{state}", json.dumps(invite_data), ex=ttl)
|
redis_client.set(f"invitedata-{state}", json.dumps(invite_data), ex=ttl)
|
||||||
redis_client.raw_set(f"user_email-{state}", user_email, ex=ttl)
|
redis_client.set(f"user_email-{state}", user_email, ex=ttl)
|
||||||
redis_client.raw_set(f"user_uuid-{state}", user_uuid, ex=ttl)
|
redis_client.set(f"user_uuid-{state}", user_uuid, ex=ttl)
|
||||||
redis_client.raw_set(
|
redis_client.set(
|
||||||
f"invited_user_email_address-{state}",
|
f"invited_user_email_address-{state}",
|
||||||
invited_user_email_address,
|
invited_user_email_address,
|
||||||
ex=ttl,
|
ex=ttl,
|
||||||
@@ -163,7 +163,7 @@ def set_up_your_profile():
|
|||||||
state = request.args.get("state")
|
state = request.args.get("state")
|
||||||
login_gov_error = request.args.get("error")
|
login_gov_error = request.args.get("error")
|
||||||
|
|
||||||
if redis_client.raw_get(f"invitedata-{state}") is None:
|
if redis_client.get(f"invitedata-{state}") is None:
|
||||||
access_token = sign_in._get_access_token(code, state)
|
access_token = sign_in._get_access_token(code, state)
|
||||||
debug_msg("Got the access token for login.gov")
|
debug_msg("Got the access token for login.gov")
|
||||||
user_email, user_uuid = sign_in._get_user_email_and_uuid(access_token)
|
user_email, user_uuid = sign_in._get_user_email_and_uuid(access_token)
|
||||||
@@ -195,7 +195,7 @@ def set_up_your_profile():
|
|||||||
|
|
||||||
if (
|
if (
|
||||||
form.validate_on_submit()
|
form.validate_on_submit()
|
||||||
and redis_client.raw_get(f"invitedata-{state}") is not None
|
and redis_client.get(f"invitedata-{state}") is not None
|
||||||
):
|
):
|
||||||
invite_data, user_email, user_uuid, invited_user_email_address = (
|
invite_data, user_email, user_uuid, invited_user_email_address = (
|
||||||
get_invite_data_from_redis(state)
|
get_invite_data_from_redis(state)
|
||||||
|
|||||||
@@ -51,7 +51,6 @@ from app.utils.templates import get_template
|
|||||||
from app.utils.user import user_has_permissions
|
from app.utils.user import user_has_permissions
|
||||||
from notifications_utils import SMS_CHAR_COUNT_LIMIT
|
from notifications_utils import SMS_CHAR_COUNT_LIMIT
|
||||||
from notifications_utils.insensitive_dict import InsensitiveDict
|
from notifications_utils.insensitive_dict import InsensitiveDict
|
||||||
from notifications_utils.logging import scrub
|
|
||||||
from notifications_utils.recipients import RecipientCSV, first_column_headings
|
from notifications_utils.recipients import RecipientCSV, first_column_headings
|
||||||
from notifications_utils.sanitise_text import SanitiseASCII
|
from notifications_utils.sanitise_text import SanitiseASCII
|
||||||
|
|
||||||
@@ -953,9 +952,6 @@ def send_notification(service_id, template_id):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
current_app.logger.info(
|
|
||||||
hilite(scrub(f"Recipient for the one-off will be {recipient}"))
|
|
||||||
)
|
|
||||||
keys = []
|
keys = []
|
||||||
values = []
|
values = []
|
||||||
for k, v in session["placeholders"].items():
|
for k, v in session["placeholders"].items():
|
||||||
@@ -971,6 +967,12 @@ def send_notification(service_id, template_id):
|
|||||||
)
|
)
|
||||||
my_data = {"filename": filename, "template_id": template_id, "data": data}
|
my_data = {"filename": filename, "template_id": template_id, "data": data}
|
||||||
upload_id = s3upload(service_id, my_data)
|
upload_id = s3upload(service_id, my_data)
|
||||||
|
|
||||||
|
# To debug messages that the user reports have not been sent, we log
|
||||||
|
# the csv filename and the job id. The user will give us the file name,
|
||||||
|
# so we can search on that to obtain the job id, which we can use elsewhere
|
||||||
|
# on the API side to find out what happens to the message.
|
||||||
|
current_app.logger.info(hilite(f"One-off file: {filename} job_id: {upload_id}"))
|
||||||
form = CsvUploadForm()
|
form = CsvUploadForm()
|
||||||
form.file.data = my_data
|
form.file.data = my_data
|
||||||
form.file.name = filename
|
form.file.name = filename
|
||||||
@@ -989,19 +991,6 @@ def send_notification(service_id, template_id):
|
|||||||
valid="True",
|
valid="True",
|
||||||
)
|
)
|
||||||
|
|
||||||
# Here we are attempting to cleverly link the job id to the one-off recipient
|
|
||||||
# If we know the partial phone number of the recipient, we can search
|
|
||||||
# on that initially and find this, which will give us the job_id
|
|
||||||
# And once we know the job_id, we can search on that and it might tell us something
|
|
||||||
# about report generation.
|
|
||||||
current_app.logger.info(
|
|
||||||
hilite(
|
|
||||||
scrub(
|
|
||||||
f"Created job to send one-off, recipient is {recipient}, job_id is {upload_id}"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
session.pop("recipient")
|
session.pop("recipient")
|
||||||
session.pop("placeholders")
|
session.pop("placeholders")
|
||||||
|
|
||||||
@@ -1033,14 +1022,17 @@ def send_notification(service_id, template_id):
|
|||||||
job_id=upload_id,
|
job_id=upload_id,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
total = notifications["total"]
|
||||||
|
current_app.logger.info(
|
||||||
|
hilite(
|
||||||
|
f"job_id: {upload_id} has notifications: {total} and attempts: {attempts}"
|
||||||
|
)
|
||||||
|
)
|
||||||
return redirect(
|
return redirect(
|
||||||
url_for(
|
url_for(
|
||||||
".view_job",
|
".view_job",
|
||||||
service_id=service_id,
|
service_id=service_id,
|
||||||
job_id=upload_id,
|
job_id=upload_id,
|
||||||
from_job=upload_id,
|
|
||||||
notification_id=notifications["notifications"][0]["id"],
|
|
||||||
# used to show the final step of the tour (help=3) or not show
|
# used to show the final step of the tour (help=3) or not show
|
||||||
# a back link on a just sent one off notification (help=0)
|
# a back link on a just sent one off notification (help=0)
|
||||||
help=request.args.get("help"),
|
help=request.args.get("help"),
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ def activate_user(user_id):
|
|||||||
user = User.from_id(user_id)
|
user = User.from_id(user_id)
|
||||||
|
|
||||||
# TODO add org invites back in the new way
|
# TODO add org invites back in the new way
|
||||||
# organization_id = redis_client.raw_get(
|
# organization_id = redis_client.get(
|
||||||
# f"organization-invite-{user.email_address}"
|
# f"organization-invite-{user.email_address}"
|
||||||
# )
|
# )
|
||||||
# user_api_client.add_user_to_organization(
|
# user_api_client.add_user_to_organization(
|
||||||
|
|||||||
@@ -390,7 +390,7 @@ class Service(JSONModel, SortByNameMixin):
|
|||||||
def get_data_retention_item(self, id):
|
def get_data_retention_item(self, id):
|
||||||
return next((dr for dr in self.data_retention if dr["id"] == id), None)
|
return next((dr for dr in self.data_retention if dr["id"] == id), None)
|
||||||
|
|
||||||
def get_days_of_retention(self, notification_type):
|
def get_days_of_retention(self, notification_type, number_of_days):
|
||||||
return next(
|
return next(
|
||||||
(
|
(
|
||||||
dr
|
dr
|
||||||
@@ -398,7 +398,10 @@ class Service(JSONModel, SortByNameMixin):
|
|||||||
if dr["notification_type"] == notification_type
|
if dr["notification_type"] == notification_type
|
||||||
),
|
),
|
||||||
{},
|
{},
|
||||||
).get("days_of_retention", current_app.config["ACTIVITY_STATS_LIMIT_DAYS"])
|
).get(
|
||||||
|
"days_of_retention",
|
||||||
|
current_app.config["ACTIVITY_STATS_LIMIT_DAYS"].get(number_of_days),
|
||||||
|
)
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def organization(self):
|
def organization(self):
|
||||||
|
|||||||
@@ -64,10 +64,22 @@
|
|||||||
|
|
||||||
{% if current_user.has_permissions('view_activity') %}
|
{% if current_user.has_permissions('view_activity') %}
|
||||||
<p class="font-body-sm">
|
<p class="font-body-sm">
|
||||||
<a href="{{ download_link }}" download="download" class="usa-link">Download this report (<abbr title="Comma separated values">CSV</abbr>)</a>
|
<a href="{{ download_link_seven_day }}" download="download" class="usa-link">Download all data last 7 days (<abbr title="Comma separated values">CSV</abbr>)</a>
|
||||||
 
|
 
|
||||||
Data available for {{ partials.service_data_retention_days }} days
|
Data available for {{ partials.service_data_retention_days }} days
|
||||||
</p>
|
</p>
|
||||||
|
<p class="font-body-sm">
|
||||||
|
<a href="{{ download_link_five_day }}" download="download" class="usa-link">Download all data last 5 days (<abbr title="Comma separated values">CSV</abbr>)</a>
|
||||||
|
 
|
||||||
|
</p>
|
||||||
|
<p class="font-body-sm">
|
||||||
|
<a href="{{ download_link_three_day }}" download="download" class="usa-link">Download all data last 3 days (<abbr title="Comma separated values">CSV</abbr>)</a>
|
||||||
|
 
|
||||||
|
</p>
|
||||||
|
<p class="font-body-sm">
|
||||||
|
<a href="{{ download_link_today }}" download="download" class="usa-link">Download all data today (<abbr title="Comma separated values">CSV</abbr>)</a>
|
||||||
|
 
|
||||||
|
</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{{ ajax_block(
|
{{ ajax_block(
|
||||||
|
|||||||
@@ -21,7 +21,8 @@ Notify.gov
|
|||||||
<h1 class="font-serif-2xl usa-hero__heading">Reach people where they are with government-powered text messages</h1>
|
<h1 class="font-serif-2xl usa-hero__heading">Reach people where they are with government-powered text messages</h1>
|
||||||
<p class="font-sans-lg">Notify.gov is a text message service that helps federal, state, local, tribal and territorial governments more effectively communicate with the people they serve.</p>
|
<p class="font-sans-lg">Notify.gov is a text message service that helps federal, state, local, tribal and territorial governments more effectively communicate with the people they serve.</p>
|
||||||
<div class="usa-button-group margin-bottom-5">
|
<div class="usa-button-group margin-bottom-5">
|
||||||
<a class="usa-button usa-button--big margin-right-2" href="{{ url_for('main.sign_in' ) }}">Sign in</a>
|
<a class="usa-button usa-button login-button login-button--primary margin-right-2" href="{{ initial_signin_url }}">Sign in with <img src="{{ asset_url('images/logo-login.svg') }}" alt="Login.gov logo">
|
||||||
|
</a>
|
||||||
if you are an existing pilot partner
|
if you are an existing pilot partner
|
||||||
</div>
|
</div>
|
||||||
<p class="font-sans-md">Currently we are only working with select pilot partners. If you are interested in using Notify.gov in the future, please contact <br><a href="mailto:tts-benefits-studio@gsa.gov">tts-benefits-studio@gsa.gov</a> to learn more.</p>
|
<p class="font-sans-md">Currently we are only working with select pilot partners. If you are interested in using Notify.gov in the future, please contact <br><a href="mailto:tts-benefits-studio@gsa.gov">tts-benefits-studio@gsa.gov</a> to learn more.</p>
|
||||||
|
|||||||
@@ -32,22 +32,6 @@
|
|||||||
<a class="usa-link usa-button" href="{{ initial_signin_url }}">Sign in with Login.gov</a>
|
<a class="usa-link usa-button" href="{{ initial_signin_url }}">Sign in with Login.gov</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<div class="tablet:grid-col-6 tablet:grid-offset-1 margin-top-2 padding-y-2 padding-x-4 bg-base-lightest">
|
</div>
|
||||||
<h2 class="font-body-lg">Effective April 16, 2024 Notify.gov requires you sign-in through Login.gov</h2>
|
|
||||||
<p>Why are we doing this?</p>
|
|
||||||
<ul class="usa-list">
|
|
||||||
<li><strong>Enhanced security:</strong> Login.gov is really secure and trustworthy</li>
|
|
||||||
<li><strong>One single source for signing in:</strong> You can use Login.gov for other services within the federal government</li>
|
|
||||||
<li><strong>2FA flexibility:</strong> Login.gov supports multiple methods for users to verify their identity.</li>
|
|
||||||
</ul>
|
|
||||||
<p>What do I need to do?</p>
|
|
||||||
<ul class="usa-list">
|
|
||||||
<li>If you have a Login.gov account, start using it to sign in to Notify today.</li>
|
|
||||||
<li>If you don’t have a Login.gov account, you must create one to continue to access Notify.</li>
|
|
||||||
</ul>
|
|
||||||
<div class="border-bottom border-base-lighter margin-y-4"></div>
|
|
||||||
<a class="usa-link usa-button usa-button--outline margin-bottom-3" href="{{ initial_signin_url }}">Create Login.gov account</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ from flask_login import current_user
|
|||||||
from app.models.spreadsheet import Spreadsheet
|
from app.models.spreadsheet import Spreadsheet
|
||||||
from app.utils import hilite
|
from app.utils import hilite
|
||||||
from app.utils.templates import get_sample_template
|
from app.utils.templates import get_sample_template
|
||||||
from notifications_utils.logging import scrub
|
|
||||||
from notifications_utils.recipients import RecipientCSV
|
from notifications_utils.recipients import RecipientCSV
|
||||||
|
|
||||||
|
|
||||||
@@ -74,12 +73,11 @@ def generate_notifications_csv(**kwargs):
|
|||||||
|
|
||||||
# This generates the "batch" csv report
|
# This generates the "batch" csv report
|
||||||
if kwargs.get("job_id"):
|
if kwargs.get("job_id"):
|
||||||
# The kwargs contain the job id, which is linked to the recipient's partial phone number in other debug
|
|
||||||
# Some unit tests are mocking the kwargs and turning them into a function instead of dict,
|
# Some unit tests are mocking the kwargs and turning them into a function instead of dict,
|
||||||
# hence the try/except.
|
# hence the try/except.
|
||||||
try:
|
try:
|
||||||
current_app.logger.info(
|
current_app.logger.info(
|
||||||
hilite(f"Setting up report with kwargs {scrub(json.dumps(kwargs))}")
|
hilite(f"Setting up report with kwargs {json.dumps(kwargs)}")
|
||||||
)
|
)
|
||||||
except TypeError:
|
except TypeError:
|
||||||
pass
|
pass
|
||||||
@@ -89,7 +87,7 @@ def generate_notifications_csv(**kwargs):
|
|||||||
# we display to 999 characters, because we don't want to show the contents for reports with thousands of rows.
|
# we display to 999 characters, because we don't want to show the contents for reports with thousands of rows.
|
||||||
current_app.logger.info(
|
current_app.logger.info(
|
||||||
hilite(
|
hilite(
|
||||||
f"Original csv for job_id {kwargs['job_id']}: {scrub(original_file_contents[0:999])}"
|
f"Original csv for job_id {kwargs['job_id']}: {original_file_contents[0:999]}"
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
original_upload = RecipientCSV(
|
original_upload = RecipientCSV(
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
env: production
|
env: production
|
||||||
instances: 2
|
instances: 2
|
||||||
memory: 1G
|
memory: 2G
|
||||||
public_admin_route: beta.notify.gov
|
public_admin_route: beta.notify.gov
|
||||||
cloud_dot_gov_route: notify.app.cloud.gov
|
cloud_dot_gov_route: notify.app.cloud.gov
|
||||||
redis_enabled: 1
|
redis_enabled: 1
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
import os
|
import os
|
||||||
import socket
|
|
||||||
import sys
|
import sys
|
||||||
import traceback
|
import traceback
|
||||||
|
import multiprocessing
|
||||||
import eventlet
|
|
||||||
import gunicorn
|
import gunicorn
|
||||||
|
|
||||||
workers = 5
|
# Let gunicorn figure out the right number of workers
|
||||||
|
# The recommended formula is cpu_count() * 2 + 1
|
||||||
|
# but we have an unusual configuration with a lot of cpus and not much memory
|
||||||
|
# so adjust it.
|
||||||
|
workers = multiprocessing.cpu_count()
|
||||||
worker_class = "eventlet"
|
worker_class = "eventlet"
|
||||||
bind = "0.0.0.0:{}".format(os.getenv("PORT"))
|
bind = "0.0.0.0:{}".format(os.getenv("PORT"))
|
||||||
disable_redirect_access_to_syslog = True
|
disable_redirect_access_to_syslog = True
|
||||||
@@ -17,21 +19,3 @@ def worker_abort(worker):
|
|||||||
worker.log.info("worker received ABORT")
|
worker.log.info("worker received ABORT")
|
||||||
for stack in sys._current_frames().values():
|
for stack in sys._current_frames().values():
|
||||||
worker.log.error("".join(traceback.format_stack(stack)))
|
worker.log.error("".join(traceback.format_stack(stack)))
|
||||||
|
|
||||||
|
|
||||||
def fix_ssl_monkeypatching():
|
|
||||||
"""
|
|
||||||
eventlet works by monkey-patching core IO libraries (such as ssl) to be non-blocking. However, there's currently
|
|
||||||
a bug: In the normal socket library it may throw a timeout error as a `socket.timeout` exception. However
|
|
||||||
eventlet.green.ssl's patch raises an ssl.SSLError('timed out',) instead. redispy handles socket.timeout but not
|
|
||||||
ssl.SSLError, so we solve this by monkey patching the monkey patching code to raise the correct exception type
|
|
||||||
:scream:
|
|
||||||
https://github.com/eventlet/eventlet/issues/692
|
|
||||||
"""
|
|
||||||
# this has probably already been called somewhere in gunicorn internals, however, to be sure, we invoke it again.
|
|
||||||
# eventlet.monkey_patch can be called multiple times without issue
|
|
||||||
eventlet.monkey_patch()
|
|
||||||
eventlet.green.ssl.timeout_exc = socket.timeout
|
|
||||||
|
|
||||||
|
|
||||||
fix_ssl_monkeypatching()
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ applications:
|
|||||||
- route: ((cloud_dot_gov_route))
|
- route: ((cloud_dot_gov_route))
|
||||||
|
|
||||||
services:
|
services:
|
||||||
- notify-admin-redis-((env))
|
- notify-admin-redis-v70-((env))
|
||||||
- notify-api-csv-upload-bucket-((env))
|
- notify-api-csv-upload-bucket-((env))
|
||||||
- notify-admin-logo-upload-bucket-((env))
|
- notify-admin-logo-upload-bucket-((env))
|
||||||
|
|
||||||
|
|||||||
@@ -133,19 +133,13 @@ class RedisClient:
|
|||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def raw_set(self, key, value, ex=None, px=None, nx=False, xx=False):
|
|
||||||
self.redis_store.set(key, value, ex, px, nx, xx)
|
|
||||||
|
|
||||||
def set(
|
def set(
|
||||||
self, key, value, ex=None, px=None, nx=False, xx=False, raise_exception=False
|
self, key, value, ex=None, px=None, nx=False, xx=False, raise_exception=False
|
||||||
):
|
):
|
||||||
key = prepare_value(key)
|
key = prepare_value(key)
|
||||||
value = prepare_value(value)
|
value = prepare_value(value)
|
||||||
if self.active:
|
if self.active:
|
||||||
try:
|
self.redis_store.set(key, value, ex, px, nx, xx)
|
||||||
self.redis_store.set(key, value, ex, px, nx, xx)
|
|
||||||
except Exception as e:
|
|
||||||
self.__handle_exception(e, raise_exception, "set", key)
|
|
||||||
|
|
||||||
def incr(self, key, raise_exception=False):
|
def incr(self, key, raise_exception=False):
|
||||||
key = prepare_value(key)
|
key = prepare_value(key)
|
||||||
@@ -155,16 +149,10 @@ class RedisClient:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.__handle_exception(e, raise_exception, "incr", key)
|
self.__handle_exception(e, raise_exception, "incr", key)
|
||||||
|
|
||||||
def raw_get(self, key):
|
|
||||||
return self.redis_store.get(key)
|
|
||||||
|
|
||||||
def get(self, key, raise_exception=False):
|
def get(self, key, raise_exception=False):
|
||||||
key = prepare_value(key)
|
key = prepare_value(key)
|
||||||
if self.active:
|
if self.active:
|
||||||
try:
|
return self.redis_store.get(key)
|
||||||
return self.redis_store.get(key)
|
|
||||||
except Exception as e:
|
|
||||||
self.__handle_exception(e, raise_exception, "get", key)
|
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|||||||
@@ -70,6 +70,7 @@ def configure_handler(handler, app, formatter):
|
|||||||
handler.addFilter(AppNameFilter(app.config["NOTIFY_APP_NAME"]))
|
handler.addFilter(AppNameFilter(app.config["NOTIFY_APP_NAME"]))
|
||||||
handler.addFilter(RequestIdFilter())
|
handler.addFilter(RequestIdFilter())
|
||||||
handler.addFilter(ServiceIdFilter())
|
handler.addFilter(ServiceIdFilter())
|
||||||
|
handler.addFilter(PIIFilter())
|
||||||
|
|
||||||
return handler
|
return handler
|
||||||
|
|
||||||
@@ -134,13 +135,30 @@ class JSONFormatter(BaseJSONFormatter):
|
|||||||
return log_record
|
return log_record
|
||||||
|
|
||||||
|
|
||||||
def scrub(msg):
|
class PIIFilter(logging.Filter):
|
||||||
# Eventually we want to scrub all messages in all logs for phone numbers
|
def scrub(self, msg):
|
||||||
# and email addresses, masking them. Ultimately this will probably get
|
# Eventually we want to scrub all messages in all logs for phone numbers
|
||||||
# refactored into a 'SafeLogger' subclass or something, but let's start here
|
# and email addresses, masking them. Ultimately this will probably get
|
||||||
# with phones.
|
# refactored into a 'SafeLogger' subclass or something, but let's start here
|
||||||
phones = re.findall("(?:\\+ *)?\\d[\\d\\- ]{7,}\\d", msg)
|
# with phones.
|
||||||
phones = [phone.replace("-", "").replace(" ", "") for phone in phones]
|
|
||||||
for phone in phones:
|
# Sometimes just an exception object is passed in for the message, skip those.
|
||||||
msg = msg.replace(phone, f"1XXXXX{phone[-5:]}")
|
if not isinstance(msg, str):
|
||||||
return msg
|
return msg
|
||||||
|
phones = re.findall("(?:\\+ *)?\\d[\\d\\- ]{7,}\\d", msg)
|
||||||
|
phones = [phone.replace("-", "").replace(" ", "") for phone in phones]
|
||||||
|
for phone in phones:
|
||||||
|
msg = msg.replace(phone, "1XXXXXXXXXX")
|
||||||
|
|
||||||
|
emails = re.findall(
|
||||||
|
r"[\w\.-]+@[\w\.-]+", msg
|
||||||
|
) # ['alice@google.com', 'bob@abc.com']
|
||||||
|
for email in emails:
|
||||||
|
# do something with each found email string
|
||||||
|
masked_email = "XXXXX@XXXXXXX"
|
||||||
|
msg = msg.replace(email, masked_email)
|
||||||
|
return msg
|
||||||
|
|
||||||
|
def filter(self, record):
|
||||||
|
record.msg = self.scrub(record.msg)
|
||||||
|
return record
|
||||||
|
|||||||
1307
package-lock.json
generated
1307
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
11
package.json
11
package.json
@@ -21,6 +21,9 @@
|
|||||||
"author": "General Services Administration",
|
"author": "General Services Administration",
|
||||||
"license": "CC0",
|
"license": "CC0",
|
||||||
"homepage": "https://github.com/GSA/notifications-admin#readme",
|
"homepage": "https://github.com/GSA/notifications-admin#readme",
|
||||||
|
"overrides": {
|
||||||
|
"graceful-fs": "^4.2.11"
|
||||||
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@uswds/uswds": "^3.4.1",
|
"@uswds/uswds": "^3.4.1",
|
||||||
"cbor-js": "0.1.0",
|
"cbor-js": "0.1.0",
|
||||||
@@ -28,12 +31,12 @@
|
|||||||
"govuk_frontend_toolkit": "8.1.0",
|
"govuk_frontend_toolkit": "8.1.0",
|
||||||
"govuk-frontend": "2.13.0",
|
"govuk-frontend": "2.13.0",
|
||||||
"hogan": "1.0.2",
|
"hogan": "1.0.2",
|
||||||
"jquery": "3.6.1",
|
"jquery": "3.7.1",
|
||||||
"morphdom": "2.6.1",
|
"morphdom": "2.6.1",
|
||||||
"python": "^0.0.4",
|
"python": "^0.0.4",
|
||||||
"query-command-supported": "1.0.0",
|
"query-command-supported": "1.0.0",
|
||||||
"sass-embedded": "^1.69.5",
|
"sass-embedded": "^1.69.5",
|
||||||
"socket.io-client": "^4.7.5",
|
"socket.io-client": "^4.2.0",
|
||||||
"textarea-caret": "3.1.0",
|
"textarea-caret": "3.1.0",
|
||||||
"timeago": "1.6.7"
|
"timeago": "1.6.7"
|
||||||
},
|
},
|
||||||
@@ -42,8 +45,8 @@
|
|||||||
"@babel/preset-env": "7.19.4",
|
"@babel/preset-env": "7.19.4",
|
||||||
"@uswds/compile": "^1.1.0",
|
"@uswds/compile": "^1.1.0",
|
||||||
"better-npm-audit": "^3.7.3",
|
"better-npm-audit": "^3.7.3",
|
||||||
"gulp": "4.0.2",
|
"gulp": "^4.0.2",
|
||||||
"gulp-add-src": "1.0.0",
|
"gulp-add-src": "^1.0.0",
|
||||||
"gulp-babel": "8.0.0",
|
"gulp-babel": "8.0.0",
|
||||||
"gulp-better-rollup": "4.0.1",
|
"gulp-better-rollup": "4.0.1",
|
||||||
"gulp-clean-css": "4.3.0",
|
"gulp-clean-css": "4.3.0",
|
||||||
|
|||||||
142
poetry.lock
generated
142
poetry.lock
generated
@@ -42,13 +42,13 @@ tests-no-zope = ["attrs[tests-mypy]", "cloudpickle", "hypothesis", "pympler", "p
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bandit"
|
name = "bandit"
|
||||||
version = "1.7.8"
|
version = "1.7.9"
|
||||||
description = "Security oriented static analyser for python code."
|
description = "Security oriented static analyser for python code."
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.8"
|
python-versions = ">=3.8"
|
||||||
files = [
|
files = [
|
||||||
{file = "bandit-1.7.8-py3-none-any.whl", hash = "sha256:509f7af645bc0cd8fd4587abc1a038fc795636671ee8204d502b933aee44f381"},
|
{file = "bandit-1.7.9-py3-none-any.whl", hash = "sha256:52077cb339000f337fb25f7e045995c4ad01511e716e5daac37014b9752de8ec"},
|
||||||
{file = "bandit-1.7.8.tar.gz", hash = "sha256:36de50f720856ab24a24dbaa5fee2c66050ed97c1477e0a1159deab1775eab6b"},
|
{file = "bandit-1.7.9.tar.gz", hash = "sha256:7c395a436743018f7be0a4cbb0a4ea9b902b6d87264ddecf8cfdc73b4f78ff61"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
@@ -182,17 +182,17 @@ files = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "boto3"
|
name = "boto3"
|
||||||
version = "1.34.119"
|
version = "1.34.128"
|
||||||
description = "The AWS SDK for Python"
|
description = "The AWS SDK for Python"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.8"
|
python-versions = ">=3.8"
|
||||||
files = [
|
files = [
|
||||||
{file = "boto3-1.34.119-py3-none-any.whl", hash = "sha256:8f9c43c54b3dfaa36c4a0d7b42c417227a515bc7a2e163e62802780000a5a3e2"},
|
{file = "boto3-1.34.128-py3-none-any.whl", hash = "sha256:a048ff980a81cd652724a73bc496c519b336fabe19cc8bfc6c53b2ff6eb22c7b"},
|
||||||
{file = "boto3-1.34.119.tar.gz", hash = "sha256:cea2365a25b2b83a97e77f24ac6f922ef62e20636b42f9f6ee9f97188f9c1c03"},
|
{file = "boto3-1.34.128.tar.gz", hash = "sha256:43a6e99f53a8d34b3b4dbe424dbcc6b894350dc41a85b0af7c7bc24a7ec2cead"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
botocore = ">=1.34.119,<1.35.0"
|
botocore = ">=1.34.128,<1.35.0"
|
||||||
jmespath = ">=0.7.1,<2.0.0"
|
jmespath = ">=0.7.1,<2.0.0"
|
||||||
s3transfer = ">=0.10.0,<0.11.0"
|
s3transfer = ">=0.10.0,<0.11.0"
|
||||||
|
|
||||||
@@ -201,13 +201,13 @@ crt = ["botocore[crt] (>=1.21.0,<2.0a0)"]
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "botocore"
|
name = "botocore"
|
||||||
version = "1.34.119"
|
version = "1.34.128"
|
||||||
description = "Low-level, data-driven core of boto 3."
|
description = "Low-level, data-driven core of boto 3."
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.8"
|
python-versions = ">=3.8"
|
||||||
files = [
|
files = [
|
||||||
{file = "botocore-1.34.119-py3-none-any.whl", hash = "sha256:4bdf7926a1290b2650d62899ceba65073dd2693e61c35f5cdeb3a286a0aaa27b"},
|
{file = "botocore-1.34.128-py3-none-any.whl", hash = "sha256:db67fda136c372ab3fa432580c819c89ba18d28a6152a4d2a7ea40d44082892e"},
|
||||||
{file = "botocore-1.34.119.tar.gz", hash = "sha256:b253f15b24b87b070e176af48e8ef146516090429d30a7d8b136a4c079b28008"},
|
{file = "botocore-1.34.128.tar.gz", hash = "sha256:8d8e03f7c8c080ecafda72036eb3b482d649f8417c90b5dca33b7c2c47adb0c9"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
@@ -216,7 +216,7 @@ python-dateutil = ">=2.1,<3.0.0"
|
|||||||
urllib3 = {version = ">=1.25.4,<2.2.0 || >2.2.0,<3", markers = "python_version >= \"3.10\""}
|
urllib3 = {version = ">=1.25.4,<2.2.0 || >2.2.0,<3", markers = "python_version >= \"3.10\""}
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
crt = ["awscrt (==0.20.9)"]
|
crt = ["awscrt (==0.20.11)"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cachecontrol"
|
name = "cachecontrol"
|
||||||
@@ -591,13 +591,13 @@ test-randomorder = ["pytest-randomly"]
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cyclonedx-python-lib"
|
name = "cyclonedx-python-lib"
|
||||||
version = "7.4.0"
|
version = "7.4.1"
|
||||||
description = "Python library for CycloneDX"
|
description = "Python library for CycloneDX"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = "<4.0,>=3.8"
|
python-versions = "<4.0,>=3.8"
|
||||||
files = [
|
files = [
|
||||||
{file = "cyclonedx_python_lib-7.4.0-py3-none-any.whl", hash = "sha256:fc423e7f46d772e5ded29a48cb0743233e692e5853c49b829efc0f59014efde1"},
|
{file = "cyclonedx_python_lib-7.4.1-py3-none-any.whl", hash = "sha256:73bf8d5c09ad10698c75d3ce3f123c84c9aff3959d67b8b5ca9e5a7c5da43abe"},
|
||||||
{file = "cyclonedx_python_lib-7.4.0.tar.gz", hash = "sha256:09b10736a7f440262578fa40f470b448de1ebf3c7a71e2ff0a4af0781d3a3b42"},
|
{file = "cyclonedx_python_lib-7.4.1.tar.gz", hash = "sha256:23bf8196e008bb8e06c1040ad2ab69492891d8a581cb2aefa36a77f199790a37"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
@@ -741,18 +741,18 @@ testing = ["hatch", "pre-commit", "pytest", "tox"]
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "filelock"
|
name = "filelock"
|
||||||
version = "3.14.0"
|
version = "3.15.1"
|
||||||
description = "A platform independent file lock."
|
description = "A platform independent file lock."
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.8"
|
python-versions = ">=3.8"
|
||||||
files = [
|
files = [
|
||||||
{file = "filelock-3.14.0-py3-none-any.whl", hash = "sha256:43339835842f110ca7ae60f1e1c160714c5a6afd15a2873419ab185334975c0f"},
|
{file = "filelock-3.15.1-py3-none-any.whl", hash = "sha256:71b3102950e91dfc1bb4209b64be4dc8854f40e5f534428d8684f953ac847fac"},
|
||||||
{file = "filelock-3.14.0.tar.gz", hash = "sha256:6ea72da3be9b8c82afd3edcf99f2fffbb5076335a5ae4d03248bb5b6c3eae78a"},
|
{file = "filelock-3.15.1.tar.gz", hash = "sha256:58a2549afdf9e02e10720eaa4d4470f56386d7a6f72edd7d0596337af8ed7ad8"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
docs = ["furo (>=2023.9.10)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"]
|
docs = ["furo (>=2023.9.10)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"]
|
||||||
testing = ["covdefaults (>=2.3)", "coverage (>=7.3.2)", "diff-cover (>=8.0.1)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)", "pytest-timeout (>=2.2)"]
|
testing = ["covdefaults (>=2.3)", "coverage (>=7.3.2)", "diff-cover (>=8.0.1)", "pytest (>=7.4.3)", "pytest-asyncio (>=0.21)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)", "pytest-timeout (>=2.2)"]
|
||||||
typing = ["typing-extensions (>=4.8)"]
|
typing = ["typing-extensions (>=4.8)"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1701,40 +1701,40 @@ files = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "newrelic"
|
name = "newrelic"
|
||||||
version = "9.10.0"
|
version = "9.11.0"
|
||||||
description = "New Relic Python Agent"
|
description = "New Relic Python Agent"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
|
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
|
||||||
files = [
|
files = [
|
||||||
{file = "newrelic-9.10.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:a4d4e5670082225ca7ef0ee986ef8e6588f4e530a05d43d66f9368459c0b1f18"},
|
{file = "newrelic-9.11.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:87670d872c3abc36203e10f93d266c8f36ad2bd06fb54e790001a409f9e2f40f"},
|
||||||
{file = "newrelic-9.10.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:f4605bc4feb114235e242dfe260b75ec85d0894f5400aa7f30e75fbbc0423b3f"},
|
{file = "newrelic-9.11.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:11653fd14f55999c5058b4dde8c721833076c0bd3efe668296725a622e9e7de8"},
|
||||||
{file = "newrelic-9.10.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:d3be6c97d007ceb142f908f5ab2444807b44dc600a0b7f3254dc685b5b03fd10"},
|
{file = "newrelic-9.11.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:72dd3eb190c62bb54aa59029f0d6ac1420c2050b3aaf88d947fc7f62ec58d97f"},
|
||||||
{file = "newrelic-9.10.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:4e573d49c1543a488d6567906a9b2cb0c748cdbf80724c322b06874f8e47c789"},
|
{file = "newrelic-9.11.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:02eab15af4a08b870bcfdbc56390ecbb9dcacd144fe77f39a26d1be207bd30f0"},
|
||||||
{file = "newrelic-9.10.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ae0515f7ab19f1a5dd14e31506420d1b86014c5e1340c2a210833248bc765dae"},
|
{file = "newrelic-9.11.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f477cdda9b998205084b822089b3ee4a8a2d9cd66b6f12487c9f9002566c5cb"},
|
||||||
{file = "newrelic-9.10.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:acf5cdcafd2971933ad2f9e836284957f4a3eababe88f063cf53b1b1f67f1a16"},
|
{file = "newrelic-9.11.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dcec4173cd0f83420e6f61f92955065f1d460075af5e5bf88a5fea746e3cc180"},
|
||||||
{file = "newrelic-9.10.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:5d18236bf4a80fca4eb1db03448ed72bf8e16b84b3a4ed5fcc29bb91c2d05d54"},
|
{file = "newrelic-9.11.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:8664e3b9e6ee0f78806b0cf7c90656a1a86d13232c2e0be18a1b1eb452f3f5d1"},
|
||||||
{file = "newrelic-9.10.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:744c815f15ec06e441c11a6c57042d2eca8c41401c11de6f47b3e105d952b9bd"},
|
{file = "newrelic-9.11.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7f1e473eb0505cb91ab9a4155321eabe13a2f6b93fb3c41d6f10e5486276be60"},
|
||||||
{file = "newrelic-9.10.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:524ed5bfa09d330746b45e0087765da994ca34802cce032063041e404e58414c"},
|
{file = "newrelic-9.11.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9f95eb366ff714bce32476d256551b853247a72398ec46a89148ef5108509aa8"},
|
||||||
{file = "newrelic-9.10.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8ad9cd5459b8c620ab7a876bd5d920c3ef2943948d1262a42289d4f8d16dadab"},
|
{file = "newrelic-9.11.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:553674a66ef2c2206852b415b74e3c2fb7ed2b92e9800b68394d577f6aa1133e"},
|
||||||
{file = "newrelic-9.10.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:4404c649b5e6165dcdd59091092c19b292a43cc96520d5ffd718b628fb866096"},
|
{file = "newrelic-9.11.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:21e7b52d5b214bba3534ced166e6ec991117772815020bec38b0571fdcecbaf4"},
|
||||||
{file = "newrelic-9.10.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e2576bbec0b640d9b76454dcfd5b2f03078e0bb062a7ea3952a8db7b9972c352"},
|
{file = "newrelic-9.11.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:10cb7f7a78c49580602b90f367f3378264e495f2f3706734f88ced7e7ca9b033"},
|
||||||
{file = "newrelic-9.10.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77537a020ce84033f39210e46cc43bb3927cec3fb4b34b5c4df802e96fddaedf"},
|
{file = "newrelic-9.11.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:34b25d1beaf19825409f3d915a5bafa87b7b9230415821422be1e78e988750b7"},
|
||||||
{file = "newrelic-9.10.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2236f70b8c6aa79635f2175e7315d032f3a80dfd65ad9c9ed12a921f5df4c655"},
|
{file = "newrelic-9.11.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2b02139458aefba86a4572cb8214f91a942103d24d5502395f64d6d7a4ad3f25"},
|
||||||
{file = "newrelic-9.10.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b8201a33caf7632b2e55e3f9687584ad6956aaf5751485cdb2bad7c428a9b400"},
|
{file = "newrelic-9.11.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:3283885bcf31d9cbf8facb0004508a4eaa652a62471e0b724d26f9738a291979"},
|
||||||
{file = "newrelic-9.10.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:6ed4bc2c9a44dfe59958eeecf1f327f0a0fb6324b5e609515bc511944d12db74"},
|
{file = "newrelic-9.11.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:0d43a0891bf71333f6a6253cf87dea2c9009e22699a2acfd93608125a33b1936"},
|
||||||
{file = "newrelic-9.10.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1cc3ddb26c0615ba4e18f87453bca57f0688a43d2fcdd50e2771a77515cfc3ba"},
|
{file = "newrelic-9.11.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b7903ba71ce5a4b2840f6d3c63ecd0fb3a018d2aceb915b48133c13c4a60185f"},
|
||||||
{file = "newrelic-9.10.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:09912303e04bee6aa1fe1c671e87b4e8e55461081a96210895828798f5ba8c3f"},
|
{file = "newrelic-9.11.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d88fa17a515fb002eb14570800e4bfa69ac87ac27e6e2a96bc2bc9b60c80057a"},
|
||||||
{file = "newrelic-9.10.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:40368dca0d423efe40b210686d7018787d4365a24ee1deca136b3b7c9d850325"},
|
{file = "newrelic-9.11.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:6ceac1d8f13da38fa1b41c8202a91d3b4345e06adb655deaae0df08911fda56f"},
|
||||||
{file = "newrelic-9.10.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:56f4c309a07a2c66243b12d18056c32aa704735469741495642c31be4a1c77fa"},
|
{file = "newrelic-9.11.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ffc0d8d490de0f12df70db637481aaadb8a43fb6d71ba8866dc14242aa5edad4"},
|
||||||
{file = "newrelic-9.10.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4d68fc707d896dc7da8d6939bcc1f995bf9e463c2b911fc63250a10e1502a234"},
|
{file = "newrelic-9.11.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f6e1bb0df8ff2b54195baac41fddc0e15ea1bdf1deb6af49153487696355181"},
|
||||||
{file = "newrelic-9.10.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7cd462804a6ede617fb3b4b126e9083b3ee8b4ed1250f7cc12299ebacb785432"},
|
{file = "newrelic-9.11.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b5d2d0814e1aa9de5bd55797ff8c426d98200ba46ca14dbca15557d0f17cfb4e"},
|
||||||
{file = "newrelic-9.10.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:ceef4fef2a5cffb69e9e1742bd18a35625ca62c3856c7016c22be68ec876753d"},
|
{file = "newrelic-9.11.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b33539345c7cf349b65a176a30ab38e2998b071512a7450f5c5b89ac6c097006"},
|
||||||
{file = "newrelic-9.10.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:1f11d9c17b50982fcc39de71f6592a61920ec5e5c29b9105edc9f8fb7f2480b9"},
|
{file = "newrelic-9.11.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:7c073f4c26539d6d74fbf4bac7f5046cac578975fb2cf77b156f802f1b39835e"},
|
||||||
{file = "newrelic-9.10.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf6757d422954e61082715dbba4208cae17bf3720006bc337c3f87f19ede2876"},
|
{file = "newrelic-9.11.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76eb4cc599645a38a459b0002696d9c84844fecb02cf07bc18a4a91f737e438e"},
|
||||||
{file = "newrelic-9.10.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ae84bacfdc60792bd04e681027cc5c58e6737a04c652e9be2eda84abe21f57f5"},
|
{file = "newrelic-9.11.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:35d08587e694f5c517e55fb7119f924c64569d2e7ec4968ef761fc1f7bd1f40c"},
|
||||||
{file = "newrelic-9.10.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:667722cf1f4ed9f6cd99f4fbe247fc2bdb941935528e14a93659ba2c651dc889"},
|
{file = "newrelic-9.11.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bc5c1b8a51946f64c34fc5fa29ce0221c4927a65c7f4435b3b8adeb29b9812d2"},
|
||||||
{file = "newrelic-9.10.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d0c18210648889416da3de61aa282248e012cb507ba9841511407f922fff9a52"},
|
{file = "newrelic-9.11.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:2010ed2793294a7e3c1057ec301d48997ed05dcef114d4c25120ac771f66bac1"},
|
||||||
{file = "newrelic-9.10.0.tar.gz", hash = "sha256:02db25b0fd2fc835efe4a7f1c92dbc5bbb95125341aba07152041aa6a5666cda"},
|
{file = "newrelic-9.11.0.tar.gz", hash = "sha256:94369792d61ccf21469c35cf66886c32350a180d8e782c0d28ec66411db29474"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
@@ -1841,13 +1841,13 @@ dev = ["black", "mypy", "pytest"]
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "packageurl-python"
|
name = "packageurl-python"
|
||||||
version = "0.15.0"
|
version = "0.15.1"
|
||||||
description = "A purl aka. Package URL parser and builder"
|
description = "A purl aka. Package URL parser and builder"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.7"
|
python-versions = ">=3.7"
|
||||||
files = [
|
files = [
|
||||||
{file = "packageurl-python-0.15.0.tar.gz", hash = "sha256:f219b2ce6348185a27bd6a72e6fdc9f984e6c9fa157effa7cb93e341c49cdcc2"},
|
{file = "packageurl_python-0.15.1-py3-none-any.whl", hash = "sha256:f7a44ddb9caaf6197b3b62b890ed0be5cb15e962accab2a51db36846d5174562"},
|
||||||
{file = "packageurl_python-0.15.0-py3-none-any.whl", hash = "sha256:cdc6bd42dc30c4fc7f8f0ccb721fc31f8c33985dbffccb6e6be4c72874de48ca"},
|
{file = "packageurl_python-0.15.1.tar.gz", hash = "sha256:9a37b9a7cad9a2872b4612151ba3749fd9dec90485577c14d374b6e66b7edf03"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
@@ -1858,13 +1858,13 @@ test = ["pytest"]
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "packaging"
|
name = "packaging"
|
||||||
version = "24.0"
|
version = "24.1"
|
||||||
description = "Core utilities for Python packages"
|
description = "Core utilities for Python packages"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.7"
|
python-versions = ">=3.8"
|
||||||
files = [
|
files = [
|
||||||
{file = "packaging-24.0-py3-none-any.whl", hash = "sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5"},
|
{file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"},
|
||||||
{file = "packaging-24.0.tar.gz", hash = "sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9"},
|
{file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1891,13 +1891,13 @@ files = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "phonenumbers"
|
name = "phonenumbers"
|
||||||
version = "8.13.38"
|
version = "8.13.39"
|
||||||
description = "Python version of Google's common library for parsing, formatting, storing and validating international phone numbers."
|
description = "Python version of Google's common library for parsing, formatting, storing and validating international phone numbers."
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = "*"
|
python-versions = "*"
|
||||||
files = [
|
files = [
|
||||||
{file = "phonenumbers-8.13.38-py2.py3-none-any.whl", hash = "sha256:d22aa747fb591ef2a18afec13cab5a0e294ab20fce5a1560e4949e459e70eeef"},
|
{file = "phonenumbers-8.13.39-py2.py3-none-any.whl", hash = "sha256:3ad2d086fa71e7eef409001b9195ac54bebb0c6e3e752209b558ca192c9229a0"},
|
||||||
{file = "phonenumbers-8.13.38.tar.gz", hash = "sha256:2822c74ee9334e9d8ad792fc352cc8d21004307349b6b1bb61da12937fa2eaba"},
|
{file = "phonenumbers-8.13.39.tar.gz", hash = "sha256:db7ca4970d206b2056231105300753b1a5b229f43416f8c2b3010e63fbb68d77"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2585,13 +2585,13 @@ toml = ["tomli (>=2.0.1)"]
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "redis"
|
name = "redis"
|
||||||
version = "5.0.4"
|
version = "5.0.6"
|
||||||
description = "Python client for Redis database and key-value store"
|
description = "Python client for Redis database and key-value store"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.7"
|
python-versions = ">=3.7"
|
||||||
files = [
|
files = [
|
||||||
{file = "redis-5.0.4-py3-none-any.whl", hash = "sha256:7adc2835c7a9b5033b7ad8f8918d09b7344188228809c98df07af226d39dec91"},
|
{file = "redis-5.0.6-py3-none-any.whl", hash = "sha256:c0d6d990850c627bbf7be01c5c4cbaadf67b48593e913bb71c9819c30df37eee"},
|
||||||
{file = "redis-5.0.4.tar.gz", hash = "sha256:ec31f2ed9675cc54c21ba854cfe0462e6faf1d83c8ce5944709db8a4700b9c61"},
|
{file = "redis-5.0.6.tar.gz", hash = "sha256:38473cd7c6389ad3e44a91f4c3eaf6bcb8a9f746007f29bf4fb20824ff0b2197"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
@@ -2726,13 +2726,13 @@ fixture = ["fixtures"]
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "responses"
|
name = "responses"
|
||||||
version = "0.25.0"
|
version = "0.25.3"
|
||||||
description = "A utility library for mocking out the `requests` Python library."
|
description = "A utility library for mocking out the `requests` Python library."
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.8"
|
python-versions = ">=3.8"
|
||||||
files = [
|
files = [
|
||||||
{file = "responses-0.25.0-py3-none-any.whl", hash = "sha256:2f0b9c2b6437db4b528619a77e5d565e4ec2a9532162ac1a131a83529db7be1a"},
|
{file = "responses-0.25.3-py3-none-any.whl", hash = "sha256:521efcbc82081ab8daa588e08f7e8a64ce79b91c39f6e62199b19159bea7dbcb"},
|
||||||
{file = "responses-0.25.0.tar.gz", hash = "sha256:01ae6a02b4f34e39bffceb0fc6786b67a25eae919c6368d05eabc8d9576c2a66"},
|
{file = "responses-0.25.3.tar.gz", hash = "sha256:617b9247abd9ae28313d57a75880422d55ec63c29d33d629697590a034358dba"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
@@ -2958,24 +2958,24 @@ files = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "typing-extensions"
|
name = "typing-extensions"
|
||||||
version = "4.12.1"
|
version = "4.12.2"
|
||||||
description = "Backported and Experimental Type Hints for Python 3.8+"
|
description = "Backported and Experimental Type Hints for Python 3.8+"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.8"
|
python-versions = ">=3.8"
|
||||||
files = [
|
files = [
|
||||||
{file = "typing_extensions-4.12.1-py3-none-any.whl", hash = "sha256:6024b58b69089e5a89c347397254e35f1bf02a907728ec7fee9bf0fe837d203a"},
|
{file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"},
|
||||||
{file = "typing_extensions-4.12.1.tar.gz", hash = "sha256:915f5e35ff76f56588223f15fdd5938f9a1cf9195c0de25130c627e4d597f6d1"},
|
{file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "urllib3"
|
name = "urllib3"
|
||||||
version = "2.2.1"
|
version = "2.2.2"
|
||||||
description = "HTTP library with thread-safe connection pooling, file post, and more."
|
description = "HTTP library with thread-safe connection pooling, file post, and more."
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.8"
|
python-versions = ">=3.8"
|
||||||
files = [
|
files = [
|
||||||
{file = "urllib3-2.2.1-py3-none-any.whl", hash = "sha256:450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d"},
|
{file = "urllib3-2.2.2-py3-none-any.whl", hash = "sha256:a448b2f64d686155468037e1ace9f2d2199776e17f0a46610480d311f73e3472"},
|
||||||
{file = "urllib3-2.2.1.tar.gz", hash = "sha256:d0570876c61ab9e520d776c38acbbb5b05a776d3f9ff98a5c8fd5162a444cf19"},
|
{file = "urllib3-2.2.2.tar.gz", hash = "sha256:dd505485549a7a552833da5e6063639d0d177c04f23bc3864e41e5dc5f612168"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
@@ -3115,4 +3115,4 @@ files = [
|
|||||||
[metadata]
|
[metadata]
|
||||||
lock-version = "2.0"
|
lock-version = "2.0"
|
||||||
python-versions = "^3.12.2"
|
python-versions = "^3.12.2"
|
||||||
content-hash = "ce5863177e0d58f7e4affdb2d7c4a23721a6eb3199183cd92874c0315c0d6afa"
|
content-hash = "b45f2c38493f81bd7fc9d4bfd294b001d71e4082380eb0851d4f3ea8dcdb949c"
|
||||||
|
|||||||
@@ -39,8 +39,8 @@ wtforms = "~=3.1"
|
|||||||
markdown = "^3.5.2"
|
markdown = "^3.5.2"
|
||||||
async-timeout = "^4.0.3"
|
async-timeout = "^4.0.3"
|
||||||
bleach = "^6.1.0"
|
bleach = "^6.1.0"
|
||||||
boto3 = "^1.34.119"
|
boto3 = "^1.34.128"
|
||||||
botocore = "^1.34.119"
|
botocore = "^1.34.128"
|
||||||
cachetools = "^5.3.3"
|
cachetools = "^5.3.3"
|
||||||
cffi = "^1.16.0"
|
cffi = "^1.16.0"
|
||||||
cryptography = "^42.0.8"
|
cryptography = "^42.0.8"
|
||||||
@@ -50,10 +50,10 @@ jmespath = "^1.0.1"
|
|||||||
mistune = "0.8.4"
|
mistune = "0.8.4"
|
||||||
numpy = "^1.26.4"
|
numpy = "^1.26.4"
|
||||||
ordered-set = "^4.1.0"
|
ordered-set = "^4.1.0"
|
||||||
phonenumbers = "^8.13.38"
|
phonenumbers = "^8.13.39"
|
||||||
pycparser = "^2.22"
|
pycparser = "^2.22"
|
||||||
python-json-logger = "^2.0.7"
|
python-json-logger = "^2.0.7"
|
||||||
redis = "^5.0.4"
|
redis = "^5.0.6"
|
||||||
regex = "^2024.5.15"
|
regex = "^2024.5.15"
|
||||||
s3transfer = "^0.10.1"
|
s3transfer = "^0.10.1"
|
||||||
shapely = "^2.0.4"
|
shapely = "^2.0.4"
|
||||||
@@ -67,7 +67,7 @@ python-dateutil = "^2.9.0.post0"
|
|||||||
pyyaml = "^6.0.1"
|
pyyaml = "^6.0.1"
|
||||||
requests = "^2.32.3"
|
requests = "^2.32.3"
|
||||||
six = "^1.16.0"
|
six = "^1.16.0"
|
||||||
urllib3 = "^2.2.1"
|
urllib3 = "^2.2.2"
|
||||||
webencodings = "^0.5.1"
|
webencodings = "^0.5.1"
|
||||||
flask-socketio = "^5.3.6"
|
flask-socketio = "^5.3.6"
|
||||||
|
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
python-3.12.x
|
python-3.12.3
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
terraform {
|
terraform {
|
||||||
required_version = "~> 1.0"
|
required_version = "~> 1.7"
|
||||||
required_providers {
|
required_providers {
|
||||||
cloudfoundry = {
|
cloudfoundry = {
|
||||||
source = "cloudfoundry-community/cloudfoundry"
|
source = "cloudfoundry-community/cloudfoundry"
|
||||||
version = "0.53.0"
|
version = "0.53.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ locals {
|
|||||||
recursive_delete = false
|
recursive_delete = false
|
||||||
}
|
}
|
||||||
|
|
||||||
module "redis" {
|
module "redis" { # default v6.2; delete after v7.0 resource is bound
|
||||||
source = "github.com/18f/terraform-cloudgov//redis?ref=v0.7.1"
|
source = "github.com/18f/terraform-cloudgov//redis?ref=v0.7.1"
|
||||||
|
|
||||||
cf_org_name = local.cf_org_name
|
cf_org_name = local.cf_org_name
|
||||||
@@ -16,6 +16,20 @@ module "redis" {
|
|||||||
redis_plan_name = "redis-dev"
|
redis_plan_name = "redis-dev"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
module "redis-v70" {
|
||||||
|
source = "github.com/GSA-TTS/terraform-cloudgov//redis?ref=v1.0.0"
|
||||||
|
|
||||||
|
cf_org_name = local.cf_org_name
|
||||||
|
cf_space_name = local.cf_space_name
|
||||||
|
name = "${local.app_name}-redis-v70-${local.env}"
|
||||||
|
redis_plan_name = "redis-dev"
|
||||||
|
json_params = jsonencode(
|
||||||
|
{
|
||||||
|
"engineVersion" : "7.0",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
module "logo_upload_bucket" {
|
module "logo_upload_bucket" {
|
||||||
source = "github.com/18f/terraform-cloudgov//s3?ref=v0.7.1"
|
source = "github.com/18f/terraform-cloudgov//s3?ref=v0.7.1"
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
terraform {
|
terraform {
|
||||||
required_version = "~> 1.0"
|
required_version = "~> 1.7"
|
||||||
required_providers {
|
required_providers {
|
||||||
cloudfoundry = {
|
cloudfoundry = {
|
||||||
source = "cloudfoundry-community/cloudfoundry"
|
source = "cloudfoundry-community/cloudfoundry"
|
||||||
version = "0.53.0"
|
version = "0.53.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
terraform {
|
terraform {
|
||||||
required_version = "~> 1.0"
|
required_version = "~> 1.7"
|
||||||
required_providers {
|
required_providers {
|
||||||
cloudfoundry = {
|
cloudfoundry = {
|
||||||
source = "cloudfoundry-community/cloudfoundry"
|
source = "cloudfoundry-community/cloudfoundry"
|
||||||
version = "0.53.0"
|
version = "0.53.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ locals {
|
|||||||
recursive_delete = false
|
recursive_delete = false
|
||||||
}
|
}
|
||||||
|
|
||||||
module "redis" {
|
module "redis" { # default v6.2; delete after v7.0 resource is bound
|
||||||
source = "github.com/18f/terraform-cloudgov//redis?ref=v0.7.1"
|
source = "github.com/18f/terraform-cloudgov//redis?ref=v0.7.1"
|
||||||
|
|
||||||
cf_org_name = local.cf_org_name
|
cf_org_name = local.cf_org_name
|
||||||
@@ -16,6 +16,20 @@ module "redis" {
|
|||||||
redis_plan_name = "redis-3node-large"
|
redis_plan_name = "redis-3node-large"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
module "redis-v70" {
|
||||||
|
source = "github.com/GSA-TTS/terraform-cloudgov//redis?ref=v1.0.0"
|
||||||
|
|
||||||
|
cf_org_name = local.cf_org_name
|
||||||
|
cf_space_name = local.cf_space_name
|
||||||
|
name = "${local.app_name}-redis-v70-${local.env}"
|
||||||
|
redis_plan_name = "redis-3node-large"
|
||||||
|
json_params = jsonencode(
|
||||||
|
{
|
||||||
|
"engineVersion" : "7.0",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
module "logo_upload_bucket" {
|
module "logo_upload_bucket" {
|
||||||
source = "github.com/18f/terraform-cloudgov//s3?ref=v0.7.1"
|
source = "github.com/18f/terraform-cloudgov//s3?ref=v0.7.1"
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
terraform {
|
terraform {
|
||||||
required_version = "~> 1.0"
|
required_version = "~> 1.7"
|
||||||
required_providers {
|
required_providers {
|
||||||
cloudfoundry = {
|
cloudfoundry = {
|
||||||
source = "cloudfoundry-community/cloudfoundry"
|
source = "cloudfoundry-community/cloudfoundry"
|
||||||
version = "0.53.0"
|
version = "0.53.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ locals {
|
|||||||
recursive_delete = true
|
recursive_delete = true
|
||||||
}
|
}
|
||||||
|
|
||||||
module "redis" {
|
module "redis" { # default v6.2; delete after v7.0 resource is bound
|
||||||
source = "github.com/18f/terraform-cloudgov//redis?ref=v0.7.1"
|
source = "github.com/18f/terraform-cloudgov//redis?ref=v0.7.1"
|
||||||
|
|
||||||
cf_org_name = local.cf_org_name
|
cf_org_name = local.cf_org_name
|
||||||
@@ -16,6 +16,20 @@ module "redis" {
|
|||||||
redis_plan_name = "redis-dev"
|
redis_plan_name = "redis-dev"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
module "redis-v70" {
|
||||||
|
source = "github.com/GSA-TTS/terraform-cloudgov//redis?ref=v1.0.0"
|
||||||
|
|
||||||
|
cf_org_name = local.cf_org_name
|
||||||
|
cf_space_name = local.cf_space_name
|
||||||
|
name = "${local.app_name}-redis-v70-${local.env}"
|
||||||
|
redis_plan_name = "redis-dev"
|
||||||
|
json_params = jsonencode(
|
||||||
|
{
|
||||||
|
"engineVersion" : "7.0",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
module "logo_upload_bucket" {
|
module "logo_upload_bucket" {
|
||||||
source = "github.com/18f/terraform-cloudgov//s3?ref=v0.7.1"
|
source = "github.com/18f/terraform-cloudgov//s3?ref=v0.7.1"
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
terraform {
|
terraform {
|
||||||
required_version = "~> 1.0"
|
required_version = "~> 1.7"
|
||||||
required_providers {
|
required_providers {
|
||||||
cloudfoundry = {
|
cloudfoundry = {
|
||||||
source = "cloudfoundry-community/cloudfoundry"
|
source = "cloudfoundry-community/cloudfoundry"
|
||||||
version = "0.53.0"
|
version = "0.53.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
terraform {
|
terraform {
|
||||||
required_version = "~> 1.0"
|
required_version = "~> 1.7"
|
||||||
required_providers {
|
required_providers {
|
||||||
cloudfoundry = {
|
cloudfoundry = {
|
||||||
source = "cloudfoundry-community/cloudfoundry"
|
source = "cloudfoundry-community/cloudfoundry"
|
||||||
version = "0.53.0"
|
version = "0.53.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,15 @@ locals {
|
|||||||
recursive_delete = true
|
recursive_delete = true
|
||||||
}
|
}
|
||||||
|
|
||||||
module "redis" {
|
resource "null_resource" "prevent_destroy" {
|
||||||
|
|
||||||
|
lifecycle {
|
||||||
|
prevent_destroy = false # destroying staging is allowed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
module "redis" { # default v6.2; delete after v7.0 resource is bound
|
||||||
source = "github.com/18f/terraform-cloudgov//redis?ref=v0.7.1"
|
source = "github.com/18f/terraform-cloudgov//redis?ref=v0.7.1"
|
||||||
|
|
||||||
cf_org_name = local.cf_org_name
|
cf_org_name = local.cf_org_name
|
||||||
@@ -16,6 +24,20 @@ module "redis" {
|
|||||||
redis_plan_name = "redis-dev"
|
redis_plan_name = "redis-dev"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
module "redis-v70" {
|
||||||
|
source = "github.com/GSA-TTS/terraform-cloudgov//redis?ref=v1.0.0"
|
||||||
|
|
||||||
|
cf_org_name = local.cf_org_name
|
||||||
|
cf_space_name = local.cf_space_name
|
||||||
|
name = "${local.app_name}-redis-v70-${local.env}"
|
||||||
|
redis_plan_name = "redis-dev"
|
||||||
|
json_params = jsonencode(
|
||||||
|
{
|
||||||
|
"engineVersion" : "7.0",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
module "logo_upload_bucket" {
|
module "logo_upload_bucket" {
|
||||||
source = "github.com/18f/terraform-cloudgov//s3?ref=v0.7.1"
|
source = "github.com/18f/terraform-cloudgov//s3?ref=v0.7.1"
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
terraform {
|
terraform {
|
||||||
required_version = "~> 1.0"
|
required_version = "~> 1.7"
|
||||||
required_providers {
|
required_providers {
|
||||||
cloudfoundry = {
|
cloudfoundry = {
|
||||||
source = "cloudfoundry-community/cloudfoundry"
|
source = "cloudfoundry-community/cloudfoundry"
|
||||||
version = "0.53.0"
|
version = "0.53.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -228,12 +228,18 @@ def test_can_show_notifications_if_data_retention_not_available(
|
|||||||
url_for,
|
url_for,
|
||||||
".download_notifications_csv",
|
".download_notifications_csv",
|
||||||
message_type=None,
|
message_type=None,
|
||||||
|
number_of_days="seven_day",
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
create_active_user_with_permissions(),
|
create_active_user_with_permissions(),
|
||||||
{"status": "failed"},
|
{"status": "failed"},
|
||||||
partial(url_for, ".download_notifications_csv", status="failed"),
|
partial(
|
||||||
|
url_for,
|
||||||
|
".download_notifications_csv",
|
||||||
|
status="failed",
|
||||||
|
number_of_days="seven_day",
|
||||||
|
),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
create_active_user_with_permissions(),
|
create_active_user_with_permissions(),
|
||||||
@@ -242,15 +248,13 @@ def test_can_show_notifications_if_data_retention_not_available(
|
|||||||
url_for,
|
url_for,
|
||||||
".download_notifications_csv",
|
".download_notifications_csv",
|
||||||
message_type="sms",
|
message_type="sms",
|
||||||
|
number_of_days="seven_day",
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
create_active_user_view_permissions(),
|
create_active_user_view_permissions(),
|
||||||
{},
|
{},
|
||||||
partial(
|
partial(url_for, ".download_notifications_csv", number_of_days="seven_day"),
|
||||||
url_for,
|
|
||||||
".download_notifications_csv",
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
create_active_caseworking_user(),
|
create_active_caseworking_user(),
|
||||||
|
|||||||
@@ -1893,26 +1893,22 @@ def app_with_socketio():
|
|||||||
(
|
(
|
||||||
SERVICE_ONE_ID,
|
SERVICE_ONE_ID,
|
||||||
{"start_date": "2024-01-01", "days": 7},
|
{"start_date": "2024-01-01", "days": 7},
|
||||||
{"service_id": SERVICE_ONE_ID, "start_date": "2024-01-01", "days": 7}
|
{"service_id": SERVICE_ONE_ID, "start_date": "2024-01-01", "days": 7},
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
SERVICE_TWO_ID,
|
SERVICE_TWO_ID,
|
||||||
{"start_date": "2023-06-01", "days": 7},
|
{"start_date": "2023-06-01", "days": 7},
|
||||||
{"service_id": SERVICE_TWO_ID, "start_date": "2023-06-01", "days": 7}
|
{"service_id": SERVICE_TWO_ID, "start_date": "2023-06-01", "days": 7},
|
||||||
),
|
),
|
||||||
]
|
],
|
||||||
)
|
)
|
||||||
def test_fetch_daily_stats(
|
def test_fetch_daily_stats(
|
||||||
app_with_socketio, mocker,
|
app_with_socketio, mocker, service_id, date_range, expected_call_args
|
||||||
service_id,
|
|
||||||
date_range,
|
|
||||||
expected_call_args
|
|
||||||
):
|
):
|
||||||
app, socketio = app_with_socketio
|
app, socketio = app_with_socketio
|
||||||
|
|
||||||
mocker.patch(
|
mocker.patch(
|
||||||
"app.main.views.dashboard.get_stats_date_range",
|
"app.main.views.dashboard.get_stats_date_range", return_value=date_range
|
||||||
return_value=date_range
|
|
||||||
)
|
)
|
||||||
|
|
||||||
mock_service_api = mocker.patch(
|
mock_service_api = mocker.patch(
|
||||||
@@ -1920,9 +1916,9 @@ def test_fetch_daily_stats(
|
|||||||
return_value={
|
return_value={
|
||||||
date_range["start_date"]: {
|
date_range["start_date"]: {
|
||||||
"email": {"delivered": 0, "failure": 0, "requested": 0},
|
"email": {"delivered": 0, "failure": 0, "requested": 0},
|
||||||
"sms": {"delivered": 0, "failure": 1, "requested": 1}
|
"sms": {"delivered": 0, "failure": 1, "requested": 1},
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
client = SocketIOTestClient(app, socketio)
|
client = SocketIOTestClient(app, socketio)
|
||||||
@@ -1930,22 +1926,22 @@ def test_fetch_daily_stats(
|
|||||||
connected = client.is_connected()
|
connected = client.is_connected()
|
||||||
assert connected, "Client should be connected"
|
assert connected, "Client should be connected"
|
||||||
|
|
||||||
client.emit('fetch_daily_stats', service_id)
|
client.emit("fetch_daily_stats", service_id)
|
||||||
|
|
||||||
received = client.get_received()
|
received = client.get_received()
|
||||||
assert received, "Should receive a response message"
|
assert received, "Should receive a response message"
|
||||||
assert received[0]['name'] == 'daily_stats_update'
|
assert received[0]["name"] == "daily_stats_update"
|
||||||
assert received[0]['args'][0] == {
|
assert received[0]["args"][0] == {
|
||||||
date_range["start_date"]: {
|
date_range["start_date"]: {
|
||||||
"email": {"delivered": 0, "failure": 0, "requested": 0},
|
"email": {"delivered": 0, "failure": 0, "requested": 0},
|
||||||
"sms": {"delivered": 0, "failure": 1, "requested": 1}
|
"sms": {"delivered": 0, "failure": 1, "requested": 1},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
mock_service_api.assert_called_once_with(
|
mock_service_api.assert_called_once_with(
|
||||||
service_id,
|
service_id,
|
||||||
start_date=expected_call_args["start_date"],
|
start_date=expected_call_args["start_date"],
|
||||||
days=expected_call_args["days"]
|
days=expected_call_args["days"],
|
||||||
)
|
)
|
||||||
finally:
|
finally:
|
||||||
client.disconnect()
|
client.disconnect()
|
||||||
|
|||||||
@@ -19,10 +19,12 @@ def test_non_logged_in_user_can_see_homepage(
|
|||||||
"Reach people where they are with government-powered text messages"
|
"Reach people where they are with government-powered text messages"
|
||||||
)
|
)
|
||||||
|
|
||||||
assert page.select_one("a.usa-button.usa-button--big")["href"] == url_for(
|
assert (
|
||||||
"main.sign_in",
|
page.select_one(
|
||||||
|
"a.usa-button.login-button.login-button--primary.margin-right-2"
|
||||||
|
).text
|
||||||
|
== "Sign in with \n"
|
||||||
)
|
)
|
||||||
|
|
||||||
assert page.select_one("meta[name=description]") is not None
|
assert page.select_one("meta[name=description]") is not None
|
||||||
# This area is hidden for the pilot
|
# This area is hidden for the pilot
|
||||||
# assert normalize_spaces(page.select_one('#whos-using-notify').text) == (
|
# assert normalize_spaces(page.select_one('#whos-using-notify').text) == (
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import uuid
|
|||||||
import pytest
|
import pytest
|
||||||
from flask import url_for
|
from flask import url_for
|
||||||
|
|
||||||
from app.main.views.sign_in import _reformat_keystring
|
|
||||||
from app.models.user import User
|
from app.models.user import User
|
||||||
from tests.conftest import SERVICE_ONE_ID, normalize_spaces
|
from tests.conftest import SERVICE_ONE_ID, normalize_spaces
|
||||||
|
|
||||||
@@ -20,25 +19,12 @@ def test_render_sign_in_template_for_new_user(client_request):
|
|||||||
# then these indices need to be 1 instead of 0.
|
# then these indices need to be 1 instead of 0.
|
||||||
# Currently it's not enabled for the test or production environments.
|
# Currently it's not enabled for the test or production environments.
|
||||||
assert page.select("main a")[0].text == "Sign in with Login.gov"
|
assert page.select("main a")[0].text == "Sign in with Login.gov"
|
||||||
assert page.select("main a")[1].text == "Create Login.gov account"
|
|
||||||
|
|
||||||
# TODO: We'll have to adjust this depending on whether Login.gov is
|
# TODO: We'll have to adjust this depending on whether Login.gov is
|
||||||
# enabled or not; fix this in the future.
|
# enabled or not; fix this in the future.
|
||||||
assert "Sign in again" not in normalize_spaces(page.text)
|
assert "Sign in again" not in normalize_spaces(page.text)
|
||||||
|
|
||||||
|
|
||||||
def test_reformat_keystring():
|
|
||||||
orig = "-----BEGIN PRIVATE KEY----- blah blah blah -----END PRIVATE KEY-----"
|
|
||||||
expected = """-----BEGIN PRIVATE KEY-----
|
|
||||||
blah
|
|
||||||
blah
|
|
||||||
blah
|
|
||||||
-----END PRIVATE KEY-----
|
|
||||||
"""
|
|
||||||
reformatted = _reformat_keystring(orig)
|
|
||||||
assert reformatted == expected
|
|
||||||
|
|
||||||
|
|
||||||
def test_sign_in_explains_session_timeout(client_request):
|
def test_sign_in_explains_session_timeout(client_request):
|
||||||
client_request.logout()
|
client_request.logout()
|
||||||
page = client_request.get("main.sign_in", next="/foo")
|
page = client_request.get("main.sign_in", next="/foo")
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ def test_landing_page(end_to_end_context):
|
|||||||
"heading",
|
"heading",
|
||||||
name="Reach people where they are with government-powered text messages",
|
name="Reach people where they are with government-powered text messages",
|
||||||
)
|
)
|
||||||
sign_in_button = page.get_by_role("link", name="Sign in")
|
sign_in_button = page.get_by_role("link", name="Sign in with")
|
||||||
benefits_studio_email = page.get_by_role("link", name="tts-benefits-studio@gsa.gov")
|
benefits_studio_email = page.get_by_role("link", name="tts-benefits-studio@gsa.gov")
|
||||||
|
|
||||||
# Check to make sure the elements are visible.
|
# Check to make sure the elements are visible.
|
||||||
@@ -31,7 +31,8 @@ def test_landing_page(end_to_end_context):
|
|||||||
expect(benefits_studio_email).to_be_visible()
|
expect(benefits_studio_email).to_be_visible()
|
||||||
|
|
||||||
# Check to make sure the sign-in button and email links are correct.
|
# Check to make sure the sign-in button and email links are correct.
|
||||||
expect(sign_in_button).to_have_attribute("href", "/sign-in")
|
href_value = sign_in_button.get_attribute("href")
|
||||||
|
assert href_value is not None, "The sign-in button does not have an href attribute"
|
||||||
expect(benefits_studio_email).to_have_attribute(
|
expect(benefits_studio_email).to_have_attribute(
|
||||||
"href", "mailto:tts-benefits-studio@gsa.gov"
|
"href", "mailto:tts-benefits-studio@gsa.gov"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -64,8 +64,6 @@ def test_should_not_raise_exception_if_raise_set_to_false(
|
|||||||
):
|
):
|
||||||
mock_logger = mocker.patch("flask.Flask.logger")
|
mock_logger = mocker.patch("flask.Flask.logger")
|
||||||
|
|
||||||
assert failing_redis_client.get("get_key") is None
|
|
||||||
assert failing_redis_client.set("set_key", "set_value") is None
|
|
||||||
assert failing_redis_client.incr("incr_key") is None
|
assert failing_redis_client.incr("incr_key") is None
|
||||||
assert failing_redis_client.exceeded_rate_limit("rate_limit_key", 100, 100) is False
|
assert failing_redis_client.exceeded_rate_limit("rate_limit_key", 100, 100) is False
|
||||||
assert failing_redis_client.delete("delete_key") is None
|
assert failing_redis_client.delete("delete_key") is None
|
||||||
@@ -73,8 +71,6 @@ def test_should_not_raise_exception_if_raise_set_to_false(
|
|||||||
assert failing_redis_client.delete_by_pattern("pattern") == 0
|
assert failing_redis_client.delete_by_pattern("pattern") == 0
|
||||||
|
|
||||||
assert mock_logger.mock_calls == [
|
assert mock_logger.mock_calls == [
|
||||||
call.exception("Redis error performing get on get_key"),
|
|
||||||
call.exception("Redis error performing set on set_key"),
|
|
||||||
call.exception("Redis error performing incr on incr_key"),
|
call.exception("Redis error performing incr on incr_key"),
|
||||||
call.exception("Redis error performing rate-limit-pipeline on rate_limit_key"),
|
call.exception("Redis error performing rate-limit-pipeline on rate_limit_key"),
|
||||||
call.exception("Redis error performing delete on delete_key"),
|
call.exception("Redis error performing delete on delete_key"),
|
||||||
|
|||||||
@@ -51,11 +51,16 @@ def test_base_json_formatter_contains_service_id():
|
|||||||
assert service_id_filter.filter(record).service_id == "no-service-id"
|
assert service_id_filter.filter(record).service_id == "no-service-id"
|
||||||
|
|
||||||
|
|
||||||
def test_scrub():
|
def test_pii_filter():
|
||||||
result = logging.scrub(
|
record = builtin_logging.LogRecord(
|
||||||
"This is a message with 17775554324, and also 18884449323 and also 17775554324"
|
name="log thing",
|
||||||
)
|
level="info",
|
||||||
assert (
|
pathname="path",
|
||||||
result
|
lineno=123,
|
||||||
== "This is a message with 1XXXXX54324, and also 1XXXXX49323 and also 1XXXXX54324"
|
msg="phone1: 1555555555, phone2: 1555555554, email1: fake@fake.gov, email2: fake@fake2.fake.gov",
|
||||||
|
exc_info=None,
|
||||||
|
args=None,
|
||||||
)
|
)
|
||||||
|
pii_filter = logging.PIIFilter()
|
||||||
|
clean_msg = "phone1: 1XXXXXXXXXX, phone2: 1XXXXXXXXXX, email1: XXXXX@XXXXXXX, email2: XXXXX@XXXXXXX"
|
||||||
|
assert pii_filter.filter(record).msg == clean_msg
|
||||||
|
|||||||
Reference in New Issue
Block a user