1
0
mirror of https://github.com/peeringdb/peeringdb.git synced 2024-05-11 05:55:09 +00:00
Files
peeringdb-peeringdb/peeringdb_server/api_key_views.py
Matt Griswold 8cc0f13ec1 Support 202102 (#950)
* install django-grainy

* nsp to grainy first iteration

* nsp to grainy second iteration

* grainy and django-grainy pinned to latest releases

* Fix typo

* Update djangorestframework, peeringdb, django-ratelimit

* Rewrite login view ratelimit decorator

* Relock pipfile

* add list() to make copy of dictionaries before iterating

* relock pipfile with python3.9
change docker to use python3.9

* add ordering to admin search queryset for deskproticket and email

* add org api key and begin to write tests

* additional key tests

* add drf-api-keys to pipfile

* Wire orgapikey to modelviewsetpermissions

* Update api key helper functions

* add put test

* Add Org API key tab to frontend

* Add user api key model

* Update user key handling and tests

* Update APIPermissionsApplicator to make it work w requests

* Add org api key perm panel

* add org key permissions

* Add user api key views

* Add templates for handling user api key (adding, not revoking)

* relock pipfile

* assorted fixes and tweaks

* Add general user group permissions and org user group perms

* refactor org api key perms

* Add tests for api keys

* Add docstrings to permissions helpers

* Add api key examples

* squash migrations

* remove custom api key header config

* Change api key test setup

* Update permissions for grainy change

* Bump up pipfile and pipfile.lock

* Add API Key to Verification Queue Item

* Delete travis

* Add workaround to Dockerfile

* update pipfile and sort out migrations

* Add comment to Dockerfile

* Re-add API Key migrations

* Add locale to .gitignore

* remove suggest functionality from ix

* Update test to recognize that IX api no longer has suggest function

* Add test to outlaw POSTing an IX w its org equal to the suggest entity org

* Add meta information geowarning

* Add alert to demonstrate UI

* Add error to fac update

* Add template warning for geovalidation

* Add geowarning meta js

* cover absent meta_response test case

* Update styles for geowarning

* refactor geotag warning implementation

* null lat and long on unsuccessful geo locate

* modify geovalidation frontend update

* Add deskproticket model email field

* Add missing span

* add email to org keys

* Add email to org key tests

* update serializer with rdap validation wrapper

* update admin for api keys

* Enable writing an email as part of org key creation

* Add email validation to org api key form

* fix css style on perm row

* Add suggested info to api response

* display suggested address on frontend

* add needs geocode to serializer

* save lat long on forward geonormalization

* add address suggestion submit button

* Add suggested address popin to ADD facility form

* Fix css

* add lat and long rounding to geocodenabled model clean method

* add migration and regression test for lat long decimal db constraint

* Add another regression test for model decimal places

* Get deskpro functions passing isort and flake

* Update ticket_queue_deletion_prevented

* update ticket_queue_deletion_prevented for use with org api key

* add template for org key dpt from asnauto skipvq

* Update deskproticket for rdap error

* add facility aka

* add aka to serializer and views

* black and isort test api keys

* fix typo in org key deskpro template

* skip or rewrite unapplicable org key tests, and add as_set tests

* adjust api key test comments

* Add vqi_notify to signals

* Add reversion comments for api keys and helper function

* update how org keys are added to verification queue items

* rename verification queue item fk from api_key to org_key

* fix group id error

* update key tests with correct http header info

* check both user and key, not just user

* templates fiex

* adapt deskpro integration to work with email only

* make org api keys editable for desc and email

* pipfile relock

* edit test setupdata settings for groups

* Change comment to signify we don't need to remove code

* address untranslated accept button

* Add docstrings to the serializer functions

* Add loading shim

* Add migration for all longname and aka

* Add aka and long name to views and serializers

* delete migration w decimals

* standardize serializer lat and long fields

* Add clean rounding for lat and long

* fix serializer error

* api key admin improvements

* fix linebreak in user api key form

* remove debug prints

* Add rounding util

* Add rounding to lat and long fields

* remove 'clean' from geocode method (logic now in admin form)

* remove erroneous tests

* revert serializer changes

* Fix migrations

* Add long name and aka to admin models

* Update API key docs

* Add documentation for api keys

* fix typo

* fix org api key revoke broken by editable api keys

* doc tweaks

* doc tweaks

* doc tweaks

* black format

* fix migration hierarchy

* docs

* docs

* api key permissions screenshot

* formatting

* formatting

* padding fixed

* remove one image

* fix get_user_from_request type checking
take out POST only valdiator for entity suggest

* didnt mean to commit the django-peeringdb mount

* fix suggest on PUT net
fix tests

* black formatting

* update org key permission template

* install rust for cryptography

* pipfile relock (django-peeringdb to 2.6)

Co-authored-by: Stefan Pratter <stefan@20c.com>
Co-authored-by: Elliot Frank <elliot@20c.com>
2021-03-09 13:30:30 -06:00

345 lines
8.8 KiB
Python

"""
Views for organization api key management
"""
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 peeringdb_server.forms import OrgAdminUserPermissionForm, OrganizationAPIKeyForm
from grainy.const import PERM_READ
from django_grainy.models import UserPermission
from django_handleref.models import HandleRefModel
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import override
from peeringdb_server.org_admin_views import (
load_entity_permissions,
org_admin_required,
)
from peeringdb_server.models import (
OrganizationAPIKey,
OrganizationAPIPermission,
UserAPIKey,
User,
)
def save_key_permissions(org, key, perms):
"""
Save key permissions for the specified org and key
perms should be a dict of permissioning ids and permission levels
"""
# wipe all the key's perms for the targeted org
key.grainy_permissions.filter(namespace__startswith=org.grainy_namespace).delete()
# collect permissioning namespaces from the provided permissioning ids
grainy_perms = {}
for id, permissions in list(perms.items()):
if not permissions & PERM_READ:
permissions = permissions | PERM_READ
if id == "org.%d" % org.id:
grainy_perms[org.grainy_namespace] = permissions
grainy_perms[
f"{org.grainy_namespace}.network.*.poc_set.private"
] = permissions
elif id == "net":
grainy_perms[f"{org.grainy_namespace}.network"] = permissions
grainy_perms[
f"{org.grainy_namespace}.network.*.poc_set.private"
] = permissions
elif id == "ix":
grainy_perms[f"{org.grainy_namespace}.internetexchange"] = permissions
elif id == "fac":
grainy_perms[f"{org.grainy_namespace}.facility"] = permissions
elif id.find(".") > -1:
id = id.split(".")
if id[0] == "net":
grainy_perms[f"{org.grainy_namespace}.network.{id[1]}"] = permissions
grainy_perms[
f"{org.grainy_namespace}.network.{id[1]}.poc_set.private"
] = permissions
elif id[0] == "ix":
grainy_perms[
f"{org.grainy_namespace}.internetexchange.{id[1]}"
] = permissions
elif id[0] == "fac":
grainy_perms[f"{org.grainy_namespace}.facility.{id[1]}"] = permissions
# save
for ns, p in list(grainy_perms.items()):
OrganizationAPIPermission.objects.create(
namespace=ns, permission=p, org_api_key=key
)
return grainy_perms
def load_all_key_permissions(org):
"""
Returns dict of all users with all their permissions for
the given org
"""
rv = {}
for key in org.api_keys.filter(revoked=False):
kperms, perms = load_entity_permissions(org, key)
rv[key.prefix] = {
"prefix": key.prefix,
"perms": perms,
"name": key.name,
}
return rv
@login_required
@org_admin_required
def manage_key_add(request, **kwargs):
"""
Create a new Organization API key
Requires a name for the key.
"""
api_key_form = OrganizationAPIKeyForm(request.POST)
if api_key_form.is_valid():
name = api_key_form.cleaned_data.get("name")
org_id = api_key_form.cleaned_data.get("org_id")
email = api_key_form.cleaned_data.get("email")
api_key, key = OrganizationAPIKey.objects.create_key(
org_id=org_id, name=name, email=email
)
return JsonResponse(
{
"status": "ok",
"name": api_key.name,
"email": api_key.email,
"prefix": api_key.prefix,
"org_id": api_key.org_id,
"key": key,
}
)
else:
return JsonResponse(api_key_form.errors, status=400)
@login_required
@org_admin_required
def manage_key_update(request, **kwargs):
"""
Updated existing Organization API key
"""
prefix = request.POST.get("prefix")
org = kwargs.get("org")
api_key_form = OrganizationAPIKeyForm(request.POST)
if api_key_form.is_valid():
name = api_key_form.cleaned_data.get("name")
email = api_key_form.cleaned_data.get("email")
# attempt to retrieve api for key prefix + org combination
try:
api_key = OrganizationAPIKey.objects.get(prefix=prefix, org=org)
except OrganizationAPIKey.DoesNotExist:
return JsonResponse({"non_field_errors": [_("Key not found")]}, status=404)
# update name and email fields of key
api_key.name = name
api_key.email = email
api_key.save()
return JsonResponse(
{
"status": "ok",
"name": api_key.name,
"email": api_key.email,
"prefix": api_key.prefix,
}
)
else:
return JsonResponse(api_key_form.errors, status=400)
@login_required
@org_admin_required
def manage_key_revoke(request, **kwargs):
"""
Revoke an existing API key.
"""
org = kwargs.get("org")
prefix = request.POST.get("prefix")
try:
api_key = OrganizationAPIKey.objects.get(org=org, prefix=prefix)
except OrganizationAPIKey.DoesNotExist:
return JsonResponse({"non_field_errors": [_("Key not found")]}, status=404)
api_key.revoked = True
api_key.save()
return JsonResponse(
{
"status": "ok",
}
)
@login_required
@org_admin_required
def key_permissions(request, **kwargs):
"""
Returns JsonResponse with list of key 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 key in org.api_keys.filter(revoked=False).all():
kperms, perms = load_entity_permissions(org, key)
perms_rv[key.prefix] = perms
return JsonResponse({"status": "ok", "key_permissions": perms_rv})
@login_required
@csrf_protect
@org_admin_required
def key_permission_update(request, **kwargs):
"""
Update/Add a user's permission
perms = permission level
entity = permission id
"""
org = kwargs.get("org")
prefix = request.POST.get("key_prefix")
key = OrganizationAPIKey.objects.get(prefix=prefix)
kperms, perms = load_entity_permissions(org, key)
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_key_permissions(org, key, perms)
return JsonResponse({"status": "ok"})
@login_required
@csrf_protect
@org_admin_required
def key_permission_remove(request, **kwargs):
"""
Remove a keys permission
entity = permission id
"""
org = kwargs.get("org")
prefix = request.POST.get("key_prefix")
key = OrganizationAPIKey.objects.get(prefix=prefix)
entity = request.POST.get("entity")
kperms, perms = load_entity_permissions(org, key)
if entity in perms:
del perms[entity]
save_key_permissions(org, key, perms)
return JsonResponse({"status": "ok"})
"""
USER API KEY MANAGEMENT
"""
def convert_to_bool(data):
if data is None:
return False
return data.lower() == "true"
@login_required
def add_user_key(request, **kwargs):
"""
Create a new User API key
Requires a name and a readonly boolean.
"""
user = request.user
name = request.POST.get("name")
readonly = convert_to_bool(request.POST.get("readonly"))
api_key, key = UserAPIKey.objects.create_key(
name=name,
user=user,
readonly=readonly,
)
return JsonResponse(
{
"status": "ok",
"name": api_key.name,
"prefix": api_key.prefix,
"readonly": api_key.readonly,
"key": key,
}
)
@login_required
def remove_user_key(request, **kwargs):
"""
Revoke user api key
"""
user = request.user
prefix = request.POST.get("prefix")
try:
api_key = UserAPIKey.objects.get(user=user, prefix=prefix)
except UserAPIKey.DoesNotExist:
return JsonResponse({"non_field_errors": [_("Key not found")]}, status=404)
api_key.revoked = True
api_key.save()
return JsonResponse(
{
"status": "ok",
}
)