1
0
mirror of https://github.com/peeringdb/peeringdb.git synced 2024-05-11 05:55:09 +00:00

June updates (#751)

* Add pointer from API docs to tutorial #650

* Sorting by clicking table headers should use local-compare #356

* Mark IXP peering LAN as bogon #352

* Add help text to "Add (Facility, Network, Exchange)" tab #669

* Add Looking Glass field to the IX object #672

* Add read-only Superuser #679

* Make spelling of traffic levels consistent #519 (#723)

* Offer 2FA (#290)

* Show "Last Updated" fields on fac, ix, org records (#526)

* Enable sort and reverse sort of IP column in IX display (#72)

* IRR validation not handling unexpected characters gracefully (#712)

* Support alternative direction of writing, e.g. Arabic (#618)

* Undeleting an ixlan with an emtpy IPv4 XOR IPv6 field throws a silly error (#644)

* Changing org while adding net results in 500 #654

* missing delete button for organisations (#121)

* When changing owner of an ix admin GUI borks because of "Ixlan for exchange already exists" #666

* Selection should only present undeleted objects (#664)

* change default encoding of API calls to 'utf-8' #663

* Posting https://www.peeringdb.com onto social media doesn't select a good preview image #537

* Revert "Add Looking Glass field to the IX object #672"

This reverts commit 4daf2520043c241fabe9a521757efa86a274e28a.

Conflicts:
	peeringdb_server/migrations/0037_ix_looking_glass.py
	peeringdb_server/views.py

* 500 Internal Error when creating IX where prefix already exists elsewhere #718

* Fix graceful restore of soft-deleted objects with translation active (#580)

* Don't return any POC data with status=deleted #569
Hard delete soft-deleted pocs after grace period #566

* django-peeringdb from github@2.0.0.2-beta

Co-authored-by: Stefan Pratter <stefan@20c.com>
This commit is contained in:
Matt Griswold
2020-06-24 12:55:01 -05:00
committed by GitHub
parent 09b4759b02
commit af6974e3d3
60 changed files with 1797 additions and 336 deletions

View File

@@ -22,6 +22,8 @@ from django.views.decorators.csrf import csrf_protect, ensure_csrf_cookie
from django.views.decorators.http import require_http_methods
from django.urls import resolve, reverse, Resolver404
from django.template import loader
from django.utils import translation
from django.utils.translation import ugettext_lazy as _
from django.utils.crypto import constant_time_compare
from django_namespace_perms.util import (
get_perms,
@@ -39,6 +41,9 @@ import requests
from oauth2_provider.decorators import protected_resource
from oauth2_provider.oauth2_backends import get_oauthlib_core
from django_otp.plugins.otp_email.models import EmailDevice
import two_factor.views
from peeringdb_server import settings
from peeringdb_server.search import search
from peeringdb_server.stats import stats as global_stats
@@ -85,10 +90,6 @@ from ratelimit.decorators import ratelimit, is_ratelimited
RATELIMITS = dj_settings.RATELIMITS
from django.utils.translation import ugettext_lazy as _
# lazy init for translations
# _ = lambda s: s
BASE_ENV = {
"RECAPTCHA_PUBLIC_KEY": dj_settings.RECAPTCHA_PUBLIC_KEY,
@@ -333,6 +334,7 @@ def cancel_affiliation_request(request, uoar_id):
@csrf_protect
@ensure_csrf_cookie
@login_required
@ratelimit(key="ip", method="POST", rate=RATELIMITS["view_affiliate_to_org_POST"])
def view_affiliate_to_org(request):
"""
@@ -340,9 +342,6 @@ def view_affiliate_to_org(request):
an ASN they provide
"""
if not request.user.is_authenticated:
return view_login(request)
if request.method == "POST":
# check if request was blocked by rate limiting
@@ -413,6 +412,7 @@ def view_affiliate_to_org(request):
@csrf_protect
@ensure_csrf_cookie
@login_required
@ratelimit(key="ip", rate=RATELIMITS["resend_confirmation_mail"])
def resend_confirmation_mail(request):
was_limited = getattr(request, "limited", False)
@@ -426,9 +426,6 @@ def resend_confirmation_mail(request):
],
)
if not request.user.is_authenticated:
return view_login(request)
request.user.send_email_confirmation(request=request)
return view_index(request, errors=[_("We have resent your confirmation email")])
@@ -441,11 +438,9 @@ def view_profile(request):
@csrf_protect
@ensure_csrf_cookie
@login_required
def view_set_user_locale(request):
if not request.user.is_authenticated:
return view_login(request)
if request.method in ["GET", "HEAD"]:
return view_verify(request)
elif request.method == "POST":
@@ -457,8 +452,6 @@ def view_set_user_locale(request):
loc = form.cleaned_data.get("locale")
request.user.set_locale(loc)
from django.utils import translation
translation.activate(loc)
request.session[translation.LANGUAGE_SESSION_KEY] = loc
@@ -467,8 +460,6 @@ def view_set_user_locale(request):
@protected_resource(scopes=["profile"])
def view_profile_v1(request):
# if not request.user.is_authenticated:
# return view_login(request)
oauth = get_oauthlib_core()
scope_email, _request = oauth.verify_request(request, scopes=["email"])
scope_networks, _request = oauth.verify_request(request, scopes=["networks"])
@@ -483,7 +474,7 @@ def view_profile_v1(request):
given_name=request.user.first_name,
family_name=request.user.last_name,
name=request.user.full_name,
verified_user=user.is_verified,
verified_user=user.is_verified_user,
)
# only add email fields if email scope is present
@@ -507,12 +498,10 @@ def view_profile_v1(request):
@csrf_protect
@ensure_csrf_cookie
@login_required
@ratelimit(key="ip", rate=RATELIMITS["view_verify_POST"], method="POST")
def view_verify(request):
if not request.user.is_authenticated:
return view_login(request)
if request.method in ["GET", "HEAD"]:
template = loader.get_template("site/verify.html")
env = BASE_ENV.copy()
@@ -568,11 +557,9 @@ def view_verify(request):
@csrf_protect
@ensure_csrf_cookie
@login_required
def view_password_change(request):
if not request.user.is_authenticated:
return view_login(request)
if request.method in ["GET", "HEAD"]:
return view_verify(request)
elif request.method == "POST":
@@ -826,27 +813,6 @@ def view_registration(request):
return JsonResponse({"status": "ok"})
@ensure_csrf_cookie
def view_login(request, errors=None):
"""
login page view
"""
if not errors:
errors = []
if request.user.is_authenticated:
return view_index(request, errors=[_("Already logged in")])
template = loader.get_template("site/login.html")
redir = request.GET.get("next", request.POST.get("next"))
env = BASE_ENV.copy()
env.update({"errors": errors, "next": redir})
update_env_beta_sync_dt(env)
return HttpResponse(template.render(env, request))
@ensure_csrf_cookie
def view_index(request, errors=None):
"""
@@ -1006,6 +972,12 @@ def view_organization(request, id):
"notify_incomplete": True,
"value": data.get("country", dismiss),
},
{
"readonly": True,
"name": "updated",
"label": _("Last Updated"),
"value": data.get("updated", dismiss),
},
{
"name": "notes",
"label": _("Notes"),
@@ -1150,6 +1122,12 @@ def view_facility(request, id):
"label": _("NPA-NXX"),
"value": data.get("npanxx", dismiss),
},
{
"readonly": True,
"name": "updated",
"label": _("Last Updated"),
"value": data.get("updated", dismiss),
},
{
"name": "notes",
"label": _("Notes"),
@@ -1181,7 +1159,6 @@ def view_facility(request, id):
"label": _("Sales Phone"),
"value": data.get("sales_phone", dismiss),
},
],
}
@@ -1287,6 +1264,12 @@ def view_exchange(request, id):
},
],
},
{
"readonly": True,
"name": "updated",
"label": _("Last Updated"),
"value": data.get("updated", dismiss),
},
{
"name": "notes",
"label": _("Notes"),
@@ -1669,7 +1652,7 @@ def view_network(request, id):
# Add POC data to dataset
data["poc_set"] = network_d.get("poc_set")
if not request.user.is_authenticated or not request.user.is_verified:
if not request.user.is_authenticated or not request.user.is_verified_user:
cnt = network.poc_set.filter(status="ok", visible="Users").count()
data["poc_hidden"] = cnt > 0
else:
@@ -1856,52 +1839,198 @@ def request_search(request):
def request_logout(request):
logout(request)
return view_index(request)
return redirect("/")
@csrf_protect
@ensure_csrf_cookie
@ratelimit(key="ip", rate=RATELIMITS["request_login_POST"], method="POST")
def request_login(request):
if request.user.is_authenticated:
return view_index(request)
# We are using django-otp's EmailDevice model
# to handle email as a recovery option for one
# time passwords.
#
# Unlike all the other devices supported by
# django-two-factor-auth it's token field is
# not an integer field. So the token to be verified
# needs to be turned into a string
#
# So we monkey patch it's verify_token function
# to do just that
if request.method in ["GET", "HEAD"]:
return view_login(request)
EmailDevice._verify_token = EmailDevice.verify_token
def verify_token(self, token):
return self._verify_token(str(token))
EmailDevice.verify_token = verify_token
was_limited = getattr(request, "limited", False)
if was_limited:
return view_login(
request, errors=[_("Please wait a bit before trying to login again.")]
)
username = request.POST["username"]
password = request.POST["password"]
redir = request.POST.get("next") or "/"
if redir == "/logout":
redir = "/"
class LoginView(two_factor.views.LoginView):
try:
resolve(redir)
except Resolver404:
if not is_oauth_authorize(redir):
"""
We extend the `LoginView` class provided
by `two_factor` because we need to add some
pdb specific functionality and checks
"""
def get(self, *args, **kwargs):
"""
If a user is already authenticated, don't show the
login process, instead redirect to /
"""
if self.request.user.is_authenticated:
return redirect("/")
return super().get(*args, **kwargs)
@ratelimit(key="ip", rate=RATELIMITS["request_login_POST"], method="POST")
def post(self, *args, **kwargs):
"""
Posts to the `auth` step of the authentication
process need to be rate limited
"""
was_limited = getattr(self.request, "limited", False)
if self.get_step_index() == 0 and was_limited:
self.rate_limit_message = _("Please wait a bit before trying to login again.")
return self.render_goto_step("auth")
return super().post(*args, **kwargs)
def get_context_data(self, form, **kwargs):
"""
If post request was rate limited the rate limit message
needs to be communicated via the template context
"""
context = super().get_context_data(form, **kwargs)
context.update(rate_limit_message=getattr(self, "rate_limit_message", None))
if "other_devices" in context:
context["other_devices"] += [
self.get_email_device()
]
return context
def get_email_device(self):
"""
Returns an EmailDevice instance for the requesting user
which can be used for one time passwords.
"""
user = self.get_user()
if user.email_confirmed:
# only users with confirmed emails should have
# the option to request otp to their email address
try:
# check if user already has an EmailDevice instance
device = EmailDevice.objects.get(user=user)
if not device.confirmed:
# sync confirmed status
device.confirmed = True
device.save()
except EmailDevice.DoesNotExist:
# create EmaiLDevice object for user if it does
# not exist
device = EmailDevice.objects.create(user=user, confirmed=True)
# django-two-factor-auth needs this property set to something
device.method = "email"
return device
else:
# if user does NOT have a confirmed email address but
# somehow has an EmailDevice object, delete it.
try:
device = EmailDevice.objects.get(user=user)
device.delete()
except EmailDevice.DoesNotExist:
pass
return None
def get_device(self, step=None):
"""
We override this so we can enable EmailDevice as a
challenge device for one time passwords
"""
if not self.device_cache:
challenge_device_id = self.request.POST.get('challenge_device', None)
if challenge_device_id:
device = self.get_email_device()
if device.persistent_id == challenge_device_id:
self.device_cache = device
return self.device_cache
return super().get_device(step=step)
def get_success_url(self):
return self.get_redirect_url()
def get_redirect_url(self):
"""
Specifies which redirect urls are valid
"""
redir = self.request.POST.get("next") or "/"
# if the redirect url is to logout that makes little
# sense as the user would get logged out immediately
# after logging in, substitute with a redirect to `/` instead
if redir == "/logout":
redir = "/"
user = authenticate(username=username, password=password)
if user is not None:
if user.is_active:
login(request, user)
# check if the redirect url can be resolved to a view
# if yes, it's a valid redirect
from django.utils import translation
try:
resolve(redir)
except Resolver404:
user_language = user.get_locale()
translation.activate(user_language)
request.session[translation.LANGUAGE_SESSION_KEY] = user_language
# url could not be resolved to a view, so it's likely
# invalid or pointing somewhere externally, the only
# external urls we want to allow are the redirect urls
# of oauth applications set up in peeringdb
if not is_oauth_authorize(redir):
redir = "/"
return redir
def done(self, form_list, **kwargs):
"""
User authenticated succesfully, set language options
"""
response = super().done(form_list, **kwargs)
# TODO: do this via signal instead?
user_language = self.get_user().get_locale()
translation.activate(user_language)
self.request.session[translation.LANGUAGE_SESSION_KEY] = user_language
return redirect(self.get_success_url())
return HttpResponseRedirect(redir)
return view_login(request, errors=[_("Account disabled.")])
return view_login(request, errors=[_("Invalid username/password.")])
@require_http_methods(["POST"])