| @ -1,22 +0,0 @@ | |||
| from rest_framework import authentication | |||
| from edivorce.apps.core.models import BceidUser | |||
| class BCeIDAuthentication(authentication.BaseAuthentication): | |||
| """ | |||
| Make the DRF user the BCeID user populated in our middleware, to avoid DRF | |||
| overwriting our user for API calls. | |||
| This relies on our middleware entirely for authentication. | |||
| """ | |||
| def authenticate(self, request): | |||
| try: | |||
| request.user = request._user # pylint: disable=protected-access | |||
| except: | |||
| request.user = request._request.user # pylint: disable=protected-access | |||
| return (request.user, None) | |||
| def get_user(self, pk): | |||
| return BceidUser.objects.get(pk=pk) | |||
| @ -1,156 +0,0 @@ | |||
| import datetime | |||
| from ipaddress import ip_address, ip_network | |||
| from django.conf import settings | |||
| from django.shortcuts import redirect | |||
| from django.utils import timezone | |||
| from django.utils.deprecation import MiddlewareMixin | |||
| from ..models import BceidUser | |||
| login_delta = datetime.timedelta(hours=2) | |||
| class AnonymousUser: | |||
| """ | |||
| Anonymous user, present mainly to provide authentication checks in templates | |||
| """ | |||
| guid = None | |||
| display_name = '' | |||
| has_accepted_terms = False | |||
| @property | |||
| def is_authenticated(self): | |||
| return False | |||
| @property | |||
| def is_anonymous(self): | |||
| return True | |||
| anonymous_user = AnonymousUser() | |||
| class BceidMiddleware(MiddlewareMixin): # pylint: disable=too-few-public-methods | |||
| """ | |||
| Simple authentication middleware for operating in the BC Government | |||
| OpenShift environment, with SiteMinder integration. | |||
| For our purposes, SiteMinder is configured to add the following headers: | |||
| BCeID: | |||
| - SMGOV_USERGUID | |||
| - SMGOV_USERDISPLAYNAME | |||
| - SM_USER | |||
| BC Services Card: | |||
| - SMGOV_USERGUID | |||
| - SMGOV_GIVENNAMES | |||
| - SMGOV_SURNAME | |||
| - SM_USER | |||
| The first two are provided on pages configured to be protected by | |||
| SiteMinder, which is currently just /login. When a user goes to the login | |||
| page, if the user is logged in, SiteMinder adds those headers with their | |||
| BCeID values; if they're not logged in, it routes them through its | |||
| login/signup page and then back to the login page, with those headers in | |||
| place. For unprotected pages, those headers are stripped if present, | |||
| preventing spoofing. | |||
| The third header is populated on every request that's proxied through | |||
| SiteMinder. For logged in users, it contains their ???; for anonymous | |||
| users, it's empty. | |||
| When we detect authentication by the presence of the first two headers, we | |||
| store those values in the user's session. On all requests, we use them to | |||
| access a local proxy object for the user (available as request.user). For | |||
| users that are not logged in, an Anonymous User substitute is present. | |||
| In a local development environment, we generate a guid based on the login | |||
| name and treat that guid/login name as guid/display name. | |||
| """ | |||
| def process_request(self, request): # pylint: disable=too-many-branches | |||
| """ | |||
| Return None after populating request.user, or necessary redirects. | |||
| If the request is not coming from inside the BC Government data centre, | |||
| redirect the request through the proxy server. | |||
| If the SiteMinder headers are present, indicating the user has just | |||
| authenticated, save those headers to the session. | |||
| Get the user's GUID and display name. If they're present, and the user | |||
| has authenticated (or we're in a local development environment), add | |||
| the local proxy user to the request; if not, store the anonymous user | |||
| instance. | |||
| """ | |||
| # HTTP_SM_USER is available on both secure and unsecure pages. If it | |||
| # has a value then we know that the user is still logged into BCeID. | |||
| # This is an additional check to make sure we aren't letting users | |||
| # access the site via their session variables after logging out of bceid | |||
| # | |||
| # Note: It's still possible that a user has logged out of one BCeID and | |||
| # logged into another BCeID via www.bceid.ca without clicking the logout | |||
| # link on our app or closing the browser. This is an extreme edge case, | |||
| # and it's not pragmatic to code against it at this time. | |||
| siteminder_user = request.META.get('HTTP_SM_USER', '') | |||
| is_localdev = settings.DEPLOYMENT_TYPE in ['localdev', 'minishift'] | |||
| update_user = False | |||
| using_bc_services_card = False | |||
| guid = request.META.get('HTTP_SMGOV_USERGUID', '') | |||
| given_names = request.META.get('HTTP_SMGOV_GIVENNAMES', '') | |||
| surname = request.META.get('HTTP_SMGOV_SURNAME', '') | |||
| displayname = request.META.get('HTTP_SMGOV_USERDISPLAYNAME', '') | |||
| # HTTP_SMGOV_USERDISPLAYNAME is not included when BC Services Card authentication is used. | |||
| if not displayname and (surname or given_names): | |||
| displayname = "{0} {1}".format(given_names, surname) | |||
| using_bc_services_card = True | |||
| # HTTP_SM_USER is typically '.' when BC Services Card authentication is used. | |||
| if (not siteminder_user or siteminder_user == '.') and given_names and surname: | |||
| siteminder_user = "{0}{1}".format(given_names.split(None, 1)[0], surname) | |||
| if guid: | |||
| request.session['smgov_userguid'] = guid | |||
| else: | |||
| guid = request.session.get('smgov_userguid') | |||
| if displayname: | |||
| request.session['smgov_userdisplayname'] = displayname | |||
| else: | |||
| displayname = request.session.get('smgov_userdisplayname') | |||
| if is_localdev: | |||
| guid = request.session.get('fake_bceid_guid') | |||
| displayname = request.session.get('login_name') | |||
| if guid and (siteminder_user or is_localdev): | |||
| request.user, created = BceidUser.objects.get_or_create(user_guid=guid) | |||
| if created: | |||
| request.session['first_login'] = True | |||
| if siteminder_user: | |||
| if created or not request.user.sm_user: | |||
| request.user.sm_user = siteminder_user | |||
| update_user = True | |||
| if request.user.is_bcsc != using_bc_services_card: | |||
| request.user.is_bcsc = using_bc_services_card | |||
| update_user = True | |||
| if request.user.display_name != displayname: | |||
| request.user.display_name = displayname | |||
| update_user = True | |||
| if (request.user.last_login is None or | |||
| timezone.now() - request.user.last_login > login_delta): | |||
| request.user.last_login = timezone.now() | |||
| update_user = True | |||
| if update_user: | |||
| request.user.save() | |||
| else: | |||
| request.user = anonymous_user | |||
| return None | |||
| @ -0,0 +1,39 @@ | |||
| from mozilla_django_oidc.auth import OIDCAuthenticationBackend | |||
| from ..models import BceidUser | |||
| class EDivorceKeycloakBackend(OIDCAuthenticationBackend): | |||
| def verify_claims(self, claims): | |||
| verified = super(EDivorceKeycloakBackend, self).verify_claims(claims) | |||
| print(claims) | |||
| return verified | |||
| def create_user(self, claims): | |||
| user = super(EDivorceKeycloakBackend, self).create_user(claims) | |||
| user.first_name = claims.get('given_name', '') | |||
| user.last_name = claims.get('family_name', '') | |||
| user.display_name = "{} {}".format(user.first_name, user.last_name).strip() | |||
| user.sm_user = claims.get('preferred_username', '') | |||
| user.user_guid = claims.get('universal-id', '') | |||
| user.save() | |||
| return user | |||
| def update_user(self, user, claims): | |||
| user.first_name = claims.get('given_name', '') | |||
| user.last_name = claims.get('family_name', '') | |||
| user.display_name = "{} {}".format(user.first_name, user.last_name).strip() | |||
| user.sm_user = claims.get('preferred_username', '') | |||
| user.user_guid = claims.get('universal-id', '') | |||
| user.save() | |||
| return user | |||
| def filter_users_by_claims(self, claims): | |||
| user_guid = claims.get('universal-id') | |||
| if not user_guid: | |||
| return self.UserModel.objects.none() | |||
| return self.UserModel.objects.filter(user_guid=user_guid) | |||
| @ -0,0 +1,20 @@ | |||
| # -*- coding: utf-8 -*- | |||
| # Generated by Django 1.11.29 on 2020-10-15 17:38 | |||
| from __future__ import unicode_literals | |||
| from django.db import migrations, models | |||
| class Migration(migrations.Migration): | |||
| dependencies = [ | |||
| ('core', '0019_auto_20191008_2141'), | |||
| ] | |||
| operations = [ | |||
| migrations.AddField( | |||
| model_name='bceiduser', | |||
| name='is_bcsc', | |||
| field=models.BooleanField(default=False), | |||
| ), | |||
| ] | |||
| @ -0,0 +1,102 @@ | |||
| # Generated by Django 2.2.15 on 2020-10-09 21:12 | |||
| import django.contrib.auth.models | |||
| import django.contrib.auth.validators | |||
| from django.db import migrations, models | |||
| import django.utils.timezone | |||
| def set_username(apps, schema_editor): | |||
| Series = apps.get_model('core', 'bceiduser') | |||
| for series in Series.objects.all().iterator(): | |||
| series.username = 'user' + str(series.id) | |||
| series.save() | |||
| def reverse_func(apps, schema_editor): | |||
| pass # code for reverting migration, if any | |||
| class Migration(migrations.Migration): | |||
| dependencies = [ | |||
| ('core', '0023_auto_20201006_1314'), | |||
| ('auth', '0011_update_proxy_permissions'), | |||
| ] | |||
| operations = [ | |||
| migrations.AlterModelOptions( | |||
| name='bceiduser', | |||
| options={'verbose_name': 'user', 'verbose_name_plural': 'users'}, | |||
| ), | |||
| migrations.AlterModelManagers( | |||
| name='bceiduser', | |||
| managers=[ | |||
| ('objects', django.contrib.auth.models.UserManager()), | |||
| ], | |||
| ), | |||
| migrations.AddField( | |||
| model_name='bceiduser', | |||
| name='email', | |||
| field=models.EmailField(blank=True, max_length=254, verbose_name='email address'), | |||
| ), | |||
| migrations.AddField( | |||
| model_name='bceiduser', | |||
| name='first_name', | |||
| field=models.CharField(blank=True, max_length=30, verbose_name='first name'), | |||
| ), | |||
| migrations.AddField( | |||
| model_name='bceiduser', | |||
| name='groups', | |||
| field=models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.Group', verbose_name='groups'), | |||
| ), | |||
| migrations.AddField( | |||
| model_name='bceiduser', | |||
| name='is_superuser', | |||
| field=models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status'), | |||
| ), | |||
| migrations.AddField( | |||
| model_name='bceiduser', | |||
| name='last_name', | |||
| field=models.CharField(blank=True, max_length=150, verbose_name='last name'), | |||
| ), | |||
| migrations.AddField( | |||
| model_name='bceiduser', | |||
| name='password', | |||
| field=models.CharField(default='', max_length=128, verbose_name='password'), | |||
| preserve_default=False, | |||
| ), | |||
| migrations.AddField( | |||
| model_name='bceiduser', | |||
| name='user_permissions', | |||
| field=models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.Permission', verbose_name='user permissions'), | |||
| ), | |||
| migrations.AddField( | |||
| model_name='bceiduser', | |||
| name='username', | |||
| field=models.CharField(blank=True, default='', max_length=150), | |||
| preserve_default=False, | |||
| ), | |||
| migrations.RunPython(set_username, reverse_func), | |||
| migrations.AlterField( | |||
| model_name='bceiduser', | |||
| name='username', | |||
| field=models.CharField(default='', error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username'), | |||
| preserve_default=False, | |||
| ), | |||
| migrations.AlterField( | |||
| model_name='bceiduser', | |||
| name='date_joined', | |||
| field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined'), | |||
| ), | |||
| migrations.AlterField( | |||
| model_name='bceiduser', | |||
| name='last_login', | |||
| field=models.DateTimeField(blank=True, null=True, verbose_name='last login'), | |||
| ), | |||
| migrations.AddField( | |||
| model_name='bceiduser', | |||
| name='is_staff', | |||
| field=models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status'), | |||
| ), | |||
| ] | |||
| @ -0,0 +1,19 @@ | |||
| # Generated by Django 2.2.15 on 2020-10-19 16:42 | |||
| from django.db import migrations | |||
| class Migration(migrations.Migration): | |||
| dependencies = [ | |||
| ('core', '0024_auto_20201009_1235'), | |||
| ('core', '0024_bceiduser_is_bcsc'), | |||
| ('core', '0020_bceiduser_is_bcsc'), | |||
| ] | |||
| operations = [ | |||
| migrations.RemoveField( | |||
| model_name='bceiduser', | |||
| name='is_bcsc', | |||
| ), | |||
| ] | |||
| @ -1,155 +0,0 @@ | |||
| <!DOCTYPE html> | |||
| <html lang="en"> | |||
| <head> | |||
| <title>Government of British Columbia</title> | |||
| <meta charset="utf-8"/> | |||
| <meta http-equiv="X-UA-Compatible" content="IE=Edge"/> | |||
| <meta name="keywords" content=""/> | |||
| <meta name="description" content=""/> | |||
| <meta name="Author" content=""/> | |||
| <link rel="icon" href="https://logon7.gov.bc.ca/clp/images/favicon.ico"> | |||
| <title>CLP</title> | |||
| <!-- mobile settings --> | |||
| <meta name="viewport" | |||
| content="width=device-width, maximum-scale=1, initial-scale=1, user-scalable=0"/> | |||
| <!-- JAVASCRIPT FILES --> | |||
| <script type="text/javascript" src="https://logon7.gov.bc.ca/clp/plugins/jquery-2.1.4.min.js"></script> | |||
| <script type="text/javascript" src="https://logon7.gov.bc.ca/clp/plugins/bootstrap/js/bootstrap.min.js"></script> | |||
| <script type="text/javascript" src="https://logon7.gov.bc.ca/clp/plugins/datepicker/js/bootstrap-datepicker.min.js"></script> | |||
| <script type="text/javascript" src="https://logon7.gov.bc.ca/clp/js/common.js"></script> | |||
| <script type="text/javascript" src="https://logon7.gov.bc.ca/clp/js/brandexample.js"></script> | |||
| <script type="text/javascript" src="https://logon7.gov.bc.ca/clp/js/form-validation.js"></script> | |||
| <script type="text/javascript" src="https://logon7.gov.bc.ca/clp/js/footer.js"></script> | |||
| <!-- CORE CSS --> | |||
| <link href="https://logon7.gov.bc.ca/clp/plugins/bootstrap/css/bootstrap.min.css" rel="stylesheet" type="text/css"/> | |||
| <link href="https://logon7.gov.bc.ca/clp/css/font-awesome.css" rel="stylesheet" type="text/css"/> | |||
| <link href="https://logon7.gov.bc.ca/clp/css/main.css" rel="stylesheet" type="text/css"/> | |||
| <link href="https://logon7.gov.bc.ca/clp/plugins/datepicker/css/bootstrap-datepicker3.min.css" rel="stylesheet" type="text/css"/> | |||
| </head> | |||
| <body> | |||
| <div id="wrapper" class="main"> | |||
| <header id="bcGov" class="no-gov-brand"> | |||
| <div class="container"> | |||
| <div class="seperator"></div> | |||
| <div id="login-to" class="site-title">FAKE BCeID LOGIN – FOR SOFTWARE DEVELOPMENT PURPOSES ONLY</div> | |||
| </div> | |||
| </header> | |||
| <header id="client"> | |||
| <div class="container"> | |||
| <div> | |||
| <img src="https://logon7.gov.bc.ca/clp/branding/bceid_default_logo.jpg" class="logo" alt=""/> | |||
| </div> | |||
| </div> | |||
| </header> | |||
| <form method="POST" name="login" id="login-form" class="login-bceid-form" novalidate> | |||
| <section class="container"> | |||
| <div class="row"> | |||
| <div class="col-sm-7 col-md-8"> | |||
| <div class="panel"> | |||
| <div class="panel-heading"> | |||
| Log in with | |||
| <span id="bceidLogo" class="environment-logo bceid-environment-devv2"></span> | |||
| </div> | |||
| <div class="panel-body"> | |||
| <div class="form-element form-group"> | |||
| <label class="control-label label-with-instruction" | |||
| for="username">User ID</label> | |||
| <div class="instruction">Use a Basic BCeID</div> | |||
| <input name="user" id="username" type="text" size="20" autofocus | |||
| class="form-control input-200" autocomplete="off"/> | |||
| </div> | |||
| <div class="form-element form-group"> | |||
| <label class="control-label" for="password">Password</label> | |||
| <input name="password" type="password" class="form-control input-200" | |||
| id="password" size="20" autocomplete="off"/> | |||
| </div> | |||
| <div class="bg-error hidden"> | |||
| <div class="help-block field-help-block"> | |||
| <i class="fa fa-fw fa-exclamation-circle"></i><span | |||
| class="field-help-text"></span> | |||
| </div> | |||
| </div> | |||
| <div class="login-form-action"> | |||
| <input type="submit" name="btnSubmit" class="btn btn-primary" | |||
| value="Continue"/> | |||
| </div> | |||
| <div class="forgot"> | |||
| <a href="{% url 'register' %}" class="link-forgot">Forgot your user ID or password?</a> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| <div class="panel"> | |||
| <div class="panel-body" style="padding-bottom: 15px;"> | |||
| <div> | |||
| <strong>No account?</strong> | |||
| </div> | |||
| <a href="{% url 'register' %}" | |||
| style="text-decoration: underline; font-size: 16px; font-weight: 600;">Register for a | |||
| BCeID</a> | |||
| </div> | |||
| </div> | |||
| <div> | |||
| ◀ <a href="./" class="link-cancel">Cancel | |||
| and return to localdev</a> | |||
| </div> | |||
| <hr class="visible-xs"> | |||
| </div> | |||
| <div class="col-sm-5 col-md-4"> | |||
| </div> | |||
| </div> | |||
| </section> | |||
| </form> | |||
| </div> | |||
| <footer class="footer-stick-to-bottom"> | |||
| <div class="container" id="AccessMessage"> | |||
| <div class="row"> | |||
| <div class="col-sm-12"> | |||
| <p>Access to or unauthorized use of data on this computer system by any person other than the authorized | |||
| employee(s) or owner(s) of an account is strictly prohibited and may result in legal action against | |||
| such person.</p> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| <div id="footerWrapper"> | |||
| <div class="container"> | |||
| <div class="row"> | |||
| <div class="col-sm-12"> | |||
| <ul class="inline"> | |||
| <li data-order="1"><a href="http://www.gov.bc.ca/com/disclaimer.html">Disclaimer</a></li> | |||
| <li data-order="2"><a href="http://www.gov.bc.ca/com/privacy.html">Privacy</a></li> | |||
| <li data-order="3"><a href="http://www.gov.bc.ca/com/accessibility.html">Accessibility</a></li> | |||
| <li data-order="4"><a href="http://www.gov.bc.ca/com/copyright.html">Copyright</a></li> | |||
| </ul> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| </footer> | |||
| </body> | |||
| </html> | |||
| @ -1,16 +0,0 @@ | |||
| <html> | |||
| <head> | |||
| <title>Debug</title> | |||
| </head> | |||
| <body> | |||
| {% for k, v in request.META.items %} | |||
| {% if k.isupper %} | |||
| {{ k }} = {{ v }}<br/> | |||
| {% endif %} | |||
| {% endfor %} | |||
| </body> | |||
| </html> | |||
| @ -1,17 +0,0 @@ | |||
| <html> | |||
| <head> | |||
| <title>Register</title> | |||
| </head> | |||
| <body> | |||
| <h1>localdev registration is not implemented</h1> | |||
| <p>Enter <strong>any user id</strong> with the password <em><strong>divorce</strong></em> to | |||
| simulate a login in your localdev envirommnent.</p> | |||
| <a href="{% url 'login' %}">Goto Login</a> | |||
| </body> | |||
| </html> | |||
| @ -1,32 +0,0 @@ | |||
| import uuid | |||
| import binascii | |||
| from encodings.utf_8 import decode | |||
| from django.conf import settings | |||
| from django.shortcuts import render, redirect | |||
| from django.views.decorators.csrf import csrf_exempt | |||
| @csrf_exempt | |||
| def bceid(request): | |||
| """ fake bceid login for developer workstation environment """ | |||
| if request.method == "POST": | |||
| login_name = request.POST.get('user', '') | |||
| password = request.POST.get('password', '') | |||
| # just in case anyone from the general public discovers the dev server | |||
| # make sure they don't accidentally login and think this is production | |||
| if password.lower() != 'divorce': | |||
| return redirect(settings.PROXY_BASE_URL + settings.FORCE_SCRIPT_NAME[:-1] + '/bceid') | |||
| # convert the login name to a guid | |||
| hex_name = decode(binascii.hexlify(str.encode(login_name)))[0] | |||
| fake_guid = hex_name.rjust(32, '0') | |||
| # save the guid in a session variable | |||
| request.session['login_name'] = login_name | |||
| request.session['fake_bceid_guid'] = fake_guid | |||
| return redirect(settings.PROXY_BASE_URL + settings.FORCE_SCRIPT_NAME[:-1] + '/login') | |||
| else: | |||
| return render(request, 'localdev/bceid.html') | |||
| @ -1,12 +1,12 @@ | |||
| from django.conf.urls import url | |||
| from django.contrib.auth.decorators import login_required | |||
| from edivorce.apps.poc import views | |||
| from ..core.decorators import bceid_required | |||
| urlpatterns = [ | |||
| url(r'scan', bceid_required(views.UploadScan.as_view()), name="poc-scan"), | |||
| url(r'hub', bceid_required(views.EfilingHubUpload.as_view()), name="poc-hub"), | |||
| url(r'storage/doc/(?P<document_id>\d+)', bceid_required(views.view_document_file), name="poc-storage-download"), | |||
| url(r'storage/delete/(?P<pk>\d+)', bceid_required(views.UploadStorageDelete.as_view()), name="poc-storage-delete"), | |||
| url(r'storage', bceid_required(views.UploadStorage.as_view()), name="poc-storage"), | |||
| url(r'scan', login_required(views.UploadScan.as_view()), name="poc-scan"), | |||
| url(r'hub', login_required(views.EfilingHubUpload.as_view()), name="poc-hub"), | |||
| url(r'storage/doc/(?P<document_id>\d+)', login_required(views.view_document_file), name="poc-storage-download"), | |||
| url(r'storage/delete/(?P<pk>\d+)', login_required(views.UploadStorageDelete.as_view()), name="poc-storage-delete"), | |||
| url(r'storage', login_required(views.UploadStorage.as_view()), name="poc-storage"), | |||
| ] | |||