diff --git a/.github/workflows/tfrs-release.yaml b/.github/workflows/tfrs-release.yaml index c592c6883..8e14754a3 100644 --- a/.github/workflows/tfrs-release.yaml +++ b/.github/workflows/tfrs-release.yaml @@ -1,19 +1,22 @@ ## For each release, the value of name, branches, RELEASE_NAME and PR_NUMBER need to be adjusted accordingly ## For each release, update lib/config.js: version and releaseBranch -name: TFRS release-2.4.0 +name: TFRS release-2.5.0 on: push: - branches: [ release-2.4.0 ] + branches: [ release-2.5.0 ] + paths: + - frontend/** + - backend/** workflow_dispatch: workflow_call: env: ## The pull request number of the Tracking pull request to merge the release branch to main ## Also remember to update the version in .pipeline/lib/config.js - PR_NUMBER: 2112 - RELEASE_NAME: release-2.4.0 + PR_NUMBER: 2187 + RELEASE_NAME: release-2.5.0 concurrency: group: ${{ github.workflow }}-${{ github.ref }} diff --git a/.pipeline/lib/config.js b/.pipeline/lib/config.js index f50c5fe7f..d5af1e1fa 100644 --- a/.pipeline/lib/config.js +++ b/.pipeline/lib/config.js @@ -1,7 +1,7 @@ 'use strict'; const options= require('@bcgov/pipeline-cli').Util.parseArguments() const changeId = options.pr //aka pull-request -const version = '2.4.0' +const version = '2.5.0' const name = 'tfrs' const ocpName = 'apps.silver.devops' @@ -13,7 +13,7 @@ options.git.repository='tfrs' const phases = { build: { namespace:'0ab226-tools' , name: `${name}`, phase: 'build' , changeId:changeId, suffix: `-build-${changeId}` , instance: `${name}-build-${changeId}` , version:`${version}-${changeId}`, tag:`build-${version}-${changeId}`, - releaseBranch: 'release-2.4.0' + releaseBranch: 'release-2.5.0' }, dev: {namespace:'0ab226-dev' , name: `${name}`, phase: 'dev' , changeId:changeId, suffix: `-dev` , instance: `${name}-dev` , version:`${version}`, tag:`dev-${version}`, dbServiceName: 'tfrs-spilo', @@ -24,14 +24,7 @@ const phases = { frontendDebugEnabled: 'true', backendCpuRequest: '200m', backendCpuLimit: '400m', backendMemoryRequest: '600Mi', backendMemoryLimit: '1200Mi', backendHealthCheckDelay: 30, backendHost: `tfrs-backend-dev.${ocpName}.gov.bc.ca`, backendReplicas: 2, - backendKeycloakSaBaseurl: 'https://dev.loginproxy.gov.bc.ca', - backendKeycloakSaClientId: 'tfrs-on-gold-4308', - backendKeycloakSaRealm: 'standard', backendKeycloakAudience: 'tfrs-on-gold-4308', - backendKeycloakCertsUrl: 'https://dev.loginproxy.gov.bc.ca/auth/realms/standard/protocol/openid-connect/certs', - backendKeycloakClientId: 'tfrs-on-gold-4308', - backendKeycloakIssuer: 'https://dev.loginproxy.gov.bc.ca/auth/realms/standard', - backendKeycloakRealm: 'standard', backendWellKnownEndpoint: 'https://dev.loginproxy.gov.bc.ca/auth/realms/standard/.well-known/openid-configuration', celeryCpuRequest: '100m', celeryCpuLimit: '250m', celeryMemoryRequest: '1600Mi', celeryMemoryLimit: '3Gi', scanHandlerCpuRequest: '25m', scanHandlerCpuLimit: '50m', scanHandlerMemoryRequest: '50Mi', scanHandlerMemoryLimit: '100Mi', @@ -53,14 +46,7 @@ const phases = { frontendDebugEnabled: 'true', backendCpuRequest: '200m', backendCpuLimit: '400m', backendMemoryRequest: '600Mi', backendMemoryLimit: '1200Mi', backendHealthCheckDelay: 30, backendHost: `tfrs-backend-test.${ocpName}.gov.bc.ca`, backendReplicas: 4, - backendKeycloakSaBaseurl: 'https://test.loginproxy.gov.bc.ca', - backendKeycloakSaClientId: 'tfrs-on-gold-4308', - backendKeycloakSaRealm: 'standard', backendKeycloakAudience: 'tfrs-on-gold-4308', - backendKeycloakCertsUrl: 'https://test.loginproxy.gov.bc.ca/auth/realms/standard/protocol/openid-connect/certs', - backendKeycloakClientId: 'tfrs-on-gold-4308', - backendKeycloakIssuer: 'https://test.loginproxy.gov.bc.ca/auth/realms/standard', - backendKeycloakRealm: 'standard', backendWellKnownEndpoint: 'https://test.loginproxy.gov.bc.ca/auth/realms/standard/.well-known/openid-configuration', celeryCpuRequest: '100m', celeryCpuLimit: '250m', celeryMemoryRequest: '1600Mi', celeryMemoryLimit: '3Gi', scanHandlerCpuRequest: '25m', scanHandlerCpuLimit: '50m', scanHandlerMemoryRequest: '50Mi', scanHandlerMemoryLimit: '100Mi', @@ -82,14 +68,7 @@ const phases = { frontendDebugEnabled: 'false', backendCpuRequest: '200m', backendCpuLimit: '400m', backendMemoryRequest: '600Mi', backendMemoryLimit: '1200Mi', backendHealthCheckDelay: 30, backendHost: `tfrs-backend-prod.${ocpName}.gov.bc.ca`, backendReplicas: 4, - backendKeycloakSaBaseurl: 'https://oidc.gov.bc.ca', - backendKeycloakSaClientId: 'tfrs-on-gold-4308', - backendKeycloakSaRealm: 'standard', backendKeycloakAudience: 'tfrs-on-gold-4308', - backendKeycloakCertsUrl: 'https://loginproxy.gov.bc.ca/auth/realms/standard/protocol/openid-connect/certs', - backendKeycloakClientId: 'tfrs-on-gold-4308', - backendKeycloakIssuer: 'https://loginproxy.gov.bc.ca/auth/realms/standard', - backendKeycloakRealm: 'standard', backendWellKnownEndpoint: 'https://loginproxy.gov.bc.ca/auth/realms/standard/.well-known/openid-configuration', celeryCpuRequest: '100m', celeryCpuLimit: '250mm', celeryMemoryRequest: '1600Mi', celeryMemoryLimit: '3Gi', scanHandlerCpuRequest: '25m', scanHandlerCpuLimit: '50m', scanHandlerMemoryRequest: '50Mi', scanHandlerMemoryLimit: '100Mi', diff --git a/.pipeline/lib/deploy.js b/.pipeline/lib/deploy.js index b28b5c343..bd664a3ed 100755 --- a/.pipeline/lib/deploy.js +++ b/.pipeline/lib/deploy.js @@ -23,14 +23,7 @@ module.exports = settings => { 'ENV_NAME': phases[phase].phase, 'NAMESPACE': phases[phase].namespace, 'VERSION': phases[phase].tag, - 'KEYCLOAK_SA_BASEURL': phases[phase].backendKeycloakSaBaseurl, - 'KEYCLOAK_SA_CLIENT_ID': phases[phase].backendKeycloakSaClientId, - 'KEYCLOAK_SA_REALM': phases[phase].backendKeycloakSaRealm, 'KEYCLOAK_AUDIENCE': phases[phase].backendKeycloakAudience, - 'KEYCLOAK_CERTS_URL': phases[phase].backendKeycloakCertsUrl, - 'KEYCLOAK_CLIENT_ID': phases[phase].backendKeycloakClientId, - 'KEYCLOAK_ISSUER': phases[phase].backendKeycloakIssuer, - 'KEYCLOAK_REALM':phases[phase].backendKeycloakRealm, 'CPU_REQUEST':phases[phase].backendCpuRequest, 'CPU_LIMIT':phases[phase].backendCpuLimit, 'MEMORY_REQUEST':phases[phase].backendMemoryRequest, diff --git a/backend/api/app.py b/backend/api/app.py index 89ad11cfb..55b5e57dd 100644 --- a/backend/api/app.py +++ b/backend/api/app.py @@ -31,11 +31,10 @@ from django.db.models.signals import post_migrate from minio import Minio -from api.services.KeycloakAPI import list_users, get_token from db_comments.db_actions import create_db_comments, \ create_db_comments_from_models from tfrs.settings import AMQP_CONNECTION_PARAMETERS, MINIO, DOCUMENTS_API, \ - KEYCLOAK, EMAIL, TESTING, RUNSERVER + EMAIL, RUNSERVER class APIAppConfig(AppConfig): diff --git a/backend/api/keycloak_authentication.py b/backend/api/keycloak_authentication.py index 2dccf993d..ec21020b0 100644 --- a/backend/api/keycloak_authentication.py +++ b/backend/api/keycloak_authentication.py @@ -1,4 +1,3 @@ -import os import json import jwt import requests @@ -6,15 +5,14 @@ from django.core.cache import caches from django.conf import settings from django.db.models import Q -from django.http import HttpResponseServerError from rest_framework import authentication from rest_framework import exceptions from api.models.User import User from api.models.UserCreationRequest import UserCreationRequest from api.models.UserLoginHistory import UserLoginHistory -from api.services.KeycloakAPI import map_user -from tfrs.settings import WELL_KNOWN_ENDPOINT +from tfrs.settings import WELL_KNOWN_ENDPOINT, KEYCLOAK_AUDIENCE +import tfrs.settings cache = caches['keycloak'] @@ -32,7 +30,8 @@ def refresh_jwk(self): self.jwks = jwks def __init__(self): - if not settings.KEYCLOAK['TESTING_ENABLED']: + self.unit_testing_enabled = getattr(tfrs.settings, 'UNIT_TESTING_ENABLED', False) + if not self.unit_testing_enabled: self.refresh_jwk() def create_login_history(self, user_token, success = False, error = None, path = ''): @@ -58,9 +57,6 @@ def create_login_history(self, user_token, success = False, error = None, path = def authenticate(self, request): """Verify the JWT token and find the correct user in the DB""" - if not settings.KEYCLOAK['ENABLED']: - # fall through - return None auth = request.META.get('HTTP_AUTHORIZATION', None) @@ -69,7 +65,7 @@ def authenticate(self, request): raise exceptions.AuthenticationFailed( 'Authorization header required') - if settings.KEYCLOAK['TESTING_ENABLED']: + if self.unit_testing_enabled: try: user = User.objects.get(keycloak_user_id=auth['preferred_username']) return user, None @@ -77,6 +73,7 @@ def authenticate(self, request): print("Testing User does not exist") raise User.DoesNotExist(str(exc)) + print("auth", auth) try: scheme, token = auth.split() except ValueError: @@ -112,7 +109,7 @@ def authenticate(self, request): token, signing_key.key, algorithms=["RS256"], - audience=settings.KEYCLOAK['AUDIENCE'], + audience=KEYCLOAK_AUDIENCE, options={"verify_exp": True}, ) except (jwt.InvalidTokenError, jwt.ExpiredSignature, jwt.DecodeError) as exc: diff --git a/backend/api/migrations/0204_auto_20230321_0204.py b/backend/api/migrations/0204_auto_20230321_0204.py new file mode 100644 index 000000000..377a64f1d --- /dev/null +++ b/backend/api/migrations/0204_auto_20230321_0204.py @@ -0,0 +1,19 @@ +# Generated by Django 3.2.18 on 2023-03-21 02:04 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('api', '0203_auto_20230312_0823'), + ] + + operations = [ + migrations.RunSQL( + "UPDATE organization_address SET address_line_2 = address_line_2 || ' ' || address_line_3;" + ), + migrations.RunSQL( + "UPDATE organization_address SET state = state || ' ' || county" + ), + ] diff --git a/backend/api/migrations/0205_auto_20230321_0206.py b/backend/api/migrations/0205_auto_20230321_0206.py new file mode 100644 index 000000000..a147ead48 --- /dev/null +++ b/backend/api/migrations/0205_auto_20230321_0206.py @@ -0,0 +1,56 @@ +# Generated by Django 3.2.18 on 2023-03-21 02:06 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('api', '0204_auto_20230321_0204'), + ] + + operations = [ + migrations.RemoveField( + model_name='organizationaddress', + name='address_line_3', + ), + migrations.RemoveField( + model_name='organizationaddress', + name='county', + ), + migrations.AddField( + model_name='organizationaddress', + name='attorney_address_other', + field=models.CharField(blank=True, max_length=100, null=True), + ), + migrations.AddField( + model_name='organizationaddress', + name='attorney_city', + field=models.CharField(blank=True, max_length=100, null=True), + ), + migrations.AddField( + model_name='organizationaddress', + name='attorney_country', + field=models.CharField(blank=True, max_length=100, null=True), + ), + migrations.AddField( + model_name='organizationaddress', + name='attorney_postal_code', + field=models.CharField(blank=True, max_length=10, null=True), + ), + migrations.AddField( + model_name='organizationaddress', + name='attorney_province', + field=models.CharField(blank=True, max_length=50, null=True), + ), + migrations.AddField( + model_name='organizationaddress', + name='attorney_representativename', + field=models.CharField(blank=True, max_length=500, null=True), + ), + migrations.AddField( + model_name='organizationaddress', + name='attorney_street_address', + field=models.CharField(blank=True, max_length=500, null=True), + ), + ] diff --git a/backend/api/models/OrganizationAddress.py b/backend/api/models/OrganizationAddress.py index a717845ad..6b97eaf01 100644 --- a/backend/api/models/OrganizationAddress.py +++ b/backend/api/models/OrganizationAddress.py @@ -48,12 +48,6 @@ class OrganizationAddress(Auditable, EffectiveDates): null=True, db_comment="The second line of the organization's address." ) - address_line_3 = models.CharField( - blank=True, - max_length=100, - null=True, - db_comment="The third line of the organization's address." - ) city = models.CharField( blank=True, max_length=100, @@ -72,24 +66,61 @@ class OrganizationAddress(Auditable, EffectiveDates): null=True, db_comment="State or Province" ) - county = models.CharField( + country = models.CharField( + blank=True, + max_length=100, + null=True, + db_comment="Country" + ) + other = models.CharField( + blank=True, + max_length=100, + null=True, + db_comment="Other Address Details" + ) + attorney_city = models.CharField( + blank=True, + max_length=100, + null=True, + db_comment="City" + ) + attorney_postal_code = models.CharField( + blank=True, + max_length=10, + null=True, + db_comment="Postal Code" + ) + attorney_province = models.CharField( blank=True, max_length=50, null=True, - db_comment="County Name" + db_comment="Province" ) - country = models.CharField( + attorney_country = models.CharField( blank=True, max_length=100, null=True, - db_comment="Country" + db_comment="Attorney Country" ) - other = models.CharField( + attorney_address_other = models.CharField( blank=True, max_length=100, null=True, db_comment="Other Address Details" ) + attorney_street_address = models.CharField( + blank=True, + max_length=500, + null=True, + db_comment="Street Addrees PO Box" + ) + attorney_representativename = models.CharField( + blank=True, + max_length=500, + null=True, + db_comment="Representative Name" + ) + class Meta: db_table = 'organization_address' diff --git a/backend/api/notifications/notifications.py b/backend/api/notifications/notifications.py index 8d3301451..719548eeb 100644 --- a/backend/api/notifications/notifications.py +++ b/backend/api/notifications/notifications.py @@ -139,10 +139,11 @@ def send_email_for_notification(notification: NotificationMessage): return email_recipient = notification.user.email + organization_name = notification.user.organization.name from email.message import EmailMessage import smtplib msg = EmailMessage() - msg.set_content('You have received a new notification in TFRS.\nPlease sign in to view it.') + msg.set_content('{organization_name} has received a new notification in TFRS.\nPlease sign in to view it.'.format(organization_name=organization_name)) bcgov_cid = make_msgid() msg.add_alternative("""\ @@ -173,7 +174,7 @@ def send_email_for_notification(notification: NotificationMessage): line-height: 20px; padding-top: 20px; padding-bottom: 20px;"> -

You have received a new notification in TFRS.

+

{organization_name} has received a new notification in TFRS.

Please sign in to view it.

@@ -182,7 +183,7 @@ def send_email_for_notification(notification: NotificationMessage): - """.format(bcgov_cid=bcgov_cid[1:-1]), subtype='html') + """.format(bcgov_cid=bcgov_cid[1:-1], organization_name=organization_name), subtype='html') with open('assets/bcgov.png', 'rb') as image: msg.get_payload()[1].add_related( diff --git a/backend/api/serializers/ComplianceReport.py b/backend/api/serializers/ComplianceReport.py index cb262b64d..c37eb1618 100644 --- a/backend/api/serializers/ComplianceReport.py +++ b/backend/api/serializers/ComplianceReport.py @@ -605,7 +605,9 @@ def get_summary(self, obj): lines['25'] = lines['23'] - lines['24'] lines['27'] = lines['25'] + lines['26'] - if created_on < 2023: + # Penalty adjustment made by business area for + # 2023 and above compliance periods + if int(obj.compliance_period.description) <= 2022: lines['28'] = (lines['27'] * Decimal('-200.00')).max(Decimal(0)) else: lines['28'] = (lines['27'] * Decimal('-600.00')).max(Decimal(0)) @@ -1310,7 +1312,7 @@ def update(self, instance, validated_data): if summary_data and instance.supplements_id and \ summary_data.get('credits_offset_b', 0) and \ - summary_data.get('credits_offset_b', 0) > max_credit_offset: + summary_data.get('credits_offset_b', 0) > max_credit_offset and not self.strip_summary: raise (serializers.ValidationError( 'Insufficient available credit balance. Please adjust Line 26b.' )) diff --git a/backend/api/serializers/OrganizationAddressSerializer.py b/backend/api/serializers/OrganizationAddressSerializer.py index ce92ad5e3..7b78ea7de 100644 --- a/backend/api/serializers/OrganizationAddressSerializer.py +++ b/backend/api/serializers/OrganizationAddressSerializer.py @@ -44,5 +44,7 @@ def create(self, validated_data): class Meta: model = OrganizationAddress fields = ( - 'id', 'address_line_1', 'address_line_2', 'address_line_3', - 'city', 'postal_code', 'state', 'county', 'country') + 'id', 'address_line_1', 'address_line_2', + 'city', 'postal_code', 'state', 'country','attorney_city', + 'attorney_postal_code','attorney_province','attorney_country','attorney_address_other', + 'attorney_street_address','attorney_representativename') diff --git a/backend/api/serializers/User.py b/backend/api/serializers/User.py index 909c46ef7..25502d08b 100644 --- a/backend/api/serializers/User.py +++ b/backend/api/serializers/User.py @@ -187,8 +187,14 @@ def update(self, instance, validated_data): # if a user is mapped, then we limit the supplier's ability to edit external user account info if request.user.is_government_user or not instance.is_mapped: - UserCreationRequest.objects.filter(user_id=instance.id) \ - .update(external_username=request.data["external_username"], keycloak_email=request.data["keycloak_email"]) + external_user_info = {} + if request.data.get("external_username") is not None: + external_user_info['external_username'] = request.data["external_username"] + if request.data.get("keycloak_email") is not None: + external_user_info['keycloak_email'] = request.data["keycloak_email"] + if external_user_info: + UserCreationRequest.objects.filter(user_id=instance.id) \ + .update(**external_user_info) instance.save() diff --git a/backend/api/services/KeycloakAPI.py b/backend/api/services/KeycloakAPI.py deleted file mode 100644 index 2ab19f6e1..000000000 --- a/backend/api/services/KeycloakAPI.py +++ /dev/null @@ -1,134 +0,0 @@ -import requests -from tfrs.settings import KEYCLOAK - - -def get_token(): - """ - This function will generate the token for the Service Account. - This token is most likely going to be used to update information - for the logged-in user (not to be confused with the service account) - such as auto-mapping the user upon first login. - """ - token_url = '{keycloak}/auth/realms/{realm}/protocol/openid-connect/token'.format( - keycloak=KEYCLOAK['SERVICE_ACCOUNT_KEYCLOAK_API_BASE'], - realm=KEYCLOAK['SERVICE_ACCOUNT_REALM']) - - response = requests.post(token_url, - auth=(KEYCLOAK['SERVICE_ACCOUNT_CLIENT_ID'], - KEYCLOAK['SERVICE_ACCOUNT_CLIENT_SECRET']), - data={'grant_type': 'client_credentials'}) - - token = response.json()['access_token'] - - return token - - -def list_users(token): - """ - Retrieves the list of users found in Keycloak. - Not to be confused with the list of users found in the actual - database. - """ - users_url = '{keycloak}/auth/admin/realms/{realm}/users'.format( - keycloak=KEYCLOAK['SERVICE_ACCOUNT_KEYCLOAK_API_BASE'], - realm=KEYCLOAK['SERVICE_ACCOUNT_REALM']) - - headers = {'Authorization': 'Bearer {}'.format(token)} - - response = requests.get(users_url, - headers=headers) - - all_users = response.json() - for user in all_users: - users_detail_url = '{keycloak}/auth/admin/realms/{realm}/users/{user_id}/federated-identity'.format( - keycloak=KEYCLOAK['SERVICE_ACCOUNT_KEYCLOAK_API_BASE'], - realm=KEYCLOAK['SERVICE_ACCOUNT_REALM'], - user_id=user['id']) - - response = requests.get(users_detail_url, - headers=headers) - - if response.status_code != 200: - raise RuntimeError( - 'bad response code: {}'.format(response.status_code)) - - -def associate_federated_identity_with_user(token, id, provider, username): - users_url = '{keycloak}/auth/admin/realms/{realm}/users/{user_id}/federated-identity/{provider}'.format( - keycloak=KEYCLOAK['SERVICE_ACCOUNT_KEYCLOAK_API_BASE'], - realm=KEYCLOAK['SERVICE_ACCOUNT_REALM'], - user_id=id, - provider=provider) - - headers = {'Authorization': 'Bearer {}'.format(token)} - - data = { - 'userName': username - } - - response = requests.post(users_url, - headers=headers, - json=data) - - -def map_user(keycloak_user_id, tfrs_user_id): - """ - Maps the logged-in user to their keycloak account. - Please note that the get_token doesn't refer to the logged-in user's - account. - get_token retrieves the token for the service account that's going to - update the user information in keycloak. - """ - users_url = '{keycloak}/auth/admin/realms/{realm}/users/{user_id}'.format( - keycloak=KEYCLOAK['SERVICE_ACCOUNT_KEYCLOAK_API_BASE'], - realm=KEYCLOAK['SERVICE_ACCOUNT_REALM'], - user_id=keycloak_user_id) - - headers = {'Authorization': 'Bearer {}'.format(get_token())} - - data = { - 'attributes': { - 'user_id': tfrs_user_id - } - } - - response = requests.put( - users_url, - headers=headers, - json=data - ) - - if response.status_code not in [200, 201, 204]: - raise RuntimeError('bad response code: {}'.format(response.status_code)) - - -def create_user(token, user_name, maps_to_id): - """ - Creates the user account in Keycloak - """ - users_url = '{keycloak}/auth/admin/realms/{realm}/users'.format( - keycloak=KEYCLOAK['SERVICE_ACCOUNT_KEYCLOAK_API_BASE'], - realm=KEYCLOAK['SERVICE_ACCOUNT_REALM']) - - headers = {'Authorization': 'Bearer {}'.format(token)} - - data = { - 'enabled': True, - 'username': user_name, - 'attributes': { - 'user_id': maps_to_id - } - } - - response = requests.post(users_url, - headers=headers, - json=data) - - if response.status_code != 204: - raise RuntimeError( - 'bad response code: {}'.format(response.status_code)) - - created_user_response = requests.get(response.headers['Location'], - headers=headers) - - return created_user_response.json()['id'] diff --git a/backend/api/tests/base_test_case.py b/backend/api/tests/base_test_case.py index 652d1a4a5..2654ab6c3 100644 --- a/backend/api/tests/base_test_case.py +++ b/backend/api/tests/base_test_case.py @@ -103,39 +103,14 @@ def setUp(self): self.patcher = mock.patch('api.notifications.notifications.send_amqp_notification') self.patcher.start() - # generate a new RSA key - - private_key = rsa.generate_private_key( - public_exponent=65537, - key_size=2048, - backend=default_backend() - ) - - # override the jwt verification keys for testing - - settings.KEYCLOAK['ENABLED'] = True - settings.KEYCLOAK['TESTING_ENABLED'] = True - settings.KEYCLOAK['DOWNLOAD_CERTS'] = False - settings.KEYCLOAK['ISSUER'] = 'https://dev.loginproxy.gov.bc.ca/auth/realms/standard' - settings.KEYCLOAK['AUDIENCE'] = 'tfrs-on-gold-4308' - settings.KEYCLOAK['RS256_KEY'] = private_key.public_key().public_bytes( - format=serialization.PublicFormat.SubjectPublicKeyInfo, - encoding=serialization.Encoding.PEM - ).decode('utf-8') - - # the private half, used to sign our jwt (keycloak does this in actual use) - - self.private_key = private_key.private_bytes( - format=serialization.PrivateFormat.TraditionalOpenSSL, - encryption_algorithm=serialization.NoEncryption(), - encoding=serialization.Encoding.PEM - ).decode('utf-8') - self.users = dict(map( lambda u: (u, User.objects.get_by_natural_key(u)), self.usernames )) + # override the user authentication for testing + settings.UNIT_TESTING_ENABLED = True + self.clients = dict( map(lambda user: ( user.username, diff --git a/backend/api/tests/test_user_login_history.py b/backend/api/tests/test_user_login_history.py index cae933cb8..8312553d5 100644 --- a/backend/api/tests/test_user_login_history.py +++ b/backend/api/tests/test_user_login_history.py @@ -1,5 +1,4 @@ -from django.test import TestCase -from api.models import UserLoginHistory +from api.models.UserLoginHistory import UserLoginHistory from api.tests.base_test_case import BaseTestCase class UserLoginHistoryTestCase(BaseTestCase): diff --git a/backend/api/tests/test_users.py b/backend/api/tests/test_users.py index e9e9dda46..7021ba7b6 100644 --- a/backend/api/tests/test_users.py +++ b/backend/api/tests/test_users.py @@ -28,6 +28,7 @@ from .base_test_case import BaseTestCase from api.models.User import User +from api.models.UserCreationRequest import UserCreationRequest class TestUsers(BaseTestCase): @@ -115,3 +116,32 @@ def test_update_self(self): self.assertEqual(user.phone, '123456788') self.assertEqual(user.cell_phone, '123456789') self.assertNotEqual(user.username, 'new_user_1') + + def test_update_self_with_external_info(self): + """Test that updating external user info works""" + + payload = { + 'external_username': 'bceid1', + 'keycloak_email': 'email1@test.com' + } + + user = User.objects.get(id=self.users['fs_user_1'].id) + + user_creation_request = UserCreationRequest.objects.create( + external_username='', + keycloak_email='', + user=self.users['fs_user_1'] + ) + + response = self.clients['fs_user_1'].patch( + '/api/users/{}'.format(user.id), + content_type='application/json', + data=json.dumps(payload)) + + self.assertEqual(response.status_code, status.HTTP_200_OK) + + # not all fields should've been updated + user_creation_request = UserCreationRequest.objects.filter(user=self.users['fs_user_1'].id).first() + + self.assertEqual(user_creation_request.external_username, 'bceid1') + self.assertEqual(user_creation_request.keycloak_email, 'email1@test.com') \ No newline at end of file diff --git a/backend/api/viewsets/ComplianceReport.py b/backend/api/viewsets/ComplianceReport.py index 2fdf95838..684d5bafc 100644 --- a/backend/api/viewsets/ComplianceReport.py +++ b/backend/api/viewsets/ComplianceReport.py @@ -26,7 +26,7 @@ from api.paginations import BasicPagination from django.db.models import Q, F, Value, DateField from django.db.models.functions import Concat, Cast - +from django.db.models import Max class ComplianceReportViewSet(AuditableMixin, mixins.CreateModelMixin, mixins.RetrieveModelMixin, @@ -75,7 +75,10 @@ def get_queryset(self): if sorts: sortCondition = sorts[0].get('desc') sortId = sorts[0].get('id') - key_maps = {'compliance-period':'compliance_period__description', 'organization':'organization__name', 'updateTimestamp':'compliance_period__effective_date'} + key_maps = {'compliance-period':'compliance_period__description', + 'organization':'organization__name', + 'updateTimestamp':'compliance_period__effective_date', + 'submissionDate':'compliance_reports__update_timestamp'} if sortId=='displayname': if sortCondition: qs = qs.annotate(display_name=Concat(F('type__the_type'), Value(' '), F('compliance_period__description'))).order_by('-display_name') @@ -94,7 +97,11 @@ def get_queryset(self): else: sortType = "-" if sortCondition else "" sortString = f"{sortType}{key_maps[sortId]}" - qs = qs.order_by(sortString) + if sortType: + qs = qs.annotate(reports_updatedtime=Max('compliance_reports__update_timestamp')).order_by('-reports_updatedtime') + else: + qs = qs.annotate(reports_updatedtime=Max('compliance_reports__update_timestamp')).order_by('reports_updatedtime') + else: qs=qs.order_by('-compliance_period__effective_date') filters = request.data.get('filters') @@ -432,6 +439,13 @@ def list(self, request, *args, **kwargs): def paginated(self, request): queryset = self.get_queryset() page = self.paginate_queryset(queryset) + sorts = request.data.get('sorts') + if sorts: + if request.data.get('sorts')[0].get('id') == 'updateTimestamp': + if request.data.get('sorts')[0].get('desc'): + page = sorted(page, key=lambda x: [x.sort_date]) + else: + page = sorted(page, key=lambda x: [x.sort_date], reverse=True) if page is not None: serializer = self.get_serializer(page, many=True) return self.get_paginated_response(serializer.data) diff --git a/backend/tfrs/keycloak.py b/backend/tfrs/keycloak.py deleted file mode 100644 index 7646472f8..000000000 --- a/backend/tfrs/keycloak.py +++ /dev/null @@ -1,20 +0,0 @@ -import os - - -def config(): - return { - 'ENABLED': True, - 'REALM': os.getenv('KEYCLOAK_REALM_URL', 'https://dev.loginproxy.gov.bc.ca/auth/realms/standard'), - 'CLIENT_ID': os.getenv('KEYCLOAK_CLIENT_ID', 'tfrs-on-gold-4308'), - 'AUDIENCE': os.getenv('KEYCLOAK_AUDIENCE', 'tfrs-on-gold-4308'), - 'ISSUER': os.getenv('KEYCLOAK_ISSUER', 'https://dev.loginproxy.gov.bc.ca/auth/realms/standard'), - 'CERTS_URL': os.getenv('KEYCLOAK_CERTS_URL', - 'https://dev.loginproxy.gov.bc.ca/auth/realms/standard/protocol/openid-connect/certs'), - 'DOWNLOAD_CERTS': bool(os.getenv('KEYCLOAK_DOWNLOAD_CERTS', 'true').lower() in ['true', '1']), - 'SERVICE_ACCOUNT_REALM': os.getenv('KEYCLOAK_SA_REALM', 'standard'), - 'SERVICE_ACCOUNT_CLIENT_ID': os.getenv('KEYCLOAK_SA_CLIENT_ID', 'tfrs-on-gold-4308'), - 'SERVICE_ACCOUNT_KEYCLOAK_API_BASE': os.getenv('KEYCLOAK_SA_BASEURL', 'https://dev.loginproxy.gov.bc.ca'), - 'SERVICE_ACCOUNT_CLIENT_SECRET': os.getenv('KEYCLOAK_SA_CLIENT_SECRET', ''), - 'RS256_KEY': None, - 'TESTING_ENABLED': os.getenv('TESTING_ENABLED', False) - } diff --git a/backend/tfrs/settings.py b/backend/tfrs/settings.py index 9afc37b05..0a3ca78f0 100644 --- a/backend/tfrs/settings.py +++ b/backend/tfrs/settings.py @@ -18,7 +18,6 @@ from . import minio from . import amqp -from . import keycloak from . import email BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) @@ -38,6 +37,10 @@ WELL_KNOWN_ENDPOINT = os.getenv('WELL_KNOWN_ENDPOINT', 'https://dev.loginproxy.gov.bc.ca/auth/realms/standard/.well-known/openid-configuration') +KEYCLOAK_AUDIENCE = os.getenv('KEYCLOAK_AUDIENCE', 'tfrs-on-gold-4308') + +UNIT_TESTING_ENABLED = False + # SECURITY WARNING: don't run with debug turned on in production! # DEBUG = True DEBUG = os.getenv('DJANGO_DEBUG', 'False') == 'True' @@ -147,8 +150,6 @@ credentials=PlainCredentials(AMQP['USER'], AMQP['PASSWORD']) ) -KEYCLOAK = keycloak.config() - EMAIL = email.config() MINIO = minio.config() diff --git a/charts/tfrs-clamav/tfrs-clamav-wiremind-dev.yaml b/charts/tfrs-clamav/tfrs-clamav-wiremind-dev.yaml index 2c91cecb0..a743ce4a1 100644 --- a/charts/tfrs-clamav/tfrs-clamav-wiremind-dev.yaml +++ b/charts/tfrs-clamav/tfrs-clamav-wiremind-dev.yaml @@ -6,9 +6,9 @@ replicaCount: 1 image: # TODO: Switch to clamav/clamav container - repository: mailu/clamav + repository: artifacts.developer.gov.bc.ca/docker-remote/mailu/clamav tag: master@sha256:48c846508ebbb12dbce8389ca638e6314d988bfa0cae6e141370496a59a37e15 # If not defined, uses appVersion - pullPolicy: IfNotPresent + pullPolicy: docker-artifactory-secret priorityClassName: "" diff --git a/charts/tfrs-clamav/tfrs-clamav-wiremind-prod.yaml b/charts/tfrs-clamav/tfrs-clamav-wiremind-prod.yaml index 5f57e505e..307625e43 100644 --- a/charts/tfrs-clamav/tfrs-clamav-wiremind-prod.yaml +++ b/charts/tfrs-clamav/tfrs-clamav-wiremind-prod.yaml @@ -6,9 +6,9 @@ replicaCount: 1 image: # TODO: Switch to clamav/clamav container - repository: mailu/clamav + repository: artifacts.developer.gov.bc.ca/docker-remote/mailu/mailu/clamav tag: master@sha256:48c846508ebbb12dbce8389ca638e6314d988bfa0cae6e141370496a59a37e15 # If not defined, uses appVersion - pullPolicy: IfNotPresent + pullPolicy: docker-artifactory-secret priorityClassName: "" diff --git a/charts/tfrs-clamav/tfrs-clamav-wiremind-test.yaml b/charts/tfrs-clamav/tfrs-clamav-wiremind-test.yaml index 0d865b319..a7f1e6d40 100644 --- a/charts/tfrs-clamav/tfrs-clamav-wiremind-test.yaml +++ b/charts/tfrs-clamav/tfrs-clamav-wiremind-test.yaml @@ -6,9 +6,9 @@ replicaCount: 1 image: # TODO: Switch to clamav/clamav container - repository: mailu/clamav + repository: artifacts.developer.gov.bc.ca/docker-remote/mailu/mailu/clamav tag: master@sha256:48c846508ebbb12dbce8389ca638e6314d988bfa0cae6e141370496a59a37e15 # If not defined, uses appVersion - pullPolicy: IfNotPresent + pullPolicy: docker-artifactory-secret priorityClassName: "" diff --git a/charts/tfrs-rabbitmq/values-dev.yaml b/charts/tfrs-rabbitmq/values-dev.yaml index 2356dc56e..09cd94d29 100644 --- a/charts/tfrs-rabbitmq/values-dev.yaml +++ b/charts/tfrs-rabbitmq/values-dev.yaml @@ -1,7 +1,7 @@ global: imageRegistry: "artifacts.developer.gov.bc.ca/docker-remote" imagePullSecrets: - - default-dockercfg-qh29b + - docker-artifactory-secret image: repository: bitnami/rabbitmq diff --git a/charts/tfrs-rabbitmq/values-prod.yaml b/charts/tfrs-rabbitmq/values-prod.yaml index 11790365e..4b9a4cfaf 100644 --- a/charts/tfrs-rabbitmq/values-prod.yaml +++ b/charts/tfrs-rabbitmq/values-prod.yaml @@ -1,7 +1,7 @@ global: imageRegistry: "artifacts.developer.gov.bc.ca/docker-remote" imagePullSecrets: - - default-dockercfg-qh29b + - docker-artifactory-secret image: repository: bitnami/rabbitmq diff --git a/charts/tfrs-rabbitmq/values-test.yaml b/charts/tfrs-rabbitmq/values-test.yaml index 91052a5d6..fdb572f90 100644 --- a/charts/tfrs-rabbitmq/values-test.yaml +++ b/charts/tfrs-rabbitmq/values-test.yaml @@ -1,7 +1,7 @@ global: imageRegistry: "artifacts.developer.gov.bc.ca/docker-remote" imagePullSecrets: - - default-dockercfg-qh29b + - docker-artifactory-secret image: repository: bitnami/rabbitmq diff --git a/docker-compose-bckp.yml b/docker-compose-bckp.yml index 765121803..d335e2bf9 100644 --- a/docker-compose-bckp.yml +++ b/docker-compose-bckp.yml @@ -47,16 +47,6 @@ services: - RABBITMQ_PASSWORD=rabbitmq - RABBITMQ_HOST=rabbit - RABBITMQ_PORT=5672 - - KEYCLOAK_ENABLED=True - - KEYCLOAK_AUDIENCE=tfrs-on-gold-4308 - - KEYCLOAK_CLIENT_ID=tfrs-on-gold-4308 - - KEYCLOAK_REALM=standard - - KEYCLOAK_ISSUER=http://localhost:8888/auth/realms/tfrs - - KEYCLOAK_CERTS_URL=http://keycloak:8080/auth/realms/tfrs/protocol/openid-connect/certs - - KEYCLOAK_SA_BASEURL=http://keycloak:8080 - - KEYCLOAK_SA_REALM=tfrs - - KEYCLOAK_SA_CLIENT_ID=tfrs-app-sa - - KEYCLOAK_SA_CLIENT_SECRET=06dc71d6-1800-4f5d-b7b3-4c4fda226599 - DOCUMENTS_API_ENABLED=True - SMTP_SERVER_HOST=smtplogger - SMTP_SERVER_PORT=2500 @@ -105,14 +95,6 @@ services: - RABBITMQ_PASSWORD=rabbitmq - RABBITMQ_HOST=rabbit - RABBITMQ_PORT=5672 - - KEYCLOAK_ENABLED=False - - KEYCLOAK_AUTHORITY=http://localhost:8888/auth/realms/tfrs - - KEYCLOAK_CLIENT_ID=tfrs-on-gold-4308 - - KEYCLOAK_REALM=http://localhost:8888/auth/realms/tfrs - - KEYCLOAK_ISSUER=http://localhost:8888/auth/realms/tfrs - - KEYCLOAK_CALLBACK_URL=http://localhost:3000/authCallback - - KEYCLOAK_POST_LOGOUT_URL=http://localhost:3000/ - - KEYCLOAK_CERTS_URL=http://keycloak:8080/auth/realms/tfrs/protocol/openid-connect/certs volumes: - ./frontend:/app - node_modules:/app/node_modules @@ -144,33 +126,6 @@ services: ports: - 15672:15672 - 5672:5672 - postgres_keycloak: - image: postgres - volumes: - - postgres_keycloak_data:/var/lib/postgresql/data - environment: - POSTGRES_DB: keycloak - POSTGRES_USER: keycloak - POSTGRES_PASSWORD: keycloak - keycloak: - build: - context: ./keycloak - dockerfile: Dockerfile-keycloak - command: -Dkeycloak.migration.action=import -Dkeycloak.migration.provider=singleFile -Dkeycloak.migration.file=/tmp/realm.json -Dkeycloak.migration.strategy=IGNORE_EXISTING - environment: - DB_VENDOR: POSTGRES - DB_ADDR: postgres_keycloak - DB_DATABASE: keycloak - DB_USER: keycloak - DB_PASSWORD: keycloak - KEYCLOAK_USER: admin - KEYCLOAK_PASSWORD: admin - KEYCLOAK_LOGLEVEL: WARN - ROOT_LOGLEVEL: WARN - ports: - - 8888:8080 - depends_on: - - postgres_keycloak minio: image: minio/minio volumes: @@ -190,5 +145,4 @@ services: volumes: node_modules: postgres_data: - postgres_keycloak_data: minio_data: diff --git a/docker-compose.yml b/docker-compose.yml index b327a5ae9..6fbd7f4ca 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -28,16 +28,6 @@ services: - RABBITMQ_PASSWORD=rabbitmq - RABBITMQ_HOST=rabbit - RABBITMQ_PORT=5672 - - KEYCLOAK_ENABLED=True - - KEYCLOAK_AUDIENCE=tfrs-on-gold-4308 - - KEYCLOAK_CLIENT_ID=tfrs-on-gold-4308 - - KEYCLOAK_REALM=standard - - KEYCLOAK_ISSUER=https://dev.loginproxy.gov.bc.ca/auth/realms/standard - - KEYCLOAK_CERTS_URL=https://dev.loginproxy.gov.bc.ca/auth/realms/standard/protocol/openid-connect/certs - - KEYCLOAK_SA_BASEURL=https://dev.loginproxy.gov.bc.ca - - KEYCLOAK_SA_REALM=tfrs-on-gold-4308 - - KEYCLOAK_SA_CLIENT_ID=tfrs-app-sa - - KEYCLOAK_SA_CLIENT_SECRET=06dc71d6-1800-4f5d-b7b3-4c4fda226599 - DOCUMENTS_API_ENABLED=True - SMTP_SERVER_HOST=smtplogger - SMTP_SERVER_PORT=2500 @@ -123,5 +113,4 @@ services: volumes: node_modules: postgres_data: - postgres_keycloak_data: minio_data: diff --git a/frontend/src/actions/organizationActions.js b/frontend/src/actions/organizationActions.js index 77c774c3a..8842782f1 100644 --- a/frontend/src/actions/organizationActions.js +++ b/frontend/src/actions/organizationActions.js @@ -170,7 +170,7 @@ const addOrganizationSuccess = response => ({ const updateOrganization = (data, id) => (dispatch) => { dispatch(updateOrganizationRequest({ id, data })) - + return axios.put(`${Routes.BASE_URL}${Routes.ORGANIZATIONS_API}/${id}`, data) .then((response) => { dispatch(updateOrganizationSuccess(response.data)) diff --git a/frontend/src/app/components/AddressBuilder.js b/frontend/src/app/components/AddressBuilder.js index 275c3ec86..47c3b68c5 100644 --- a/frontend/src/app/components/AddressBuilder.js +++ b/frontend/src/app/components/AddressBuilder.js @@ -6,15 +6,11 @@ const AddressBuilder = (prop) => { } if (prop.address_line_2) { - address += ` ${prop.address_line_2}` - } - - if (prop.address_line_3) { - address += ` ${prop.address_line_3}` + address += `, ${prop.address_line_2}` } if (prop.city) { - address += ` ${prop.city}` + address += `, ${prop.city}` } if (prop.state) { @@ -25,6 +21,10 @@ const AddressBuilder = (prop) => { address += `, ${prop.postal_code}` } + if (prop.country) { + address += `, ${prop.country}` + } + return address } diff --git a/frontend/src/app/components/Unverified.js b/frontend/src/app/components/Unverified.js index 4ab1d7d2e..90b013c7e 100644 --- a/frontend/src/app/components/Unverified.js +++ b/frontend/src/app/components/Unverified.js @@ -40,7 +40,7 @@ Unverified.propTypes = { const mapDispatchToProps = (dispatch) => { return { - logout: () => dispatch(logout) + logout: () => dispatch(logout()) } } diff --git a/frontend/src/compliance_reporting/ComplianceReportingEditContainer.js b/frontend/src/compliance_reporting/ComplianceReportingEditContainer.js index 9ceff570e..52d89bce9 100644 --- a/frontend/src/compliance_reporting/ComplianceReportingEditContainer.js +++ b/frontend/src/compliance_reporting/ComplianceReportingEditContainer.js @@ -574,14 +574,8 @@ class ComplianceReportingEditContainer extends Component { } let organizationAddress = null - - if (item.hasSnapshot && - this.props.complianceReporting.snapshot && - this.props.complianceReporting.snapshot.organization.organizationAddress) { - ({ organizationAddress } = this.props.complianceReporting.snapshot.organization) - } else if (this.props.loggedInUser.organization.organizationAddress && - !this.props.loggedInUser.isGovernmentUser) { - ({ organizationAddress } = this.props.loggedInUser.organization) + if (item && item.organization && item.organization.organizationAddress) { + organizationAddress = item.organization.organizationAddress } return ([ @@ -614,18 +608,36 @@ class ComplianceReportingEditContainer extends Component { , +
,

- {organizationAddress && - AddressBuilder({ - address_line_1: organizationAddress.addressLine1, - address_line_2: organizationAddress.addressLine2, - address_line_3: organizationAddress.addressLine3, - city: organizationAddress.city, - state: organizationAddress.state, - postal_code: organizationAddress.postalCode - }) + {organizationAddress + ? ['Head Office: ', AddressBuilder({ + address_line_1: organizationAddress.addressLine1, + address_line_2: organizationAddress.addressLine2, + city: organizationAddress.city, + state: organizationAddress.state, + postal_code: organizationAddress.postalCode, + country: organizationAddress.country + })] + : null }

, +

+ {organizationAddress + ? ['B.C. Attorney Office: ', + organizationAddress.attorneyRepresentativename ? organizationAddress.attorneyRepresentativename + ', ' : '', + AddressBuilder({ + address_line_1: organizationAddress.attorneyStreetAddress, + address_line_2: organizationAddress.attorneyAddressOther, + city: organizationAddress.attorneyCity, + state: organizationAddress.attorneyProvince, + postal_code: organizationAddress.attorneyPostalCode, + country: organizationAddress.attorneyCountry + }) + ] + : null + } +

, { expect(tree).toMatchSnapshot() }) - test('Checking Functionality', ()=> { - const props= { - loggedInUser : { - isGovernmentUser: true + const props= { + loggedInUser : { + isGovernmentUser: true + }, + items : [{ + id: 654, + status: { + fuelSupplierStatus: "Submitted", + directorStatus: "Unreviewed", + analystStatus: "Unreviewed", + managerStatus: "Unreviewed" }, - items : [{ - id: 654, - status: { - fuelSupplierStatus: "Submitted", - directorStatus: "Unreviewed", - analystStatus: "Unreviewed", - managerStatus: "Unreviewed" - }, - type: "Compliance Report", - organization: { - id: 13, - name: "South Coast Fuels Co.", - type: 2, - status: { - id: 2, - status: "Archived", - description: "Inactive" - } - }, - compliancePeriod: { - id: 13, - description: "2023", - effectiveDate: "2023-01-01", - expirationDate: "2023-12-31", - displayOrder: 13 - }, - updateTimestamp: "2022-04-01T18:24:30.004626Z", - hasSnapshot: true, - readOnly: true, - groupId: 654, - supplementalReports: [], - supplements: null, - displayName: "Compliance Report for 2023", - sortDate: "2022-04-01T18:24:30.004626Z", - originalReportId: 654 - }], - isFetching:true, - navigate : jest.fn(), - itemsCount:0, - isEmpty: true, - isFetching: false, - getComplianceReports: jest.fn() - } + type: "Exclusion Report", + organization: { + id: 13, + name: "South Coast Fuels Co.", + type: 2, + status: { + id: 2, + status: "Archived", + description: "Inactive" + } + }, + compliancePeriod: { + id: 13, + description: "2023", + effectiveDate: "2023-01-01", + expirationDate: "2023-12-31", + displayOrder: 13 + }, + updateTimestamp: "2022-04-01T18:24:30.004626Z", + hasSnapshot: true, + readOnly: true, + groupId: 654, + supplementalReports: [{ + status:{ + fuelSupplierStatus: "Submitted", + directorStatus: "Unreviewed", + analystStatus: "Recommended", + managerStatus: "Unreviewed" + }, + supplementalReports: [{ + status:{ + fuelSupplierStatus: "Submitted", + directorStatus: "Unreviewed", + analystStatus: "Recommended", + managerStatus: "Unreviewed" + } + }], + } + ], + supplements: null, + displayName: "Compliance Report for 2023", + sortDate: "2022-04-01T18:24:30.004626Z", + originalReportId: 654 + }, { + id: 655, + status: { + fuelSupplierStatus: "Submitted", + directorStatus: "Unreviewed", + analystStatus: "Unreviewed", + managerStatus: "Unreviewed" + }, + type: "Compliance Report", + organization: { + id: 13, + name: "South Coast Fuels Co.", + type: 2, + status: { + id: 2, + status: "Archived", + description: "Inactive" + } + }, + compliancePeriod: { + id: 13, + description: "2023", + effectiveDate: "2023-01-01", + expirationDate: "2023-12-31", + displayOrder: 13 + }, + updateTimestamp: "2022-04-01T18:24:30.004626Z", + hasSnapshot: true, + readOnly: true, + groupId: 655, + supplementalReports: [], + supplements: 'test', + displayName: "Compliance Report for 2023", + sortDate: "2022-04-01T18:24:30.004626Z", + originalReportId: 654 + }], + isFetching:true, + navigate : jest.fn(), + itemsCount:0, + isEmpty: true, + isFetching: false, + getComplianceReports: jest.fn() + } + + test('Passing Items to render the table', ()=> { + const component = renderer.create( @@ -89,4 +142,39 @@ describe('ComplianceReportingTable', ()=> { const tree = component.toJSON() expect(tree).toMatchSnapshot() }) + + test('Checking Row onClick', ()=> { + render( + + + + ) + const tableRowEl = screen.getAllByRole('row') + fireEvent.click(tableRowEl[2]) + expect(props.navigate).toHaveBeenCalled() + }) + + test('Checking getComplianceReports triggering or not after clicking tableHeader', ()=> { + render( + + + + ) + + const tableHeader = screen.getByRole('columnheader', {name : 'Submission Date'}) + fireEvent.click(tableHeader) + expect(props.getComplianceReports).toHaveBeenCalled() + }) + + test('Checking Pagesize Change', ()=> { + render( + + + + ) + + const selectEl = screen.getByRole('combobox') + fireEvent.change(selectEl, {target: {value: '5'}}) + expect(props.getComplianceReports).toHaveBeenCalled() + }) }) \ No newline at end of file diff --git a/frontend/src/compliance_reporting/__tests__/components/__snapshots__/ComplianceReportingTable.test.js.snap b/frontend/src/compliance_reporting/__tests__/components/__snapshots__/ComplianceReportingTable.test.js.snap index 978afe5ce..1ce67b089 100644 --- a/frontend/src/compliance_reporting/__tests__/components/__snapshots__/ComplianceReportingTable.test.js.snap +++ b/frontend/src/compliance_reporting/__tests__/components/__snapshots__/ComplianceReportingTable.test.js.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`ComplianceReportingTable Checking Functionality 1`] = ` +exports[`ComplianceReportingTable Passing Items to render the table 1`] = `
@@ -189,13 +189,37 @@ exports[`ComplianceReportingTable Checking Functionality 1`] = ` onTouchStart={[Function]} />
+
+
+ Submission Date +
+
+
@@ -371,13 +395,37 @@ exports[`ComplianceReportingTable Checking Functionality 1`] = ` value="" />
+
+ +
@@ -458,7 +506,7 @@ exports[`ComplianceReportingTable Checking Functionality 1`] = ` } } > - - + Recommended Acceptance - Analyst
+
+ + 2022-04-01 11:24 am PDT + +
- -   - +
- -   - + South Coast Fuels Co.
- -   - + Compliance Report for 2023
- -   - + Submitted
- -   - + -
+ Submitted +
+
-   + 2022-04-01 11:24 am PDT
-   + 2022-04-01 11:24 am PDT
@@ -722,6 +797,21 @@ exports[`ComplianceReportingTable Checking Functionality 1`] = `  
+
+ +   + +
+
+ +   + +
+
+ +   + +
+
+ +   + +
+
+ +   + +
+
+ +   + +
+
+ +   + +
+
+ +   + +
@@ -1670,7 +1865,7 @@ exports[`ComplianceReportingTable should render the component 1`] = ` className="rt-thead -header" style={ { - "minWidth": "520px", + "minWidth": "615px", } } > @@ -1846,13 +2041,37 @@ exports[`ComplianceReportingTable should render the component 1`] = ` onTouchStart={[Function]} /> +
+
+ Submission Date +
+
+
@@ -2028,13 +2247,37 @@ exports[`ComplianceReportingTable should render the component 1`] = ` value="" />
+
+ +
@@ -2152,6 +2395,21 @@ exports[`ComplianceReportingTable should render the component 1`] = `  
+
+ +   + +
+
+ +   + +
+
+ +   + +
+
+ +   + +
+
+ +   + +
+
+ +   + +
+
+ +   + +
+
+ +   + +
+
+ +   + +
+
+ +   + +
diff --git a/frontend/src/compliance_reporting/components/ComplianceReportStatus.js b/frontend/src/compliance_reporting/components/ComplianceReportStatus.js index 358d470bc..96f8b632b 100644 --- a/frontend/src/compliance_reporting/components/ComplianceReportStatus.js +++ b/frontend/src/compliance_reporting/components/ComplianceReportStatus.js @@ -3,24 +3,9 @@ const ComplianceReportStatus = (item) => { return 'Supplemental Requested' } - if (item.status.analystStatus === 'Recommended') { - return 'Recommended Acceptance - Analyst' - } - - if (item.status.analystStatus === 'Not Recommended') { - return 'Recommended Rejection - Analyst' - } - if (item.status.analystStatus === 'Requested Supplemental') { return 'Supplemental Requested' } - if (item.status.analystStatus === 'Recommended') { - return 'Recommended Acceptance - Analyst' - } - - if (item.status.analystStatus === 'Not Recommended') { - return 'Recommended Rejection - Analyst' - } if (item.status.directorStatus === 'Accepted') { return 'Accepted' @@ -38,6 +23,14 @@ const ComplianceReportStatus = (item) => { return 'Recommended Rejection - Manager' } + if (item.status.analystStatus === 'Recommended') { + return 'Recommended Acceptance - Analyst' + } + + if (item.status.analystStatus === 'Not Recommended') { + return 'Recommended Rejection - Analyst' + } + if (item.status.fuelSupplierStatus) { return item.status.fuelSupplierStatus } diff --git a/frontend/src/compliance_reporting/components/ComplianceReportingTable.js b/frontend/src/compliance_reporting/components/ComplianceReportingTable.js index b3ca2f125..030e2a006 100644 --- a/frontend/src/compliance_reporting/components/ComplianceReportingTable.js +++ b/frontend/src/compliance_reporting/components/ComplianceReportingTable.js @@ -111,18 +111,15 @@ class ComplianceReportingTable extends Component { minWidth: 75 }, { accessor: (item) => { - // Temporarily left commented out for posterity and client feedback - // let report = item - // const { supplementalReports } = item - // if (supplementalReports.length > 0) { - // [report] = supplementalReports - // } - // while (report.supplementalReports && report.supplementalReports.length > 0) { - // [report] = report.supplementalReports - // } - // return ComplianceReportStatus(report) - - return ComplianceReportStatus(item) + let report = item + const { supplementalReports } = item + if (supplementalReports.length > 0) { + [report] = supplementalReports + } + while (report.supplementalReports && report.supplementalReports.length > 0) { + [report] = report.supplementalReports + } + return ComplianceReportStatus(report) }, className: 'col-status', Header: 'Current Status', @@ -142,7 +139,22 @@ class ComplianceReportingTable extends Component { } ) - }] + }, + { + accessor: item => item.updateTimestamp, + className: 'col-date', + Header: 'Submission Date', + id: 'submissionDate', + minWidth: 95, + Cell: row => ( + + { + row.original.updateTimestamp && moment(row.original.updateTimestamp).tz('America/Vancouver').format('YYYY-MM-DD h:mm a z') + } + + ) + } + ] const tableHeader = this.state.filters?.find(val => val.tableId) const filterable = true diff --git a/frontend/src/constants/routes/SecureDocumentUpload.js b/frontend/src/constants/routes/SecureDocumentUpload.js index 4e9bde35f..e80b9f3ee 100644 --- a/frontend/src/constants/routes/SecureDocumentUpload.js +++ b/frontend/src/constants/routes/SecureDocumentUpload.js @@ -3,7 +3,7 @@ const BASE_PATH = '/part_3_agreements' const SECURE_DOCUMENT_UPLOAD = { API, - ADD: `${BASE_PATH}/add/:type?`, + ADD: `${BASE_PATH}/add/:type`, COMMENTS_API: `${API}_comments`, DETAILS: `${BASE_PATH}/view/:id`, EDIT: `${BASE_PATH}/edit/:id`, diff --git a/frontend/src/dashboard/components/ComplianceReports.js b/frontend/src/dashboard/components/ComplianceReports.js index 29ed4fdd0..8fbaa93d7 100644 --- a/frontend/src/dashboard/components/ComplianceReports.js +++ b/frontend/src/dashboard/components/ComplianceReports.js @@ -9,7 +9,7 @@ const ComplianceReports = (props) => { const { isFinding, items, isGettingDashboard } = props.complianceReports const navigate = useNavigate() const complianceManagerIds = [] - const exclusionManagerIds= [] + const exclusionManagerIds = [] const placeholder = [] if (isFinding || isGettingDashboard) { @@ -32,9 +32,8 @@ const ComplianceReports = (props) => { } items.forEach((item) => { - let deepestSupplementalReport = null; - let { id } = item - let id_2 = id + const { id } = item + let id2 = id let { status } = item const { supplementalReports, type } = item const reportType = (type === 'Compliance Report') ? 'complianceReports' : 'exclusionReports' @@ -46,7 +45,7 @@ const ComplianceReports = (props) => { deepestSupplementalReport.supplementalReports.length > 0) { [deepestSupplementalReport] = deepestSupplementalReport.supplementalReports } - ({ status, id:id_2 } = deepestSupplementalReport) + ({ status, id: id2 } = deepestSupplementalReport) } if (status.fuelSupplierStatus === 'Submitted' && status.analystStatus === 'Unreviewed') { @@ -55,24 +54,21 @@ const ComplianceReports = (props) => { } if (['Not Recommended', 'Recommended'].indexOf(status.analystStatus) >= 0 && - status.managerStatus === 'Unreviewed' && status.directorStatus === "Unreviewed") { - if(placeholder.includes(id_2)){ - return + status.managerStatus === 'Unreviewed' && status.directorStatus === 'Unreviewed') { + if (placeholder.includes(id2)) { + return + } else { + placeholder.push(id2) } - else { - placeholder.push(id_2) - } - if (reportType === 'complianceReports'){ + if (reportType === 'complianceReports') { complianceManagerIds.push(id) awaitingReview[reportType].manager += 1 awaitingReview[reportType].total += 1 - - }else { + } else { exclusionManagerIds.push(id) awaitingReview[reportType].manager += 1 awaitingReview[reportType].total += 1 } - } if (['Not Recommended', 'Recommended'].indexOf(status.managerStatus) >= 0 && @@ -84,7 +80,7 @@ const ComplianceReports = (props) => { return (
-

Compliance & Exclusion Reports

+

Compliance & Exclusion Reports

There are:
@@ -119,12 +115,13 @@ const ComplianceReports = (props) => {
@@ -199,13 +196,14 @@ const ComplianceReports = (props) => { props.setFilter([{ id: 'compliance-period', value: '' - },{ - id:'managerIds', + }, + { + id: 'managerIds', value: { - ids : exclusionManagerIds + ids: exclusionManagerIds } }, { - tableId: [ { + tableId: [{ id: 'displayname', value: 'Exclusion Report' }, { @@ -213,13 +211,13 @@ const ComplianceReports = (props) => { value: 'Analyst' }] } - ], 'compliance-reporting') + ], 'compliance-reporting') return navigate(COMPLIANCE_REPORTING.LIST) }} type="button" > - {awaitingReview.exclusionReports.manager} awaiting compliance manager review + {awaitingReview.exclusionReports.manager} awaiting compliance manager review
diff --git a/frontend/src/dashboard/components/CreditTransactionsBCEID.js b/frontend/src/dashboard/components/CreditTransactionsBCEID.js index dd7f569f1..f9fe59bfb 100644 --- a/frontend/src/dashboard/components/CreditTransactionsBCEID.js +++ b/frontend/src/dashboard/components/CreditTransactionsBCEID.js @@ -22,7 +22,7 @@ const CreditTransactions = (props) => { items.forEach((item) => { if (['Buy', 'Sell'].indexOf(item.type.theType) >= 0) { - if (!item.isRescinded && ['Accepted', 'Submitted'].indexOf(item.status.status) >= 0) { + if (!item.isRescinded && ['Accepted', 'Submitted', 'Draft'].indexOf(item.status.status) >= 0) { inProgress.creditTransfers += 1 } } @@ -50,14 +50,14 @@ const CreditTransactions = (props) => { value: 'Credit Transfer' }, { id: 'status', - value: 'Proposed,Signed' + value: 'Proposed,Signed,Draft' }], 'credit-transfers') return navigate(CREDIT_TRANSACTIONS.LIST) }} type="button" > - Credit transfer(s) in progress + Credit transfer(s) in progress (including draft)
diff --git a/frontend/src/dashboard/components/OrganizationDetails.js b/frontend/src/dashboard/components/OrganizationDetails.js index 2c127b700..ff875a749 100644 --- a/frontend/src/dashboard/components/OrganizationDetails.js +++ b/frontend/src/dashboard/components/OrganizationDetails.js @@ -36,6 +36,7 @@ const OrganizationDetails = props => { {props.loggedInUser && props.loggedInUser.hasPermission(PERMISSIONS_ORGANIZATIONS.EDIT_FUEL_SUPPLIER) && + props.isGovernmentUser &&
- ) + ) + } } OrganizationEditForm.defaultProps = { @@ -237,27 +319,42 @@ OrganizationEditForm.propTypes = { name: PropTypes.string, addressLine1: PropTypes.string, addressLine2: PropTypes.string, - addressLine3: PropTypes.string, city: PropTypes.string, postalCode: PropTypes.string, state: PropTypes.string, country: PropTypes.string, - county: PropTypes.string, actionsType: PropTypes.number, status: PropTypes.number, - type: PropTypes.number + type: PropTypes.number, + org_status: PropTypes.number, + org_name: PropTypes.string, + org_addressLine1: PropTypes.string, + org_addressLine2: PropTypes.string, + org_city: PropTypes.string, + org_country: PropTypes.string, + org_postalCode: PropTypes.string, + org_state: PropTypes.string, + att_representativeName: PropTypes.string, + att_city: PropTypes.string, + att_country: PropTypes.string, + att_otherAddress: PropTypes.string, + att_streetAddress: PropTypes.string, + att_province: PropTypes.string, + att_postalCode: PropTypes.string }), handleInputChange: PropTypes.func.isRequired, handleSubmit: PropTypes.func.isRequired, loggedInUser: PropTypes.shape({ - hasPermission: PropTypes.func + hasPermission: PropTypes.func, + isGovernmentUser: PropTypes.bool }), referenceData: PropTypes.shape({ organizationActionsTypes: PropTypes.arrayOf(PropTypes.shape()), organizationStatuses: PropTypes.arrayOf(PropTypes.shape()), organizationTypes: PropTypes.arrayOf(PropTypes.shape()) }), - mode: PropTypes.oneOf(['add', 'edit', 'admin_edit']) + mode: PropTypes.oneOf(['add', 'edit', 'admin_edit']), + formIsValid: PropTypes.bool.isRequired } export default OrganizationEditForm diff --git a/frontend/src/organizations/components/__tests__/OrganizationEditForm.test.js b/frontend/src/organizations/components/__tests__/OrganizationEditForm.test.js new file mode 100644 index 000000000..dc86f0c12 --- /dev/null +++ b/frontend/src/organizations/components/__tests__/OrganizationEditForm.test.js @@ -0,0 +1,125 @@ +import React from 'react' +import { BrowserRouter } from 'react-router-dom' +import { render, screen, fireEvent } from '@testing-library/react' +import renderer from 'react-test-renderer' +import OrganizationEditForm from '../OrganizationEditForm' +import FontAwesome from '../../../../src/app/FontAwesome' // eslint-disable-line no-unused-vars + +const mockedNavigator = jest.fn() +jest.mock('react-router-dom', () => ({ + ...jest.requireActual('react-router-dom'), + useNavigate: () => mockedNavigator +})) + +describe('OrganizationEditForm', () => { + const props = { + fields: { + org_name: 'TFRS', + org_status: 1, + org_actionsType: '', + org_type: 2, + org_addressLine1: '', + org_addressLine2: '', + org_city: '', + org_state: '', + org_country: '', + org_postalCode: '', + att_representativeName: '', + att_streetAddress: '', + att_otherAddress: '', + att_city: '', + att_province: '', + att_country: '', + att_postalCode: '200001' + }, + handleInputChange: jest.fn(), + handleSubmit: jest.fn(), + loggedInUser: { + hasPermission: jest.fn() + }, + mode: 'add', + referenceData: { + organizationStatuses: [ + { id: 1, description: 'Active' }, + { id: 2, description: 'Inactive' } + ], + organizationTypes: [ + { id: 1 }, + { id: 2, description: 'Part 3 Fuel Supplier' } + ] + }, + formIsValid: true + } + test('Should render the component', () => { + props.isGovernmentUser = true + const component = renderer.create( + + + + ) + + const tree = component.toJSON() + expect(tree).toMatchSnapshot() + }) + + test('Checking SaveButton', () => { + props.isGovernmentUser = true + render( + + + + ) + + const saveBtnEl = screen.getByRole('button', { name: /Save/i }) + console.log(saveBtnEl) + fireEvent.click(saveBtnEl) + expect(props.handleSubmit).toHaveBeenCalledTimes(1) + }) + + test('Checking Back Button', () => { + render( + + ) + const backBtnEl = screen.getByRole('button', { name: /Back/i }) + const val = fireEvent.click(backBtnEl) + expect(val).toBe(true) + }) + + test('Supplier Status should be in the DOM', () => { + props.loggedInUser.hasPermission = (val) => val === 'EDIT_FUEL_SUPPLIERS' + render( + + + + ) + const statusEl = screen.getByLabelText('Active') + expect(statusEl).toBeInTheDocument() + }) + test('Supplier Status should not be in the DOM', () => { + // makikng the hasPermission function to return false + props.loggedInUser.hasPermission = (val) => val === 'EDIT_FUEL' + render( + + + + ) + const statusEl1 = screen.queryByLabelText('Active') + expect(statusEl1).not.toBeInTheDocument() + }) + + test('Checking Edit mode of the organization', () => { + props.mode = 'edit' + props.isGovernmentUser = true + render( + + + + ) + const headerEl = screen.getByRole('heading', { name: 'Edit Organization' }) + expect(headerEl).toBeInTheDocument() + const supplierTypeEl = screen.queryByRole('radio', { + name: 'Supplier Type :' + }) + expect(supplierTypeEl).not.toBeInTheDocument() + }) +}) diff --git a/frontend/src/organizations/components/__tests__/__snapshots__/OrganizationEditForm.test.js.snap b/frontend/src/organizations/components/__tests__/__snapshots__/OrganizationEditForm.test.js.snap new file mode 100644 index 000000000..a09329cf2 --- /dev/null +++ b/frontend/src/organizations/components/__tests__/__snapshots__/OrganizationEditForm.test.js.snap @@ -0,0 +1,370 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`OrganizationEditForm Should render the component 1`] = ` +
+

+ Create + Organization +

+
+
+
+
+
+ +
+
+
+
+
+

+ Head Office: +

+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+

+ Corporation or Attorney in B.C. +

+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+
+
+
+ + +
+
+
+`; diff --git a/frontend/src/schedule_summary/__tests__/Part3/Part3LineData.test.js b/frontend/src/schedule_summary/__tests__/Part3/Part3LineData.test.js index 9ac02f8ea..b51c8db9b 100644 --- a/frontend/src/schedule_summary/__tests__/Part3/Part3LineData.test.js +++ b/frontend/src/schedule_summary/__tests__/Part3/Part3LineData.test.js @@ -19,7 +19,7 @@ describe('Part3 Line Data', () => { } part3[SCHEDULE_SUMMARY.LINE_25][2] = cellFormatNumeric(summary.lines['25']) part3[SCHEDULE_SUMMARY.LINE_26][2] = cellFormatNumeric(summary.lines['26']) - const result = lineData(part3, summary, complianceReport) + const result = lineData(part3, summary, 2023, complianceReport) expect(result[SCHEDULE_SUMMARY.LINE_26][2].value).toBe(0) expect(result[SCHEDULE_SUMMARY.LINE_26][2].className).toBe('numeric') }) @@ -38,7 +38,7 @@ describe('Part3 Line Data', () => { } part3[SCHEDULE_SUMMARY.LINE_25][2] = cellFormatNumeric(summary.lines['25']) part3[SCHEDULE_SUMMARY.LINE_26][2] = cellFormatNumeric(summary.lines['26']) - const result = lineData(part3, summary, complianceReport) + const result = lineData(part3, summary, 2023, complianceReport) expect(result[SCHEDULE_SUMMARY.LINE_26][2].value).toBe(20) }) @@ -57,7 +57,7 @@ describe('Part3 Line Data', () => { } part3[SCHEDULE_SUMMARY.LINE_25][2] = cellFormatNumeric(summary.lines['25']) part3[SCHEDULE_SUMMARY.LINE_26][2] = cellFormatNumeric(summary.lines['26']) - const result = lineData(part3, summary, complianceReport) + const result = lineData(part3, summary, 2023, complianceReport) expect(result[SCHEDULE_SUMMARY.LINE_26][2].value).toBe(20) }) }) diff --git a/frontend/src/secure_file_submission/components/SecureFileSubmissionFormDetails.js b/frontend/src/secure_file_submission/components/SecureFileSubmissionFormDetails.js index 2cb141362..216ed7af5 100644 --- a/frontend/src/secure_file_submission/components/SecureFileSubmissionFormDetails.js +++ b/frontend/src/secure_file_submission/components/SecureFileSubmissionFormDetails.js @@ -184,6 +184,19 @@ class SecureFileSubmissionFormDetails extends Component {
+
+
+

Use the Attachment Type category "Other" to submit documents and attachments associated with existing:

+
    +
  • Compliance Reports
  • +
  • Exclusion Reports
  • +
  • Carbon Intensity Applications
  • +
  • Initiative Plans
  • +
  • Credit Transaction Proposal
  • +
+

Use the Comment field to identify what records this file submission apply to. For example, "This submission pertains to our 2021 Compliance Report".

+
+
diff --git a/frontend/src/secure_file_submission/components/SecureFileSubmissionsPage.js b/frontend/src/secure_file_submission/components/SecureFileSubmissionsPage.js index ae7f648f3..c9611fdf4 100644 --- a/frontend/src/secure_file_submission/components/SecureFileSubmissionsPage.js +++ b/frontend/src/secure_file_submission/components/SecureFileSubmissionsPage.js @@ -23,6 +23,11 @@ const SecureFileSubmissionsPage = (props) => { evidence to the Government of British Columbia.

} + {!props.loggedInUser.isGovernmentUser && +

+ Use the New Submission category "Other" to submit documents associated with existing: Compliance and Exclusion Reports, Carbon Intensity Applications, Initiative Plans and Credit Transaction Proposals. +

+ }
{props.loggedInUser.hasPermission(PERMISSIONS_CREDIT_TRANSACTIONS.PROPOSE) && diff --git a/openshift-v4/templates/backend/backend-dc.yaml b/openshift-v4/templates/backend/backend-dc.yaml index 94d3545a7..5165ddf34 100644 --- a/openshift-v4/templates/backend/backend-dc.yaml +++ b/openshift-v4/templates/backend/backend-dc.yaml @@ -27,33 +27,6 @@ parameters: - name: VERSION displayName: null description: image tag name for output - required: true -- name: KEYCLOAK_SA_BASEURL - displayName: KEYCLOAK_SA_BASEURL - required: true -- name: KEYCLOAK_SA_CLIENT_ID - displayName: KEYCLOAK_SA_CLIENT_ID - required: true -- name: KEYCLOAK_SA_REALM - displayName: KEYCLOAK_SA_REALM - description: 'Valid values: tfrs-dev, tfrs, tfrs' - required: true -- name: KEYCLOAK_AUDIENCE - displayName: KEYCLOAK_AUDIENCE - description: 'Valid values: tfrs-dev, tfrs, tfrs' - required: true -- name: KEYCLOAK_CERTS_URL - displayName: KEYCLOAK_CERTS_URL - required: true -- name: KEYCLOAK_CLIENT_ID - displayName: KEYCLOAK_CLIENT_ID - description: 'Valid values: tfrs-dev, tfrs, tfrs' - required: true -- name: KEYCLOAK_ISSUER - displayName: KEYCLOAK_ISSUER - required: true -- name: KEYCLOAK_REALM - displayName: KEYCLOAK_REALM required: true - name: CPU_REQUEST displayName: Requested CPU @@ -79,6 +52,10 @@ parameters: displayName: The database service name description: The database service name required: true +- name: KEYCLOAK_AUDIENCE + displayName: The keycloak audience + description: The keycloak audience + required: true - name: WELL_KNOWN_ENDPOINT displayName: The database service name description: The database service name @@ -151,26 +128,6 @@ objects: - containerPort: 8080 protocol: TCP env: - - name: KEYCLOAK_SA_BASEURL - value: ${KEYCLOAK_SA_BASEURL} - - name: KEYCLOAK_SA_CLIENT_ID - value: ${KEYCLOAK_SA_CLIENT_ID} - - name: KEYCLOAK_SA_REALM - value: ${KEYCLOAK_SA_REALM} - - name: KEYCLOAK_AUDIENCE - value: ${KEYCLOAK_AUDIENCE} - - name: KEYCLOAK_CERTS_URL - value: ${KEYCLOAK_CERTS_URL} - - name: KEYCLOAK_CLIENT_ID - value: ${KEYCLOAK_CLIENT_ID} - - name: KEYCLOAK_ENABLED - value: 'true' - - name: KEYCLOAK_ISSUER - value: ${KEYCLOAK_ISSUER} - - name: KEYCLOAK_REALM - value: ${KEYCLOAK_REALM} - - name: KEYCLOAK_SA_CLIENT_SECRET - value: notbeingused - name: EMAIL_FROM_ADDRESS value: donotreply@gov.bc.ca - name: EMAIL_SENDING_ENABLED @@ -251,6 +208,8 @@ objects: key: DJANGO_SECRET_KEY - name: APP_CONFIG value: "/opt/app-root/src/gunicorn.cfg.py" + - name: KEYCLOAK_AUDIENCE + value: "${KEYCLOAK_AUDIENCE}" - name: WELL_KNOWN_ENDPOINT value: "${WELL_KNOWN_ENDPOINT}" resources: diff --git a/openshift-v4/templates/backend/backend-secrets.yaml b/openshift-v4/templates/backend/backend-secrets.yaml index ba4c573c4..c664635ae 100644 --- a/openshift-v4/templates/backend/backend-secrets.yaml +++ b/openshift-v4/templates/backend/backend-secrets.yaml @@ -1,22 +1,12 @@ apiVersion: template.openshift.io/v1 kind: Template parameters: -- name: KEYCLOAK_SA_CLIENT_SECRET - description: teh keycloak sa client secret - required: true - name: DJANGO_SECRET_KEY description: "secret used by Django" from: "[a-zA-Z0-9]{50}" generate: expression required: true objects: -- apiVersion: v1 - kind: Secret - metadata: - annotations: null - name: keycloak-sa-client-secret - stringData: - KEYCLOAK_SA_CLIENT_SECRET: ${KEYCLOAK_SA_CLIENT_SECRET} - apiVersion: v1 kind: Secret metadata: diff --git a/openshift-v4/templates/keycloak/README.md b/openshift-v4/templates/keycloak/README.md deleted file mode 100644 index f2f75a3c6..000000000 --- a/openshift-v4/templates/keycloak/README.md +++ /dev/null @@ -1,16 +0,0 @@ -### Files included -* keycloak-secret.yaml includes keycloak secrets, it is only used by .pipeline/lib/keycloak.js - -### Create Secret keycloak-secret.yaml in tools, dev, test and prod env. The value for tools and dev should be same -oc process -f keycloak-secret.yaml -KEYCLOAK_SA_CLIENT_SECRET=[Clients->sa client->Credentials->secret] \ -clientId=[sa client] \ -clientSecret=[same value of KEYCLOAK_SA_CLIENT_SECRET] \ -tfrsPublic=[public client id, it is not tfrs, on sso console click Clients->tfrs] \ -realmId=[realmId] \ -host=[sso host name] \ -| oc create -f - -n 0ab226-xxx --dry-run=client -Notes: in keycloak, there are two clients: one is sa client, the other one is public client - - - diff --git a/openshift-v4/templates/keycloak/keycloak-secret.yaml b/openshift-v4/templates/keycloak/keycloak-secret.yaml deleted file mode 100644 index 0681f3474..000000000 --- a/openshift-v4/templates/keycloak/keycloak-secret.yaml +++ /dev/null @@ -1,37 +0,0 @@ -apiVersion: template.openshift.io/v1 -kind: Template -metadata: - creationTimestamp: null - name: tfrs-keycloak-secret -parameters: - - name: KEYCLOAK_SA_CLIENT_SECRET - description: teh secret for private client - required: true - - name: clientId - description: - required: true - - name: clientSecret - description: - required: true - - name: tfrsPublic - description: the key in the pubic client url, is not the client id - required: true - - name: realmId - description: - required: true - - name: host - description: host is sso host name, such as sso-dev.[ocp name].gov.bc.ca,sso-test.[ocp name].gov.bc.ca and sso.[ocp name].gov.bc.ca - required: true -objects: - - apiVersion: v1 - kind: Secret - metadata: - name: tfrs-keycloak - labels: {} - stringData: - KEYCLOAK_SA_CLIENT_SECRET: "${KEYCLOAK_SA_CLIENT_SECRET}" - clientId: "${clientId}" - clientSecret: "${clientSecret}" - tfrsPublic: "${tfrsPublic}" - realmId: "${realmId}" - host: "${host}" diff --git a/openshift-v4/templates/nagios/nagios-dc.yaml b/openshift-v4/templates/nagios/nagios-dc.yaml index b694b34f8..24ec18251 100644 --- a/openshift-v4/templates/nagios/nagios-dc.yaml +++ b/openshift-v4/templates/nagios/nagios-dc.yaml @@ -7,28 +7,6 @@ metadata: parameters: - name: ENV_NAME required: true -- name: KEYCLOAK_CLIENT_ID - displayName: KEYCLOAK_CLIENT_ID - description: 'Valid values: tfrs-dev, tfrs, tfrs' - required: true -- name: KEYCLOAK_SA_REALM - displayName: KEYCLOAK_SA_REALM - description: 'Valid values: tfrs-dev, tfrs, tfrs' - required: true -- name: KEYCLOAK_SA_CLIENT_ID - displayName: KEYCLOAK_SA_CLIENT_ID - description: 'Valid values: tfrs-dev-django-sa, tfrs-django-sa, tfrs-django-sa' - required: true -- name: KEYCLOAK_SA_BASEURL - displayName: KEYCLOAK_SA_BASEURL - description: 'Valid values: https://dev.oidc.gov.bc.ca, https://test.oidc.gov.bc.ca, - https://oidc.gov.bc.ca' - required: true -- name: KEYCLOAK_REALM - displayName: KEYCLOAK_REALM - description: 'Valid values: https://dev.oidc.gov.bc.ca/auth/realms/tfrs-dev, https://test.oidc.gov.bc.ca/auth/realms/tfrs, - https://oidc.gov.bc.ca/auth/realms/tfrs' - required: true - name: SMTP_SERVER_HOST displayName: SMTP_SERVER_HOST description: All environment use same email server @@ -135,21 +113,6 @@ objects: secretKeyRef: name: tfrs-minio-${ENV_NAME} key: MINIO_SECRET_KEY - - name: KEYCLOAK_SA_CLIENT_SECRET - valueFrom: - secretKeyRef: - name: keycloak-sa-client-secret - key: KEYCLOAK_SA_CLIENT_SECRET - - name: KEYCLOAK_CLIENT_ID - value: "${KEYCLOAK_CLIENT_ID}" - - name: KEYCLOAK_SA_REALM - value: "${KEYCLOAK_SA_REALM}" - - name: KEYCLOAK_SA_CLIENT_ID - value: "${KEYCLOAK_SA_CLIENT_ID}" - - name: KEYCLOAK_SA_BASEURL - value: "${KEYCLOAK_SA_BASEURL}" - - name: KEYCLOAK_REALM - value: "${KEYCLOAK_REALM}" - name: SMTP_SERVER_HOST value: "${SMTP_SERVER_HOST}" - name: DATABASE_SERVICE_NAME diff --git a/openshift-v4/templates/nagios/nagios3/commands.cfg b/openshift-v4/templates/nagios/nagios3/commands.cfg index 5ba2b4cdc..36b12d124 100644 --- a/openshift-v4/templates/nagios/nagios3/commands.cfg +++ b/openshift-v4/templates/nagios/nagios3/commands.cfg @@ -75,11 +75,6 @@ define command { command_line /etc/nagios3/commands/check_minio_connection.sh } -define command { - command_name check_keycloak_connection - command_line /etc/nagios3/commands/check_keycloak_connection.sh -} - define command { command_name check_email_connection command_line /etc/nagios3/commands/check_email_connection.sh diff --git a/openshift-v4/templates/nagios/nagios3/commands/check_keycloak_connection.py b/openshift-v4/templates/nagios/nagios3/commands/check_keycloak_connection.py deleted file mode 100755 index ec7ddc0c7..000000000 --- a/openshift-v4/templates/nagios/nagios3/commands/check_keycloak_connection.py +++ /dev/null @@ -1,62 +0,0 @@ -import os -import requests - -KEYCLOAK = { - 'REALM': os.getenv('KEYCLOAK_REALM', 'http://localhost:8888/auth/realms/tfrs'), - 'CLIENT_ID': os.getenv('KEYCLOAK_CLIENT_ID', 'tfrs-app'), - 'SERVICE_ACCOUNT_REALM': os.getenv('KEYCLOAK_SA_REALM', 'tfrs'), - 'SERVICE_ACCOUNT_CLIENT_ID': os.getenv('KEYCLOAK_SA_CLIENT_ID', 'tfrs'), - 'SERVICE_ACCOUNT_KEYCLOAK_API_BASE': os.getenv('KEYCLOAK_SA_BASEURL', 'http://localhost:8888'), - 'SERVICE_ACCOUNT_CLIENT_SECRET': os.getenv('KEYCLOAK_SA_CLIENT_SECRET', '') - } - -""" -This function will generate the token for the Service Account. -This token is most likely going to be used to update information -for the logged-in user (not to be confused with the service account) -such as auto-mapping the user upon first login. -""" -token_url = '{keycloak}/auth/realms/{realm}/protocol/openid-connect/token'.format( - keycloak=KEYCLOAK['SERVICE_ACCOUNT_KEYCLOAK_API_BASE'], - realm=KEYCLOAK['SERVICE_ACCOUNT_REALM']) - -response = requests.post(token_url, - auth=(KEYCLOAK['SERVICE_ACCOUNT_CLIENT_ID'], - KEYCLOAK['SERVICE_ACCOUNT_CLIENT_SECRET']), - data={'grant_type': 'client_credentials'}) - -token = response.json()['access_token'] - -""" -Retrieves the list of users found in Keycloak. -Not to be confused with the list of users found in the actual -database. -""" -users_url = '{keycloak}/auth/admin/realms/{realm}/users'.format( - keycloak=KEYCLOAK['SERVICE_ACCOUNT_KEYCLOAK_API_BASE'], - realm=KEYCLOAK['SERVICE_ACCOUNT_REALM']) - -headers = {'Authorization': 'Bearer {}'.format(token)} - -response = requests.get(users_url, - headers=headers) - -i=0 -all_users = response.json() -for user in all_users: - i=i+1 - users_detail_url = '{keycloak}/auth/admin/realms/{realm}/users/{user_id}/federated-identity'.format( - keycloak=KEYCLOAK['SERVICE_ACCOUNT_KEYCLOAK_API_BASE'], - realm=KEYCLOAK['SERVICE_ACCOUNT_REALM'], - user_id=user['id']) - - response = requests.get(users_detail_url, - headers=headers) - if i>=1: - break - - -if response.status_code == 200: - print('OK - Keycloak connection checking passed') -else: - print('CRITICAL - Keycloak connection checking failed') diff --git a/openshift-v4/templates/nagios/nagios3/commands/check_keycloak_connection.sh b/openshift-v4/templates/nagios/nagios3/commands/check_keycloak_connection.sh deleted file mode 100755 index 31b77c503..000000000 --- a/openshift-v4/templates/nagios/nagios3/commands/check_keycloak_connection.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/bash - -keycloakConnectionTest=$(python3 /etc/nagios3/commands/check_keycloak_connection.py) -echo $keycloakConnectionTest -if [[ $keycloakConnectionTest == OK* ]]; -then - exit 0 -else - exit 2 -fi \ No newline at end of file diff --git a/openshift-v4/templates/nagios/nagios3/conf.d/services-other-dev.cfg b/openshift-v4/templates/nagios/nagios3/conf.d/services-other-dev.cfg index 39a5924ba..646222a32 100644 --- a/openshift-v4/templates/nagios/nagios3/conf.d/services-other-dev.cfg +++ b/openshift-v4/templates/nagios/nagios3/conf.d/services-other-dev.cfg @@ -37,19 +37,6 @@ define service { notification_period 24x7 notifications_enabled 1 } -define service { - host_name keycloak-dev - service_description Keycloak connection check - check_command check_keycloak_connection - check_interval 5 - retry_interval 1 - max_check_attempts 5 - check_period 24x7 - contact_groups tfrs-devops - notification_interval 0 - notification_period 24x7 - notifications_enabled 1 -} define service { host_name email-dev service_description Email connection check diff --git a/openshift-v4/templates/nagios/nagios3/conf.d/services-other-prod.cfg b/openshift-v4/templates/nagios/nagios3/conf.d/services-other-prod.cfg index dfb9f34d2..64114e318 100644 --- a/openshift-v4/templates/nagios/nagios3/conf.d/services-other-prod.cfg +++ b/openshift-v4/templates/nagios/nagios3/conf.d/services-other-prod.cfg @@ -38,19 +38,6 @@ define service { # notification_period 24x7 # notifications_enabled 1 #} -define service { - host_name keycloak-prod - service_description Keycloak connection check - check_command check_keycloak_connection - check_interval 5 - retry_interval 1 - max_check_attempts 5 - check_period 24x7 - contact_groups tfrs-devops - notification_interval 0 - notification_period 24x7 - notifications_enabled 1 -} define service { host_name email-prod service_description Email connection check diff --git a/openshift-v4/templates/nagios/nagios3/conf.d/services-other-test.cfg b/openshift-v4/templates/nagios/nagios3/conf.d/services-other-test.cfg index 2be0dce15..5ecceda70 100644 --- a/openshift-v4/templates/nagios/nagios3/conf.d/services-other-test.cfg +++ b/openshift-v4/templates/nagios/nagios3/conf.d/services-other-test.cfg @@ -38,19 +38,6 @@ define service { # notification_period 24x7 # notifications_enabled 1 #} -define service { - host_name keycloak-test - service_description Keycloak connection check - check_command check_keycloak_connection - check_interval 5 - retry_interval 1 - max_check_attempts 5 - check_period 24x7 - contact_groups tfrs-devops - notification_interval 0 - notification_period 24x7 - notifications_enabled 1 -} define service { host_name email-test service_description Email connection check diff --git a/openshift-v4/templates/nginx-runtime/Dockerfile b/openshift-v4/templates/nginx-runtime/Dockerfile index 627c83015..c371da15e 100644 --- a/openshift-v4/templates/nginx-runtime/Dockerfile +++ b/openshift-v4/templates/nginx-runtime/Dockerfile @@ -1,5 +1,5 @@ # Use the offical nginx (based on debian) -FROM nginx:stable +FROM artifacts.developer.gov.bc.ca/docker-remote/nginx:stable ENV STI_SCRIPTS_PATH=/usr/libexec/s2i diff --git a/openshift-v4/templates/nginx-runtime/nginx-runtime.yaml b/openshift-v4/templates/nginx-runtime/nginx-runtime.yaml index 0258f36f2..fec18c8cb 100644 --- a/openshift-v4/templates/nginx-runtime/nginx-runtime.yaml +++ b/openshift-v4/templates/nginx-runtime/nginx-runtime.yaml @@ -37,12 +37,14 @@ objects: source: contextDir: openshift-v4/templates/nginx-runtime git: - ref: openshift-v4-migration + ref: master uri: https://github.com/bcgov/tfrs.git type: Git strategy: dockerStrategy: {} type: Docker + pullSecret: + name: docker-artifactory-secret triggers: [] status: lastVersion: 0 diff --git a/security-scan/scan-coordinator/Dockerfile b/security-scan/scan-coordinator/Dockerfile index 8ac32fe2b..144b54f38 100644 --- a/security-scan/scan-coordinator/Dockerfile +++ b/security-scan/scan-coordinator/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.19 +FROM artifacts.developer.gov.bc.ca/docker-remote/golang:1.19 WORKDIR /usr/src/app