mirror of
https://github.com/peeringdb/peeringdb.git
synced 2024-05-11 05:55:09 +00:00
* fix issue where ix-f import would raise suggestions ipaddresses not that ixlan (#764) * IX-F Suggestions: Leaving the editor and returning to it via back button issues (#765) * IX-F importer: Clicking "Preview" (IXP Update Tools) on /net/ page resulted in 170 ticket resolutions (#769) More robust testing * black formatting (was lost after pyupgrade) * Add regex searching to deskpro ticket subjects * Change operational error * IX-F suggestions: consolidate delete+add (#770) * Add reset functions to commandline tool * Fix commandline tool bugs * Fix reset commandline tool bugs * add commandline tool * Ixlan needs to be set for import commandline tool * Add email model * Add admin view to emails * Allow network and ix to be null * save emails as part of ixf import * Add email model * Add email delete * add iregex search and better comments * fix ixlan selection for import * redefine migration dependencies for this branch * only enable search w start and end char * Add caption to regex search * Remove delete all ixfmemberdata option * [beta] IX-F importer: don't bother about missing IPv{4,6} address when network is not doing IPv{4,6} (#771) * Add cmdline tests * Resolve email conflicts * Add cmd tool reset tests * add autocomplete to commandline tool * Fix email bugs * Fix email migrations * Fix typos * [beta] IX-F importer: prevent Admin Committee overload by initially limiting importer to IXes enabled by AC (#772) * Finalize regex search for emails and deskprotickets * Fix keyword bug * fix typo * protocol-conflict will now be handled in the notification consolidation 771 changes where if the network indicates neither ipv4 nor ipv6 support, it is handled as supporting both (eg the network didnt configure these at all) realised that the importer command re instantiates the `Importer` class for each ixlan it processes, so moved the sending of consolidated notifications (#772) out of the `update` function and into the command itself after its done processing all the ixlans. This means for tests you will need to call `importer.notify_proposals` after `importer.update` to test the consolidated notifications. fixed several MultipleObjectsReturned errors when network switch protocol support in between imports * should be checking for "ix" in the form data (#773) * Fix cmd ixf tests * fix issue in log_peer * Add commit check for reset tool * fix importer bugs * remove dupe IXFImportEmail definition * ixfimportemail support ix__name and net__name searching * ticket resolution responses * Add commit to command ixf import changes * fix modify entry header * remove whitespace in notification about remote data changes * Begin updating tests * ixf-import command line tool to queue * refactor conflict inserts * Update import protocol tests, including tests for 770 * More test edits * Change cmd tests * better ixfmemberdata error handling and fix some test data * dont reset the same ixfmemberdata requirement * fix many bugs add many tests * remove debug message * fix bug during import when consolidating delete+add * fix perfomance issue in IXFMemberData listing * dont show reset flags on prod env * Add regex search tests * Add 772 tests * remove debug output * fix `test_resolve_deskpro_ticket` test * black formatting * remove dupe import * fix issue with unique constraint error handling * add test for ixp / network ip protocol notification * add missing test data Co-authored-by: Stefan Pratter <stefan@20c.com> Co-authored-by: Elliot Frank <elliot@20c.com>
539 lines
15 KiB
Python
539 lines
15 KiB
Python
"""
|
|
Views for organization administrative actions
|
|
"""
|
|
from django.contrib.auth.decorators import login_required
|
|
from django.views.decorators.csrf import csrf_protect
|
|
from django.http import JsonResponse
|
|
from django.template import loader
|
|
from django.conf import settings
|
|
from .forms import OrgAdminUserPermissionForm
|
|
|
|
from peeringdb_server.models import (
|
|
User,
|
|
Organization,
|
|
Network,
|
|
NetworkContact,
|
|
InternetExchange,
|
|
Facility,
|
|
UserOrgAffiliationRequest,
|
|
)
|
|
|
|
import django_namespace_perms.util as nsp
|
|
from django_namespace_perms.constants import *
|
|
from django_namespace_perms.models import UserPermission
|
|
|
|
from django_handleref.models import HandleRefModel
|
|
|
|
from django.utils.translation import ugettext_lazy as _
|
|
from django.utils.translation import override
|
|
|
|
|
|
def save_user_permissions(org, user, perms):
|
|
"""
|
|
Save user permissions for the specified org and user
|
|
|
|
perms should be a dict of permissioning ids and permission levels
|
|
"""
|
|
|
|
# wipe all the user's perms for the targeted org
|
|
|
|
user.userpermission_set.filter(namespace__startswith=org.nsp_namespace).delete()
|
|
|
|
# collect permissioning namespaces from the provided permissioning ids
|
|
|
|
nsp_perms = {}
|
|
|
|
for id, permissions in list(perms.items()):
|
|
|
|
if not permissions & PERM_READ:
|
|
permissions = permissions | PERM_READ
|
|
|
|
if id == "org.%d" % org.id:
|
|
nsp_perms[org.nsp_namespace] = permissions
|
|
nsp_perms[
|
|
NetworkContact.nsp_namespace_from_id(org.id, "*", "private")
|
|
] = permissions
|
|
elif id == "net":
|
|
nsp_perms[
|
|
Network.nsp_namespace_from_id(org.id, "*").strip(".*")
|
|
] = permissions
|
|
nsp_perms[
|
|
NetworkContact.nsp_namespace_from_id(org.id, "*", "private")
|
|
] = permissions
|
|
elif id == "ix":
|
|
nsp_perms[
|
|
InternetExchange.nsp_namespace_from_id(org.id, "*").strip(".*")
|
|
] = permissions
|
|
elif id == "fac":
|
|
nsp_perms[
|
|
Facility.nsp_namespace_from_id(org.id, "*").strip(".*")
|
|
] = permissions
|
|
elif id.find(".") > -1:
|
|
id = id.split(".")
|
|
if id[0] == "net":
|
|
nsp_perms[Network.nsp_namespace_from_id(org.id, id[1])] = permissions
|
|
nsp_perms[
|
|
NetworkContact.nsp_namespace_from_id(org.id, id[1], "private")
|
|
] = permissions
|
|
elif id[0] == "ix":
|
|
nsp_perms[
|
|
InternetExchange.nsp_namespace_from_id(org.id, id[1])
|
|
] = permissions
|
|
elif id[0] == "fac":
|
|
nsp_perms[Facility.nsp_namespace_from_id(org.id, id[1])] = permissions
|
|
|
|
# save
|
|
for ns, p in list(nsp_perms.items()):
|
|
UserPermission.objects.create(namespace=ns, permissions=p, user=user)
|
|
|
|
return nsp_perms
|
|
|
|
|
|
def load_all_user_permissions(org):
|
|
"""
|
|
Returns dict of all users with all their permissions for
|
|
the given org
|
|
"""
|
|
|
|
rv = {}
|
|
for user in org.usergroup.user_set.all():
|
|
uperms, perms = load_user_permissions(org, user)
|
|
rv[user.id] = {
|
|
"id": user.id,
|
|
"perms": perms,
|
|
"name": f"{user.full_name} <{user.email}> {user.username}",
|
|
}
|
|
return rv
|
|
|
|
|
|
def load_user_permissions(org, user):
|
|
"""
|
|
Returns user's permissions for the specified org
|
|
"""
|
|
|
|
# load all of the user's permissions related to this org
|
|
uperms = {
|
|
p.namespace: p.permissions
|
|
for p in user.userpermission_set.filter(namespace__startswith=org.nsp_namespace)
|
|
}
|
|
|
|
perms = {}
|
|
|
|
extract_permission_id(uperms, perms, org, org)
|
|
|
|
# extract user's permissioning ids from nsp_namespaces targeting
|
|
# organization's entities
|
|
for model in [Network, InternetExchange, Facility]:
|
|
extract_permission_id(uperms, perms, model, org)
|
|
|
|
# extract user's permissioning ids from nsp_namespaces targeting
|
|
# organization's entities by their id (eg user has perms only
|
|
# to THAT specific network)
|
|
for net in org.net_set_active:
|
|
extract_permission_id(uperms, perms, net, org)
|
|
|
|
for net in org.ix_set_active:
|
|
extract_permission_id(uperms, perms, net, org)
|
|
|
|
for net in org.fac_set_active:
|
|
extract_permission_id(uperms, perms, net, org)
|
|
|
|
return uperms, perms
|
|
|
|
|
|
def permission_ids(org):
|
|
"""
|
|
returns a dict of a valid permissioning ids for
|
|
the specified organization
|
|
"""
|
|
|
|
perms = {
|
|
"org.%d" % org.id: _("Organization and all Entities it owns"),
|
|
"net": _("Any Network"),
|
|
"fac": _("Any Facility"),
|
|
"ix": _("Any Exchange"),
|
|
}
|
|
|
|
perms.update(
|
|
{
|
|
"net.%d" % net.id: _("Network - %(net_name)s") % {"net_name": net.name}
|
|
for net in org.net_set_active
|
|
}
|
|
)
|
|
|
|
perms.update(
|
|
{
|
|
"ix.%d" % ix.id: _("Exchange - %(ix_name)s") % {"ix_name": ix.name}
|
|
for ix in org.ix_set_active
|
|
}
|
|
)
|
|
|
|
perms.update(
|
|
{
|
|
"fac.%d" % fac.id: _("Facility - %(fac_name)s") % {"fac_name": fac.name}
|
|
for fac in org.fac_set_active
|
|
}
|
|
)
|
|
|
|
return perms
|
|
|
|
|
|
def extract_permission_id(source, dest, entity, org):
|
|
"""
|
|
extract a user's permissioning id for the specified
|
|
entity from source <dict> and store it in dest <dict>
|
|
|
|
source should be a dict containing django-namespace-perms
|
|
(namespace, level) items
|
|
|
|
dest should be a dict where permission ids are to be
|
|
exracted to
|
|
|
|
entity can either be a HandleRef instance or clas
|
|
|
|
org needs to be an Organization instance that owns the
|
|
entity
|
|
"""
|
|
|
|
if isinstance(entity, HandleRefModel):
|
|
# instance
|
|
k = entity.nsp_namespace
|
|
j = "%s.%d" % (entity.ref_tag, entity.id)
|
|
else:
|
|
# class
|
|
k = entity.nsp_namespace_from_id(org.id, "*").strip(".*")
|
|
j = entity.handleref.tag
|
|
if k in source:
|
|
dest[j] = source[k]
|
|
|
|
|
|
def org_admin_required(fnc):
|
|
"""
|
|
Decorator function that ensures that the requesting user
|
|
has administrative rights to the targeted organization
|
|
|
|
Also sets "org" in kwargs
|
|
"""
|
|
|
|
def callback(request, **kwargs):
|
|
org_id = request.POST.get("org_id", request.GET.get("org_id"))
|
|
|
|
if not org_id:
|
|
return JsonResponse({}, status=400)
|
|
|
|
try:
|
|
org = Organization.objects.get(id=org_id)
|
|
if not nsp.has_perms(request.user, org.nsp_namespace_manage, "update"):
|
|
return JsonResponse({}, status=403)
|
|
kwargs["org"] = org
|
|
return fnc(request, **kwargs)
|
|
except Organization.DoesNotExist:
|
|
return JsonResponse(
|
|
{"non_field_errors": [_("Invalid organization specified")]}, status=400
|
|
)
|
|
|
|
return callback
|
|
|
|
|
|
def target_user_validate(fnc):
|
|
"""
|
|
Decorator function that ensures that the targeted user
|
|
is a member of the targeted organization
|
|
|
|
Should be below org_admin_required
|
|
|
|
Also sets "user" in kwargs
|
|
"""
|
|
|
|
def callback(request, **kwargs):
|
|
|
|
user_id = request.POST.get("user_id", request.GET.get("user_id"))
|
|
org = kwargs.get("org")
|
|
|
|
if not user_id:
|
|
return JsonResponse({}, status=400)
|
|
|
|
try:
|
|
user = User.objects.get(id=user_id)
|
|
except User.DoesNotExist:
|
|
return JsonResponse({}, status=400)
|
|
|
|
if not user.is_org_member(org) and not user.is_org_admin(org):
|
|
return JsonResponse({}, status=403)
|
|
|
|
kwargs["user"] = user
|
|
return fnc(request, **kwargs)
|
|
|
|
return callback
|
|
|
|
|
|
@login_required
|
|
@org_admin_required
|
|
def users(request, **kwargs):
|
|
"""
|
|
Returns JsonResponse with a list of all users in the specified org
|
|
"""
|
|
|
|
org = kwargs.get("org")
|
|
|
|
rv = {
|
|
"users": [
|
|
{
|
|
"id": user.id,
|
|
"name": f"{user.full_name} <{user.email}, {user.username}>",
|
|
}
|
|
for user in org.usergroup.user_set.all()
|
|
]
|
|
}
|
|
|
|
rv.update({"status": "ok"})
|
|
|
|
return JsonResponse(rv)
|
|
|
|
|
|
@login_required
|
|
@org_admin_required
|
|
@target_user_validate
|
|
def manage_user_delete(request, **kwargs):
|
|
"""
|
|
remove user from org
|
|
"""
|
|
|
|
org = kwargs.get("org")
|
|
user = kwargs.get("user")
|
|
|
|
save_user_permissions(org, user, {})
|
|
org.usergroup.user_set.remove(user)
|
|
org.admin_usergroup.user_set.remove(user)
|
|
|
|
return JsonResponse({"status": "ok"})
|
|
|
|
|
|
@login_required
|
|
@org_admin_required
|
|
@target_user_validate
|
|
def manage_user_update(request, **kwargs):
|
|
"""
|
|
udpate a user in the org
|
|
|
|
right now this only allows for moving the user either
|
|
to admin or member group
|
|
"""
|
|
|
|
org = kwargs.get("org")
|
|
user = kwargs.get("user")
|
|
group = request.POST.get("group")
|
|
if group not in ["member", "admin"]:
|
|
return JsonResponse({"group": _("Needs to be member or admin")}, status=400)
|
|
|
|
if group == "admin":
|
|
org.usergroup.user_set.remove(user)
|
|
org.admin_usergroup.user_set.add(user)
|
|
elif group == "member":
|
|
org.usergroup.user_set.add(user)
|
|
org.admin_usergroup.user_set.remove(user)
|
|
|
|
return JsonResponse({"status": "ok"})
|
|
|
|
|
|
@login_required
|
|
@org_admin_required
|
|
def user_permissions(request, **kwargs):
|
|
"""
|
|
Returns JsonRespone with list of user's permissions for the targeted
|
|
org an entities under it
|
|
|
|
Permisions are returned as a dict of permissioning ids and permission
|
|
levels.
|
|
|
|
Permissioning ids serve as a wrapper for actual permissioning namespaces
|
|
so we can expose them to the organization admins for changes without allowing
|
|
them to set permissioning namespaces directly.
|
|
"""
|
|
|
|
org = kwargs.get("org")
|
|
perms_rv = {}
|
|
for user in org.usergroup.user_set.all():
|
|
uperms, perms = load_user_permissions(org, user)
|
|
perms_rv[user.id] = perms
|
|
|
|
return JsonResponse({"status": "ok", "user_permissions": perms_rv})
|
|
|
|
|
|
@login_required
|
|
@csrf_protect
|
|
@org_admin_required
|
|
@target_user_validate
|
|
def user_permission_update(request, **kwargs):
|
|
"""
|
|
Update/Add a user's permission
|
|
|
|
perms = permission level
|
|
entity = permission id
|
|
"""
|
|
|
|
org = kwargs.get("org")
|
|
user = kwargs.get("user")
|
|
|
|
uperms, perms = load_user_permissions(org, user)
|
|
form = OrgAdminUserPermissionForm(request.POST)
|
|
if not form.is_valid():
|
|
return JsonResponse(form.errors, status=400)
|
|
|
|
level = form.cleaned_data.get("perms")
|
|
entity = form.cleaned_data.get("entity")
|
|
perms[entity] = level
|
|
save_user_permissions(org, user, perms)
|
|
|
|
return JsonResponse({"status": "ok"})
|
|
|
|
|
|
@login_required
|
|
@csrf_protect
|
|
@org_admin_required
|
|
@target_user_validate
|
|
def user_permission_remove(request, **kwargs):
|
|
"""
|
|
Remove a user's permission
|
|
|
|
entity = permission id
|
|
"""
|
|
|
|
org = kwargs.get("org")
|
|
user = kwargs.get("user")
|
|
entity = request.POST.get("entity")
|
|
uperms, perms = load_user_permissions(org, user)
|
|
if entity in perms:
|
|
del perms[entity]
|
|
save_user_permissions(org, user, perms)
|
|
|
|
return JsonResponse({"status": "ok"})
|
|
|
|
|
|
@login_required
|
|
@org_admin_required
|
|
def permissions(request, **kwargs):
|
|
"""
|
|
Returns list of permissioning ids with labels that
|
|
are valid to be permissioned out to regular org users
|
|
|
|
Permissioning ids serve as a wrapper for actual permissioning namespaces
|
|
so we can expose them to the organization admins for changes without allowing
|
|
them to set permissioning namespaces directly.
|
|
"""
|
|
|
|
org = kwargs.get("org")
|
|
|
|
perms = [{"id": id, "name": name} for id, name in list(permission_ids(org).items())]
|
|
perms = sorted(perms, key=lambda x: x.get("name"))
|
|
return JsonResponse({"status": "ok", "permissions": perms})
|
|
|
|
|
|
@login_required
|
|
@csrf_protect
|
|
@org_admin_required
|
|
def uoar_approve(request, **kwargs):
|
|
"""
|
|
Approve a user request to affiliate with the organization
|
|
"""
|
|
|
|
org = kwargs.get("org")
|
|
|
|
try:
|
|
uoar = UserOrgAffiliationRequest.objects.get(id=request.POST.get("id"))
|
|
if uoar.org != org:
|
|
return JsonResponse({}, status=403)
|
|
|
|
try:
|
|
user = uoar.user
|
|
except User.DoesNotExist:
|
|
uoar.delete()
|
|
return JsonResponse({"status": "ok"})
|
|
|
|
uoar.approve()
|
|
|
|
# notify rest of org admins that the affiliation request has been
|
|
# approved
|
|
|
|
for admin_user in org.admin_usergroup.user_set.all():
|
|
if admin_user != request.user:
|
|
with override(admin_user.locale):
|
|
admin_user.email_user(
|
|
_("%(user_name)s's afilliation request has been approved")
|
|
% {"user_name": uoar.user.full_name},
|
|
loader.get_template(
|
|
"email/notify-org-admin-user-affil-approved.txt"
|
|
).render(
|
|
{
|
|
"user": request.user,
|
|
"uoar": uoar,
|
|
"org_management_url": "%s/org/%d#users"
|
|
% (settings.BASE_URL, org.id),
|
|
}
|
|
),
|
|
)
|
|
|
|
return JsonResponse(
|
|
{
|
|
"status": "ok",
|
|
"full_name": user.full_name,
|
|
"id": user.id,
|
|
"email": user.email,
|
|
}
|
|
)
|
|
|
|
except UserOrgAffiliationRequest.DoesNotExist:
|
|
return JsonResponse({"status": "ok"})
|
|
|
|
return JsonResponse({"status": "ok"})
|
|
|
|
|
|
@login_required
|
|
@csrf_protect
|
|
@org_admin_required
|
|
def uoar_deny(request, **kwargs):
|
|
"""
|
|
Approve a user request to affiliate with the organization
|
|
"""
|
|
|
|
org = kwargs.get("org")
|
|
|
|
try:
|
|
uoar = UserOrgAffiliationRequest.objects.get(id=request.POST.get("id"))
|
|
if uoar.org != org:
|
|
return JsonResponse({}, status=403)
|
|
|
|
try:
|
|
user = uoar.user
|
|
uoar.deny()
|
|
|
|
except User.DoesNotExist:
|
|
uoar.delete()
|
|
return JsonResponse({"status": "ok"})
|
|
|
|
# notify rest of org admins that the affiliation request has been
|
|
# approved
|
|
|
|
for user in org.admin_usergroup.user_set.all():
|
|
if user != request.user:
|
|
with override(user.locale):
|
|
user.email_user(
|
|
_("%(user_name)s's afilliation request has been denied")
|
|
% {"user_name": uoar.user.full_name},
|
|
loader.get_template(
|
|
"email/notify-org-admin-user-affil-denied.txt"
|
|
).render(
|
|
{
|
|
"user": request.user,
|
|
"uoar": uoar,
|
|
"org_management_url": "%s/org/%d#users"
|
|
% (settings.BASE_URL, org.id),
|
|
}
|
|
),
|
|
)
|
|
|
|
except UserOrgAffiliationRequest.DoesNotExist:
|
|
return JsonResponse({"status": "ok"})
|
|
|
|
return JsonResponse({"status": "ok"})
|