mirror of
https://github.com/cisagov/manage.get.gov.git
synced 2025-08-03 16:32:15 +02:00
Merge branch 'main' into dk/1208-dnssec-addtl-items
This commit is contained in:
commit
5145e3782e
22 changed files with 269 additions and 160 deletions
|
@ -106,6 +106,7 @@ class EPPLibWrapper:
|
|||
# Flag that the pool is frozen,
|
||||
# then restart the pool.
|
||||
self.pool_status.pool_hanging = True
|
||||
logger.error("Pool timed out")
|
||||
self.start_connection_pool()
|
||||
except (ValueError, ParsingError) as err:
|
||||
message = f"{cmd_type} failed to execute due to some syntax error."
|
||||
|
@ -174,6 +175,7 @@ class EPPLibWrapper:
|
|||
|
||||
def _create_pool(self, client, login, options):
|
||||
"""Creates and returns new pool instance"""
|
||||
logger.info("New pool was created")
|
||||
return EPPConnectionPool(client, login, options)
|
||||
|
||||
def start_connection_pool(self, restart_pool_if_exists=True):
|
||||
|
@ -187,7 +189,7 @@ class EPPLibWrapper:
|
|||
# Since we reuse the same creds for each pool, we can test on
|
||||
# one socket, and if successful, then we know we can connect.
|
||||
if not self._test_registry_connection_success():
|
||||
logger.warning("Cannot contact the Registry")
|
||||
logger.warning("start_connection_pool() -> Cannot contact the Registry")
|
||||
self.pool_status.connection_success = False
|
||||
else:
|
||||
self.pool_status.connection_success = True
|
||||
|
@ -197,6 +199,7 @@ class EPPLibWrapper:
|
|||
if self._pool is not None and restart_pool_if_exists:
|
||||
logger.info("Connection pool restarting...")
|
||||
self.kill_pool()
|
||||
logger.info("Old pool killed")
|
||||
|
||||
self._pool = self._create_pool(self._client, self._login, self.pool_options)
|
||||
|
||||
|
@ -221,6 +224,7 @@ class EPPLibWrapper:
|
|||
credentials are valid, and/or if the Registrar
|
||||
can be contacted
|
||||
"""
|
||||
# This is closed in test_connection_success
|
||||
socket = Socket(self._client, self._login)
|
||||
can_login = False
|
||||
|
||||
|
|
|
@ -31,6 +31,7 @@ class Socket:
|
|||
|
||||
def connect(self):
|
||||
"""Use epplib to connect."""
|
||||
logger.info("Opening socket on connection pool")
|
||||
self.client.connect()
|
||||
response = self.client.send(self.login)
|
||||
if self.is_login_error(response.code):
|
||||
|
@ -40,11 +41,13 @@ class Socket:
|
|||
|
||||
def disconnect(self):
|
||||
"""Close the connection."""
|
||||
logger.info("Closing socket on connection pool")
|
||||
try:
|
||||
self.client.send(commands.Logout())
|
||||
self.client.close()
|
||||
except Exception:
|
||||
except Exception as err:
|
||||
logger.warning("Connection to registry was not cleanly closed.")
|
||||
logger.error(err)
|
||||
|
||||
def send(self, command):
|
||||
"""Sends a command to the registry.
|
||||
|
@ -77,19 +80,17 @@ class Socket:
|
|||
try:
|
||||
self.client.connect()
|
||||
response = self.client.send(self.login)
|
||||
except LoginError as err:
|
||||
if err.should_retry() and counter < 10:
|
||||
except (LoginError, OSError) as err:
|
||||
logger.error(err)
|
||||
should_retry = True
|
||||
if isinstance(err, LoginError):
|
||||
should_retry = err.should_retry()
|
||||
if should_retry and counter < 3:
|
||||
counter += 1
|
||||
sleep((counter * 50) / 1000) # sleep 50 ms to 150 ms
|
||||
else: # don't try again
|
||||
return False
|
||||
# Occurs when an invalid creds are passed in - such as on localhost
|
||||
except OSError as err:
|
||||
logger.error(err)
|
||||
return False
|
||||
else:
|
||||
self.disconnect()
|
||||
|
||||
# If we encounter a login error, fail
|
||||
if self.is_login_error(response.code):
|
||||
logger.warning("A login error was found in test_connection_success")
|
||||
|
@ -97,3 +98,5 @@ class Socket:
|
|||
|
||||
# Otherwise, just return true
|
||||
return True
|
||||
finally:
|
||||
self.disconnect()
|
||||
|
|
|
@ -125,10 +125,14 @@ class TestConnectionPool(TestCase):
|
|||
xml = (location).read_bytes()
|
||||
return xml
|
||||
|
||||
def do_nothing(command):
|
||||
pass
|
||||
|
||||
# Mock what happens inside the "with"
|
||||
with ExitStack() as stack:
|
||||
stack.enter_context(patch.object(EPPConnectionPool, "_create_socket", self.fake_socket))
|
||||
stack.enter_context(patch.object(Socket, "connect", self.fake_client))
|
||||
stack.enter_context(patch.object(EPPConnectionPool, "kill_all_connections", do_nothing))
|
||||
stack.enter_context(patch.object(SocketTransport, "send", self.fake_send))
|
||||
stack.enter_context(patch.object(SocketTransport, "receive", fake_receive))
|
||||
# Restart the connection pool
|
||||
|
|
|
@ -98,13 +98,17 @@ class EPPConnectionPool(ConnectionPool):
|
|||
"""Kills all active connections in the pool."""
|
||||
try:
|
||||
if len(self.conn) > 0 or len(self.greenlets) > 0:
|
||||
logger.info("Attempting to kill connections")
|
||||
gevent.killall(self.greenlets)
|
||||
|
||||
self.greenlets.clear()
|
||||
for connection in self.conn:
|
||||
connection.disconnect()
|
||||
self.conn.clear()
|
||||
|
||||
# Clear the semaphore
|
||||
self.lock = BoundedSemaphore(self.size)
|
||||
logger.info("Finished killing connections")
|
||||
else:
|
||||
logger.info("No connections to kill.")
|
||||
except Exception as err:
|
||||
|
|
|
@ -136,6 +136,8 @@ MIDDLEWARE = [
|
|||
"allow_cidr.middleware.AllowCIDRMiddleware",
|
||||
# django-cors-headers: listen to cors responses
|
||||
"corsheaders.middleware.CorsMiddleware",
|
||||
# custom middleware to stop caching from CloudFront
|
||||
"registrar.no_cache_middleware.NoCacheMiddleware",
|
||||
# serve static assets in production
|
||||
"whitenoise.middleware.WhiteNoiseMiddleware",
|
||||
# provide security enhancements to the request/response cycle
|
||||
|
@ -617,6 +619,8 @@ SECURE_SSL_REDIRECT = True
|
|||
ALLOWED_HOSTS = [
|
||||
"getgov-stable.app.cloud.gov",
|
||||
"getgov-staging.app.cloud.gov",
|
||||
"getgov-development.app.cloud.gov",
|
||||
"getgov-ky.app.cloud.gov",
|
||||
"getgov-es.app.cloud.gov",
|
||||
"getgov-nl.app.cloud.gov",
|
||||
"getgov-rh.app.cloud.gov",
|
||||
|
|
|
@ -5,7 +5,6 @@ from django.db import models
|
|||
|
||||
from .domain_invitation import DomainInvitation
|
||||
from .transition_domain import TransitionDomain
|
||||
from .domain_information import DomainInformation
|
||||
from .domain import Domain
|
||||
|
||||
from phonenumber_field.modelfields import PhoneNumberField # type: ignore
|
||||
|
@ -97,51 +96,6 @@ class User(AbstractUser):
|
|||
new_domain_invitation = DomainInvitation(email=transition_domain_email.lower(), domain=new_domain)
|
||||
new_domain_invitation.save()
|
||||
|
||||
def check_transition_domains_on_login(self):
|
||||
"""When a user first arrives on the site, we need to check
|
||||
if they are logging in with the same e-mail as a
|
||||
transition domain and update our database accordingly."""
|
||||
|
||||
for transition_domain in TransitionDomain.objects.filter(username=self.email):
|
||||
# Looks like the user logged in with the same e-mail as
|
||||
# one or more corresponding transition domains.
|
||||
# Create corresponding DomainInformation objects.
|
||||
|
||||
# NOTE: adding an ADMIN user role for this user
|
||||
# for each domain should already be done
|
||||
# in the invitation.retrieve() method.
|
||||
# However, if the migration scripts for transition
|
||||
# domain objects were not executed correctly,
|
||||
# there could be transition domains without
|
||||
# any corresponding Domain & DomainInvitation objects,
|
||||
# which means the invitation.retrieve() method might
|
||||
# not execute.
|
||||
# Check that there is a corresponding domain object
|
||||
# for this transition domain. If not, we have an error
|
||||
# with our data and migrations need to be run again.
|
||||
|
||||
# Get the domain that corresponds with this transition domain
|
||||
domain_exists = Domain.objects.filter(name=transition_domain.domain_name).exists()
|
||||
if not domain_exists:
|
||||
logger.warn(
|
||||
"""There are transition domains without
|
||||
corresponding domain objects!
|
||||
Please run migration scripts for transition domains
|
||||
(See data_migration.md)"""
|
||||
)
|
||||
# No need to throw an exception...just create a domain
|
||||
# and domain invite, then proceed as normal
|
||||
self.create_domain_and_invite(transition_domain)
|
||||
|
||||
domain = Domain.objects.get(name=transition_domain.domain_name)
|
||||
|
||||
# Create a domain information object, if one doesn't
|
||||
# already exist
|
||||
domain_info_exists = DomainInformation.objects.filter(domain=domain).exists()
|
||||
if not domain_info_exists:
|
||||
new_domain_info = DomainInformation(creator=self, domain=domain)
|
||||
new_domain_info.save()
|
||||
|
||||
def on_each_login(self):
|
||||
"""Callback each time the user is authenticated.
|
||||
|
||||
|
@ -152,17 +106,6 @@ class User(AbstractUser):
|
|||
as a transition domain and update our domainInfo objects accordingly.
|
||||
"""
|
||||
|
||||
# PART 1: TRANSITION DOMAINS
|
||||
#
|
||||
# NOTE: THIS MUST RUN FIRST
|
||||
# (If we have an issue where transition domains were
|
||||
# not fully converted into Domain and DomainInvitation
|
||||
# objects, this method will fill in the gaps.
|
||||
# This will ensure the Domain Invitations method
|
||||
# runs correctly (no missing invites))
|
||||
self.check_transition_domains_on_login()
|
||||
|
||||
# PART 2: DOMAIN INVITATIONS
|
||||
self.check_domain_invitations_on_login()
|
||||
|
||||
class Meta:
|
||||
|
|
18
src/registrar/no_cache_middleware.py
Normal file
18
src/registrar/no_cache_middleware.py
Normal file
|
@ -0,0 +1,18 @@
|
|||
"""Middleware to add Cache-control: no-cache to every response.
|
||||
|
||||
Used to force Cloudfront caching to leave us alone while we develop
|
||||
better caching responses.
|
||||
"""
|
||||
|
||||
|
||||
class NoCacheMiddleware:
|
||||
|
||||
"""Middleware to add a single header to every response."""
|
||||
|
||||
def __init__(self, get_response):
|
||||
self.get_response = get_response
|
||||
|
||||
def __call__(self, request):
|
||||
response = self.get_response(request)
|
||||
response["Cache-Control"] = "no-cache"
|
||||
return response
|
|
@ -11,23 +11,18 @@
|
|||
<h1>
|
||||
{% translate "You are not authorized to view this page" %}
|
||||
</h1>
|
||||
|
||||
<h2>
|
||||
{% translate "Status 401" %}
|
||||
</h2>
|
||||
|
||||
|
||||
{% if friendly_message %}
|
||||
<p>{{ friendly_message }}</p>
|
||||
{% else %}
|
||||
<p>{% translate "Authorization failed." %}</p>
|
||||
{% endif %}
|
||||
|
||||
<p>
|
||||
You must be an authorized user and need to be signed in to view this page.
|
||||
Would you like to <a href="{% url 'login' %}"> try logging in again?</a>
|
||||
You must be an authorized user and signed in to view this page. If you are an authorized user,
|
||||
<strong><a href="{% url 'login' %}"> try signing in again</a>.</strong>
|
||||
</p>
|
||||
<p>
|
||||
If you'd like help with this error <a class="usa-link" rel="noopener noreferrer" target="_blank" href="{% public_site_url 'contact/' %}">contact us</a>.
|
||||
</p>
|
||||
If you'd like help with this error <a class="usa-link" rel="noopener noreferrer" target="_blank" href="{% public_site_url 'contact/' %}">contact us</a>.</p>
|
||||
|
||||
|
||||
{% if log_identifier %}
|
||||
<p>Here's a unique identifier for this error.</p>
|
||||
|
@ -35,6 +30,7 @@
|
|||
<p>{% translate "Please include it if you contact us." %}</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="tablet:grid-col-4">
|
||||
<img
|
||||
src="{% static 'img/registrar/dotgov_401_illo.svg' %}"
|
||||
|
@ -43,4 +39,4 @@
|
|||
</div>
|
||||
</div>
|
||||
</main>
|
||||
{% endblock %}
|
||||
{% endblock %}
|
|
@ -4,6 +4,7 @@
|
|||
{% block title %}Security email | {{ domain.name }} | {% endblock %}
|
||||
|
||||
{% block domain_content %}
|
||||
{% include "includes/form_errors.html" with form=form %}
|
||||
|
||||
<h1>Security email</h1>
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
{% block title %}Your contact information | {{ domain.name }} | {% endblock %}
|
||||
|
||||
{% block domain_content %}
|
||||
{% include "includes/form_errors.html" with form=form %}
|
||||
|
||||
<h1>Your contact information</h1>
|
||||
|
||||
|
|
|
@ -11,12 +11,22 @@ If you’re not affiliated with the above domain{% if domains|length > 1 %}s{% e
|
|||
|
||||
CREATE A LOGIN.GOV ACCOUNT
|
||||
|
||||
You can’t use your old credentials to access the new registrar. Access is now managed through Login.gov, a simple and secure process for signing into many government services with one account. Follow these steps to create your Login.gov account <https://login.gov/help/get-started/create-your-account/>.
|
||||
You can’t use your old credentials to access the new registrar. Access is now managed through Login.gov, a simple and secure process for signing in to many government services with one account.
|
||||
|
||||
When creating an account, you’ll need to provide the same email address you used to log in to the old registrar. That will ensure your domains are linked to your Login.gov account.
|
||||
When creating a Login.gov account, you’ll need to provide the same email address you used to sign in to the old registrar. That will link your domain{% if domains|length > 1 %}s{% endif %} to your account.
|
||||
|
||||
If you need help finding the email address you used in the past, let us know in a reply to this email.
|
||||
|
||||
YOU MUST VERIFY YOUR IDENTITY WITH LOGIN.GOV
|
||||
|
||||
We require you to verify your identity with Login.gov as part of the account creation process. This is an extra layer of security that requires you to prove you are you, and not someone pretending to be you.
|
||||
|
||||
When you try to access the registrar with your Login.gov account, we’ll ask you to verify your identity if you haven’t already. You’ll only have to verify your identity once. You’ll need a state-issued ID, a Social Security number, and a phone number for identity verification.
|
||||
|
||||
Follow these steps to create your Login.gov account <https://login.gov/help/get-started/create-your-account/>.
|
||||
|
||||
Read more about verifying your identity with Login.gov <https://login.gov/help/verify-your-identity/how-to-verify-your-identity/>.
|
||||
|
||||
CHECK YOUR .GOV DOMAIN CONTACTS
|
||||
|
||||
This is a good time to check who has access to your .gov domain{% if domains|length > 1 %}s{% endif %}. The admin, technical, and billing contacts listed for your domain{% if domains|length > 1 %}s{% endif %} in our old system also received this email. In our new registrar, these contacts are all considered “domain managers.” We no longer have the admin, technical, and billing roles, and you aren’t limited to three domain managers like in the old system.
|
||||
|
|
|
@ -627,22 +627,10 @@ class TestUser(TestCase):
|
|||
TransitionDomain.objects.all().delete()
|
||||
User.objects.all().delete()
|
||||
|
||||
def test_check_transition_domains_on_login(self):
|
||||
"""A user's on_each_login callback checks transition domains.
|
||||
Makes DomainInformation object."""
|
||||
self.domain, _ = Domain.objects.get_or_create(name=self.domain_name)
|
||||
|
||||
self.user.on_each_login()
|
||||
self.assertTrue(DomainInformation.objects.get(domain=self.domain))
|
||||
|
||||
def test_check_transition_domains_without_domains_on_login(self):
|
||||
"""A user's on_each_login callback checks transition domains.
|
||||
"""A user's on_each_login callback does not check transition domains.
|
||||
This test makes sure that in the event a domain does not exist
|
||||
for a given transition domain, both a domain and domain invitation
|
||||
are created."""
|
||||
self.user.on_each_login()
|
||||
self.assertTrue(Domain.objects.get(name=self.domain_name))
|
||||
|
||||
domain = Domain.objects.get(name=self.domain_name)
|
||||
self.assertTrue(DomainInvitation.objects.get(email=self.email, domain=domain))
|
||||
self.assertTrue(DomainInformation.objects.get(domain=domain))
|
||||
self.assertFalse(Domain.objects.filter(name=self.domain_name).exists())
|
||||
|
|
|
@ -62,6 +62,9 @@
|
|||
10038 OUTOFSCOPE http://app:8080/delete
|
||||
10038 OUTOFSCOPE http://app:8080/withdraw
|
||||
10038 OUTOFSCOPE http://app:8080/withdrawconfirmed
|
||||
10038 OUTOFSCOPE http://app:8080/dns
|
||||
10038 OUTOFSCOPE http://app:8080/dnssec
|
||||
10038 OUTOFSCOPE http://app:8080/dns/dnssec
|
||||
# This URL always returns 404, so include it as well.
|
||||
10038 OUTOFSCOPE http://app:8080/todo
|
||||
# OIDC isn't configured in the test environment and DEBUG=True so this gives a 500 without CSP headers
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue