Skip to content

Commit 1beba00

Browse files
committed
DBC22-2535 IDIR login for admin/cms
1 parent fadcda4 commit 1beba00

24 files changed

+412
-30
lines changed
Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
11
from django.conf import settings
22
from allauth.account.adapter import DefaultAccountAdapter
3+
from allauth.socialaccount.adapter import DefaultSocialAccountAdapter
34

45

56
class AccountAdapter(DefaultAccountAdapter):
7+
''' Governs accounts internally, for things like redirect urls. '''
68

79
def get_login_redirect_url(self, request):
810
return settings.FRONTEND_BASE_URL
911

1012
def get_logout_redirect_url(self, request):
11-
return settings.FRONTEND_BASE_URL
13+
if request.path.startswith('/drivebc-'):
14+
return '/' + request.path.split('/')[1] + '/'
15+
return settings.FRONTEND_BASE_URL
16+
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{% extends "admin/login.html" %}
2+
3+
{% block content %}
4+
<p>You do not have permission to view this page.</p>
5+
{% endblock %}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{% extends "admin/login.html" %}
2+
3+
{% block content %}
4+
<p>An email has been sent to the admins. Please wait for them to contact you.</p>
5+
{% endblock %}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{% extends "admin/login.html" %}
2+
3+
{% block branding %}
4+
<h1 id="site-name"><a href="{% url 'admin:index' %}">DriveBC Administration</a></h1>
5+
{% endblock %}
6+
7+
{% block content %}
8+
9+
<p>This part of the DriveBC website requires you to be authenticated with your
10+
IDIR, using MFA</p>
11+
12+
<p style="text-align: center"><a class="button" href="/accounts/oidc/idir/login/?process=login&next=%2Fdrivebc-admin%2F&auth_params=kc_idp_hint=azureidir">Log in with IDIR</a></p>
13+
14+
{% endblock %}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{% extends "admin/login.html" %}
2+
3+
{% block branding %}
4+
<h1 id="site-name"><a href="{% url 'admin:index' %}">DriveBC Administration</a></h1>
5+
{% endblock %}
6+
7+
{% block content %}
8+
<p>To access the DriveBC Admin site, you need to be granted access.</p>
9+
10+
<p>Click the button below to trigger an access request to be sent to the admins
11+
for your IDIR.</p>
12+
13+
<div style="text-align: center">
14+
<form method="post" action="{% url 'admin-access-requested' %}">
15+
{% csrf_token %}
16+
<button class="button" type="submit" style="padding: 4px 5px;">Request Access</button></p>
17+
</form>
18+
</div>
19+
{% endblock %}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<p>IDIR user {{ name }} ({{ email }}) is requesting access to the DriveBC admin
2+
interface. You can access their user record at the following link to toggle the
3+
staff flag on their account:</p>
4+
5+
<p><a href="{{ url }}">{{ url }}</a></p>
6+
7+
<p>The system will NOT notify them of the change; you need to tell them yourself.</p>
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
IDIR user {{ name }} ({{ email }}) is requesting access to the DriveBC admin
2+
interface. You can access their user record at the following link to toggle the
3+
staff flag on their account:
4+
5+
{{ url }}
6+
7+
The system will NOT notify them of the change; you need to tell them yourself.

src/backend/apps/authentication/views.py

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,13 @@
44

55
import environ
66
from apps.webcam.models import Webcam
7+
from django.conf import settings
78
from django.contrib.auth.tokens import PasswordResetTokenGenerator
89
from django.core.exceptions import ObjectDoesNotExist
910
from django.core.mail import EmailMultiAlternatives
1011
from django.db.utils import IntegrityError
1112
from django.http import HttpResponseRedirect
12-
from django.shortcuts import reverse
13+
from django.shortcuts import render, reverse
1314
from django.template.loader import render_to_string
1415
from django.utils.encoding import force_bytes, force_str
1516
from django.utils.http import urlsafe_base64_decode, urlsafe_base64_encode
@@ -19,6 +20,8 @@
1920
from rest_framework.response import Response
2021
from rest_framework.views import APIView
2122

23+
from apps.webcam.models import Webcam
24+
2225
from .models import DriveBCUser, FavouritedCameras, SavedRoutes
2326
from .serializers import FavouritedCamerasSerializer, SavedRoutesSerializer
2427

@@ -27,6 +30,38 @@
2730
env = environ.Env()
2831

2932

33+
def request_access(request):
34+
return render(request, "admin/request_access.html")
35+
36+
37+
def access_requested(request):
38+
39+
if request.method == 'POST':
40+
app = request.user._meta.app_label
41+
model = request.user._meta.model_name
42+
path = reverse('admin:%s_%s_change' % (app, model), args=[request.user.id])
43+
url = settings.FRONTEND_BASE_URL + path[1:]
44+
first = request.user.first_name
45+
last = request.user.last_name
46+
name = f'{first} {last}'
47+
context = {'name': name, 'email': request.user.email, 'url': url, }
48+
49+
text = render_to_string('email/request_admin_access.txt', context)
50+
html = render_to_string('email/request_admin_access.html', context)
51+
52+
msg = EmailMultiAlternatives(
53+
f'{name} requests access to DriveBC admin',
54+
text,
55+
'do_not_reply@gov.bc.ca',
56+
settings.ACCESS_REQUEST_RECEIVERS,
57+
)
58+
msg.attach_alternative(html, 'text/html')
59+
msg.send()
60+
return HttpResponseRedirect(request.path)
61+
62+
return render(request, "admin/access_requested.html")
63+
64+
3065
class FavouritedCamerasViewset(viewsets.ModelViewSet):
3166
queryset = FavouritedCameras.objects.all()
3267
serializer_class = FavouritedCamerasSerializer
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<p>IDIR user {{ name }} ({{ email }}) is requesting access to the Wagtail admin
2+
interface. You can access their user record at the following link to add them
3+
to the editors group:</p>
4+
5+
<p><a href="{{ url }}">{{ url }}</a></p>
6+
7+
<p>The system will NOT notify them of the change; you need to tell them yourself.</p>
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
IDIR user {{ name }} ({{ email }}) is requesting access to the Wagtail admin
2+
interface. You can access their user record at the following link to add them
3+
to the editors group:
4+
5+
{{ url }}
6+
7+
The system will NOT notify them of the change; you need to tell them yourself.
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
{% extends "admin/login.html" %}
2+
3+
{% block branding %}<h1 id="site-name">DriveBC CMS</h1>{% endblock %}
4+
5+
{% block content %}
6+
7+
{% if is_non_idir_login %}
8+
<p>
9+
You do not have permission to view this page because you're logged in with
10+
an account that is not an IDIR MFA account.
11+
</p>
12+
<p>
13+
You can sign out of your current account by clicking the button below.
14+
You'll be redirected to the login page for IDIR.
15+
</p>
16+
17+
<form method="POST" action="/drivebc-cms/logout/">
18+
{% csrf_token %}
19+
<button type="submit" class="button" style="padding: 4px 5px;">Log out</button>
20+
</form>
21+
{% else %}
22+
<p>You do not have permission to view this page.</p>
23+
{% endif %}
24+
{% endblock %}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{% extends "wagtailadmin/base.html" %}
2+
3+
{% block titletag %}Access Requested{% endblock %}
4+
5+
{% block content %}
6+
{% include "wagtailadmin/shared/header.html" with title="Access Requested" %}
7+
8+
<div class="nice-padding">
9+
<p>A message has been sent to the CMS admins. Please wait for them to
10+
contact you.</p>
11+
</div>
12+
{% endblock %}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{% load i18n wagtailadmin_tags %}
2+
3+
{% block request_access %}
4+
<h2 class="w-panel_header">Need to be granted access</h2>
5+
6+
<p>You can see this page because you're logged in with an IDIR, but you do
7+
not have access to any functionality yet because you need to be added to the
8+
<em>editors</em> group.</p>
9+
10+
<p>Click the button below to send a message to CMS admins requesting access for your IDIR.</p>
11+
12+
<form method="POST" action="access-requested">
13+
<div><button class="button" type="submit">Request Access</button></div>
14+
</form>
15+
{% endblock %}

src/backend/apps/cms/urls.py

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,23 @@
1+
from functools import wraps
2+
3+
from allauth.account.decorators import secure_admin_login
4+
from allauth.account.views import LogoutView as AllauthLogoutView
5+
from django.conf import settings
6+
from django.shortcuts import redirect
17
from django.urls import include, path
28
from rest_framework import routers
39
from wagtail import urls as wagtail_urls
410
from wagtail.admin import urls as wagtailadmin_urls
11+
from wagtail.admin.auth import reject_request, permission_denied
12+
from wagtail.admin.views.account import LoginView, LogoutView as WagtailLogoutView
513
from wagtail.api.v2.router import WagtailAPIRouter
614
from wagtail.api.v2.views import PagesAPIViewSet
715
from wagtail.documents import urls as wagtaildocs_urls
816
from wagtail.documents.api.v2.views import DocumentsAPIViewSet
917
from wagtail.images.api.v2.views import ImagesAPIViewSet
18+
from wagtail.utils.urlpatterns import decorate_urlpatterns
1019

11-
from .views import AdvisoryAPI, BulletinAPI
20+
from .views import AdvisoryAPI, BulletinAPI, access_denied_idir
1221

1322
wagtail_api_router = WagtailAPIRouter('wagtailapi')
1423
wagtail_api_router.register_endpoint('pages', PagesAPIViewSet)
@@ -19,7 +28,36 @@
1928
cms_api_router.register('advisories', AdvisoryAPI)
2029
cms_api_router.register('bulletins', BulletinAPI)
2130

31+
32+
def require_idir_auth(view_func):
33+
def decorated_view(request, *args, **kwargs):
34+
user = request.user
35+
36+
if user.is_anonymous:
37+
return reject_request(request)
38+
39+
if user.username.endswith('azureidir'):
40+
return view_func(request, *args, **kwargs)
41+
42+
return redirect("cms_denied_idir")
43+
44+
return decorated_view
45+
46+
if settings.FORCE_IDIR_AUTHENTICATION:
47+
# login_view = secure_admin_login(LoginView.as_view())
48+
# logout_view = AllauthLogoutView.as_view()
49+
login_view = LoginView.as_view()
50+
logout_view = WagtailLogoutView.as_view()
51+
decorate_urlpatterns(wagtailadmin_urls.urlpatterns, require_idir_auth)
52+
else:
53+
login_view = LoginView.as_view()
54+
logout_view = WagtailLogoutView.as_view()
55+
56+
2257
urlpatterns = [
58+
path("login/", login_view, name="cms_login"),
59+
path("logout/", logout_view, name="cms_logout"),
60+
path("denied", access_denied_idir, name="cms_denied_idir"),
2361
path('', include(wagtailadmin_urls)),
2462
path('documents', include(wagtaildocs_urls)),
2563
path('pages', include(wagtail_urls)),

src/backend/apps/cms/views.py

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
from django.conf import settings
2+
from django.core.mail import EmailMultiAlternatives
3+
from django.http import HttpResponseRedirect
4+
from django.shortcuts import render, reverse
5+
from django.template.loader import render_to_string
6+
from django.views.decorators.csrf import csrf_exempt
7+
from rest_framework import viewsets
8+
19
from apps.cms.models import Advisory, Bulletin
210
from apps.cms.serializers import (
311
AdvisorySerializer,
@@ -6,7 +14,6 @@
614
)
715
from apps.shared.enums import CacheKey, CacheTimeout
816
from apps.shared.views import CachedListModelMixin
9-
from rest_framework import viewsets
1017

1118

1219
class CMSViewSet(viewsets.ReadOnlyModelViewSet):
@@ -36,3 +43,40 @@ class BulletinTestAPI(CachedListModelMixin, CMSViewSet):
3643

3744
class BulletinAPI(BulletinTestAPI):
3845
serializer_class = BulletinSerializer
46+
47+
48+
@csrf_exempt
49+
def access_requested(request):
50+
51+
if request.method == 'POST':
52+
app = request.user._meta.app_label
53+
model = request.user._meta.model_name
54+
path = reverse('admin:%s_%s_change' % (app, model), args=[request.user.id])
55+
url = settings.FRONTEND_BASE_URL + path[1:]
56+
first = request.user.first_name
57+
last = request.user.last_name
58+
name = f'{first} {last}'
59+
context = {'name': name,
60+
'email': request.user.email,
61+
'url': url, }
62+
63+
text = render_to_string('email/request_wagtail_access.txt', context)
64+
html = render_to_string('email/request_wagtail_access.html', context)
65+
66+
msg = EmailMultiAlternatives(
67+
f'{name} requests access to Wagtail admin',
68+
text,
69+
'do_not_reply@gov.bc.ca',
70+
settings.ACCESS_REQUEST_RECEIVERS,
71+
)
72+
msg.attach_alternative(html, 'text/html')
73+
msg.send()
74+
return HttpResponseRedirect(request.path)
75+
76+
return render(request, 'wagtailadmin/access_requested.html')
77+
78+
79+
def access_denied_idir(request):
80+
return render(request, 'wagtailadmin/access_denied.html', context={
81+
"is_non_idir_login": True,
82+
})

0 commit comments

Comments
 (0)