code review feedback

This commit is contained in:
Kenneth Kehl
2024-05-31 09:41:36 -07:00
41 changed files with 281 additions and 108 deletions

95
.github/workflows/codeql.yml vendored Normal file
View File

@@ -0,0 +1,95 @@
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
#
# ******** NOTE ********
# We have attempted to detect the languages in your repository. Please check
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
#
name: "CodeQL"
on:
push:
branches: [ "main", "production" ]
pull_request:
branches: [ "main", "production" ]
schedule:
- cron: '18 5 * * 3'
jobs:
analyze:
name: Analyze (${{ matrix.language }})
# Runner size impacts CodeQL analysis time. To learn more, please see:
# - https://gh.io/recommended-hardware-resources-for-running-codeql
# - https://gh.io/supported-runners-and-hardware-resources
# - https://gh.io/using-larger-runners (GitHub.com only)
# Consider using larger runners or machines with greater resources for possible analysis time improvements.
runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }}
timeout-minutes: ${{ (matrix.language == 'swift' && 120) || 360 }}
permissions:
# required for all workflows
security-events: write
# required to fetch internal or private CodeQL packs
packages: read
# only required for workflows in private repositories
actions: read
contents: read
strategy:
fail-fast: false
matrix:
include:
- language: javascript-typescript
build-mode: none
- language: python
build-mode: none
# CodeQL supports the following values keywords for 'language': 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'swift'
# Use `c-cpp` to analyze code written in C, C++ or both
# Use 'java-kotlin' to analyze code written in Java, Kotlin or both
# Use 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both
# To learn more about changing the languages that are analyzed or customizing the build mode for your analysis,
# see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning.
# If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how
# your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages
steps:
- name: Checkout repository
uses: actions/checkout@v4
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
with:
languages: ${{ matrix.language }}
build-mode: ${{ matrix.build-mode }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
# queries: security-extended,security-and-quality
# If the analyze step fails for one of the languages you are analyzing with
# "We were unable to automatically build your code", modify the matrix above
# to set the build mode to "manual" for that language. Then modify this step
# to build your code.
# Command-line programs to run using the OS shell.
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
- if: matrix.build-mode == 'manual'
shell: bash
run: |
echo 'If you are using a "manual" build mode for one or more of the' \
'languages you are analyzing, replace this with the commands to build' \
'your code, for example:'
echo ' make bootstrap'
echo ' make release'
exit 1
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
with:
category: "/language:${{matrix.language}}"

View File

@@ -76,8 +76,9 @@ py-test: ## Run python unit tests
poetry run coverage html -d .coverage_cache
.PHONY: dead-code
dead-code:
poetry run vulture ./app --min-confidence=100
dead-code: ## 60% is our aspirational goal, but currently breaks the build
poetry run vulture ./app ./notifications_utils --min-confidence=100
.PHONY: e2e-test
e2e-test: export NEW_RELIC_ENVIRONMENT=test

View File

@@ -38,7 +38,7 @@ class Config(object):
NR_MONITOR_ON = settings and settings.monitor_mode
COMMIT_HASH = getenv("COMMIT_HASH", "--------")[0:7]
GOVERNMENT_EMAIL_DOMAIN_NAMES = ["gov"]
GOVERNMENT_EMAIL_DOMAIN_NAMES = ["gov", "mil", "si.edu"]
# Logging
NOTIFY_LOG_LEVEL = getenv("NOTIFY_LOG_LEVEL", "INFO")

View File

@@ -26,6 +26,7 @@ from app.main.views import sign_in
from app.main.views.verify import activate_user
from app.models.user import InvitedOrgUser, InvitedUser, User
from app.utils import hide_from_search_engines, hilite
from app.utils.user import is_gov_user
@main.route("/register", methods=["GET", "POST"])
@@ -147,6 +148,11 @@ def check_invited_user_email_address_matches_expected(
flash("You cannot accept an invite for another person.")
abort(403)
if not is_gov_user(user_email):
debug_msg("invited user has a non-government email address.")
flash("You must use a government email address.")
abort(403)
@main.route("/set-up-your-profile", methods=["GET", "POST"])
@hide_from_search_engines

View File

@@ -948,7 +948,9 @@ def send_notification(service_id, template_id):
vals = ",".join(values)
data = f"{data}\r\n{vals}"
filename = f"one-off-{current_user.name}-{uuid.uuid4()}.csv"
filename = (
f"one-off-{uuid.uuid4()}.csv" # {current_user.name} removed from filename
)
my_data = {"filename": filename, "template_id": template_id, "data": data}
upload_id = s3upload(service_id, my_data)
form = CsvUploadForm()

View File

@@ -4,7 +4,16 @@ import uuid
import jwt
import requests
from flask import Response, current_app, redirect, render_template, request, url_for
from flask import (
Response,
abort,
current_app,
flash,
redirect,
render_template,
request,
url_for,
)
from flask_login import current_user
from app import login_manager, user_api_client
@@ -15,6 +24,7 @@ from app.models.user import User
from app.utils import hide_from_search_engines
from app.utils.login import is_safe_redirect_url
from app.utils.time import is_less_than_days_ago
from app.utils.user import is_gov_user
from notifications_utils.url_safe_token import generate_token
@@ -88,6 +98,12 @@ def _do_login_dot_gov():
try:
access_token = _get_access_token(code, state)
user_email, user_uuid = _get_user_email_and_uuid(access_token)
if not is_gov_user(user_email):
current_app.logger.error(
"invited user has a non-government email address."
)
flash("You must use a government email address.")
abort(403)
redirect_url = request.args.get("next")
user = user_api_client.get_user_by_uuid_or_email(user_uuid, user_email)

View File

@@ -32,7 +32,7 @@ def using_notify_nav():
"link": "main.trial_mode_new",
},
{
"name": "Pricing",
"name": "Tracking usage",
"link": "main.pricing",
},
{

View File

@@ -40,7 +40,7 @@ def get_s3_object(
teststr = str(s3.Bucket(bucket_name).creation_date).lower()
if "magicmock" not in teststr:
raise Exception(
f"xxxxxtest not mocked, use @mock_aws creation date is {teststr}"
f"Test is not mocked, use @mock_aws or the relevant mocker.patch to avoid accessing S3"
)
return obj

View File

@@ -47,7 +47,7 @@
{% endblock %}
{% block content %}
{% block flash_messages %}
{% include 'flash_messages.html' %}
{% include 'new/components/flash_messages.html' %}
{% endblock %}
{% block maincolumn_content %}
<div class="grid-row">

View File

@@ -1,57 +0,0 @@
{% extends "/new/base.html" %}
{% block per_page_title %}
{% block service_page_title %}{% endblock %}{% if current_service.name %} {{ current_service.name }}{% endif %}
{% block org_page_title %}{% endblock %}{% if current_org.name %} {{ current_org.name }}{% endif %}
{% endblock %}
{% block main %}
<div class="grid-container">
{% block serviceNavigation %}
{% if current_org.name %}
{% else %}
{% include "new/components/service_navigation.html" %}
{% endif %}
{% endblock %}
{#
The withnav_template can serve as a replacement for both settings_template and org_template.html.
The file service_navigation.html is included only in withnav_template. It's not used in settings_template. That is one out of the two differences between settings template and withnav template. As a result, when other templates extend settings_template, they include the serviceNavigation block but keep it empty. The settings_template.html is specifically used for these pages in the app: manage-users.html, service-settings.html, and user-profile.html.
In addition, serviceNavigation should be empty on templates that previously extended org_template. For templates that previously extended org_template.html, there's an addition of the orgNavBreadcrumb block.
{% block orgNavBreadcrumb %}
{% include "/new/components/org_nav_breadcrumb.html" %}
{% endblock %}
#}
{% if current_org.name %}
{% block orgNavBreadcrumb %}{% include "/new/components/org_nav_breadcrumb.html" %}{% endblock %}
{% endif %}
<div class="grid-row margin-top-5">
<div class="tablet:grid-col-3">
{% block sideNavigation %}
{% if org_navigation_links %}
{% include "/new/components/org_nav.html" %}
{% else %}
{% include "/new/components/main_nav.html" %}
{% endif %}
{#
Include settings_nav.html for child templates that previously extended settings_template.
Include "org_nav.html" for child templates that previously extended org_template html
#}
{% endblock %}
</div>
<div class="tablet:grid-col-9 tablet:padding-left-4">
{% block beforeContent %}
{% block backLink %}{% endblock %}
{% endblock %}
<main id="main-content" role="main" class="usa-prose site-prose margin-bottom-10">
{% block content %}
{% include 'flash_messages.html' %}
{% block maincolumn_content %}{% endblock %}
{% endblock %}
</main>
</div>
</div>
</div>
{% endblock %}

View File

@@ -1,3 +1,4 @@
{# This template is an old version #}
{% if help %}
{% include 'partials/tour.html' %}
{% else %}

View File

@@ -1,3 +1,4 @@
{# This template is an old version #}
<nav class="navigation">
<ul>
<li><a class="usa-link{{ org_navigation.is_selected('dashboard') }}" href="{{ url_for('.organization_dashboard', org_id=current_org.id) }}">Usage</a></li>

View File

@@ -1,3 +1,4 @@
{# This template is an old version #}
{% macro navigation_service_name(service) %}
<div class="font-body-2xl text-bold">
{{ service.name }}

View File

@@ -1,3 +1,4 @@
{# This template is an old version #}
{% if help %}
{% include 'partials/tour.html' %}
{% else %}

View File

@@ -1,4 +1,5 @@
{% extends "base.html" %}
{# This template is an old version #}
{% extends "admin_template.html" %}
{% block per_page_title %}
{% block service_page_title %}{% endblock %} {{ current_service.name }}

View File

@@ -0,0 +1,36 @@
{# This template is an old version #}
{% extends "admin_template.html" %}
{% block per_page_title %}
{% block service_page_title %}{% endblock %} {{ current_service.name }}
{% endblock %}
{% block main %}
<div class="grid-container">
{% include "service_navigation.html" %}
<div class="grid-row margin-top-5">
{% if help %}
<div class="tablet:grid-col-3">
{% else %}
<div class="tablet:grid-col-3">
{% endif %}
{% include "main_nav.html" %}
</div>
{% if help %}
<div class="grid-col-8">
{% else %}
<div class="tablet:grid-col-9 tablet:padding-left-4">
{% endif %}
{% block beforeContent %}
{% block backLink %}{% endblock %}
{% endblock %}
<main id="main-content" role="main" class="usa-prose site-prose margin-bottom-10">
{% block content %}
{% include 'flash_messages.html' %}
{% block maincolumn_content %}{% endblock %}
{% endblock %}
</main>
</div>
</div>
</div>
{% endblock %}

View File

@@ -59,7 +59,7 @@
{% set notification = job.notifications[0] %}
<tr class="table-row" id="{{ job.job_id }}">
<td class="table-field file-name">
{{ notification.job.original_file_name if notification.job.original_file_name else 'Manually entered number'}}
{{ notification.job.original_file_name[:12] if notification.job.original_file_name else 'Manually entered number'}}
<br>
<a class="usa-link file-list-filename" href="{{ job.view_job_link }}">View Batch</a>
</td>

View File

@@ -1,4 +1,4 @@
{% extends "settings_template.html" %}
{% extends "withnav_template.html" %}
{% from "components/tick-cross.html" import tick_cross %}
{% from "components/live-search.html" import live_search %}
{% from "components/components/button/macro.njk" import usaButton %}
@@ -7,6 +7,12 @@
Team members
{% endblock %}
{% block serviceNavigation %}{% endblock %}
{% block sideNavigation %}
{% include "/new/components/settings_nav.html" %}
{% endblock %}
{% block maincolumn_content %}
<div class="button-flex-header">

View File

@@ -1,4 +1,4 @@
{% extends "org_template.html" %}
{% extends "withnav_template.html" %}
{% from "components/page-header.html" import page_header %}
{% block org_page_title %}

View File

@@ -2,7 +2,7 @@
{% from "components/big-number.html" import big_number %}
{% from "components/live-search.html" import live_search %}
{% from "components/pill.html" import pill %}
{% extends "org_template.html" %}
{% extends "withnav_template.html" %}
{% block org_page_title %}
Usage

View File

@@ -1,4 +1,4 @@
{% extends "org_template.html" %}
{% extends "withnav_template.html" %}
{% from "components/page-footer.html" import page_footer %}
{% from "components/page-header.html" import page_header %}
{% from "components/list-entry.html" import list_entry %}

View File

@@ -1,4 +1,4 @@
{% extends "org_template.html" %}
{% extends "withnav_template.html" %}
{% from "components/page-header.html" import page_header %}
{% from "components/page-footer.html" import page_footer %}
{% from "components/form.html" import form_wrapper %}

View File

@@ -1,4 +1,4 @@
{% extends "org_template.html" %}
{% extends "withnav_template.html" %}
{% from "components/page-header.html" import page_header %}
{% from "components/page-footer.html" import page_footer %}
{% from "components/form.html" import form_wrapper %}

View File

@@ -1,4 +1,4 @@
{% extends "org_template.html" %}
{% extends "withnav_template.html" %}
{% from "components/page-header.html" import page_header %}
{% from "components/page-footer.html" import page_footer %}
{% from "components/form.html" import form_wrapper %}

View File

@@ -1,4 +1,4 @@
{% extends "org_template.html" %}
{% extends "withnav_template.html" %}
{% from "components/page-footer.html" import page_footer %}
{% from "components/page-header.html" import page_header %}
{% from "components/form.html" import form_wrapper %}

View File

@@ -1,4 +1,4 @@
{% extends "org_template.html" %}
{% extends "withnav_template.html" %}
{% from "components/table.html" import mapping_table, optional_text_field, row, text_field, edit_field with context %}
{% block org_page_title %}

View File

@@ -1,4 +1,4 @@
{% extends "org_template.html" %}
{% extends "withnav_template.html" %}
{% from "components/live-search.html" import live_search %}
{% block org_page_title %}

View File

@@ -1,4 +1,4 @@
{% extends "org_template.html" %}
{% extends "withnav_template.html" %}
{% from "components/table.html" import list_table, row, field, hidden_field_heading %}
{% from "components/page-footer.html" import page_footer %}
{% from "components/live-search.html" import live_search %}

View File

@@ -1,4 +1,4 @@
{% extends "org_template.html" %}
{% extends "withnav_template.html" %}
{% from "components/page-footer.html" import page_footer %}
{% from "components/page-header.html" import page_header %}

View File

@@ -45,7 +45,7 @@
{% block backLink %}{% endblock %}
<main id="main-content" role="main">
{% block content %}
{% include 'flash_messages.html' %}
{% include 'new/components/flash_messages.html' %}
{% block platform_admin_content %}{% endblock %}
{% endblock %}
</main>

View File

@@ -15,7 +15,7 @@ Message parts
{{ content_metadata(
data={
"Last updated": "February 5, 2024"
"Last updated": "April 10, 2024"
}
) }}
@@ -26,13 +26,15 @@ more parts towards the allowance if you:</p>
<ul class="list list-bullet">
<li>send text messages longer than 160 characters</a></li>
<li>use certain <a class="usa-link" href="#symbols">signs and symbols</a></li>
<li>use <a class="usa-link" href="#accents">accents and accented letters</a></li>
<li>use <a class="usa-link" href="#accents">accents and accented letters, including non-romanized scripts</a></li>
</ul>
<h3 class="font-body-lg" id="long-text-messages">Long text messages</h3>
<p>If a text message is longer than 160 characters (including spaces and service name), it counts as more than one message
part.</p>
<h4>Calculation of message parts without special characters</h4>
<div class="bottom-gutter-3-2">
{% call mapping_table(
caption='Text message pricing',
@@ -72,12 +74,17 @@ and the number of parts youll have left.</p>
<p>Using them can increase the cost of sending text messages.</p>
<h3 class="font-body-lg" id="accents">Accents and accented characters</h3>
<p>Some languages use accented characters.</p>
<h3 class="font-body-lg" id="accents">Accented characters and non-romanized scripts</h3>
<p>Notify can handle a wide range of different languages and scripts. However, occasionally some phone carriers may
struggle to display special characters or non-romanized scripts. (Languages such as Arabic, Chinese, Japanese, Korean,
and Russian use non-romanized scripts with different characters.) Best practices encourage communication in the
recipients preferred language, but we are aware that, rarely, a phone carrier will not be able to handle the message.</p>
<p>The following accented characters do not affect the cost of sending text messages: Ä, É, Ö, Ü, à, ä, é, è, ì, ò, ö,
ù, ü.</p>
<p>Using other accented characters can increase the cost of sending text messages.
<p>
<p>Using other accented characters or scripts will increase the cost of sending text messages. Even one accented character
(with the exception of those noted above), or use of a non-romanized or logographic script will cause the entire message
to be calculated as detailed below.<p>
<h4>Calculation of message parts with special characters or non-romanized scripts</h4>
{% set accentedChars %}
<div class="bottom-gutter-3-2">
{% call mapping_table(

View File

@@ -1,4 +1,4 @@
{% extends "settings_template.html" %}
{% extends "withnav_template.html" %}
{% from "components/banner.html" import banner_wrapper %}
{% from "components/table.html" import mapping_table, row, settings_row, text_field, optional_text_field, edit_field, field, boolean_field with context %}
@@ -6,6 +6,12 @@
Settings
{% endblock %}
{% block serviceNavigation %}{% endblock %}
{% block sideNavigation %}
{% include "/new/components/settings_nav.html" %}
{% endblock %}
{% block maincolumn_content %}
<h1 class="font-body-lg">Settings</h1>

View File

@@ -1,4 +1,4 @@
{% extends "settings_template.html" %}
{% extends "withnav_template.html" %}
{% from "components/table.html" import list_table, row, field %}
{% from "components/table.html" import mapping_table, row, text_field, optional_text_field, edit_field, field, boolean_field with context %}
@@ -6,6 +6,12 @@
User profile
{% endblock %}
{% block serviceNavigation %}{% endblock %}
{% block sideNavigation %}
{% include "/new/components/settings_nav.html" %}
{% endblock %}
{% block maincolumn_content %}
<h1 class="font-body-2xl margin-bottom-3">User profile</h1>

View File

@@ -1,31 +1,56 @@
{% extends "base.html" %}
{% block per_page_title %}
{% block service_page_title %}{% endblock %} {{ current_service.name }}
{% if current_org.name %}
{% block org_page_title %}{% endblock %} {{ current_org.name }}
{% else %}
{% block service_page_title %}{% endblock %} {{ current_service.name }}
{% endif %}
{% endblock %}
{% block main %}
<div class="grid-container">
{% include "service_navigation.html" %}
{% block serviceNavigation %}
{% if current_org.name %}
{% else %}
{% include "new/components/service_nav.html" %}
{% endif %}
{% endblock %}
{#
The withnav_template can serve as a replacement for both settings_template and org_template.html.
The file service_navigation.html is included only in withnav_template. It's not used in settings_template. That is one out of the two differences between settings template and withnav template. As a result, when other templates extend settings_template, they include the serviceNavigation block but keep it empty. The settings_template.html is specifically used for these pages in the app: manage-users.html, service-settings.html, and user-profile.html.
In addition, serviceNavigation should be empty on templates that previously extended org_template. For templates that previously extended org_template.html, there's an addition of the orgNavBreadcrumb block.
{% block orgNavBreadcrumb %}
{% include "/new/components/org_nav_breadcrumb.html" %}
{% endblock %}
#}
{% if current_org.name %}
{% block orgNavBreadcrumb %}{% include "/new/components/org_nav_breadcrumb.html" %}{% endblock %}
{% endif %}
<div class="grid-row margin-top-5">
{% if help %}
<div class="tablet:grid-col-3">
{% else %}
<div class="tablet:grid-col-3">
{% endif %}
{% include "main_nav.html" %}
</div>
{% if help %}
<div class="grid-col-8">
{% else %}
<div class="tablet:grid-col-9 tablet:padding-left-4">
{% endif %}
<div class="tablet:grid-col-3">
{% block sideNavigation %}
{% if current_org.name %}
{% include "/new/components/org_nav.html" %}
{% else %}
{% include "/new/components/main_nav.html" %}
{% endif %}
{#
Include settings_nav.html for child templates that previously extended settings_template.
Include "org_nav.html" for child templates that previously extended org_template html
#}
{% endblock %}
</div>
<div class="tablet:grid-col-9 tablet:padding-left-4">
{% block beforeContent %}
{% block backLink %}{% endblock %}
{% endblock %}
<main id="main-content" role="main" class="usa-prose site-prose margin-bottom-10">
{% block content %}
{% include 'flash_messages.html' %}
{% include 'new/components/flash_messages.html' %}
{% block maincolumn_content %}{% endblock %}
{% endblock %}
</main>

View File

@@ -4,7 +4,6 @@ from flask import abort, current_app
from flask_login import current_user, login_required
from app import config
from app.notify_client.organizations_api_client import organizations_client
user_is_logged_in = login_required
@@ -51,7 +50,7 @@ def user_is_platform_admin(f):
def is_gov_user(email_address):
return _email_address_ends_with(
email_address, config.Config.GOVERNMENT_EMAIL_DOMAIN_NAMES
) or _email_address_ends_with(email_address, organizations_client.get_domains())
) # or _email_address_ends_with(email_address, organizations_client.get_domains())
def _email_address_ends_with(email_address, known_domains):

View File

@@ -47,7 +47,7 @@ def s3upload(
teststr = str(_s3.Bucket(bucket_name).creation_date).lower()
if "magicmock" not in teststr:
raise Exception(
f"xxxxxtest not mocked, use @mock_aws creation date is {teststr}"
f"Test is not mocked, use @mock_aws or the relevant mocker.patch to avoid accessing S3"
)
key = _s3.Object(bucket_name, file_location)
@@ -102,7 +102,7 @@ def s3download(
teststr = str(s3.Bucket(bucket_name).creation_date).lower()
if "magicmock" not in teststr:
raise Exception(
f"xxxxxtest not mocked, use @mock_aws creation date is {teststr}"
f"Test is not mocked, use @mock_aws or the relevant mocker.patch to avoid accessing S3"
)
return key.get()["Body"]
except botocore.exceptions.ClientError as error:

View File

@@ -148,7 +148,7 @@ def test_should_return_200_when_email_is_not_gov_uk(
"email_address",
[
"notfound@example.gsa.gov",
"example@lsquo.net",
"example@lsquo.si.edu",
],
)
def test_should_add_user_details_to_session(
@@ -401,6 +401,26 @@ def test_check_invited_user_email_address_doesnt_match_expected(mocker):
mock_abort.assert_called_once_with(403)
def test_check_user_email_address_fails_if_not_government_address(mocker):
mock_flash = mocker.patch("app.main.views.register.flash")
mock_abort = mocker.patch("app.main.views.register.abort")
check_invited_user_email_address_matches_expected(
"fake@fake.bogus", "Fake@Fake.BOGUS"
)
mock_flash.assert_called_once_with("You must use a government email address.")
mock_abort.assert_called_once_with(403)
def test_check_user_email_address_succeeds_if_government_address(mocker):
mock_flash = mocker.patch("app.main.views.register.flash")
mock_abort = mocker.patch("app.main.views.register.abort")
check_invited_user_email_address_matches_expected("fake@fake.mil", "Fake@Fake.MIL")
mock_flash.assert_not_called()
mock_abort.assert_not_called()
def decode_invite_data(state):
state = state.encode("utf8")
state = base64.b64decode(state)