Browse Source

Merge pull request #130 from bcgov/feature/DIV-1136

Feature/div 1136
pull/172/head
Michael Olund 5 years ago
committed by GitHub
parent
commit
e61768aa2b
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
33 changed files with 2531 additions and 577 deletions
  1. +5
    -1
      .env.example
  2. +2120
    -0
      conf/keycloak/realm-export.json
  3. +15
    -0
      docker-compose.yml
  4. +0
    -4
      eDivorce.pyproj
  5. +0
    -22
      edivorce/apps/core/authenticators.py
  6. +1
    -23
      edivorce/apps/core/decorators.py
  7. +0
    -156
      edivorce/apps/core/middleware/bceid_middleware.py
  8. +39
    -0
      edivorce/apps/core/middleware/keycloak.py
  9. +10
    -0
      edivorce/apps/core/migrations/0001_initial.py
  10. +0
    -9
      edivorce/apps/core/migrations/0007_auto_20170210_1702.py
  11. +20
    -0
      edivorce/apps/core/migrations/0020_bceiduser_is_bcsc.py
  12. +102
    -0
      edivorce/apps/core/migrations/0024_auto_20201009_1235.py
  13. +19
    -0
      edivorce/apps/core/migrations/0025_remove_bceiduser_is_bcsc.py
  14. +5
    -13
      edivorce/apps/core/models.py
  15. +7
    -1
      edivorce/apps/core/static/css/main.scss
  16. +10
    -6
      edivorce/apps/core/templates/base.html
  17. +1
    -1
      edivorce/apps/core/templates/intro.html
  18. +0
    -155
      edivorce/apps/core/templates/localdev/bceid.html
  19. +0
    -16
      edivorce/apps/core/templates/localdev/debug.html
  20. +0
    -17
      edivorce/apps/core/templates/localdev/register.html
  21. +16
    -11
      edivorce/apps/core/tests/test_api.py
  22. +2
    -5
      edivorce/apps/core/urls.py
  23. +0
    -32
      edivorce/apps/core/views/localdev.py
  24. +18
    -43
      edivorce/apps/core/views/main.py
  25. +3
    -3
      edivorce/apps/core/views/pdf.py
  26. +0
    -4
      edivorce/apps/core/views/system.py
  27. +6
    -6
      edivorce/apps/poc/urls.py
  28. +21
    -7
      edivorce/settings/base.py
  29. +12
    -4
      edivorce/settings/local.py
  30. +53
    -19
      edivorce/settings/openshift.py
  31. +2
    -0
      edivorce/urls.py
  32. +12
    -5
      requirements.txt
  33. +32
    -14
      vue/src/components/Uploader/Uploader.vue

+ 5
- 1
.env.example View File

@ -26,4 +26,8 @@ EFILING_HUB_CLIENT_SECRET=''
EFILING_HUB_API_BASE_URL=''
# BCE ID test accounts for localdev
EFILING_BCEID=
EFILING_BCEID=
# Keycloak settings
KEYCLOAK_CLIENT_ID=
KEYCLOAK_CLIENT_SECRET=

+ 2120
- 0
conf/keycloak/realm-export.json
File diff suppressed because it is too large
View File


+ 15
- 0
docker-compose.yml View File

@ -43,5 +43,20 @@ services:
- "5005:5001"
restart: always
# Keycloak
keycloak:
container_name: edivorce-keycloak
image: jboss/keycloak:9.0.3
environment:
DB_VENDOR: H2
KEYCLOAK_USER: admin
KEYCLOAK_PASSWORD: admin
KEYCLOAK_IMPORT: /tmp/realm-export.json
volumes:
- ./conf/keycloak/realm-export.json:/tmp/realm-export.json
ports:
- 8081:8080
command: ["-Dkeycloak.profile.feature.upload_scripts=enabled"]
volumes:
data-redis:

+ 0
- 4
eDivorce.pyproj View File

@ -71,9 +71,7 @@
<Content Include="edivorce\apps\core\templates\incomplete.html" />
<Content Include="edivorce\apps\core\templates\intro.html" />
<Content Include="edivorce\apps\core\templates\legal.html" />
<Content Include="edivorce\apps\core\templates\localdev\bceid.html" />
<Content Include="edivorce\apps\core\templates\localdev\debug.html" />
<Content Include="edivorce\apps\core\templates\localdev\register.html" />
<Content Include="edivorce\apps\core\templates\login.html" />
<Content Include="edivorce\apps\core\templates\logout.html" />
<Content Include="edivorce\apps\core\templates\overview.html" />
@ -139,7 +137,6 @@
<Compile Include="edivorce\apps\core\context_processors.py" />
<Compile Include="edivorce\apps\core\decorators.py" />
<Compile Include="edivorce\apps\core\middleware\basicauth_middleware.py" />
<Compile Include="edivorce\apps\core\middleware\bceid_middleware.py" />
<Compile Include="edivorce\apps\core\middleware\__init__.py" />
<Compile Include="edivorce\apps\core\migrations\0001_initial.py" />
<Compile Include="edivorce\apps\core\migrations\0002_legalform_order.py" />
@ -179,7 +176,6 @@
<Compile Include="edivorce\apps\core\utils\user_response.py" />
<Compile Include="edivorce\apps\core\utils\__init__.py" />
<Compile Include="edivorce\apps\core\views\api.py" />
<Compile Include="edivorce\apps\core\views\localdev.py" />
<Compile Include="edivorce\apps\core\views\main.py" />
<Compile Include="edivorce\apps\core\views\pdf.py" />
<Compile Include="edivorce\apps\core\views\styleguide.py" />


+ 0
- 22
edivorce/apps/core/authenticators.py View File

@ -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
- 23
edivorce/apps/core/decorators.py View File

@ -9,28 +9,6 @@ from edivorce.apps.core.utils.user_response import get_data_for_user, get_step_r
base_url = settings.PROXY_BASE_URL + settings.FORCE_SCRIPT_NAME[:-1]
def bceid_required(function=None):
"""
View decorator to check if the user is logged in to BCEID
This decorator has a dependency on bceid_middleware.py
"""
def _dec(view_func):
def _view(request, *args, **kwargs):
if not request.user.is_authenticated:
return redirect(base_url + '/login')
return view_func(request, *args, **kwargs)
_view.__name__ = view_func.__name__
_view.__dict__ = view_func.__dict__
_view.__doc__ = view_func.__doc__
return _view
return _dec if function is None else _dec(function)
def intercept(function=None):
"""
Decorator to redirect to intercept page
@ -75,7 +53,7 @@ def prequal_completed(function=None):
return redirect(reverse('prequalification', kwargs={'step': step}))
return redirect(reverse('prequalification', kwargs={'step': '01'}))
else:
return redirect(base_url + '/login')
return redirect('oidc_authentication_init')
_view.__name__ = view_func.__name__
_view.__dict__ = view_func.__dict__


+ 0
- 156
edivorce/apps/core/middleware/bceid_middleware.py View File

@ -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

+ 39
- 0
edivorce/apps/core/middleware/keycloak.py View File

@ -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)

+ 10
- 0
edivorce/apps/core/migrations/0001_initial.py View File

@ -3,6 +3,7 @@ from __future__ import unicode_literals
from django.db import migrations, models
from django.conf import settings
import django.utils.timezone
class Migration(migrations.Migration):
@ -12,6 +13,15 @@ class Migration(migrations.Migration):
]
operations = [
migrations.CreateModel(
name='BceidUser',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('user_guid', models.CharField(unique=True, max_length=36, db_index=True)),
('date_joined', models.DateTimeField(default=django.utils.timezone.now)),
('last_login', models.DateTimeField(default=django.utils.timezone.now)),
],
),
migrations.CreateModel(
name='FormQuestions',
fields=[


+ 0
- 9
edivorce/apps/core/migrations/0007_auto_20170210_1702.py View File

@ -12,15 +12,6 @@ class Migration(migrations.Migration):
]
operations = [
migrations.CreateModel(
name='BceidUser',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('user_guid', models.CharField(unique=True, max_length=36, db_index=True)),
('date_joined', models.DateTimeField(default=django.utils.timezone.now)),
('last_login', models.DateTimeField(default=django.utils.timezone.now)),
],
),
migrations.RemoveField(
model_name='profile',
name='user',


+ 20
- 0
edivorce/apps/core/migrations/0020_bceiduser_is_bcsc.py View File

@ -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),
),
]

+ 102
- 0
edivorce/apps/core/migrations/0024_auto_20201009_1235.py View File

@ -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'),
),
]

+ 19
- 0
edivorce/apps/core/migrations/0025_remove_bceiduser_is_bcsc.py View File

@ -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',
),
]

+ 5
- 13
edivorce/apps/core/models.py View File

@ -6,12 +6,13 @@ from django.db.models import F
from django.urls import reverse
from django.utils import timezone
from django.utils.encoding import python_2_unicode_compatible
from django.contrib.auth.models import AbstractUser
from edivorce.apps.core import redis
@python_2_unicode_compatible
class BceidUser(models.Model):
class BceidUser(AbstractUser):
"""
BCeID user table
"""
@ -25,21 +26,12 @@ class BceidUser(models.Model):
sm_user = models.TextField(blank=True)
""" SiteMinder user value """
date_joined = models.DateTimeField(default=timezone.now)
""" First login timestamp """
last_login = models.DateTimeField(default=timezone.now)
""" Most recent login timestamp """
has_seen_orders_page = models.BooleanField(default=False)
""" Flag for intercept page """
has_accepted_terms = models.BooleanField(default=False)
""" Flag for accepting terms of service """
is_bcsc = models.BooleanField(default=False)
""" Flag to identify BC Services Card users """
@property
def is_authenticated(self):
return True
@ -48,9 +40,9 @@ class BceidUser(models.Model):
def is_anonymous(self):
return False
is_staff = True
is_active = True
@property
def is_active(self):
return True
def has_module_perms(self, *args):
return True


+ 7
- 1
edivorce/apps/core/static/css/main.scss View File

@ -983,12 +983,18 @@ textarea {
float: right;
margin-top: 16px;
a {
input[type=submit] {
color: #ffffff;
background-color: transparent;
display: inline;
padding: 0;
line-height: 1.5;
border: none;
&:active,
&:hover {
color: #fcfcfc;
text-decoration: underline;
}
}
}


+ 10
- 6
edivorce/apps/core/templates/base.html View File

@ -13,10 +13,10 @@
<meta name="viewport" content="width=device-width, initial-scale=1">
{% compress css %}
<link rel="stylesheet" type="text/css" href="{% static "css/bootstrap.min.css" %}">
<link rel="stylesheet" type="text/css" href="{% static "css/bootstrap-datepicker3.min.css" %}">
<link rel="stylesheet" type="text/css" href="{% static "css/font-awesome.min.css" %}">
<link rel="stylesheet" type="text/css" href="{% sass_src "css/main.scss" %}" />
<link rel="stylesheet" type="text/css" href="{% static 'css/bootstrap.min.css' %}">
<link rel="stylesheet" type="text/css" href="{% static 'css/bootstrap-datepicker3.min.css' %}">
<link rel="stylesheet" type="text/css" href="{% static 'css/font-awesome.min.css' %}">
<link rel="stylesheet" type="text/css" href="{% sass_src 'css/main.scss' %}" />
{% endcompress %}
{% block extra_css %}
{% endblock %}
@ -54,8 +54,12 @@
<div class="top_banner-user">
{% if request.user.is_authenticated %}
<span>
{{ request.user.display_name}}
&nbsp;&nbsp;|&nbsp;&nbsp; <a href="{% url 'logout' %}">Log out</a>
<form action="{% url 'oidc_logout' %}" method="post">
{{ request.user.display_name}}
&nbsp;&nbsp;|&nbsp;&nbsp;
{% csrf_token %}
<input type="submit" value="Log out">
</form>
</span>
{% endif %}
</div>


+ 1
- 1
edivorce/apps/core/templates/intro.html View File

@ -51,7 +51,7 @@
<h3>
Already started completing forms online and want to pick up where you left off?
</h3>
<a href="{% url 'login' %}" class="btn btn-primary btn-lg">Returning users</a>
<a href="{% url 'oidc_authentication_init' %}" class="btn btn-primary btn-lg">Returning users</a>
</div>
</div>
{% endblock %}


+ 0
- 155
edivorce/apps/core/templates/localdev/bceid.html View File

@ -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 &ndash; 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>
&#x25C0; <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>

+ 0
- 16
edivorce/apps/core/templates/localdev/debug.html View File

@ -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>

+ 0
- 17
edivorce/apps/core/templates/localdev/register.html View File

@ -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>

+ 16
- 11
edivorce/apps/core/tests/test_api.py View File

@ -44,8 +44,8 @@ class MockRedis:
@override_settings(CLAMAV_ENABLED=False)
class APITest(APITestCase):
def setUp(self):
self.user = BceidUser.objects.create(user_guid='1234')
self.another_user = BceidUser.objects.create(user_guid='5678')
self.user = _get_or_create_user(user_guid='1234')
self.another_user = _get_or_create_user(user_guid='5678')
self.client = APIClient()
self.default_doc_type = 'MC'
self.default_party_code = 0
@ -53,10 +53,10 @@ class APITest(APITestCase):
def test_get_documents(self):
url = reverse('documents')
response = self.client.get(url)
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
response = self.client.post(url, {})
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
self.client.force_authenticate(self.user)
response = self.client.get(url)
@ -195,7 +195,7 @@ class APITest(APITestCase):
url = document.get_file_url()
response = self.client.get(url)
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
self.client.force_authenticate(self.another_user)
response = self.client.get(url)
@ -246,7 +246,7 @@ class APITest(APITestCase):
url = document.get_file_url()
response = self.client.delete(url)
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
self.assertEqual(Document.objects.count(), 1)
self.client.force_authenticate(self.another_user)
@ -267,7 +267,7 @@ class APITest(APITestCase):
'rotation': 90
}
response = self.client.put(url, data)
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
self.client.force_authenticate(self.another_user)
response = self.client.put(url, data)
@ -307,14 +307,12 @@ class APITest(APITestCase):
@mock.patch.object(Redis, 'get', MockRedis.get)
@mock.patch.object(Redis, 'delete', MockRedis.delete)
@mock.patch.object(Redis, 'exists', MockRedis.exists)
@override_settings(AUTHENTICATION_BACKENDS=('edivorce.apps.core.authenticators.BCeIDAuthentication',))
@modify_settings(MIDDLEWARE={'remove': 'edivorce.apps.core.middleware.bceid_middleware.BceidMiddleware', })
class GraphQLAPITest(GraphQLTestCase):
GRAPHQL_URL = reverse('graphql')
def setUp(self):
self.user = BceidUser.objects.create(user_guid='1234')
self.another_user = BceidUser.objects.create(user_guid='5678')
self.user = _get_or_create_user(user_guid='1234')
self.another_user = _get_or_create_user(user_guid='5678')
self.default_doc_type = 'MC'
self.default_party_code = 0
@ -512,3 +510,10 @@ def _create_file(extension='jpg'):
num_documents = Document.objects.count()
new_file = SimpleUploadedFile(f'test_file_{num_documents + 1}.{extension}', b'test content')
return new_file
def _get_or_create_user(user_guid):
try:
return BceidUser.objects.get(user_guid=user_guid)
except BceidUser.DoesNotExist:
return BceidUser.objects.create(user_guid=user_guid, username=user_guid)

+ 2
- 5
edivorce/apps/core/urls.py View File

@ -1,7 +1,7 @@
from django.conf.urls import url
from django.urls import path
from .views import main, system, pdf, api, localdev
from .views import main, system, pdf, api
urlpatterns = [
# url(r'^guide$', styleguide.guide),
@ -13,10 +13,7 @@ urlpatterns = [
# we add an extra 'x' to the file extension so the siteminder proxy doesn't treat it as an image
path('api/documents/<doc_type>/<int:party_code>/<filename>x/<int:size>/', api.DocumentView.as_view(), name='document'),
# url(r'^login/headers$', system.headers),
url(r'^login$', main.login, name="login"),
url(r'^bceid$', localdev.bceid, name="bceid"),
url(r'^signin$', main.signin, name="signin"),
url(r'^register$', main.register, name="register"),
url(r'^register_sc$', main.register_sc, name="register_sc"),
url(r'^logout$', main.logout, name="logout"),


+ 0
- 32
edivorce/apps/core/views/localdev.py View File

@ -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')

+ 18
- 43
edivorce/apps/core/views/main.py View File

@ -4,9 +4,10 @@ from django.conf import settings
from django.shortcuts import render, redirect
from django.urls import reverse
from django.utils import timezone
from django.contrib.auth.decorators import login_required
from edivorce.apps.core.utils.derived import get_derived_data
from ..decorators import bceid_required, intercept, prequal_completed
from ..decorators import intercept, prequal_completed
from ..utils.cso_filing import file_documents
from ..utils.question_step_mapping import list_of_registries
from ..utils.step_completeness import get_error_dict, get_missed_question_keys, get_step_completeness, is_complete, get_formatted_incomplete_list
@ -24,14 +25,10 @@ def home(request):
"""
This is the homepage
"""
# HTTP_SM_USER is available on both unsecure and secure pages.
# If it has a value then we know the user is logged into BCeID/siteminder
siteminder_is_authenticated = request.META.get('HTTP_SM_USER', '') != ''
# if the user is returning from BCeID registration, then log them in to the site
if siteminder_is_authenticated and request.session.get('went_to_register', False):
if request.user.is_authenticated and request.session.get('went_to_register', False):
request.session['went_to_register'] = False
return redirect(settings.PROXY_BASE_URL + settings.FORCE_SCRIPT_NAME[:-1] + '/login')
return redirect('oidc_authentication_init')
return render(request, 'intro.html', context={'hide_nav': True})
@ -71,7 +68,7 @@ def success(request):
if request.user.is_authenticated:
return redirect(reverse('overview'))
else:
return render(request, 'success.html', context={'register_url': settings.REGISTER_URL,'register_sc_url': settings.REGISTER_SC_URL})
return render(request, 'success.html', context={'register_url': settings.REGISTER_BCEID_URL,'register_sc_url': settings.REGISTER_BCSC_URL})
return redirect(reverse('incomplete'))
@ -102,7 +99,7 @@ def register(request):
return render(request, 'localdev/register.html')
request.session['went_to_register'] = True
return redirect(settings.REGISTER_URL)
return redirect(settings.REGISTER_BCEID_URL)
def register_sc(request):
"""
@ -112,38 +109,16 @@ def register_sc(request):
return render(request, 'localdev/register.html')
request.session['went_to_register'] = True
return redirect(settings.REGISTER_SC_URL)
def login(request):
"""
This page is proxy-protected by Siteminder. Users who are not
logged into BCeID will get a login page. Users who are logged into
BCeID will be redirected to the dashboard
"""
if settings.DEPLOYMENT_TYPE in ['localdev', 'minishift'] and not request.session.get('fake_bceid_guid'):
return redirect(settings.PROXY_BASE_URL + settings.FORCE_SCRIPT_NAME[:-1] + '/bceid')
return redirect(settings.REGISTER_BCSC_URL)
def signin(request):
if not request.user.is_authenticated:
# Fix for weird siteminder behaviour......
# If a user is logged into an IDIR then they can see the login page but
# the SMGOV headers are missing. If this is the case, then log them out
# of their IDIR, and redirect them back to here again....
# FUTURE DEV NOTE: The DC elements of HTTP_SM_USERDN header will tell us
# exactly how the user is logged in. But it doesn't seem like a very
# good idea at this time to rely on this magic string. e.g. CN=Smith\,
# John,OU=Users,OU=Attorney General,OU=BCGOV,DC=idir,DC=BCGOV
if request.GET.get('noretry', '') != 'true':
return redirect(settings.LOGOUT_URL_TEMPLATE % (
settings.PROXY_BASE_URL,
settings.FORCE_SCRIPT_NAME[:-1] + '/login%3Fnoretry=true'))
return render(request, '407.html')
if timezone.now() - request.user.last_login > datetime.timedelta(minutes=1):
request.user.last_login = timezone.now()
request.user.save()
## I think Django might be doing this automatically now that we have switched to mozilla-django-oidc?
#if timezone.now() - request.user.last_login > datetime.timedelta(minutes=1):
# request.user.last_login = timezone.now()
# request.user.save()
copy_session_to_db(request, request.user)
@ -164,7 +139,7 @@ def logout(request):
return response
@bceid_required
@login_required
@prequal_completed
@intercept
def overview(request):
@ -192,7 +167,7 @@ def overview(request):
return response
@bceid_required
@login_required
@prequal_completed
def dashboard_nav(request, nav_step):
"""
@ -212,13 +187,13 @@ def dashboard_nav(request, nav_step):
return render(request, template_name=template_name, context=responses_dict)
@bceid_required
@login_required
@prequal_completed
def submit_initial_files(request):
return _submit_files(request, initial=True)
@bceid_required
@login_required
@prequal_completed
def submit_final_files(request):
return _submit_files(request, initial=False)
@ -237,7 +212,7 @@ def _submit_files(request, initial=False):
return redirect(reverse('dashboard_nav', kwargs={'nav_step': nav_step}), context=responses_dict)
@bceid_required
@login_required
@prequal_completed
def question(request, step, sub_step=None):
"""
@ -309,7 +284,7 @@ def contact(request):
return render(request, 'contact-us.html', context={'active_page': 'contact'})
@bceid_required
@login_required
def intercept_page(request):
"""
On intercept, show the Orders page to get the requested orders before the


+ 3
- 3
edivorce/apps/core/views/pdf.py View File

@ -5,10 +5,10 @@ import json
from django.conf import settings
from django.http import HttpResponse
from django.template.loader import render_to_string
from django.contrib.auth.decorators import login_required
import requests
from ..decorators import bceid_required
from ..models import Document
from ..utils.derived import get_derived_data
from ..utils.user_response import get_data_for_user
@ -16,7 +16,7 @@ from ..utils.user_response import get_data_for_user
EXHIBITS = list('ABCDEFGHIJKLMNOPQRSTUVWXYZ'[::-1])
@bceid_required
@login_required
def form(request, form_number):
""" View for rendering PDF's and previews """
@ -111,7 +111,7 @@ def __add_claimant_info(responses, claimant):
return responses
@bceid_required
@login_required
def images_to_pdf(request, doc_type, party_code):
documents = Document.objects.filter(
bceid_user=request.user, doc_type=doc_type, party_code=party_code)


+ 0
- 4
edivorce/apps/core/views/system.py View File

@ -13,10 +13,6 @@ def health(request): # pylint: disable=unused-argument
return HttpResponse(Question.objects.count())
def headers(request):
return render(request, 'localdev/debug.html')
def current(request):
"""
Debug tool usable in dev and test environments, available at /current


+ 6
- 6
edivorce/apps/poc/urls.py View File

@ -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"),
]

+ 21
- 7
edivorce/settings/base.py View File

@ -11,6 +11,8 @@ https://docs.djangoproject.com/en/1.8/ref/settings/
"""
import os
from django.urls import reverse_lazy
from environs import Env
from unipath import Path
@ -45,6 +47,7 @@ INSTALLED_APPS = (
'django.contrib.messages',
'django.contrib.staticfiles',
'django.contrib.humanize',
'mozilla_django_oidc', # Load after auth
'rest_framework',
'debug_toolbar',
'corsheaders',
@ -71,11 +74,16 @@ MIDDLEWARE = (
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'edivorce.apps.core.middleware.bceid_middleware.BceidMiddleware',
'django.middleware.security.SecurityMiddleware',
'whitenoise.middleware.WhiteNoiseMiddleware',
)
AUTH_USER_MODEL = 'core.BceidUser'
AUTHENTICATION_BACKENDS = (
'edivorce.apps.core.middleware.keycloak.EDivorceKeycloakBackend',
)
ROOT_URLCONF = 'edivorce.urls'
TEMPLATES = [
@ -101,7 +109,8 @@ WSGI_APPLICATION = 'wsgi.application'
# by presence of Basic Auth headers
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'edivorce.apps.core.authenticators.BCeIDAuthentication',
'mozilla_django_oidc.contrib.drf.OIDCAuthentication',
'rest_framework.authentication.SessionAuthentication'
]
}
@ -117,7 +126,7 @@ LOGGING = {
'loggers': {
'': {
'handlers': ['console'],
'level': os.getenv('DJANGO_LOG_LEVEL', 'INFO'),
'level': env('DJANGO_LOG_LEVEL', 'INFO'),
},
},
}
@ -175,10 +184,6 @@ DEBUG_TOOLBAR_CONFIG = {
SECURE_BROWSER_XSS_FILTER = True
LOGOUT_URL = '/accounts/logout/'
# CLAMAV settings
# eFiling Hub settings
EFILING_HUB_TOKEN_BASE_URL = env('EFILING_HUB_TOKEN_BASE_URL', 'https://efiling.gov.bc.ca')
EFILING_HUB_REALM = env('EFILING_HUB_REALM', 'abc')
@ -187,3 +192,12 @@ EFILING_HUB_CLIENT_SECRET = env('EFILING_HUB_CLIENT_SECRET', 'abc')
EFILING_HUB_API_BASE_URL = env('EFILING_HUB_API_BASE_URL', 'https://efiling.gov.bc.ca')
EFILING_BCEID = env.dict('EFILING_BCEID', '', subcast=str)
# Keycloak OpenID Connect settings
# Provided by mozilla-django-oidc
LOGIN_URL = reverse_lazy('oidc_authentication_init')
OIDC_RP_SIGN_ALGO = 'RS256'
OIDC_RP_SCOPES = 'openid email profile'
# this is needed to bypass the Keycloak login screen
OIDC_AUTH_REQUEST_EXTRA_PARAMS = {'kc_idp_hint': 'bceid'}
OIDC_RP_CLIENT_SECRET = env('KEYCLOAK_CLIENT_SECRET', '')

+ 12
- 4
edivorce/settings/local.py View File

@ -21,16 +21,14 @@ WEASYPRINT_IMAGE_LOOPBACK = 'http://host.docker.internal:8000'
WEASYPRINT_CSS_LOOPBACK = WEASYPRINT_IMAGE_LOOPBACK
DEPLOYMENT_TYPE = 'localdev'
REGISTER_URL = '#'
REGISTER_SC_URL ='#'
REGISTER_BCEID_URL = '#'
REGISTER_BCSC_URL = '#'
PROXY_BASE_URL = ''
SASS_PROCESSOR_ENABLED = True
SASS_PROCESSOR_ROOT = PROJECT_ROOT + '/edivorce/apps/core/static'
SASS_OUTPUT_STYLE = 'compressed'
CORS_ORIGIN_ALLOW_ALL = True
LOGOUT_URL = '/accounts/logout/'
# CLAMAV settings
CLAMAV_ENABLED = env.bool('CLAMAV_ENABLED', True)
CLAMAV_TCP_PORT = env.int('CLAMAV_TCP_PORT', 3310)
@ -41,3 +39,13 @@ REDIS_HOST = env('REDIS_HOST', 'localhost')
REDIS_PORT = env.int('REDIS_PORT', 6379)
REDIS_DB = env('REDIS_DB', '')
REDIS_PASSWORD = env('REDIS_PASSWORD', '')
# Keycloak OpenID Connect settings
# Provided by mozilla-django-oidc
OIDC_OP_JWKS_ENDPOINT = 'http://localhost:8081/auth/realms/justice/protocol/openid-connect/certs'
OIDC_OP_AUTHORIZATION_ENDPOINT = 'http://localhost:8081/auth/realms/justice/protocol/openid-connect/auth'
OIDC_OP_TOKEN_ENDPOINT = 'http://localhost:8081/auth/realms/justice/protocol/openid-connect/token'
OIDC_OP_USER_ENDPOINT = 'http://localhost:8081/auth/realms/justice/protocol/openid-connect/userinfo'
OIDC_RP_CLIENT_ID = 'edivorce-app'
LOGIN_REDIRECT_URL = '/signin'
LOGOUT_REDIRECT_URL = '/'

+ 53
- 19
edivorce/settings/openshift.py View File

@ -1,5 +1,7 @@
from mozilla_django_oidc import utils
from .base import *
def openshift_db_config():
'''
Database config based on the django-ex openshift sample application
@ -45,42 +47,61 @@ COMPRESS_OFFLINE = True
#
# See nginx-proxy/conf.d/server.conf for related settings
#
DEPLOYMENT_TYPE = os.getenv('ENVIRONMENT_TYPE')
DEPLOYMENT_TYPE = env('ENVIRONMENT_TYPE', 'unittest')
PROXY_URL_PREFIX = ''
PROXY_BASE_URL = os.getenv('PROXY_BASE_URL', 'https://justice.gov.bc.ca')
if DEPLOYMENT_TYPE in ['dev', 'unittest']:
DEBUG = True
# Keycloak OpenID Connect settings
OIDC_OP_JWKS_ENDPOINT = 'https://sso-dev.pathfinder.gov.bc.ca/auth/realms/tz0e228w/protocol/openid-connect/certs'
OIDC_OP_AUTHORIZATION_ENDPOINT = 'https://sso-dev.pathfinder.gov.bc.ca/auth/realms/tz0e228w/protocol/openid-connect/auth'
OIDC_OP_TOKEN_ENDPOINT = 'https://sso-dev.pathfinder.gov.bc.ca/auth/realms/tz0e228w/protocol/openid-connect/token'
OIDC_OP_USER_ENDPOINT = 'https://sso-dev.pathfinder.gov.bc.ca/auth/realms/tz0e228w/protocol/openid-connect/userinfo'
OIDC_RP_CLIENT_ID = 'e-divorce-app'
if DEPLOYMENT_TYPE == 'dev':
PROXY_URL_PREFIX = os.getenv('PROXY_URL_PREFIX', '/divorce')
DEBUG = True
CSRF_COOKIE_AGE = None
SESSION_COOKIE_AGE = 3600
REGISTER_URL = 'https://www.test.bceid.ca/directories/bluepages/details.aspx?serviceID=5522'
REGISTER_SC_URL = 'https://logontest7.gov.bc.ca/clp-cgi/fed/fedLaunch.cgi?partner=fed38&partnerList=fed38&flags=0001:0,7&TARGET=http://dev.justice.gov.bc.ca/divorce/login'
LOGOUT_URL_TEMPLATE = 'https://logontest7.gov.bc.ca/clp-cgi/logoff.cgi?returl=%s%s&retnow=1'
LOGOUT_URL = LOGOUT_URL_TEMPLATE % (PROXY_BASE_URL, PROXY_URL_PREFIX)
REGISTER_BCEID_URL = 'https://www.test.bceid.ca/directories/bluepages/details.aspx?serviceID=5522'
REGISTER_BCSC_URL = 'https://logontest7.gov.bc.ca/clp-cgi/fed/fedLaunch.cgi?partner=fed38&partnerList=fed38&flags=0001:0,7&TARGET=http://dev.justice.gov.bc.ca/divorce/oidc/authenticate'
if DEPLOYMENT_TYPE == 'test':
PROXY_URL_PREFIX = os.getenv('PROXY_URL_PREFIX', '/divorce')
REGISTER_URL = 'https://www.test.bceid.ca/directories/bluepages/details.aspx?serviceID=5521'
REGISTER_SC_URL = 'https://logontest7.gov.bc.ca/clp-cgi/fed/fedLaunch.cgi?partner=fed38&partnerList=fed38&flags=0001:0,7&TARGET=http://test.justice.gov.bc.ca/divorce/login'
LOGOUT_URL_TEMPLATE = 'https://logontest7.gov.bc.ca/clp-cgi/logoff.cgi?returl=%s%s&retnow=1'
LOGOUT_URL = LOGOUT_URL_TEMPLATE % (PROXY_BASE_URL, PROXY_URL_PREFIX)
REGISTER_BCEID_URL = 'https://www.test.bceid.ca/directories/bluepages/details.aspx?serviceID=5521'
REGISTER_BCSC_URL = 'https://logontest7.gov.bc.ca/clp-cgi/fed/fedLaunch.cgi?partner=fed38&partnerList=fed38&flags=0001:0,7&TARGET=http://test.justice.gov.bc.ca/divorce/oidc/authenticate'
# Keycloak OpenID Connect settings
OIDC_OP_JWKS_ENDPOINT = 'https://sso-test.pathfinder.gov.bc.ca/auth/realms/XXXXXXXX/protocol/openid-connect/certs'
OIDC_OP_AUTHORIZATION_ENDPOINT = 'https://sso-test.pathfinder.gov.bc.ca/auth/realms/XXXXXXXX/protocol/openid-connect/auth'
OIDC_OP_TOKEN_ENDPOINT = 'https://sso-test.pathfinder.gov.bc.ca/auth/realms/XXXXXXXX/protocol/openid-connect/token'
OIDC_OP_USER_ENDPOINT = 'https://sso-test.pathfinder.gov.bc.ca/auth/realms/XXXXXXXX/protocol/openid-connect/userinfo'
OIDC_RP_CLIENT_ID = 'XXXXXXXX'
if DEPLOYMENT_TYPE == 'prod':
PROXY_URL_PREFIX = os.getenv('PROXY_URL_PREFIX', '/divorce')
REGISTER_URL = 'https://www.bceid.ca/directories/bluepages/details.aspx?serviceID=5203'
REGISTER_SC_URL = 'https://logon7.gov.bc.ca/clp-cgi/fed/fedLaunch.cgi?partner=fed49&partnerList=fed49&flags=0001:0,8&TARGET=http://justice.gov.bc.ca/divorce/login'
LOGOUT_URL_TEMPLATE = 'https://logon7.gov.bc.ca/clp-cgi/logoff.cgi?returl=%s%s&retnow=1'
LOGOUT_URL = LOGOUT_URL_TEMPLATE % (PROXY_BASE_URL, PROXY_URL_PREFIX)
REGISTER_BCEID_URL = 'https://www.bceid.ca/directories/bluepages/details.aspx?serviceID=5203'
REGISTER_BCSC_URL = 'https://logon7.gov.bc.ca/clp-cgi/fed/fedLaunch.cgi?partner=fed49&partnerList=fed49&flags=0001:0,8&TARGET=http://justice.gov.bc.ca/divorce/oidc/authenticate'
# Keycloak OpenID Connect settings
OIDC_OP_JWKS_ENDPOINT = 'https://sso.pathfinder.gov.bc.ca/auth/realms/XXXXXXXX/protocol/openid-connect/certs'
OIDC_OP_AUTHORIZATION_ENDPOINT = 'https://sso.pathfinder.gov.bc.ca/auth/realms/XXXXXXXX/protocol/openid-connect/auth'
OIDC_OP_TOKEN_ENDPOINT = 'https://sso.pathfinder.gov.bc.ca/auth/realms/XXXXXXXX/protocol/openid-connect/token'
OIDC_OP_USER_ENDPOINT = 'https://sso.pathfinder.gov.bc.ca/auth/realms/XXXXXXXX/protocol/openid-connect/userinfo'
OIDC_RP_CLIENT_ID = 'XXXXXXXX'
# Google Tag Manager (Production)
GTM_ID = 'GTM-W4Z2SPS'
if DEPLOYMENT_TYPE == 'minishift':
DEBUG = True
REGISTER_URL = '#'
REGISTER_SC_URL ='#'
REGISTER_BCEID_URL = '#'
REGISTER_BCSC_URL = '#'
PROXY_BASE_URL = ''
# Keycloak OpenID Connect settings
OIDC_OP_JWKS_ENDPOINT = 'http://localhost:8081/auth/realms/justice/protocol/openid-connect/certs'
OIDC_OP_AUTHORIZATION_ENDPOINT = 'http://localhost:8081/auth/realms/justice/protocol/openid-connect/auth'
OIDC_OP_TOKEN_ENDPOINT = 'http://localhost:8081/auth/realms/justice/protocol/openid-connect/token'
OIDC_OP_USER_ENDPOINT = 'http://localhost:8081/auth/realms/justice/protocol/openid-connect/userinfo'
# Internal Relative Urls
FORCE_SCRIPT_NAME = PROXY_URL_PREFIX + '/'
@ -101,8 +122,8 @@ SESSION_EXPIRE_AT_BROWSER_CLOSE = True
if DEPLOYMENT_TYPE != 'minishift':
SESSION_COOKIE_PATH = PROXY_URL_PREFIX
SESSION_COOKIE_SECURE=True
CSRF_COOKIE_SECURE=True
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
# CLAMAV settings
CLAMAV_ENABLED = True
@ -113,4 +134,17 @@ CLAMAV_TCP_ADDR = os.getenv('CLAMAV_TCP_ADDR', 'clamav')
REDIS_HOST = os.getenv('REDIS_HOST', 'redis')
REDIS_PORT = 6379
REDIS_DB = ''
REDIS_PASSWORD = os.getenv('REDIS_PASSWORD', '')
REDIS_PASSWORD = os.getenv('REDIS_PASSWORD', '')
# Keycloak OpenID Connect settings
LOGIN_REDIRECT_URL = PROXY_URL_PREFIX + '/signin'
LOGOUT_REDIRECT_URL = PROXY_URL_PREFIX
def monkey_absolutify(request, path):
return PROXY_BASE_URL + path
# monkey-patching mozilla_django_oidc.utils.absolutify so it doesn't
# return urls prefixed with 'http://edivorce-django:8080' on OpenShift
utils.absolutify = monkey_absolutify

+ 2
- 0
edivorce/urls.py View File

@ -22,6 +22,8 @@ if settings.ENVIRONMENT in ['localdev', 'minishift']:
urlpatterns.append(url(r'^404/$', main.page_not_found, {'exception': Exception()}))
urlpatterns.append(url(r'^500/$', main.server_error))
urlpatterns.append(url(r'^oidc/', include('mozilla_django_oidc.urls')))
urlpatterns.append(url(r'^', include('edivorce.apps.core.urls')))
handler404 = main.page_not_found


+ 12
- 5
requirements.txt View File

@ -1,15 +1,18 @@
-i https://pypi.org/simple
aniso8601==7.0.0
beautifulsoup4==4.9.3
certifi==2020.6.20
cffi==1.14.2
chardet==3.0.4
clamd==1.0.2
Django==2.2.15
cryptography==3.1; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'
django-appconf==1.0.4
django-compressor==2.4
django-cors-headers==3.5.0
django-crispy-forms==1.9.2
django-debug-toolbar==2.2
django-sass-processor==0.8
django==2.2.15
djangorestframework==3.11.1
environs==8.0.0
graphene==2.1.8
@ -18,23 +21,27 @@ graphql-core==2.3.2
graphql-relay==2.0.1
gunicorn==20.0.4
idna==2.10
josepy==1.4.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'
libsass==0.20.0
marshmallow==3.7.1
Pillow==7.2.0
mozilla-django-oidc==1.2.4
pillow==7.2.0
promise==2.3
psycopg2==2.8.5
pycparser==2.20; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'
pyopenssl==19.1.0
python-dotenv==0.14.0
pytz==2020.1
rcssmin==1.0.6
redis==3.5.3
requests==2.24.0
rjsmin==1.1.0
Rx==1.6.1
rx==1.6.1
singledispatch==3.4.0.3
six==1.15.0
soupsieve==2.0.1
sqlparse==0.3.1
Unidecode==1.1.1
Unipath==1.1
unidecode==1.1.1
unipath==1.1
urllib3==1.25.10
whitenoise==3.3.1

+ 32
- 14
vue/src/components/Uploader/Uploader.vue View File

@ -32,6 +32,7 @@
:post-action="postAction"
:input-id="inputId"
name="file"
:headers="{ 'X-CSRFToken': getCSRFToken() }"
:class="['drop-zone', dragging ? 'dragging' : '']"
:data="inputKeys"
@input-file="inputFile"
@ -185,7 +186,7 @@
},
pdfURL() {
return `${this.$parent.proxyRootPath}pdf-images/${this.docType}/${this.party}/`;
},
}
},
methods: {
inputFile(newFile, oldFile) {
@ -220,7 +221,7 @@
// Automatically activate upload after compression completes
if (newFile && newFile.compressed && !newFile.active) {
newFile.active = true;
}
}
},
inputFilter(newFile, oldFile, prevent) {
if (newFile && !oldFile) {
@ -249,14 +250,14 @@
quality: 0.9,
maxWidth: 3300,
maxHeight: 3300,
convertSize: Infinity,
convertSize: Infinity,
success(result) {
self.$refs.upload.update(newFile, {
error: false,
file: result,
size: result.size,
type: result.type,
compressed: true
compressed: true,
});
},
error(err) {
@ -346,11 +347,12 @@
remove(file) {
const urlbase = `${this.$parent.proxyRootPath}api/documents`;
const encFilename = encodeURIComponent(file.name);
const token = this.getCSRFToken();
if (!file.error) {
// we add an extra 'x' to the file extension so the siteminder proxy doesn't treat it as an image
const url = `${urlbase}/${this.docType}/${this.party}/${encFilename}x/${file.size}/`;
axios
.delete(url)
.delete(url, { headers: { "X-CSRFToken": token } })
.then((response) => {
const pos = this.files.findIndex(
(f) => f.docType === file.docType && f.size === file.size
@ -410,13 +412,15 @@
saveMetaData() {
let allFiles = [];
this.files.forEach((file) => {
allFiles.push({
filename: file.name,
size: file.size,
width: file.width,
height: file.height,
rotation: rotateFix(file.rotation),
});
if (!file.error) {
allFiles.push({
filename: file.name,
size: file.size,
width: file.width,
height: file.height,
rotation: rotateFix(file.rotation),
});
}
});
const data = {
docType: this.docType,
@ -440,20 +444,34 @@
})
.then((response) => {
// check for errors in the graphQL response
this.retries = 0;
this.retries = 0;
if (response.data.errors && response.data.errors.length) {
response.data.errors.forEach((error) => {
console.log("error", error.message || error);
// if there was an error it's probably because the upload isn't finished yet
// mark the metadata as dirty so it will save metadata again
this.retries++;
this.isDirty = true;
});
}
})
.catch((error) => {
this.showError("Error saving metadata");
console.log("error", error);
this.retries++;
});
},
getCSRFToken() {
const name = "csrftoken";
if (document.cookie && document.cookie !== "") {
const cookies = document.cookie.split(";");
for (let i = 0; i < cookies.length; i++) {
const cookie = cookies[i].trim();
if (cookie.substring(0, name.length + 1) === name + "=") {
return decodeURIComponent(cookie.substring(name.length + 1));
}
}
}
return null;
}
},
created() {


Loading…
Cancel
Save