Merge branch 'main' into jon/541

This commit is contained in:
Jon 2023-05-08 14:45:07 -04:00 committed by GitHub
commit 381d8a6593
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
31 changed files with 846 additions and 493 deletions

View file

@ -1,15 +1,22 @@
# This workflow runs on pushes when a pull request is opened under certain branch conventions.
name: Build and deploy developer sandbox
run-name: Build and deploy developer sandbox for branch ${{ github.ref_name }}
run-name: Build and deploy developer sandbox for branch ${{ github.head_ref }}
on:
pull_request:
paths-ignore:
- 'docs/**'
jobs:
variables:
if: |
startsWith(github.head_ref, 'ik/')
|| startsWith(github.head_ref, 'jon')
|| startsWith(github.head_ref, 'sspj/')
|| startsWith(github.head_ref, 'mr/')
|| startsWith(github.head_ref, 'nmb/')
|| startsWith(github.head_ref, 'ab/')
|| startsWith(github.head_ref, 'bl/')
|| startsWith(github.head_ref, 'rjm/')
outputs:
environment: ${{ steps.var.outputs.environment}}
runs-on: "ubuntu-latest"
@ -21,14 +28,6 @@ jobs:
script: |
core.setOutput('environment', '${{ github.head_ref }}'.split("/")[0]);
deploy:
if: |
${{startsWith(github.head_ref, 'ik/')
|| startsWith(github.head_ref, 'jon')
|| startsWith(github.head_ref, 'sspj/')
|| startsWith(github.head_ref, 'mr/')
|| startsWith(github.head_ref, 'nmb/')
|| startsWith(github.head_ref, 'lmm/')
}}
runs-on: ubuntu-latest
needs: [variables]
steps:

View file

@ -14,8 +14,10 @@ on:
description: Which environment should we run migrations for?
options:
- stable
- ab
- bl
- rjm
- jon
- lmm
- ik
- sspj
- nmb

View file

@ -15,8 +15,10 @@ on:
description: Which environment should we flush and re-load data for?
options:
- stable
- ab
- bl
- rjm
- jon
- lmm
- ik
- sspj
- nmb

View file

@ -24,6 +24,12 @@ jobs:
DJANGO_SECRET_KEY: not-a-secret-jw7kQcb35fcDRIKp7K4fqZBmVvb+Sy4nkAGf44DxHi6EJl
DATABASE_URL: "postgres://not_a_user:not_a_password@not_a_host"
DJANGO_BASE_URL: "https://not_a_host"
REGISTRY_CL_ID: nothing
REGISTRY_PASSWORD: nothing
REGISTRY_CERT: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNMekNDQWRXZ0F3SUJBZ0lVWThXWEljcFVlVUk0TVUrU3NWbkIrOGErOUlnd0NnWUlLb1pJemowRUF3SXcKYlRFTE1Ba0dBMVVFQmhNQ1ZWTXhDekFKQmdOVkJBZ01Ba1JETVJNd0VRWURWUVFIREFwWFlYTm9hVzVuZEc5dQpNUXd3Q2dZRFZRUUtEQU5IVTBFeEREQUtCZ05WQkFzTUF6RTRSakVnTUI0R0ExVUVBd3dYUjA5V0lGQnliM1J2CmRIbHdaU0JTWldkcGMzUnlZWEl3SGhjTk1qTXdOREl4TVRVMU5ETTFXaGNOTWpRd05ESXdNVFUxTkRNMVdqQnQKTVFzd0NRWURWUVFHRXdKVlV6RUxNQWtHQTFVRUNBd0NSRU14RXpBUkJnTlZCQWNNQ2xkaGMyaHBibWQwYjI0eApEREFLQmdOVkJBb01BMGRUUVRFTU1Bb0dBMVVFQ3d3RE1UaEdNU0F3SGdZRFZRUUREQmRIVDFZZ1VISnZkRzkwCmVYQmxJRkpsWjJsemRISmhjakJaTUJNR0J5cUdTTTQ5QWdFR0NDcUdTTTQ5QXdFSEEwSUFCRkN4bGVsN1ZoWHkKb1ZIRWY2N3FKamo5UDk0ZWdqdXNtSWVaNFRLYkxkM3RRRVgzZnFKdVk4WmZzWWN4N0s1K0NEdnJLMnZRdjlMYgpmamhMTjZad3FqK2pVekJSTUIwR0ExVWREZ1FXQkJRUEZCRHdnSlhOUXE4a1V0K1hyYzFFWm9wbW9UQWZCZ05WCkhTTUVHREFXZ0JRUEZCRHdnSlhOUXE4a1V0K1hyYzFFWm9wbW9UQVBCZ05WSFJNQkFmOEVCVEFEQVFIL01Bb0cKQ0NxR1NNNDlCQU1DQTBnQU1FVUNJRVJEaml0VGR0UTB3eVNXb1hEbCtYbUpVUmdENUo0VHVudkFGeDlDSitCUwpBaUVBME42eTJoeGdFWkYxRXJGYW1VQW5EUHlQSFlJeFNJQkwwNW5ibE9IZFVLRT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=
REGISTRY_KEY: LS0tLS1CRUdJTiBFTkNSWVBURUQgUFJJVkFURSBLRVktLS0tLQpNSUhzTUZjR0NTcUdTSWIzRFFFRkRUQktNQ2tHQ1NxR1NJYjNEUUVGRERBY0JBakJYK1UvdUFkQ3hBSUNDQUF3CkRBWUlLb1pJaHZjTkFna0ZBREFkQmdsZ2hrZ0JaUU1FQVNvRUVHWTNnblRGZ3F0UE5sVU93a2hvSHFrRWdaQlAKMG5FMWpSRXliTHBDNHFtaGczRXdaR2lXZDFWV2RLVEtyNXF3d3hsdjhCbHB1UHhtRGN4dTA1U3VReWhMcU5hWgpVNjRoZlFyYy94cnRnT3Mwc0ZXenlhY0hEaFhiQUdTQjdTTjc2WG55NU9wWDVZVGtRTFMvRTk4YmxFY3NQUWVuCkNqNTJnQzVPZ0JtYzl1cjZlbWY2bjd6TE5vUWovSzk4MEdIWjg5OVZHQ1J3OHhGZGIyb3IyU3dMcDd0V1Ixcz0KLS0tLS1FTkQgRU5DUllQVEVEIFBSSVZBVEUgS0VZLS0tLS0K
REGISTRY_KEY_PASSPHRASE: fake
REGISTRY_HOSTNAME: localhost
steps:
- name: Check out

View file

@ -65,3 +65,40 @@ You also need to upload the `public.crt` key if recently created to the login.go
To access the AWS Simple Email Service, we need credentials from the CISA AWS
account for an IAM user who has limited access to only SES. Those credentials
need to be specified in the environment.
## REGISTRY_CL_ID and REGISTRY_PASSWORD
These are the login credentials for accessing the registry.
## REGISTRY_CERT and REGISTRY_KEY and REGISTRY_KEY_PASSPHRASE
These are the client certificate and its private key used to identify the registrar to the registry during the establishment of a TCP connection.
The private key is protected by a passphrase for safer transport and storage.
These were generated with:
```bash
openssl genpkey -out client.key \
-algorithm EC -pkeyopt ec_paramgen_curve:P-256 \
-aes-256-cbc
openssl req -new -x509 -days 365 \
-key client.key -out client.crt \
-subj "/C=US/ST=DC/L=Washington/O=GSA/OU=18F/CN=GOV Prototype Registrar"
```
(If you can't use openssl on your computer directly, you can access it using Docker as `docker run --platform=linux/amd64 -it --rm -v $(pwd):/apps -w /apps alpine/openssl`.)
Encode them using:
```bash
base64 client.key
base64 client.crt
```
You'll need to give the new certificate to the registry vendor _before_ rotating it in production.
## REGISTRY_HOSTNAME
This is the hostname at which the registry can be found.

View file

@ -1,41 +0,0 @@
# A new .gov system: Phase 1
Purpose: Summarize the priorities for 18F and CISA in pursuing an initial build of a new .gov registrar.
**The below was agreed upon on 08/04/2022**
## Goals for Phase 1
**Primary Goal:** Recreate the necessary core functionality in a new system
**Secondary Goal:** Reduce the CISA admin burden while maintaining high security standards
Deprioritized for later:
* Make getting a .gov domain as easy as getting a .com or .us
* Help more government entities set up and maintain their .gov site and infrastructure
* Build awareness and credibility of .gov domains
## Milestones
April 2023 - projected month 18F agreement ends
## Considerations and Tradeoffs
### Success for Phase 1 is...
* A new system that
* Can respond to user needs for all long term goals
* Can reduce the number of actors or decisions in a successful flow
* Upholds a security review process for getting a .gov domain
* Meets code and accessibility standards + open source policy
* Lays the foundation for a “a simple and secure registration process that works to ensure that domains are registered and maintained only by authorized individuals (DOTGOV ACT)”
* Supporting 1-2 registrant and admin flows with limited improvement and automation, based on value and complexity
* Has or is ready for an ATO
* Coordinating and navigating with procurement processes (RFPs and current vendor agreement)
### Risks
* App may be supported by a combination of manual work and automation, not fully automated
* Scope creep we build a system that cant be ATOd prior to Summer or Fall 2023
* We build out a narrow slice of the system, which may be insufficient for all registrant and administrative use cases
* We wouldnt be intentionally and directly focused on or prioritizing improving the registrant / admin experience
### Example User Stories (to be prioritized)
* As a potential registrant, I want to learn what I should know about .gov so I can build support inside my organization to get a .gov domain.
* As a registrant, I need the registrar to have strong user authentication so that sensitive domain- or account-impacting actions take place post-authentication.
* As a program lead, I need to ensure that issued domains are from authentic, eligible organizations and requested by someone with authority so that domains are only given to bona fide US-based government organizations.
* As a program lead, I need to run queries on .gov data to ensure alignment with program, agency, and Congressional reporting requirements.
* As a program lead, I want to be able to send messages to individuals, groups, or all registrants so they are aware of important information: status emails (system downtime, etc.) to updates to status of an application. (PENDING, APPROVED, etc.).

View file

@ -1,6 +1,6 @@
---
applications:
- name: getgov-lmm
- name: getgov-ab
buildpacks:
- python_buildpack
path: ../../src
@ -17,11 +17,11 @@ applications:
# Tell Django where to find its configuration
DJANGO_SETTINGS_MODULE: registrar.config.settings
# Tell Django where it is being hosted
DJANGO_BASE_URL: https://getgov-lmm.app.cloud.gov
DJANGO_BASE_URL: https://getgov-ab.app.cloud.gov
# Tell Django how much stuff to log
DJANGO_LOG_LEVEL: INFO
routes:
- route: getgov-lmm.app.cloud.gov
- route: getgov-ab.app.cloud.gov
services:
- getgov-credentials
- getgov-lmm-database
- getgov-ab-database

View file

@ -0,0 +1,27 @@
---
applications:
- name: getgov-bl
buildpacks:
- python_buildpack
path: ../../src
instances: 1
memory: 512M
stack: cflinuxfs3
timeout: 180
command: ./run.sh
health-check-type: http
health-check-http-endpoint: /health
env:
# Send stdout and stderr straight to the terminal without buffering
PYTHONUNBUFFERED: yup
# Tell Django where to find its configuration
DJANGO_SETTINGS_MODULE: registrar.config.settings
# Tell Django where it is being hosted
DJANGO_BASE_URL: https://getgov-bl.app.cloud.gov
# Tell Django how much stuff to log
DJANGO_LOG_LEVEL: INFO
routes:
- route: getgov-bl.app.cloud.gov
services:
- getgov-credentials
- getgov-bl-database

View file

@ -0,0 +1,27 @@
---
applications:
- name: getgov-rjm
buildpacks:
- python_buildpack
path: ../../src
instances: 1
memory: 512M
stack: cflinuxfs3
timeout: 180
command: ./run.sh
health-check-type: http
health-check-http-endpoint: /health
env:
# Send stdout and stderr straight to the terminal without buffering
PYTHONUNBUFFERED: yup
# Tell Django where to find its configuration
DJANGO_SETTINGS_MODULE: registrar.config.settings
# Tell Django where it is being hosted
DJANGO_BASE_URL: https://getgov-rjm.app.cloud.gov
# Tell Django how much stuff to log
DJANGO_LOG_LEVEL: INFO
routes:
- route: getgov-rjm.app.cloud.gov
services:
- getgov-credentials
- getgov-rjm-database

View file

@ -2,6 +2,6 @@
max-line-length = 88
max-complexity = 10
extend-ignore = E203
per-file-ignores = __init__.py:F401,F403
per-file-ignores = __init__.py:F401,F403,E402
# migrations are auto-generated and often break rules
exclude=registrar/migrations/*

742
src/Pipfile.lock generated

File diff suppressed because it is too large Load diff

View file

@ -27,6 +27,18 @@ services:
- DJANGO_DEBUG=True
# Tell Django where it is being hosted
- DJANGO_BASE_URL=http://localhost:8080
# Set a username for accessing the registry
- REGISTRY_CL_ID=nothing
# Set a password for accessing the registry
- REGISTRY_PASSWORD=nothing
# Set a private certifcate for accessing the registry
- REGISTRY_CERT=LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNMekNDQWRXZ0F3SUJBZ0lVWThXWEljcFVlVUk0TVUrU3NWbkIrOGErOUlnd0NnWUlLb1pJemowRUF3SXcKYlRFTE1Ba0dBMVVFQmhNQ1ZWTXhDekFKQmdOVkJBZ01Ba1JETVJNd0VRWURWUVFIREFwWFlYTm9hVzVuZEc5dQpNUXd3Q2dZRFZRUUtEQU5IVTBFeEREQUtCZ05WQkFzTUF6RTRSakVnTUI0R0ExVUVBd3dYUjA5V0lGQnliM1J2CmRIbHdaU0JTWldkcGMzUnlZWEl3SGhjTk1qTXdOREl4TVRVMU5ETTFXaGNOTWpRd05ESXdNVFUxTkRNMVdqQnQKTVFzd0NRWURWUVFHRXdKVlV6RUxNQWtHQTFVRUNBd0NSRU14RXpBUkJnTlZCQWNNQ2xkaGMyaHBibWQwYjI0eApEREFLQmdOVkJBb01BMGRUUVRFTU1Bb0dBMVVFQ3d3RE1UaEdNU0F3SGdZRFZRUUREQmRIVDFZZ1VISnZkRzkwCmVYQmxJRkpsWjJsemRISmhjakJaTUJNR0J5cUdTTTQ5QWdFR0NDcUdTTTQ5QXdFSEEwSUFCRkN4bGVsN1ZoWHkKb1ZIRWY2N3FKamo5UDk0ZWdqdXNtSWVaNFRLYkxkM3RRRVgzZnFKdVk4WmZzWWN4N0s1K0NEdnJLMnZRdjlMYgpmamhMTjZad3FqK2pVekJSTUIwR0ExVWREZ1FXQkJRUEZCRHdnSlhOUXE4a1V0K1hyYzFFWm9wbW9UQWZCZ05WCkhTTUVHREFXZ0JRUEZCRHdnSlhOUXE4a1V0K1hyYzFFWm9wbW9UQVBCZ05WSFJNQkFmOEVCVEFEQVFIL01Bb0cKQ0NxR1NNNDlCQU1DQTBnQU1FVUNJRVJEaml0VGR0UTB3eVNXb1hEbCtYbUpVUmdENUo0VHVudkFGeDlDSitCUwpBaUVBME42eTJoeGdFWkYxRXJGYW1VQW5EUHlQSFlJeFNJQkwwNW5ibE9IZFVLRT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=
# Set a private certifcate's key for accessing the registry
- REGISTRY_KEY=LS0tLS1CRUdJTiBFTkNSWVBURUQgUFJJVkFURSBLRVktLS0tLQpNSUhzTUZjR0NTcUdTSWIzRFFFRkRUQktNQ2tHQ1NxR1NJYjNEUUVGRERBY0JBakJYK1UvdUFkQ3hBSUNDQUF3CkRBWUlLb1pJaHZjTkFna0ZBREFkQmdsZ2hrZ0JaUU1FQVNvRUVHWTNnblRGZ3F0UE5sVU93a2hvSHFrRWdaQlAKMG5FMWpSRXliTHBDNHFtaGczRXdaR2lXZDFWV2RLVEtyNXF3d3hsdjhCbHB1UHhtRGN4dTA1U3VReWhMcU5hWgpVNjRoZlFyYy94cnRnT3Mwc0ZXenlhY0hEaFhiQUdTQjdTTjc2WG55NU9wWDVZVGtRTFMvRTk4YmxFY3NQUWVuCkNqNTJnQzVPZ0JtYzl1cjZlbWY2bjd6TE5vUWovSzk4MEdIWjg5OVZHQ1J3OHhGZGIyb3IyU3dMcDd0V1Ixcz0KLS0tLS1FTkQgRU5DUllQVEVEIFBSSVZBVEUgS0VZLS0tLS0K
# set a passphrase for decrypting the registry key
- REGISTRY_KEY_PASSPHRASE=fake
# Set a URI for accessing the registry
- REGISTRY_HOSTNAME=localhost
# --- These keys are obtained from `.env` file ---
# Set a private JWT signing key for Login.gov
- DJANGO_SECRET_LOGIN_KEY
@ -50,6 +62,9 @@ services:
- POSTGRES_PASSWORD=feedabee
node:
build:
context: .
dockerfile: node.Dockerfile
image: node
volumes:
- .:/app

View file

View file

@ -1,40 +0,0 @@
"""
This file defines a number of mock functions which can be used to simulate
communication with the registry until that integration is implemented.
"""
from datetime import datetime
def domain_check(_):
"""Is domain available for registration?"""
return True
def domain_info(domain):
"""What does the registry know about this domain?"""
return {
"name": domain,
"roid": "EXAMPLE1-REP",
"status": ["ok"],
"registrant": "jd1234",
"contact": {
"admin": "sh8013",
"tech": None,
},
"ns": {
f"ns1.{domain}",
f"ns2.{domain}",
},
"host": [
f"ns1.{domain}",
f"ns2.{domain}",
],
"sponsor": "ClientX",
"creator": "ClientY",
# TODO: think about timezones
"creation_date": datetime.today(),
"updator": "ClientX",
"last_update_date": datetime.today(),
"expiration_date": datetime.today(),
"last_transfer_date": datetime.today(),
}

View file

@ -0,0 +1,52 @@
import logging
from types import SimpleNamespace
try:
from epplib import constants
except ImportError:
# allow epplibwrapper to load without epplib, for testing and development
pass
logger = logging.getLogger(__name__)
NAMESPACE = SimpleNamespace(
EPP="urn:ietf:params:xml:ns:epp-1.0",
XSI="http://www.w3.org/2001/XMLSchema-instance",
FRED="noop",
NIC_CONTACT="urn:ietf:params:xml:ns:contact-1.0",
NIC_DOMAIN="urn:ietf:params:xml:ns:domain-1.0",
NIC_ENUMVAL="noop",
NIC_EXTRA_ADDR="noop",
NIC_HOST="urn:ietf:params:xml:ns:host-1.0",
NIC_KEYSET="noop",
NIC_NSSET="noop",
)
SCHEMA_LOCATION = SimpleNamespace(
XSI="urn:ietf:params:xml:ns:epp-1.0 epp-1.0.xsd",
FRED="noop fred-1.5.0.xsd",
NIC_CONTACT="urn:ietf:params:xml:ns:contact-1.0 contact-1.0.xsd",
NIC_DOMAIN="urn:ietf:params:xml:ns:domain-1.0 domain-1.0.xsd",
NIC_ENUMVAL="noop enumval-1.2.0.xsd",
NIC_EXTRA_ADDR="noop extra-addr-1.0.0.xsd",
NIC_HOST="urn:ietf:params:xml:ns:host-1.0 host-1.0.xsd",
NIC_KEYSET="noop keyset-1.3.2.xsd",
NIC_NSSET="noop nsset-1.2.2.xsd",
)
try:
constants.NAMESPACE = NAMESPACE
constants.SCHEMA_LOCATION = SCHEMA_LOCATION
except NameError:
pass
# Attn: these imports should NOT be at the top of the file
try:
from .client import CLIENT, commands
except ImportError:
pass
__all__ = [
"CLIENT",
"commands",
]

34
src/epplibwrapper/cert.py Normal file
View file

@ -0,0 +1,34 @@
import os
import tempfile
from django.conf import settings
class Cert:
"""
Location of client certificate as written to disk.
This is needed because the certificate is stored as an environment
variable but Python's ssl library requires a file.
"""
def __init__(self, data=settings.SECRET_REGISTRY_CERT) -> None:
self.filename = self._write(data)
def __del__(self):
"""Remove the files when this object is garbage collected."""
os.unlink(self.filename)
def _write(self, data) -> str:
"""Write data to a secure tempfile. Returns the path."""
_, path = tempfile.mkstemp()
with open(path, "wb") as file:
file.write(data)
return path
class Key(Cert):
"""Location of private key as written to disk."""
def __init__(self) -> None:
super().__init__(data=settings.SECRET_REGISTRY_KEY)

128
src/epplibwrapper/client.py Normal file
View file

@ -0,0 +1,128 @@
"""Provide a wrapper around epplib to handle authentication and errors."""
import logging
from time import sleep
try:
from epplib.client import Client
from epplib import commands
from epplib.exceptions import TransportError, ParsingError
from epplib.transport import SocketTransport
except ImportError:
pass
from django.conf import settings
from .cert import Cert, Key
from .errors import LoginError, RegistryError
from .socket import Socket
logger = logging.getLogger(__name__)
try:
# Write cert and key to disk
CERT = Cert()
KEY = Key()
except Exception:
CERT = None # type: ignore
KEY = None # type: ignore
logger.warning(
"Problem with client certificate. Registrar cannot contact registry.",
exc_info=True,
)
class EPPLibWrapper:
"""
A wrapper over epplib's client.
ATTN: This should not be used directly. Use `Domain` from domain.py.
"""
def __init__(self) -> None:
"""Initialize settings which will be used for all connections."""
# prepare (but do not send) a Login command
self._login = commands.Login(
cl_id=settings.SECRET_REGISTRY_CL_ID,
password=settings.SECRET_REGISTRY_PASSWORD,
obj_uris=[
"urn:ietf:params:xml:ns:domain-1.0",
"urn:ietf:params:xml:ns:contact-1.0",
],
)
# establish a client object with a TCP socket transport
self._client = Client(
SocketTransport(
settings.SECRET_REGISTRY_HOSTNAME,
cert_file=CERT.filename,
key_file=KEY.filename,
password=settings.SECRET_REGISTRY_KEY_PASSPHRASE,
)
)
# prepare a context manager which will connect and login when invoked
# (it will also logout and disconnect when the context manager exits)
self._connect = Socket(self._client, self._login)
def _send(self, command):
"""Helper function used by `send`."""
try:
with self._connect as wire:
response = wire.send(command)
except (ValueError, ParsingError) as err:
logger.warning(
"%s failed to execute due to some syntax error."
% command.__class__.__name__,
exc_info=True,
)
raise RegistryError() from err
except TransportError as err:
logger.warning(
"%s failed to execute due to a connection error."
% command.__class__.__name__,
exc_info=True,
)
raise RegistryError() from err
except LoginError as err:
logger.warning(
"%s failed to execute due to a registry login error."
% command.__class__.__name__,
exc_info=True,
)
raise RegistryError() from err
except Exception as err:
logger.warning(
"%s failed to execute due to an unknown error."
% command.__class__.__name__,
exc_info=True,
)
raise RegistryError() from err
else:
if response.code >= 2000:
raise RegistryError(response.msg)
else:
return response
def send(self, command):
"""Login, send the command, then close the connection. Tries 3 times."""
counter = 0 # we'll try 3 times
while True:
try:
return self._send(command)
except RegistryError as err:
if counter == 3: # don't try again
raise err
else:
counter += 1
sleep((counter * 50) / 1000) # sleep 50 ms to 150 ms
try:
# Initialize epplib
CLIENT = EPPLibWrapper()
logger.debug("registry client initialized")
except Exception:
CLIENT = None # type: ignore
logger.warning(
"Unable to configure epplib. Registrar cannot contact registry.", exc_info=True
)

View file

@ -0,0 +1,6 @@
class RegistryError(Exception):
pass
class LoginError(RegistryError):
pass

View file

@ -0,0 +1,37 @@
import logging
try:
from epplib import commands
except ImportError:
pass
from .errors import LoginError
logger = logging.getLogger(__name__)
class Socket:
"""Context manager which establishes a TCP connection with registry."""
def __init__(self, client, login) -> None:
"""Save the epplib client and login details."""
self.client = client
self.login = login
def __enter__(self):
"""Use epplib to connect."""
self.client.connect()
response = self.client.send(self.login)
if response.code >= 2000:
self.client.close()
raise LoginError(response.msg)
return self.client
def __exit__(self, *args, **kwargs):
"""Close the connection."""
try:
self.client.send(commands.Logout())
self.client.close()
except Exception:
logger.warning("Connection to registry was not cleanly closed.")

View file

@ -7,6 +7,8 @@ strict_optional = True
# implicit_optional: treat arguments a None default value as implicitly Optional?
# `var: int = None` is equal to `var: Optional[int] = None`
implicit_optional = True
# we'd still like mypy to succeed when 3rd party libraries are not installed
ignore_missing_imports = True
[mypy.plugins.django-stubs]
django_settings_module = "registrar.config.settings"

View file

@ -0,0 +1,11 @@
<svg width="500" height="500" viewBox="0 0 500 500" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_3396_36413)">
<path d="M485.007 0H14C5.71573 0 -1 6.71573 -1 15V485.135C-1 493.419 5.71573 500.135 14 500.135H485.007C493.291 500.135 500.007 493.419 500.007 485.135V15C500.007 6.71573 493.291 0 485.007 0Z" fill="#002B47"/>
<path d="M18.375 279.369H53.5869V314H18.375V279.369ZM120.989 336.196C124.553 339.218 130.557 340.729 139.002 340.729C150.933 340.729 158.913 336.739 162.941 328.759C165.576 323.646 166.893 315.046 166.893 302.96V294.825C163.716 300.248 160.307 304.316 156.666 307.027C150.081 312.063 141.52 314.581 130.983 314.581C114.714 314.581 101.698 308.887 91.9365 297.498C82.2523 286.032 77.4102 270.537 77.4102 251.014C77.4102 232.188 82.0973 216.383 91.4717 203.6C100.846 190.739 114.133 184.309 131.332 184.309C137.685 184.309 143.224 185.277 147.95 187.214C156.007 190.545 162.515 196.666 167.474 205.575V187.33H199.664V307.492C199.664 323.839 196.914 336.158 191.413 344.447C181.961 358.702 163.832 365.83 137.026 365.83C120.834 365.83 107.625 362.654 97.3984 356.301C87.1719 349.948 81.5163 340.457 80.4316 327.829H116.457C117.387 331.703 118.897 334.492 120.989 336.196ZM115.411 270.77C119.905 281.461 127.962 286.807 139.583 286.807C147.33 286.807 153.877 283.901 159.223 278.091C164.568 272.203 167.241 262.867 167.241 250.084C167.241 238.076 164.685 228.934 159.571 222.658C154.535 216.383 147.757 213.245 139.234 213.245C127.613 213.245 119.595 218.707 115.179 229.631C112.854 235.441 111.692 242.608 111.692 251.13C111.692 258.49 112.932 265.036 115.411 270.77ZM336.328 203.367C347.02 216.77 352.365 232.614 352.365 250.897C352.365 269.491 347.02 285.412 336.328 298.66C325.637 311.831 309.406 318.416 287.636 318.416C265.866 318.416 249.635 311.831 238.943 298.66C228.252 285.412 222.906 269.491 222.906 250.897C222.906 232.614 228.252 216.77 238.943 203.367C249.635 189.964 265.866 183.263 287.636 183.263C309.406 183.263 325.637 189.964 336.328 203.367ZM287.52 211.27C277.835 211.27 270.359 214.717 265.091 221.612C259.9 228.43 257.305 238.192 257.305 250.897C257.305 263.603 259.9 273.404 265.091 280.299C270.359 287.194 277.835 290.642 287.52 290.642C297.204 290.642 304.641 287.194 309.832 280.299C315.023 273.404 317.618 263.603 317.618 250.897C317.618 238.192 315.023 228.43 309.832 221.612C304.641 214.717 297.204 211.27 287.52 211.27ZM358.994 187.33H396.181L422.561 280.764L449.522 187.33H485.083L439.412 314H404.432L358.994 187.33Z" fill="#7AB9D5"/>
</g>
<defs>
<clipPath id="clip0_3396_36413">
<rect width="500" height="500" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

View file

@ -35,6 +35,16 @@ $letter-space--xs: .0125em;
-moz-osx-font-smoothing: grayscale;
}
body {
display: flex;
flex-direction: column;
min-height: 100vh;
}
#wrapper {
flex-grow: 1;
}
.usa-logo {
@include at-media(desktop) {
margin-top: units(2);
@ -113,8 +123,10 @@ h2 {
}
/* Make "placeholder" links visually obvious */
a[href^="https://federalist-"]::after,
a[href$="todo"]::after {
background-color: yellow;
color: color(blue-80v);
content: " [link TBD]";
font-style: italic;
}
@ -401,6 +413,9 @@ footer {
background-color: color('primary-lightest');
}
.usa-footer__secondary-section a {
color: color('primary');
}
abbr[title] {
// workaround for underlining abbr element

View file

@ -55,11 +55,18 @@ secret_key = secret("DJANGO_SECRET_KEY")
secret_aws_ses_key_id = secret("AWS_ACCESS_KEY_ID", None)
secret_aws_ses_key = secret("AWS_SECRET_ACCESS_KEY", None)
secret_registry_cl_id = secret("REGISTRY_CL_ID")
secret_registry_password = secret("REGISTRY_PASSWORD")
secret_registry_cert = b64decode(secret("REGISTRY_CERT", ""))
secret_registry_key = b64decode(secret("REGISTRY_KEY", ""))
secret_registry_key_passphrase = secret("REGISTRY_KEY_PASSPHRASE", "")
secret_registry_hostname = secret("REGISTRY_HOSTNAME")
# region: Basic Django Config-----------------------------------------------###
# Build paths inside the project like this: BASE_DIR / "subdir".
BASE_DIR = path.resolve().parent.parent
# (settings.py is in `src/registrar/config/`: BASE_DIR is `src/`)
BASE_DIR = path.resolve().parent.parent.parent
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = env_debug
@ -156,16 +163,16 @@ WSGI_APPLICATION = "registrar.config.wsgi.application"
# will place static files for deployment.
# Do not use this directory for permanent storage -
# it is for Django!
STATIC_ROOT = BASE_DIR / "public"
STATIC_ROOT = BASE_DIR / "registrar" / "public"
STATICFILES_DIRS = [
BASE_DIR / "assets",
BASE_DIR / "registrar" / "assets",
]
TEMPLATES = [
{
"BACKEND": "django.template.backends.django.DjangoTemplates",
"DIRS": [BASE_DIR / "templates"],
"DIRS": [BASE_DIR / "registrar" / "templates"],
# look for templates inside installed apps
# required by django-debug-toolbar
"APP_DIRS": True,
@ -496,6 +503,17 @@ ROOT_URLCONF = "registrar.config.urls"
# Must be relative and end with "/"
STATIC_URL = "public/"
# endregion
# region: Registry----------------------------------------------------------###
# SECURITY WARNING: keep all registry variables in production secret!
SECRET_REGISTRY_CL_ID = secret_registry_cl_id
SECRET_REGISTRY_PASSWORD = secret_registry_password
SECRET_REGISTRY_CERT = secret_registry_cert
SECRET_REGISTRY_KEY = secret_registry_key
SECRET_REGISTRY_KEY_PASSPHRASE = secret_registry_key_passphrase
SECRET_REGISTRY_HOSTNAME = secret_registry_hostname
# endregion
# region: Security and Privacy----------------------------------------------###
@ -531,12 +549,14 @@ SECURE_SSL_REDIRECT = True
# web server configurations.
ALLOWED_HOSTS = [
"getgov-stable.app.cloud.gov",
"getgov-ab.app.cloud.gov",
"getgov-bl.app.cloud.gov",
"getgov-rjm.app.cloud.gov",
"getgov-jon.app.cloud.gov",
"getgov-mr.app.cloud.gov",
"getgov-sspj.app.cloud.gov",
"getgov-nmb.app.cloud.gov",
"getgov-ik.app.cloud.gov",
"getgov-lmm.app.cloud.gov",
"get.gov",
]

View file

@ -49,6 +49,11 @@ class UserFixture:
"first_name": "Jon",
"last_name": "Roberts",
},
{
"username": "5f283494-31bd-49b5-b024-a7e7cae00848",
"first_name": "Rachid",
"last_name": "Mrad",
},
]
@classmethod

View file

@ -14,7 +14,7 @@ class Migration(migrations.Migration):
name="more_organization_information",
field=models.TextField(
blank=True,
help_text="Further information about the government organization",
help_text="More information about your organization",
null=True,
),
),

View file

@ -7,7 +7,6 @@ from django.db import models
from django_fsm import FSMField, transition # type: ignore
from api.views import in_domains
from epp.mock_epp import domain_info, domain_check
from registrar.utility import errors
from .utility.time_stamped_model import TimeStampedModel
@ -129,7 +128,7 @@ class Domain(TimeStampedModel):
"""Check if a domain is available.
Not implemented. Returns a dummy value for testing."""
return domain_check(domain)
return False # domain_check(domain)
def transfer(self):
"""Going somewhere. Not implemented."""
@ -146,7 +145,7 @@ class Domain(TimeStampedModel):
if not hasattr(self, "info"):
try:
# get info from registry
self.info = domain_info(self.name)
self.info = {} # domain_info(self.name)
except Exception as e:
logger.error(e)
# TODO: back off error handling

View file

@ -292,7 +292,7 @@ class DomainApplication(TimeStampedModel):
choices=OrganizationChoices.choices,
null=True,
blank=True,
help_text="Type of Organization",
help_text="Type of organization",
)
federally_recognized_tribe = models.BooleanField(
@ -381,7 +381,7 @@ class DomainApplication(TimeStampedModel):
more_organization_information = models.TextField(
null=True,
blank=True,
help_text="Further information about the government organization",
help_text="More information about your organization",
)
authorizing_official = models.ForeignKey(

View file

@ -32,7 +32,7 @@
<b class="review__step__name">Request #:</b> {{domainapplication.id}}</p>
<p>{% include "includes/domain_application.html" %}</p>
<p><a href="{% url 'application-withdraw-confirmation' domainapplication.id %}" class="usa-button usa-button--outline withdraw_outline">
Withdraw Request</a>
Withdraw request</a>
</p>
</div>
@ -63,7 +63,7 @@
{% endif %}
{% if domainapplication.organization_name %}
{% include "includes/summary_item.html" with title='Organization address' value=domainapplication address='true' %}
{% include "includes/summary_item.html" with title='Organization name and mailing address' value=domainapplication address='true' %}
{% endif %}
{% if domainapplication.type_of_work %}
@ -71,7 +71,7 @@
{% endif %}
{% if domainapplication.more_organization_information %}
{% include "includes/summary_item.html" with title='Further information about the government organization' value=domainapplication.more_organization_information %}
{% include "includes/summary_item.html" with title='More information about your organization' value=domainapplication.more_organization_information %}
{% endif %}
{% if domainapplication.authorizing_official %}

View file

@ -14,20 +14,23 @@
<div class="mobile-lg:grid-col-auto">
<img
class="usa-footer__logo-img"
src="{% static 'img/dottedgov-round.svg' %}"
src="{% static 'img/registrar/dotgov_logo.svg' %}"
alt="dot gov registrar logo"
width="56px"
/>
</div>
</div>
<div class="usa-footer__contact-links mobile-lg:grid-col-6">
<p class="usa-footer__contact-heading">
Contact us
</p>
<div class="usa-footer__contact-links
mobile-lg:grid-col-6 flex-align-self-center"
>
<address class="usa-footer__address">
<div class="usa-footer__contact-info grid-row grid-gap">
<div class="usa-footer__contact-info grid-row grid-gap-md">
<div class="grid-col-auto">
<a href="mailto:registrar@dotgov.gov">registrar@dotgov.gov</a>
<a href="https://federalist-877ab29f-16f6-4f12-961c-96cf064cf070.sites.pages.cloud.gov/site/cisagov/getgov-home/help/"> Help </a>
</div>
<span class=""> | </span>
<div class="grid-col-auto">
<a href="https://federalist-877ab29f-16f6-4f12-961c-96cf064cf070.sites.pages.cloud.gov/site/cisagov/getgov-home/contact/" class="usa-link">Contact us</a>
</div>
</div>
</address>
@ -71,7 +74,7 @@
<ul class="usa-identifier__required-links-list">
<li class="usa-identifier__required-links-item">
<a
href="https://home.dotgov.gov/about/"
href="https://federalist-877ab29f-16f6-4f12-961c-96cf064cf070.sites.pages.cloud.gov/site/cisagov/getgov-home/about/"
class="usa-identifier__required-link usa-link"
>About .gov</a
>
@ -84,7 +87,7 @@
>
</li>
<li class="usa-identifier__required-links-item">
<a href="https://home.dotgov.gov/privacy/" class="usa-identifier__required-link usa-link"
<a href="https://federalist-877ab29f-16f6-4f12-961c-96cf064cf070.sites.pages.cloud.gov/site/cisagov/getgov-home/privacy-policy/" class="usa-identifier__required-link usa-link"
>Privacy policy</a
>
</li>
@ -94,7 +97,7 @@
>
</li>
<li class="usa-identifier__required-links-item">
<a href="{% url 'todo' %}" class="usa-identifier__required-link usa-link"
<a href="https://federalist-877ab29f-16f6-4f12-961c-96cf064cf070.sites.pages.cloud.gov/site/cisagov/getgov-home/vulnerability-disclosure-policy/" class="usa-identifier__required-link usa-link"
>Vulnerability disclosure policy</a
>
</li>

View file

@ -118,6 +118,7 @@ class TestDomain(TestCase):
domain = Domain.objects.create(name="igorville.gov")
self.assertEqual(domain.is_active, False)
@skip("cannot activate a domain without mock registry")
def test_get_status(self):
"""Returns proper status based on `is_active`."""
domain = Domain.objects.create(name="igorville.gov")

View file

@ -1327,7 +1327,7 @@ class TestApplicationStatus(TestWithUser, WebTest):
self.assertContains(detail_page, "Admin Tester")
self.assertContains(detail_page, "Status:")
# click the "Withdraw request" button
withdraw_page = detail_page.click("Withdraw Request")
withdraw_page = detail_page.click("Withdraw request")
self.assertContains(withdraw_page, "Withdraw request for")
home_page = withdraw_page.click("Withdraw request")
# confirm that it has redirected, and the status has been updated to withdrawn