1
0
mirror of https://github.com/peeringdb/peeringdb.git synced 2024-05-11 05:55:09 +00:00
Files
peeringdb-peeringdb/peeringdb_server/signals.py
Matt Griswold fbc72ea682 Support 202104 (#980)
* Add migration for service level and terms

* Add service level and terms to UI and serializer, as well as data/enum

* Wire up data/enum endpoint and loader

* remove proto_ from ix UI

* derive fields for proto_unicast and proto_ipv6

* update tests for readonly fields

* Fix query for protocols

* Fix api bug with protocol

* add readonly fields to django admin

* rename readonly fields

* Add translation to names

* Add pdb api test for suggested facility re-add

* Add printing debuggin test

* add printing debugging serializer

* Update _undelete with _reapprove to handle pending cases

* Update tests (one is still failing)

* adjust suggest test

* Add ix_count to fac (834)

* Add test for ix_count on fac (834)

* Add fac_count to IX (836)

* add ix_count and fac_count to Network

* Refactor ix net_count filtering

* Add filtering for 834, 835, 836

* Remove duplicates from the Network's ix_count

* Setup Network for ix_count and fac_count (835)

* initial obj_counts for Facilities and Exchanges

* Add signals for updates to all counts

* add migration

* Add print statements to test

* introduce reversion to tests

* rename network count to net count across codebase

* fix network_count typo

* add migration to set default vals

* fix filter tests for obj_counts

* speed up migration

* fix failing tests

* fix final test

* sort out migration tree and add fac offered fields

* update frontend for facility dropdown offered_resilience

* First pass at advanced api search for user story 1

* melissa geo lookup first steps

* fix migration hierarchy

* working melissa integration

* begin ending filters for api endpoints

* add more org_present endpoints

* add search for IXs that match multiple networks

* extend logic to facility

* Add service level and terms to advanced search

* use address2 field for lookup

* melissa tests

* cleanup and docs

* uncomment offered_power

* developed offered_power component

* fix geo normalize existing cmd
normalize state

* change migration to match django-peeringdb

* add offered_space field

* Fill out remaining api filter fields

* Add org_not_present endpoint filter

* fix unit input ux

* more ux fixes

* remove merge cruft

* google for geocoding
various melissa improvements (consider result quality)

* fix tests

* refactor org_preset and org_not_present queries

* ix capacity api filters

* ix capacity filters for #802
advanced search ux for #802

* finalize advanced search UX for #802

* css fixes

* remove cruft

* fix net_count fac_count queries

* add new fields to create facility (#800)
tests for #802 and #800

* fix tests

* remove #800 changes

* fix capacity search

* more #800 changes to remove

* django-peeringdb 2.7.0 and pipenv relock

* black format

* pin black version

Co-authored-by: Elliot Frank <elliot@20c.com>
Co-authored-by: Stefan Pratter <stefan@20c.com>
2021-05-19 08:11:30 -05:00

541 lines
18 KiB
Python

from grainy.const import *
from datetime import datetime, timezone
import django.urls
from django.db.models import Count
from django.db.models.signals import post_save, pre_delete, pre_save
from django.contrib.contenttypes.models import ContentType
from django_grainy.models import Group, GroupPermission
from django.template import loader
from django.conf import settings
from django.dispatch import receiver
import reversion
from allauth.account.signals import user_signed_up
from corsheaders.signals import check_request_enabled
from django_peeringdb.models.abstract import AddressModel
from peeringdb_server.inet import RdapLookup, RdapException
from peeringdb_server.deskpro import (
ticket_queue,
ticket_queue_asnauto_affil,
ticket_queue_asnauto_create,
ticket_queue_vqi_notify,
)
from peeringdb_server.models import (
QUEUE_ENABLED,
QUEUE_NOTIFY,
UserOrgAffiliationRequest,
is_suggested,
VerificationQueueItem,
Organization,
InternetExchange,
Facility,
Network,
NetworkContact,
NetworkIXLan,
NetworkFacility,
)
from peeringdb_server.util import PERM_CRUD
import peeringdb_server.settings as pdb_settings
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import override
def disable_auto_now_and_save(entity):
updated_field = entity._meta.get_field("updated")
updated_field.auto_now = False
entity.save()
updated_field.auto_now = True
def update_network_attribute(instance, attribute):
"""Updates 'attribute' field in Network whenever it's called."""
if getattr(instance, "id"):
network = instance.network
setattr(network, attribute, datetime.now(timezone.utc))
disable_auto_now_and_save(network)
def network_post_revision_commit(**kwargs):
for vs in kwargs.get("versions"):
if vs.object.HandleRef.tag in ["netixlan", "poc", "netfac"]:
update_network_attribute(vs.object, f"{vs.object.HandleRef.tag}_updated")
reversion.signals.post_revision_commit.connect(network_post_revision_commit)
def update_counts_for_netixlan(netixlan):
"""
Whenever a netixlan is saved, update the ix_count for the related Network
and update net_count for the related InternetExchange.
"""
if getattr(netixlan, "id"):
network = netixlan.network
network.ix_count = (
network.netixlan_set_active.aggregate(
ix_count=Count("ixlan__ix_id", distinct=True)
)
)["ix_count"]
disable_auto_now_and_save(network)
ix = netixlan.ixlan.ix
ix.net_count = (
NetworkIXLan.objects.filter(ixlan__ix_id=ix.id, status="ok").aggregate(
net_count=Count("network_id", distinct=True)
)
)["net_count"]
disable_auto_now_and_save(ix)
def update_counts_for_netfac(netfac):
"""
Whenever a netfac is saved, update the fac_count for the related Network
and update net_count for the related Facility.
"""
if getattr(netfac, "id"):
network = netfac.network
network.fac_count = network.netfac_set_active.count()
disable_auto_now_and_save(network)
facility = netfac.facility
facility.net_count = facility.netfac_set_active.count()
disable_auto_now_and_save(facility)
def update_counts_for_ixfac(ixfac):
"""
Whenever a ixfac is saved, update the fac_count for the related Exchange
and update ix_count for the related Facility.
"""
if getattr(ixfac, "id"):
ix = ixfac.ix
ix.fac_count = ix.ixfac_set_active.count()
disable_auto_now_and_save(ix)
facility = ixfac.facility
facility.ix_count = facility.ixfac_set_active.count()
disable_auto_now_and_save(facility)
def connector_objects_post_revision_commit(**kwargs):
for vs in kwargs.get("versions"):
if vs.object.HandleRef.tag == "netixlan":
update_counts_for_netixlan(vs.object)
if vs.object.HandleRef.tag == "netfac":
update_counts_for_netfac(vs.object)
if vs.object.HandleRef.tag == "ixfac":
update_counts_for_ixfac(vs.object)
reversion.signals.post_revision_commit.connect(connector_objects_post_revision_commit)
def addressmodel_save(sender, instance=None, **kwargs):
"""
Mark address model objects for geocode sync if one of the address
fields is updated
"""
if instance.id:
# instance is being updated
old = sender.objects.get(id=instance.id)
for field in AddressModel._meta.get_fields():
if field.name in ["latitude", "longitude"]:
continue
a = getattr(instance, field.name)
b = getattr(old, field.name)
if a != b:
# print("Change in field '%s' - '%s'(%s) to '%s'(%s) - marking %s for geocode sync" % (field.name, a, type(a), b, type(b), instance))
# address model field has changed, mark for geocode sync
instance.geocode_status = False
pre_save.connect(addressmodel_save, sender=Facility)
def org_save(sender, **kwargs):
"""
we want to create a user group for an organization when that
organization is created
"""
inst = kwargs.get("instance")
# make the general member group for the org
try:
group = Group.objects.get(name=inst.group_name)
except Group.DoesNotExist:
group = Group(name=inst.group_name)
group.save()
perm = GroupPermission(
group=group, namespace=inst.grainy_namespace, permission=PERM_READ
)
perm.save()
GroupPermission(
group=group,
namespace=f"{inst.grainy_namespace}.network.*.poc_set.private",
permission=PERM_READ,
).save()
GroupPermission(
group=group,
namespace=f"{inst.grainy_namespace}.internetexchange.*.ixf_ixp_member_list_url.private",
permission=PERM_READ,
).save()
# make the admin group for the org
try:
group = Group.objects.get(name=inst.admin_group_name)
except Group.DoesNotExist:
group = Group(name=inst.admin_group_name)
group.save()
perm = GroupPermission(
group=group, namespace=inst.grainy_namespace, permission=PERM_CRUD
)
perm.save()
GroupPermission(
group=group, namespace=inst.grainy_namespace_manage, permission=PERM_CRUD
).save()
GroupPermission(
group=group,
namespace=f"{inst.grainy_namespace}.network.*.poc_set.private",
permission=PERM_CRUD,
).save()
GroupPermission(
group=group,
namespace=f"{inst.grainy_namespace}.internetexchange.*.ixf_ixp_member_list_url.private",
permission=PERM_CRUD,
).save()
if inst.status == "deleted":
for ar in inst.affiliation_requests.all():
ar.delete()
post_save.connect(org_save, sender=Organization)
def org_delete(sender, instance, **kwargs):
"""
When an organization is HARD deleted we want to also remove any
usergroups tied to the organization
"""
try:
instance.usergroup.delete()
except Group.DoesNotExist:
pass
try:
instance.admin_usergroup.delete()
except Group.DoesNotExist:
pass
for ar in instance.affiliation_requests.all():
ar.delete()
pre_delete.connect(org_delete, sender=Organization)
@receiver(user_signed_up, dispatch_uid="allauth.user_signed_up")
def new_user_to_guests(request, user, sociallogin=None, **kwargs):
"""
When a user is created via oauth login put them in the guest
group for now.
Unless pdb_settings.AUTO_VERIFY_USERS is toggled on in settings, in which
case users get automatically verified (note that this does
not include email verification, they will still need to do that)
"""
if pdb_settings.AUTO_VERIFY_USERS:
user.set_verified()
else:
user.set_unverified()
# USER TO ORGANIZATION AFFILIATION
def uoar_creation(sender, instance, created=False, **kwargs):
"""
When a user to organization affiliation request is created
we want to notify the approporiate management entity
We also want to attempt to derive the targeted organization
from the ASN the user provided
"""
if created:
if instance.asn and not instance.org_id:
network = Network.objects.filter(asn=instance.asn).first()
if network:
# network with targeted asn found, set org
instance.org = network.org
instance.status = "pending"
instance.save()
if instance.org_id and instance.org.admin_usergroup.user_set.count() > 0:
# check that user is not already a member of that org
if instance.user.groups.filter(name=instance.org.usergroup.name).exists():
instance.approve()
return
# organization exists already and has admins, notify organization
# admins
for user in instance.org.admin_usergroup.user_set.all():
with override(user.locale):
user.email_user(
_(
"User %(u_name)s wishes to be affiliated to your Organization"
)
% {"u_name": instance.user.full_name},
loader.get_template(
"email/notify-org-admin-user-affil.txt"
).render(
{
"user": instance.user,
"org": instance.org,
"org_management_url": "%s/org/%d#users"
% (settings.BASE_URL, instance.org.id),
}
),
)
else:
request_type = "be affiliated to"
rdap_data = {"emails": []}
org_created = False
net_created = False
rdap_lookup = None
if instance.asn and not instance.org_id:
# ASN specified in request, but no network found
# Lookup RDAP information
try:
rdap_lookup = rdap = RdapLookup().get_asn(instance.asn)
ok = rdap_lookup.emails
except RdapException as inst:
instance.deny()
raise
# create organization
instance.org, org_created = Organization.create_from_rdap(
rdap, instance.asn, instance.org_name
)
instance.save()
# create network
net, net_created = Network.create_from_rdap(
rdap, instance.asn, instance.org
)
# if affiliate auto appove is on, auto approve at this point
if pdb_settings.AUTO_APPROVE_AFFILIATION:
instance.approve()
return
ticket_queue_asnauto_create(
instance.user,
instance.org,
net,
rdap,
net.asn,
org_created=org_created,
net_created=net_created,
)
# if user's relationship to network can be validated now
# we can approve the ownership request right away
if instance.user.validate_rdap_relationship(rdap):
instance.approve()
ticket_queue_asnauto_affil(instance.user, instance.org, net, rdap)
return
if instance.org:
# organization has been set on affiliation request
entity_name = instance.org.name
if not instance.org.owned:
# organization is currently not owned
request_type = "request ownership of"
# if affiliate auto appove is on, auto approve at this point
if pdb_settings.AUTO_APPROVE_AFFILIATION:
instance.approve()
return
# if user's relationship to the org can be validated by
# checking the rdap information of the org's networks
# we can approve the affiliation (ownership) request right away
for asn, rdap in list(instance.org.rdap_collect.items()):
rdap_data["emails"].extend(rdap.emails)
if instance.user.validate_rdap_relationship(rdap):
ticket_queue_asnauto_affil(
instance.user,
instance.org,
Network.objects.get(asn=asn),
rdap,
)
instance.approve()
return
else:
entity_name = instance.org_name
if pdb_settings.AUTO_APPROVE_AFFILIATION:
org = Organization.objects.create(
name=instance.org_name, status="ok"
)
instance.org = org
instance.approve()
return
# organization has no owners and RDAP information could not verify the user's relationship to the organization, notify pdb staff for review
ticket_queue(
"User %s wishes to %s %s"
% (instance.user.username, request_type, entity_name),
loader.get_template("email/notify-pdb-admin-user-affil.txt").render(
{
"user": instance.user,
"instance": instance,
"base_url": settings.BASE_URL,
"org_add_url": "%s%s"
% (
settings.BASE_URL,
django.urls.reverse(
"admin:peeringdb_server_organization_add"
),
),
"net_add_url": "%s%s"
% (
settings.BASE_URL,
django.urls.reverse("admin:peeringdb_server_network_add"),
),
"review_url": "%s%s"
% (
settings.BASE_URL,
django.urls.reverse(
"admin:peeringdb_server_user_change",
args=(instance.user.id,),
),
),
"approve_url": "%s%s"
% (
settings.BASE_URL,
django.urls.reverse(
"admin:peeringdb_server_userorgaffiliationrequest_actions",
args=(instance.id, "approve_and_notify"),
),
),
"emails": list(set(rdap_data["emails"])),
"rdap_lookup": rdap_lookup,
}
),
instance.user,
)
elif instance.status == "approved" and instance.org_id:
# uoar was not created, and status is now approved, call approve
# to finalize
instance.approve()
post_save.connect(uoar_creation, sender=UserOrgAffiliationRequest)
# VERIFICATION QUEUE
if getattr(settings, "DISABLE_VERIFICATION_QUEUE", False) is False:
def verification_queue_update(sender, instance, **kwargs):
if instance.status == "pending":
try:
VerificationQueueItem.objects.get(
content_type=ContentType.objects.get_for_model(sender),
object_id=instance.id,
)
except VerificationQueueItem.DoesNotExist:
q = VerificationQueueItem(item=instance)
q.save()
else:
try:
q = VerificationQueueItem.objects.get(
content_type=ContentType.objects.get_for_model(sender),
object_id=instance.id,
)
q.delete()
except VerificationQueueItem.DoesNotExist:
pass
def verification_queue_delete(sender, instance, **kwargs):
try:
q = VerificationQueueItem.objects.get(
content_type=ContentType.objects.get_for_model(sender),
object_id=instance.id,
)
q.delete()
except VerificationQueueItem.DoesNotExist:
pass
def verification_queue_notify(sender, instance, **kwargs):
# notification was already sent
if instance.notified:
return
# no contact point exists
if not instance.user_id and not instance.org_key:
return
item = instance.item
if type(item) in QUEUE_NOTIFY and not getattr(
settings, "DISABLE_VERIFICATION_QUEUE_EMAILS", False
):
if type(item) == Network:
rdap = RdapLookup().get_asn(item.asn)
else:
rdap = None
ticket_queue_vqi_notify(instance, rdap)
instance.notified = True
instance.save()
post_save.connect(verification_queue_notify, sender=VerificationQueueItem)
for model in QUEUE_ENABLED:
post_save.connect(verification_queue_update, sender=model)
pre_delete.connect(verification_queue_delete, sender=model)
def cors_allow_api_get_to_everyone(sender, request, **kwargs):
# FIXME: path name to look for should come from config
return (
request.path == "/api" or request.path.startswith("/api/")
) and request.method in ["GET", "OPTIONS"]
check_request_enabled.connect(cors_allow_api_get_to_everyone)