diff --git a/backend.env b/backend.env new file mode 100644 index 000000000..2ebdae92e --- /dev/null +++ b/backend.env @@ -0,0 +1,22 @@ +DATABASE_NAME=postgres +DATABASE_USER=postgres +DATABASE_PASSWORD=postgres +DATABASE_ENGINE=postgresql +DATABASE_SERVICE_NAME=postgresql +POSTGRESQL_SERVICE_HOST=db +POSTGRESQL_SERVICE_PORT=5432 +KEYCLOAK_ENABLED=True +KEYCLOAK_AUDIENCE=zeva-app +KEYCLOAK_CLIENT_ID=zeva-app +KEYCLOAK_REALM=http://keycloak:8080/auth/realms/zeva +KEYCLOAK_ISSUER=http://localhost:8888/auth/realms/zeva +KEYCLOAK_CERTS_URL=http://keycloak:8080/auth/realms/zeva/protocol/openid-connect/certs +KEYCLOAK_SA_BASEURL=http://keycloak:8080 +KEYCLOAK_SA_REALM=zeva +KEYCLOAK_SA_CLIENT_ID=zeva-app-sa +KEYCLOAK_SA_CLIENT_SECRET=06dc71d6-1800-4f5d-b7b3-4c4fda226599 +SMTP_SERVER_HOST=127.0.0.1 +SMTP_SERVER_PORT=2500 +EMAIL_SENDING_ENABLED=True +EMAIL_FROM_ADDRESS=zeva-dev@test.local +DJANGO_DEBUG=True \ No newline at end of file diff --git a/backend/Dockerfile b/backend/Dockerfile index 1bbab6e22..19ce8bc20 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -1,8 +1,7 @@ -FROM python:3.7-stretch -ENV PYTHONUNBUFFERED 1 -RUN mkdir /app -WORKDIR /app -ADD requirements.txt . +FROM python:3.7.6 + +ENV PYTHONUNBUFFERED=1 + +WORKDIR /api +COPY requirements.txt /api/ RUN pip install -r requirements.txt -ADD . . -CMD python3 manage.py migrate && python3 manage.py createcachetable && python3 manage.py runserver 0.0.0.0:10102 diff --git a/backend/api/migrations/0107_modelyearreport_credit_reduction_selection.py b/backend/api/migrations/0107_modelyearreport_credit_reduction_selection.py new file mode 100644 index 000000000..b9accc399 --- /dev/null +++ b/backend/api/migrations/0107_modelyearreport_credit_reduction_selection.py @@ -0,0 +1,18 @@ +# Generated by Django 3.0.14 on 2021-06-01 15:55 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('api', '0106_auto_20210531_1443'), + ] + + operations = [ + migrations.AddField( + model_name='modelyearreport', + name='credit_reduction_selection', + field=models.CharField(max_length=1, null=True), + ), + ] diff --git a/backend/api/models/model_year_report.py b/backend/api/models/model_year_report.py index 5414fc269..ae039c183 100644 --- a/backend/api/models/model_year_report.py +++ b/backend/api/models/model_year_report.py @@ -5,6 +5,7 @@ from enumfields import EnumField from auditable.models import Auditable +from api.models.model_year_report_adjustment import ModelYearReportAdjustment from api.models.model_year_report_statuses import ModelYearReportStatuses from api.models.model_year_report_make import ModelYearReportMake from api.models.model_year_report_ldv_sales import ModelYearReportLDVSales @@ -49,6 +50,12 @@ class ModelYearReport(Auditable): statuses=[c.name for c in ModelYearReportStatuses] ) ) + credit_reduction_selection = models.CharField( + db_comment="Which ZEV credit class to use first for unspecified " + "reductions. (A or B)", + max_length=1, + null=True + ) @property def makes(self): @@ -62,16 +69,51 @@ def makes(self): def ldv_sales(self): return self.get_ldv_sales(from_gov=False) + @property + def adjustments(self): + data = ModelYearReportAdjustment.objects.filter( + model_year_report_id=self.id + ) + + return data + + def get_avg_sales(self): + avg_sales = self.organization.get_avg_ldv_sales( + year=self.model_year.name + ) + + # if this is empty that means we don't have enough ldv_sales to + # get the average. our avg_sales at this point should be from the + # current report ldv_sales + if not avg_sales: + report_ldv_sales = ModelYearReportLDVSales.objects.filter( + model_year_report_id=self.id, + model_year_id=self.model_year_id + ).order_by('-update_timestamp').first() + + if report_ldv_sales: + avg_sales = report_ldv_sales.ldv_sales + + return avg_sales + def get_ldv_sales(self, from_gov=False): row = ModelYearReportLDVSales.objects.filter( model_year_id=self.model_year_id, model_year_report_id=self.id, from_gov=from_gov ).first() - if row: return row.ldv_sales - + return None + + def get_ldv_sales_with_year(self, from_gov=False): + row = ModelYearReportLDVSales.objects.filter( + model_year_id=self.model_year_id, + model_year_report_id=self.id, + from_gov=from_gov + ).first() + if row: + return {'sales': row.ldv_sales, 'year': row.model_year.name} return None class Meta: diff --git a/backend/api/models/model_year_report_credit_reduction.py b/backend/api/models/model_year_report_credit_reduction.py new file mode 100644 index 000000000..e69de29bb diff --git a/backend/api/models/organization.py b/backend/api/models/organization.py index d46ec17b4..0d3426952 100644 --- a/backend/api/models/organization.py +++ b/backend/api/models/organization.py @@ -130,7 +130,9 @@ def get_avg_ldv_sales(self, year=None): if date.today().month < 10: year -= 1 - sales = self.ldv_sales.filter(model_year__name__lte=year).values_list( + sales = self.ldv_sales.filter( + model_year__name__lte=year + ).values_list( 'ldv_sales', flat=True )[:3] @@ -144,7 +146,7 @@ def get_avg_ldv_sales(self, year=None): return sum(list(sales)) / len(sales) - def get_current_class(self, year=None): + def get_current_class(self, year=None, avg_sales=None): # The logic below means that if we're past october, the past year # should count the current yer if not year: @@ -153,7 +155,8 @@ def get_current_class(self, year=None): if date.today().month < 10: year -= 1 - avg_sales = self.get_avg_ldv_sales(year) + if not avg_sales: + avg_sales = self.get_avg_ldv_sales(year) if not avg_sales: avg_sales = 0 diff --git a/backend/api/serializers/model_year_report.py b/backend/api/serializers/model_year_report.py index 7eb4def4b..91d7d71b3 100644 --- a/backend/api/serializers/model_year_report.py +++ b/backend/api/serializers/model_year_report.py @@ -12,7 +12,6 @@ from api.models.model_year_report_address import ModelYearReportAddress from api.models.model_year_report_make import ModelYearReportMake from api.models.model_year_report_statuses import ModelYearReportStatuses -from api.models.model_year_report_ldv_sales import ModelYearReportLDVSales from api.serializers.model_year_report_ldv_sales import ModelYearReportLDVSalesSerializer from api.models.user_profile import UserProfile from api.serializers.model_year_report_address import \ @@ -21,13 +20,14 @@ ModelYearReportMakeSerializer from api.serializers.model_year_report_history import \ ModelYearReportHistorySerializer +from api.serializers.model_year_report_adjustment import \ + ModelYearReportAdjustmentSerializer from api.serializers.user import MemberSerializer from api.serializers.vehicle import ModelYearSerializer from api.services.model_year_report import get_model_year_report_statuses class ModelYearReportSerializer(ModelSerializer): - create_user = SerializerMethodField() model_year = ModelYearSerializer() model_year_report_addresses = ModelYearReportAddressSerializer(many=True) @@ -36,9 +36,11 @@ class ModelYearReportSerializer(ModelSerializer): model_year_report_history = ModelYearReportHistorySerializer(many=True) confirmations = SerializerMethodField() statuses = SerializerMethodField() - ldv_sales_updated = SerializerMethodField() + ldv_sales = SerializerMethodField() ldv_sales_previous = SerializerMethodField() avg_sales = SerializerMethodField() + changelog = SerializerMethodField() + adjustments = ModelYearReportAdjustmentSerializer(many=True) def get_ldv_sales_previous(self, obj): year = int(obj.model_year.name) @@ -56,7 +58,8 @@ def get_ldv_sales_previous(self, obj): def get_avg_sales(self, obj): rows = ModelYearReportLDVSales.objects.filter( model_year_report_id=obj.id, - from_gov=False + from_gov=False, + model_year__name__lt=obj.model_year.name ).values_list( 'ldv_sales', flat=True )[:3] @@ -65,6 +68,7 @@ def get_avg_sales(self, obj): if rows.count() < 3: row = ModelYearReportLDVSales.objects.filter( model_year_report_id=obj.id, + model_year_id=obj.model_year_id ).first() if row: return row.ldv_sales @@ -89,7 +93,7 @@ def get_confirmations(self, obj): return confirmations - def get_ldv_sales_updated(self, obj): + def get_ldv_sales(self, obj): request = self.context.get('request') if request.user.is_government: @@ -97,6 +101,23 @@ def get_ldv_sales_updated(self, obj): return obj.ldv_sales + def get_changelog(self, obj): + request = self.context.get('request') + if request.user.is_government: + from_gov_sales = obj.get_ldv_sales_with_year(from_gov=True) + sales_changes = '' + if from_gov_sales: + not_gov_sales = obj.get_ldv_sales_with_year(from_gov=False) + sales_changes = {'from_gov': from_gov_sales['sales'], 'not_from_gov': not_gov_sales['sales'], 'year': from_gov_sales['year']} + + gov_makes = ModelYearReportMake.objects.filter( + model_year_report_id=obj.id, + from_gov=True + ) + gov_makes_additions_serializer = ModelYearReportMakeSerializer(gov_makes, many=True) + return {'makes_additions': gov_makes_additions_serializer.data, 'ldv_changes': sales_changes} + return obj.ldv_sales + def get_makes(self, obj): request = self.context.get('request') @@ -122,8 +143,8 @@ class Meta: 'organization_name', 'supplier_class', 'model_year', 'model_year_report_addresses', 'makes', 'validation_status', 'create_user', 'model_year_report_history', 'confirmations', - 'statuses', 'ldv_sales_updated', 'statuses', - 'ldv_sales_previous', 'avg_sales' + 'statuses', 'ldv_sales', 'ldv_sales_previous', 'avg_sales', + 'credit_reduction_selection', 'changelog', 'adjustments', ) @@ -185,7 +206,7 @@ def create(self, validated_data): makes = validated_data.pop('makes') model_year = validated_data.pop('model_year') confirmations = request.data.get('confirmations') - ldv_sales = request.user.organization.ldv_sales + ldv_sales = request.data.get('ldv_sales') report = ModelYearReport.objects.create( model_year_id=model_year.id, @@ -197,11 +218,16 @@ def create(self, validated_data): supplier_class=request.user.organization.supplier_class ) for each in ldv_sales: - ModelYearReportLDVSales.objects.create( - model_year=each.model_year, - ldv_sales=each.ldv_sales, - model_year_report=report - ) + model_year = ModelYear.objects.filter( + name=each.get('model_year') + ).first() + + if model_year: + ModelYearReportLDVSales.objects.create( + model_year=model_year, + ldv_sales=each.get('ldv_sales'), + model_year_report_id=report.id + ) for confirmation in confirmations: ModelYearReportConfirmation.objects.create( create_user=request.user.username, @@ -310,6 +336,33 @@ def update(self, instance, validated_data): other=address.other ) + ldv_sales = request.data.get('ldv_sales', None) + + if 'ldv_sales' in request.data: + ModelYearReportLDVSales.objects.filter( + model_year_report_id=instance.id + ).exclude( + model_year_id=instance.model_year_id + ).delete() + + for each in ldv_sales: + model_year = ModelYear.objects.filter( + name=each.get('model_year') + ).first() + + if model_year: + ModelYearReportLDVSales.objects.create( + model_year_id=model_year.id, + ldv_sales=each.get('ldv_sales'), + model_year_report_id=instance.id + ) + + if instance.get_avg_sales(): + instance.supplier_class = request.user.organization.get_current_class( + avg_sales=instance.get_avg_sales() + ) + instance.save() + for confirmation in confirmations: ModelYearReportConfirmation.objects.update_or_create( model_year_report=instance, diff --git a/backend/api/serializers/model_year_report_adjustment.py b/backend/api/serializers/model_year_report_adjustment.py new file mode 100644 index 000000000..02214a816 --- /dev/null +++ b/backend/api/serializers/model_year_report_adjustment.py @@ -0,0 +1,24 @@ +from rest_framework.serializers import ModelSerializer, SlugRelatedField +from api.models.model_year import ModelYear +from api.models.credit_class import CreditClass + +from api.models.model_year_report_adjustment import \ + ModelYearReportAdjustment + + +class ModelYearReportAdjustmentSerializer(ModelSerializer): + credit_class = SlugRelatedField( + slug_field='credit_class', + queryset=CreditClass.objects.all() + ) + model_year = SlugRelatedField( + slug_field='name', + queryset=ModelYear.objects.all() + ) + + class Meta: + model = ModelYearReportAdjustment + fields = ( + 'id', 'credit_class', 'model_year', 'is_reduction', + 'number_of_credits', + ) diff --git a/backend/api/serializers/model_year_report_assessment.py b/backend/api/serializers/model_year_report_assessment.py index 57273aabb..db2fc7ed5 100644 --- a/backend/api/serializers/model_year_report_assessment.py +++ b/backend/api/serializers/model_year_report_assessment.py @@ -5,12 +5,15 @@ ListField from api.models.account_balance import AccountBalance from api.models.credit_transaction import CreditTransaction -from api.models.model_year import ModelYear from api.models.model_year_report import ModelYearReport from api.models.model_year_report_assessment import ModelYearReportAssessment from api.models.model_year_report_assessment_comment import ModelYearReportAssessmentComment from api.serializers.model_year_report_assessment_comment import ModelYearReportAssessmentCommentSerializer from api.models.model_year_report_assessment_descriptions import ModelYearReportAssessmentDescriptions +from api.models.model_year_report_compliance_obligation import ModelYearReportComplianceObligation +from api.serializers.vehicle import ModelYearSerializer +from api.models.model_year import ModelYear + class ModelYearReportAssessmentDescriptionsSerializer(ModelSerializer): class Meta: @@ -71,19 +74,76 @@ def get_descriptions(self, obj): def get_assessment(self, obj): assessment = ModelYearReportAssessment.objects.filter( model_year_report=obj + ).first() + in_compliance = {'report': True, 'prior': True} + ##get the report + report = ModelYearReport.objects.get( + id=obj.id ) + ##get the report year + report_year_obj = ModelYear.objects.get( + id=report.model_year_id + ) + # report_year_int = int(report_year_obj.name) + report_year_str = report_year_obj.name + prior_year_str = str(int(report_year_str) - 1) + prior_year_deficit = {'model_year': prior_year_str, 'a': 0, 'b': 0} + report_year_deficit = {'model_year': report_year_str, 'a': 0, 'b': 0} + + ## try to get the report for the prior year as well as the compliance obligation (deficit) + prior_year_report_obj = ModelYearReport.objects.filter( + model_year__name=prior_year_str + ).first() + deficit_prior_year_obj = ModelYearReportComplianceObligation.objects.filter( + model_year_report_id=prior_year_report_obj, + category='CreditDeficit' + ).first() + if deficit_prior_year_obj: + in_compliance['prior'] = False + prior_year_deficit['a'] = deficit_prior_year_obj.credit_a_value + prior_year_deficit['b'] = deficit_prior_year_obj.credit_b_value + + deficit_report_year = ModelYearReportComplianceObligation.objects.filter( + model_year_report_id=obj, + category='CreditDeficit' + ).first() + + if deficit_report_year: + in_compliance['report'] = False + report_year_deficit['a'] = deficit_report_year.credit_a_value + report_year_deficit['b'] = deficit_report_year.credit_b_value + deficit_values = {'prior': prior_year_deficit, 'report': report_year_deficit} + if not assessment: - return None + return { + 'decision': {'id': None, 'description': None}, + 'penalty': None, + 'in_compliance': in_compliance, + 'deficit': deficit_values, + } + description_serializer = ModelYearReportAssessmentDescriptionsSerializer( + assessment.model_year_report_assessment_description, + read_only=True, + ) + return { - 'description': assessment.description, - 'pentalty': assessment.penalty + 'decision': {'description': description_serializer.data['description'], 'id': description_serializer.data['id'] }, + 'penalty': assessment.penalty, + 'deficit': deficit_values, + 'in_compliance': in_compliance } def get_assessment_comment(self, obj): + request = self.context.get('request') assessment_comment = ModelYearReportAssessmentComment.objects.filter( model_year_report=obj + ).order_by('-create_timestamp') - + if not request.user.is_government: + assessment_comment = ModelYearReportAssessmentComment.objects.filter( + model_year_report=obj, + to_director=False + ).order_by('-create_timestamp') if not assessment_comment: return [] serializer = ModelYearReportAssessmentCommentSerializer( diff --git a/backend/api/serializers/model_year_report_compliance_obligation.py b/backend/api/serializers/model_year_report_compliance_obligation.py index 5146abc13..4ebf75b27 100644 --- a/backend/api/serializers/model_year_report_compliance_obligation.py +++ b/backend/api/serializers/model_year_report_compliance_obligation.py @@ -1,12 +1,10 @@ from datetime import date -from django.db.models import Sum, Value, Q +from django.db.models import Sum, Value from rest_framework import serializers from api.models.account_balance import AccountBalance from api.models.credit_transaction import CreditTransaction from api.models.model_year import ModelYear from api.models.model_year_report import ModelYearReport -from api.models.model_year_report_confirmation import \ - ModelYearReportConfirmation from api.models.model_year_report_compliance_obligation import ModelYearReportComplianceObligation from api.models.model_year_report_credit_offset import ModelYearReportCreditOffset from api.models.sales_submission import SalesSubmission diff --git a/backend/api/serializers/model_year_report_make.py b/backend/api/serializers/model_year_report_make.py index 10b4c370b..59c6b3d95 100644 --- a/backend/api/serializers/model_year_report_make.py +++ b/backend/api/serializers/model_year_report_make.py @@ -7,5 +7,5 @@ class ModelYearReportMakeSerializer(ModelSerializer): class Meta: model = ModelYearReportMake fields = ( - 'make', + 'make', 'from_gov' ) diff --git a/backend/api/serializers/organization.py b/backend/api/serializers/organization.py index 697cef720..19410694f 100644 --- a/backend/api/serializers/organization.py +++ b/backend/api/serializers/organization.py @@ -71,10 +71,6 @@ class OrganizationSaveSerializer(serializers.ModelSerializer): Loads most of the fields and the balance for the Supplier """ organization_address = OrganizationAddressSaveSerializer(allow_null=True, many=True) - avg_ldv_sales = serializers.SerializerMethodField() - - def get_avg_ldv_sales(self, obj): - return obj.get_avg_ldv_sales() def create(self, validated_data): request = self.context.get('request') @@ -138,7 +134,7 @@ class Meta: fields = ( 'id', 'name', 'organization_address', 'create_timestamp', 'balance', 'is_active', 'short_name', 'create_user', 'update_user', - 'is_government', 'supplier_class', 'avg_ldv_sales', 'ldv_sales', + 'is_government', 'supplier_class', 'ldv_sales', 'has_submitted_report', ) extra_kwargs = { diff --git a/backend/api/serializers/vehicle.py b/backend/api/serializers/vehicle.py index 9b7628517..c2b4aa774 100644 --- a/backend/api/serializers/vehicle.py +++ b/backend/api/serializers/vehicle.py @@ -427,9 +427,9 @@ def get_pending_sales(self, instance): return SalesSubmissionContent.objects.filter( xls_make__iexact=instance.make, xls_model__iexact=instance.model_name, - xls_model_year=instance.model_year.name, + xls_model_year=str(instance.model_year.name) + '.0', submission__validation_status__in=[ - "SUBMITTED", "RECOMMEND_APPROVAL", "RECOMMEND_REJECTION", + "SUBMITTED", "RECOMMEND_APPROVAL", "RECOMMEND_REJECTION", "CHECKED", ] ).count() diff --git a/backend/api/services/model_year_report.py b/backend/api/services/model_year_report.py index b6303cb14..f9a805ec2 100644 --- a/backend/api/services/model_year_report.py +++ b/backend/api/services/model_year_report.py @@ -95,6 +95,28 @@ def get_model_year_report_statuses(report): 'create_user': serializer.data } + if report.validation_status == ModelYearReportStatuses.RECOMMENDED: + assessment_status = 'RECOMMENDED' + user_profile = UserProfile.objects.filter(username=report.update_user) + if user_profile.exists(): + serializer = MemberSerializer(user_profile.first(), read_only=True) + + assessment_confirmed_by = { + 'create_timestamp': report.update_timestamp, + 'create_user': serializer.data + } + + if report.validation_status == ModelYearReportStatuses.ASSESSED: + assessment_status = 'ASSESSED' + user_profile = UserProfile.objects.filter(username=report.update_user) + if user_profile.exists(): + serializer = MemberSerializer(user_profile.first(), read_only=True) + + assessment_confirmed_by = { + 'create_timestamp': report.update_timestamp, + 'create_user': serializer.data + } + return { 'supplier_information': { 'status': supplier_information_status, diff --git a/backend/api/services/vehicle.py b/backend/api/services/vehicle.py index d38679bfd..b6cc7d9e6 100644 --- a/backend/api/services/vehicle.py +++ b/backend/api/services/vehicle.py @@ -39,36 +39,23 @@ def vehicles_sales(model_year, organization): report_year = int(model_year.data['name']) org_submission = SalesSubmission.objects.filter( organization_id=organization) - from_date = None - to_date = None - from_date_str = None - to_date_str = None - if report_year == 2020: - from_date = (2018, 1, 2,) - to_date = (report_year + 1, 9, 30,) - from_date_str = "2018-01-02" - to_date_str = str(report_year + 1) + "-09-30" - else: - from_date = (report_year, 10, 1,) - to_date = (report_year + 1, 9, 30,) - from_date_str = str(report_year) + "-10-01" - to_date_str = str(report_year+1) + "-09-30" + to_date = (report_year + 1, 10, 1,) + to_date_str = str(report_year + 1) + "-10-01" - sales_from_date = xlrd.xldate.xldate_from_date_tuple(from_date, 0) sales_to_date = xlrd.xldate.xldate_from_date_tuple(to_date, 0) sales = SalesSubmissionContent.objects.values( 'xls_make', 'xls_model', 'xls_model_year' ).filter( Q(Q( Q(xls_sale_date__lte=sales_to_date) & - Q(xls_sale_date__gte=sales_from_date) & + Q(xls_model_year=str(report_year) + ".0") & Q(xls_date_type="3") & ~Q(xls_sale_date="") ) | Q( Q(xls_sale_date__lte=to_date_str) & - Q(xls_sale_date__gte=from_date_str) & + Q(xls_model_year=str(report_year) + ".0") & Q(xls_date_type="1") & ~Q(xls_sale_date="") ) @@ -82,5 +69,4 @@ def vehicles_sales(model_year, organization): make=sale['xls_make'], model_name=sale['xls_model'], model_year=model_year) - return vehicles diff --git a/backend/api/viewsets/model_year_report.py b/backend/api/viewsets/model_year_report.py index f3700c68b..9bf77ce86 100644 --- a/backend/api/viewsets/model_year_report.py +++ b/backend/api/viewsets/model_year_report.py @@ -25,12 +25,16 @@ from api.serializers.organization import OrganizationSerializer from api.serializers.organization_address import OrganizationAddressSerializer from api.serializers.vehicle import ModelYearSerializer -from api.serializers.model_year_report_assessment import ModelYearReportAssessmentSerializer -from api.models.model_year_report_assessment_comment import ModelYearReportAssessmentComment +from api.serializers.model_year_report_assessment import \ + ModelYearReportAssessmentSerializer +from api.models.model_year_report_assessment_comment import \ + ModelYearReportAssessmentComment +from api.models.model_year_report_assessment import \ + ModelYearReportAssessment from api.services.model_year_report import get_model_year_report_statuses +from api.serializers.organization_ldv_sales import \ + OrganizationLDVSalesSerializer from auditable.views import AuditableMixin -from api.models.organization_ldv_sales import OrganizationLDVSales -from api.serializers.organization_ldv_sales import OrganizationLDVSalesSerializer class ModelYearReportViewset( @@ -86,6 +90,7 @@ def retrieve(self, request, pk=None): model_year_report_id=pk, signing_authority_assertion__module="supplier_information" ).first() + if not confirmation: model_year = ModelYearSerializer(report.model_year) model_year_int = int(model_year.data['name']) @@ -117,16 +122,26 @@ def retrieve(self, request, pk=None): ).distinct() org = request.user.organization + + avg_sales = org.get_avg_ldv_sales(year=model_year_int) + ldv_sales_previous_list = org.get_ldv_sales(year=model_year_int) ldv_sales_previous = OrganizationLDVSalesSerializer( ldv_sales_previous_list, many=True) - avg_sales = 0 + # if this is empty that means we don't have enough ldv_sales to + # get the average. our avg_sales at this point should be from the + # current report ldv_sales + if not avg_sales: + report_ldv_sales = ModelYearReportLDVSales.objects.filter( + model_year_report_id=pk, + model_year__name=model_year_int + ).order_by('-update_timestamp').first() + + if report_ldv_sales: + avg_sales = report_ldv_sales.ldv_sales - if len(ldv_sales_previous_list) > 0: - avg_sales = sum( - ldv_sales_previous_list.values_list('ldv_sales', flat=True) - ) / len(ldv_sales_previous_list) + ldv_sales_previous = None return Response({ 'avg_sales': avg_sales, @@ -136,16 +151,20 @@ def retrieve(self, request, pk=None): 'makes': makes.data, 'model_year_report_history': history.data, 'validation_status': report.validation_status.value, - 'supplier_class': org.supplier_class, + 'supplier_class': org.get_current_class(avg_sales=avg_sales), 'model_year': model_year.data, 'create_user': report.create_user, 'confirmations': confirmations, 'ldv_sales': report.ldv_sales, 'statuses': get_model_year_report_statuses(report), 'ldv_sales_previous': ldv_sales_previous.data + if ldv_sales_previous else [], + 'credit_reduction_selection': report.credit_reduction_selection }) - serializer = ModelYearReportSerializer(report, context={'request': request}) + serializer = ModelYearReportSerializer( + report, context={'request': request} + ) return Response(serializer.data) @@ -170,7 +189,6 @@ def makes(self, request, pk=None): 'gov_makes': gov_makes.data }) - @action(detail=True) def submission_confirmation(self, request, pk=None): confirmation = ModelYearReportConfirmation.objects.filter( @@ -186,7 +204,7 @@ def submission_confirmation(self, request, pk=None): def submission(self, request): validation_status = request.data.get('validation_status') model_year_report_id = request.data.get('model_year_report_id') - confirmations = request.data.get('confirmation') + confirmations = request.data.get('confirmation', None) model_year_report_update = ModelYearReport.objects.filter( id=model_year_report_id @@ -202,23 +220,33 @@ def submission(self, request): update_user=request.user.username, create_user=request.user.username, ) - - confirmation = ModelYearReportConfirmation.objects.filter( - model_year_report_id=model_year_report_id, - signing_authority_assertion__module="compliance_summary" - ).values_list( - 'signing_authority_assertion_id', flat=True - ).distinct() + ## check for if validation status is recommended + if validation_status == 'RECOMMENDED': + ## do "update or create" to create the assessment object + description = request.data.get('description') + penalty = request.data.get('penalty') + ModelYearReportAssessment.objects.update_or_create( + model_year_report_id=model_year_report_id, + defaults={ + 'update_user': request.user.username, + 'model_year_report_assessment_description_id': description, + 'penalty': penalty + } - for confirmation in confirmations: - summary_confirmation = ModelYearReportConfirmation.objects.create( - create_user=request.user.username, - model_year_report_id=model_year_report_id, - has_accepted=True, - title=request.user.title, - signing_authority_assertion_id=confirmation - ) - summary_confirmation.save() + ) + + + + if confirmations: + for confirmation in confirmations: + summary_confirmation = ModelYearReportConfirmation.objects.create( + create_user=request.user.username, + model_year_report_id=model_year_report_id, + has_accepted=True, + title=request.user.title, + signing_authority_assertion_id=confirmation + ) + summary_confirmation.save() return HttpResponse( status=201, content="Report Submitted" @@ -260,7 +288,6 @@ def assessment_patch(self, request, pk): model_year = ModelYear.objects.filter( name=key ).first() - if model_year: ModelYearReportLDVSales.objects.update_or_create( model_year_id=model_year.id, @@ -273,29 +300,35 @@ def assessment_patch(self, request, pk): } ) - adjustments = request.data.get('adjusments', None) - + adjustments = request.data.get('adjustments', None) if adjustments and isinstance(adjustments, list): + ModelYearReportAdjustment.objects.filter( + model_year_report=report + ).delete() + for adjustment in adjustments: model_year = ModelYear.objects.filter( - name=adjustment.model_year + name=adjustment.get('model_year') ).first() credit_class = CreditClass.objects.filter( - credit_class=adjustment.credit_class + credit_class=adjustment.get('credit_class') ).first() is_reduction = False - if adjustment.type == 'Reduction': + if adjustment.get('type') == 'Reduction': is_reduction = True - if model_year and credit_class and adjustment.quantity: + if model_year and credit_class and adjustment.get('quantity'): ModelYearReportAdjustment.objects.create( credit_class_id=credit_class.id, model_year_id=model_year.id, - number_of_credits=adjustment.quantity, + number_of_credits=adjustment.get('quantity'), is_reduction=is_reduction, + model_year_report=report, + create_user=request.user.username, + update_user=request.user.username, ) report = get_object_or_404(ModelYearReport, pk=pk) @@ -324,13 +357,12 @@ def comment_save(self, request, pk): @action(detail=True, methods=['get']) def assessment(self, request, pk): - if not request.user.is_government: + report = get_object_or_404(ModelYearReport, pk=pk) + serializer = ModelYearReportAssessmentSerializer(report, context={'request': request}) + if not request.user.is_government and report.validation_status is not ModelYearReportStatuses.ASSESSED: return HttpResponse( status=403, content=None ) - - report = get_object_or_404(ModelYearReport, pk=pk) - serializer = ModelYearReportAssessmentSerializer(report) return Response(serializer.data) @action(detail=False) diff --git a/backend/api/viewsets/model_year_report_compliance_obligation.py b/backend/api/viewsets/model_year_report_compliance_obligation.py index 2793d087c..a05c32893 100644 --- a/backend/api/viewsets/model_year_report_compliance_obligation.py +++ b/backend/api/viewsets/model_year_report_compliance_obligation.py @@ -64,10 +64,10 @@ def get_serializer_class(self): def create(self, request, *args, **kwargs): id = request.data.get('report_id') - offset = request.data.get('offset') credit_activity = request.data.get('credit_activity') confirmations = request.data.get('confirmations') sales = request.data.get('sales', None) + credit_reduction_selection = request.data.get('credit_reduction_selection', None) if sales: model_year = ModelYearReport.objects.values_list( @@ -84,7 +84,7 @@ def create(self, request, *args, **kwargs): 'create_user': request.user.username, 'update_user': request.user.username } - ) + ) for confirmation in confirmations: ModelYearReportConfirmation.objects.create( @@ -94,19 +94,6 @@ def create(self, request, *args, **kwargs): title=request.user.title, signing_authority_assertion_id=confirmation ) - ModelYearReportCreditOffset.objects.filter( - model_year_report_id=id - ).delete() - for year, value in offset.items(): - model_year = ModelYear.objects.get(name=year) - if value['a'] > 0 or value['b'] > 0: - obj = ModelYearReportCreditOffset.objects.create( - model_year_report_id=id, - model_year=model_year, - credit_a_offset_value=value['a'], - credit_b_offset_value=value['b'] - ) - obj.save() ModelYearReportComplianceObligation.objects.filter( model_year_report_id=id ).delete() @@ -123,6 +110,15 @@ def create(self, request, *args, **kwargs): credit_b_value=b ) compliance_obj.save() + + if credit_reduction_selection: + report = ModelYearReport.objects.get( + id=id + ) + + report.credit_reduction_selection = credit_reduction_selection + report.save() + return Response(id) @action(detail=False, url_path=r'(?P\d+)') @@ -159,12 +155,6 @@ def details(self, request, *args, **kwargs): snapshot, context={'request': request, 'kwargs': kwargs}, many=True ) else: - # transactions = CreditTransaction.objects.filter( - # Q(credit_to=organization) | Q(debit_from=organization) - # ) - # serializer = ModelYearReportComplianceObligationDetailsSerializer( - # transactions, context={'request': request, 'kwargs': kwargs} - # ) report = ModelYearReport.objects.get( id=id ) @@ -312,10 +302,12 @@ def details(self, request, *args, **kwargs): return Response({ 'compliance_obligation': content, - 'compliance_offset': compliance_offset + 'compliance_offset': compliance_offset, + 'ldv_sales': report.ldv_sales }) return Response({ 'compliance_obligation': serializer.data, - 'compliance_offset': compliance_offset + 'compliance_offset': compliance_offset, + 'ldv_sales': report.ldv_sales }) diff --git a/backend/api/viewsets/organization.py b/backend/api/viewsets/organization.py index 691be14da..2ebe6c159 100644 --- a/backend/api/viewsets/organization.py +++ b/backend/api/viewsets/organization.py @@ -155,19 +155,24 @@ def supplier_transactions(self, request, pk=None): @action(detail=True, methods=['patch', 'put']) @method_decorator(permission_required('VIEW_SALES')) def ldv_sales(self, request, pk=None): + delete_id = request.data.get('id', None) if not request.user.is_government: return Response(None) organization = self.get_object() - serializer = OrganizationLDVSalesSerializer( - data=request.data, - context={ - 'organization': organization, - 'request': request - } - ) - serializer.is_valid(raise_exception=True) - serializer.save() + if delete_id: + sale = OrganizationLDVSales.objects.filter(id=delete_id).first() + sale.delete() + else: + serializer = OrganizationLDVSalesSerializer( + data=request.data, + context={ + 'organization': organization, + 'request': request + } + ) + serializer.is_valid(raise_exception=True) + serializer.save() ldv_sales = OrganizationLDVSales.objects.filter( organization_id=pk diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 000000000..97747b68e --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,83 @@ +version: "3.9" + +services: + db: + image: postgres + environment: + - POSTGRES_DB=postgres + - POSTGRES_USER=postgres + - POSTGRES_PASSWORD=postgres + healthcheck: + test: ["CMD-SHELL", "pg_isready -U postgres"] + interval: 5s + timeout: 5s + retries: 5 + keycloak: + build: ./keycloak + hostname: "keycloak" + ports: + - 8888:8080 + - 8443:8443 + expose: + - 8888 + - 8443 + api: + build: ./backend + command: > + sh -c "python manage.py migrate && + python manage.py load_ops_data --directory api/fixtures/operational/ + python manage.py load_ops_data --directory api/fixtures/test/ + python manage.py runserver 0.0.0.0:8000" + env_file: + - backend.env + - minio.env + - rabbitmq.env + volumes: + - ./backend:/api + ports: + - 8000:8000 + depends_on: + db: + condition: service_healthy + mailslurper: + build: ./mailslurper + ports: + - 2500:2500 + - 8081:8081 + - 8085:8085 + minio: + image: minio/minio + hostname: "minio" + volumes: + - ./minio/minio_files:/minio_files + env_file: + - minio.env + command: "minio server /minio_files" + ports: + - 9000:9000 + rabbitmq: + image: rabbitmq:3.7-management + hostname: "rabbitmq" + environment: + - RABBITMQ_DEFAULT_USER=rabbitmq + - RABBITMQ_DEFAULT_PASS=rabbitmq + - RABBITMQ_DEFAULT_VHOST=/zeva + - RABBITMQ_SERVER_ADDITIONAL_ERL_ARGS=-rabbit log_levels [{connection,error}] + ports: + - 15672:15672 + - 5672:5672 + web: + build: ./frontend + command: > + sh -c "npm install --force && + npm start" + env_file: + - frontend.env + - rabbitmq.env + volumes: + - ./frontend:/web + depends_on: + - rabbitmq + ports: + - 3000:3000 + - 8080:8080 diff --git a/frontend.env b/frontend.env new file mode 100644 index 000000000..969e84679 --- /dev/null +++ b/frontend.env @@ -0,0 +1,3 @@ +APIBASE=http://localhost:8000/api/ +KEYCLOAK_CERTS_URL=http://keycloak:8080/auth/realms/zeva/protocol/openid-connect/certs +KEYCLOAK_LOGOUT_REDIRECT_URL=http://localhost:3000/ \ No newline at end of file diff --git a/frontend/Dockerfile b/frontend/Dockerfile index 0c896f93b..46fd8310f 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -1,8 +1,9 @@ -FROM node:8-jessie -RUN mkdir /app -WORKDIR /app -COPY . . -RUN rm package-lock.json && \ - npm install && \ - npm rebuild node-sass -CMD npm run production +FROM node:14.16.1 + +WORKDIR /web +COPY . /web/ + +RUN npm install --force + +EXPOSE 3000 +EXPOSE 8080 \ No newline at end of file diff --git a/frontend/package.json b/frontend/package.json index c8c7214a9..7f615e9de 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "zeva-frontend", - "version": "1.26.0", + "version": "1.27.0", "private": true, "dependencies": { "@fortawesome/fontawesome-free": "^5.13.0", diff --git a/frontend/src/app/css/ComplianceReport.scss b/frontend/src/app/css/ComplianceReport.scss index 3c41b9165..d63d9b83a 100644 --- a/frontend/src/app/css/ComplianceReport.scss +++ b/frontend/src/app/css/ComplianceReport.scss @@ -22,8 +22,27 @@ width: 20%; } } + + .assessment-credit-adjustment { + .form-group { + background-color: $default-background-grey; + padding: 1rem; + + label { + color: $default-text-blue; + margin-bottom: 0; + } + + input[type=radio] { + margin-right: 0.5rem; + } + } + } } #assessment-details { + .not-in-compliance { + background-color: #f2dede; + } .text-grey { color: grey; } diff --git a/frontend/src/app/css/Suppliers.scss b/frontend/src/app/css/Suppliers.scss index e6a043b1b..4697330dc 100644 --- a/frontend/src/app/css/Suppliers.scss +++ b/frontend/src/app/css/Suppliers.scss @@ -11,7 +11,7 @@ .ldv-sales { border: 1px solid $border-grey; display: inline-block; - padding: 0.5rem; + min-width: 500px; > .row { margin-left: 0; @@ -26,14 +26,15 @@ } } - .model-year, .sales { + .model-year, .sales, .delete { display: inline-block; vertical-align: middle; } - - .model-year { - min-width: 200px; - } + .delete button { + background-color: transparent; + border: none; + color: $red; + } } } diff --git a/frontend/src/app/router.js b/frontend/src/app/router.js index f0760aaa7..17d287d05 100644 --- a/frontend/src/app/router.js +++ b/frontend/src/app/router.js @@ -173,10 +173,7 @@ class Router extends Component { /> ((typeof user.hasPermission === 'function' && user.hasPermission('EDIT_SALES')) - ? : ( - - ))} + render={() => } /> { if (decimals > 0) { newValue = newValue.toFixed(decimals); + } else if (decimals === 0) { + newValue = Math.round(newValue); } if (typeof newValue === 'number') { diff --git a/frontend/src/compliance/AssessmentContainer.js b/frontend/src/compliance/AssessmentContainer.js index 55bc0b34a..d0dd717dd 100644 --- a/frontend/src/compliance/AssessmentContainer.js +++ b/frontend/src/compliance/AssessmentContainer.js @@ -30,9 +30,7 @@ const AssessmentContainer = (props) => { const [pendingBalanceExist, setPendingBalanceExist] = useState(false); const [creditActivityDetails, setCreditActivityDetails] = useState({}); const [supplierClassInfo, setSupplierClassInfo] = useState({ ldvSales: 0, class: '' }); - const [radioSelection, setRadioSelection] = useState(''); - const [penalty, setPenalty] = useState(0); - const [radioDescriptions, setRadioDescriptions] = useState([{ id: 0, description: 'test' },]); + const [radioDescriptions, setRadioDescriptions] = useState([{ id: 0, description: 'test' }]); const [sales, setSales] = useState(0); const [statuses, setStatuses] = useState({ assessment: { @@ -40,10 +38,21 @@ const AssessmentContainer = (props) => { confirmedBy: null, }, }); - - const handleCommentChangeIdir = (text) => { - setIdirComment(text); + const handleSubmit = (status) => { + const data = { + modelYearReportId: id, + validation_status: status, + }; + if (analystAction) { + data.penalty = details.assessment.assessmentPenalty; + data.description = details.assessment.decision.id; + } + axios.patch(ROUTES_COMPLIANCE.REPORT_SUBMISSION, data).then((response) => { + history.push(ROUTES_COMPLIANCE.REPORTS); + history.replace(ROUTES_COMPLIANCE.REPORT_ASSESSMENT.replace(':id', id)); + }); }; + const handleCommentChangeBceid = (text) => { setBceidComment(text); }; @@ -70,7 +79,12 @@ const AssessmentContainer = (props) => { .then(axios.spread((reportDetailsResponse, ratioResponse, creditActivityResponse, assessmentResponse) => { const idirCommentArrayResponse = []; let bceidCommentResponse = {}; - const assessmentDescriptions = assessmentResponse.data.descriptions; + const { + assessment: { + penalty: assessmentPenalty, decision, deficit, inCompliance, + }, + descriptions: assessmentDescriptions, + } = assessmentResponse.data; setRadioDescriptions(assessmentDescriptions); assessmentResponse.data.assessmentComment.forEach((item) => { if (item.toDirector === true) { @@ -79,6 +93,7 @@ const AssessmentContainer = (props) => { bceidCommentResponse = item; } }); + let supplierClass; if (reportDetailsResponse.data.supplierClass === 'L') { supplierClass = 'Large'; @@ -88,7 +103,6 @@ const AssessmentContainer = (props) => { supplierClass = 'Small'; } const { - makes: modelYearReportMakes, modelYearReportAddresses, modelYearReportHistory, @@ -98,23 +112,36 @@ const AssessmentContainer = (props) => { confirmations, statuses: reportStatuses, ldvSales, + ldvSalesUpdated, + changelog, } = reportDetailsResponse.data; - + setModelYear(parseInt(reportModelYear.name, 10)); const filteredRatio = ratioResponse.data.filter((data) => data.modelYear === modelYear.toString())[0]; setRatios(filteredRatio); + const makesChanges = { + additions: [], + // deletions: [], + // edits: [] + }; if (modelYearReportMakes) { const currentMakes = modelYearReportMakes.map((each) => (each.make)); + const makesAdditions = modelYearReportMakes.filter((each) => (each.fromGov)); + makesChanges.additions = makesAdditions; setMakes(currentMakes); } - setStatuses(reportStatuses); setSales(ldvSales); setDetails({ + changelog, bceidComment: bceidCommentResponse, idirComment: idirCommentArrayResponse, ldvSales, class: supplierClass, assessment: { + inCompliance, + assessmentPenalty, + decision, + deficit, history: modelYearReportHistory, validationStatus, }, @@ -234,6 +261,16 @@ const AssessmentContainer = (props) => { if (loading) { return ; } + const directorAction = user.isGovernment + && ['RECOMMENDED'].indexOf(details.assessment.validationStatus) >= 0 + && user.hasPermission('SIGN_COMPLIANCE_REPORT'); + + const analystAction = user.isGovernment + && ['SUBMITTED'].indexOf(details.assessment.validationStatus) >= 0 + && user.hasPermission('RECOMMEND_COMPLIANCE_REPORT'); + const handleCommentChangeIdir = (text) => { + setIdirComment(text); + }; return ( <> { makes={makes} modelYear={modelYear} radioDescriptions={radioDescriptions} - radioSelection={radioSelection} - setRadioSelection={setRadioSelection} ratios={ratios} - setPenalty={setPenalty} statuses={statuses} user={user} sales={sales} + handleSubmit={handleSubmit} + directorAction={directorAction} + analystAction={analystAction} + setDetails={setDetails} /> ); diff --git a/frontend/src/compliance/AssessmentEditContainer.js b/frontend/src/compliance/AssessmentEditContainer.js index 27099fa0e..68db0b9ea 100644 --- a/frontend/src/compliance/AssessmentEditContainer.js +++ b/frontend/src/compliance/AssessmentEditContainer.js @@ -8,6 +8,7 @@ import CustomPropTypes from '../app/utilities/props'; import AssessmentEditPage from './components/AssessmentEditPage'; import ComplianceReportTabs from './components/ComplianceReportTabs'; import ROUTES_COMPLIANCE from '../app/routes/Compliance'; +import ROUTES_VEHICLES from '../app/routes/Vehicles'; import Loading from '../app/components/Loading'; import CONFIG from '../app/config'; @@ -30,6 +31,29 @@ const AssessmentEditContainer = (props) => { }); const [sales, setSales] = useState({}); const [ratios, setRatios] = useState({}); + const [years, setYears] = useState([]); + const [adjustments, setAdjustments] = useState([]); + + const addAdjustment = () => { + adjustments.push({ + creditClass: 'A', + quantity: 0, + type: 'Allocation', + }); + + setAdjustments([...adjustments]); + }; + + const handleChangeAdjustment = (value, property, index) => { + adjustments[index][property] = value; + + setAdjustments([...adjustments]); + }; + + const handleDeleteAdjustment = (index) => { + adjustments.splice(index, 1); + setAdjustments([...adjustments]); + }; const handleChangeMake = (event) => { const { value } = event.target; @@ -61,26 +85,29 @@ const AssessmentEditContainer = (props) => { const data = { makes, sales, + adjustments, }; axios.patch( - ROUTES_COMPLIANCE.REPORT_ASSESSMENT_SAVE.replace(/:id/g, id), - data).then(() => { - history.push(ROUTES_COMPLIANCE.REPORT_ASSESSMENT.replace(/:id/g, id)); - }); + ROUTES_COMPLIANCE.REPORT_ASSESSMENT_SAVE.replace(/:id/g, id), data, + ).then(() => { + history.push(ROUTES_COMPLIANCE.REPORT_ASSESSMENT.replace(/:id/g, id)); + }); }; const refreshDetails = () => { const detailsPromise = axios.get( - ROUTES_COMPLIANCE.REPORT_DETAILS.replace(/:id/g, id) + ROUTES_COMPLIANCE.REPORT_DETAILS.replace(/:id/g, id), ); const ratiosPromise = axios.get(ROUTES_COMPLIANCE.RATIOS); const makesPromise = axios.get(ROUTES_COMPLIANCE.MAKES.replace(/:id/g, id)); - Promise.all([detailsPromise, ratiosPromise, makesPromise]).then( - ([response, ratiosResponse, makesResponse]) => { + const yearsPromise = axios.get(ROUTES_VEHICLES.YEARS); + + Promise.all([detailsPromise, ratiosPromise, makesPromise, yearsPromise]).then( + ([response, ratiosResponse, makesResponse, yearsResponse]) => { const { makes: modelYearReportMakes, modelYear: reportModelYear, @@ -91,15 +118,27 @@ const AssessmentEditContainer = (props) => { validationStatus, ldvSales, supplierClass, - ldvSalesUpdated, + adjustments: adjustmentsData, } = response.data; const year = parseInt(reportModelYear.name, 10); - const { supplierMakes, govMakes} = makesResponse.data; + const { supplierMakes, govMakes } = makesResponse.data; setModelYear(year); setStatuses(reportStatuses); + const adjustmentArr = []; + + adjustmentsData.forEach((each) => { + adjustmentArr.push({ + creditClass: each.creditClass, + modelYear: each.modelYear, + quantity: each.numberOfCredits, + type: each.isReduction ? 'Reduction' : 'Allocation', + }); + }); + setAdjustments(adjustmentArr); + if (modelYearReportMakes) { const supplierCurrentMakes = supplierMakes.map((each) => each.make); const analystMakes = govMakes.map((each) => each.make); @@ -126,15 +165,18 @@ const AssessmentEditContainer = (props) => { }); setSales({ - [year]: ldvSalesUpdated, + [year]: ldvSales, }); const filteredRatio = ratiosResponse.data.filter( - (data) => data.modelYear === year.toString() + (data) => data.modelYear === year.toString(), )[0]; setRatios(filteredRatio); + + setYears(yearsResponse.data); + setLoading(false); - } + }, ); }; @@ -170,6 +212,11 @@ const AssessmentEditContainer = (props) => { ratios={ratios} sales={sales} supplierMakes={supplierMakesList} + years={years} + adjustments={adjustments} + addAdjustment={addAdjustment} + handleChangeAdjustment={handleChangeAdjustment} + handleDeleteAdjustment={handleDeleteAdjustment} /> ); diff --git a/frontend/src/compliance/ComplianceObligationContainer.js b/frontend/src/compliance/ComplianceObligationContainer.js index 1f1d50c3c..63606fd42 100644 --- a/frontend/src/compliance/ComplianceObligationContainer.js +++ b/frontend/src/compliance/ComplianceObligationContainer.js @@ -26,6 +26,7 @@ const ComplianceObligationContainer = (props) => { const [statuses, setStatuses] = useState({}); const [supplierClassInfo, setSupplierClassInfo] = useState({ ldvSales: 0, class: '' }); const [sales, setSales] = useState(0); + const [creditReductionSelection, setCreditReductionSelection] = useState(null); const { id } = useParams(); const [remainingABalance, setRemainingABalance] = useState({ lastYearABalance: 0, @@ -79,6 +80,187 @@ const ComplianceObligationContainer = (props) => { } }; + const calculateCreditReduction = (radioId, supplierClass, classAReduction, provisionalBalance, ldvSales, filteredRatio) => { + const zevClassACreditReduction = classAReduction; + Object.keys(provisionalBalance).forEach((each) => { + const modelYear = parseInt(each, 10); + if (modelYear === reportYear) { + provisionalBalanceCurrentYearA = parseInt(provisionalBalance[each].A, 10); + } + if (modelYear === reportYear - 1) { + provisionalBalanceLastYearA = parseInt(provisionalBalance[each].A, 10); + } + }); + let lastYearABalance = 0; + let currentYearABalance = 0; + let tempCreditADeficit = 0; + + if (supplierClass === 'L') { + let lastYearReduction = 0; + let currentYearReduction = 0; + + // Perform ZEV Class A reduction first for older year then current year. + if (provisionalBalanceLastYearA > 0 && zevClassACreditReduction >= provisionalBalanceLastYearA) { + lastYearReduction = provisionalBalanceLastYearA; + } + if (provisionalBalanceLastYearA > 0 && zevClassACreditReduction < provisionalBalanceLastYearA) { + lastYearReduction = zevClassACreditReduction; + } + + const remainingReduction = zevClassACreditReduction - lastYearReduction; + + if (provisionalBalanceCurrentYearA > 0 && remainingReduction <= provisionalBalanceCurrentYearA) { + currentYearReduction = remainingReduction; + } + if (provisionalBalanceCurrentYearA >= 0 && remainingReduction > provisionalBalanceCurrentYearA) { + currentYearReduction = provisionalBalanceCurrentYearA; + tempCreditADeficit = (remainingReduction - provisionalBalanceCurrentYearA); + } + setZevClassAReduction({ + lastYearA: formatNumeric((lastYearReduction), 2), + currentYearA: currentYearReduction, + }); + + lastYearABalance = provisionalBalanceLastYearA - lastYearReduction; + currentYearABalance = provisionalBalanceCurrentYearA - currentYearReduction; + } else { + lastYearABalance = provisionalBalanceLastYearA; + currentYearABalance = provisionalBalanceCurrentYearA; + } + + const totalReduction = ((filteredRatio.complianceRatio / 100) * ldvSales); + const leftoverReduction = ((filteredRatio.complianceRatio / 100) * ldvSales) + - ((filteredRatio.zevClassA / 100) * ldvSales); + + const unspecifiedZevClassReduction = supplierClass === 'L' ? leftoverReduction : totalReduction; + let unspecifiedZevClassCurrentYearA = 0; + let unspecifiedZevClassCurrentYearB = 0; + let unspecifiedZevClassLastYearA = 0; + let unspecifiedZevClassLastYearB = 0; + let remainingUnspecifiedReduction = 0; + let unspecifiedCreditDeficit = 0; + + Object.keys(provisionalBalance).forEach((each) => { + const modelYear = parseInt(each, 10); + if (modelYear === reportYear) { + provisionalBalanceCurrentYearA = parseInt(provisionalBalance[each].A, 10); + provisionalBalanceCurrentYearB = parseInt(provisionalBalance[each].B, 10); + } + if (modelYear === reportYear - 1) { + provisionalBalanceLastYearA = parseInt(provisionalBalance[each].A, 10); + provisionalBalanceLastYearB = parseInt(provisionalBalance[each].B, 10); + } + }); + + if (radioId === 'A') { + // Reduce older year's A credits first then older year's B. + if (lastYearABalance > 0 && lastYearABalance >= unspecifiedZevClassReduction) { + unspecifiedZevClassLastYearA = unspecifiedZevClassReduction; + } + if (lastYearABalance > 0 && lastYearABalance < unspecifiedZevClassReduction) { + unspecifiedZevClassLastYearA = lastYearABalance; + remainingUnspecifiedReduction = unspecifiedZevClassReduction - unspecifiedZevClassLastYearA; + if (remainingUnspecifiedReduction > 0 && provisionalBalanceLastYearB > 0 && provisionalBalanceLastYearB >= remainingUnspecifiedReduction) { + unspecifiedZevClassLastYearB = remainingUnspecifiedReduction; + } + if (remainingUnspecifiedReduction > 0 && provisionalBalanceLastYearB > 0 && provisionalBalanceLastYearB < remainingUnspecifiedReduction) { + unspecifiedZevClassLastYearB = provisionalBalanceLastYearB; + } + } + if (lastYearABalance === 0 && provisionalBalanceLastYearB > 0 && unspecifiedZevClassReduction >= provisionalBalanceLastYearB) { + unspecifiedZevClassLastYearB = provisionalBalanceLastYearB; + } + // Reduce current year's A credits first then current year's B. + remainingUnspecifiedReduction = unspecifiedZevClassReduction - (unspecifiedZevClassLastYearA + unspecifiedZevClassLastYearB); + + if (currentYearABalance > 0 && currentYearABalance >= remainingUnspecifiedReduction) { + unspecifiedZevClassCurrentYearA = remainingUnspecifiedReduction; + } + if (currentYearABalance === 0 && provisionalBalanceCurrentYearB > 0 && remainingUnspecifiedReduction >= provisionalBalanceCurrentYearB) { + unspecifiedZevClassCurrentYearB = provisionalBalanceCurrentYearB; + if (remainingUnspecifiedReduction > provisionalBalanceCurrentYearB) { + unspecifiedCreditDeficit = remainingUnspecifiedReduction - provisionalBalanceCurrentYearB; + } + } + if (currentYearABalance > 0 && currentYearABalance < remainingUnspecifiedReduction) { + unspecifiedZevClassCurrentYearA = currentYearABalance; + const unspecifieldBalance = unspecifiedZevClassReduction - unspecifiedZevClassCurrentYearA; + if (unspecifieldBalance > 0 && provisionalBalanceCurrentYearB > 0 && provisionalBalanceCurrentYearB >= unspecifieldBalance) { + unspecifiedZevClassCurrentYearB = unspecifieldBalance; + } + if (unspecifieldBalance > 0 && provisionalBalanceCurrentYearB > 0 && provisionalBalanceCurrentYearB < unspecifieldBalance) { + unspecifiedZevClassLastYearB = unspecifieldBalance - provisionalBalanceLastYearB; + } + } + } + + if (radioId === 'B') { + // Reduce older year's B credits first then older year's A. + if (provisionalBalanceLastYearB > 0 && provisionalBalanceLastYearB >= unspecifiedZevClassReduction) { + unspecifiedZevClassLastYearB = unspecifiedZevClassReduction; + } + if (provisionalBalanceLastYearB > 0 && provisionalBalanceLastYearB < unspecifiedZevClassReduction) { + unspecifiedZevClassLastYearB = provisionalBalanceLastYearB; + remainingUnspecifiedReduction = unspecifiedZevClassReduction - unspecifiedZevClassLastYearB; + if (remainingUnspecifiedReduction > 0 && lastYearABalance > 0 && lastYearABalance >= remainingUnspecifiedReduction) { + unspecifiedZevClassLastYearA = remainingUnspecifiedReduction; + } + if (remainingUnspecifiedReduction > 0 && lastYearABalance > 0 && lastYearABalance < remainingUnspecifiedReduction) { + unspecifiedZevClassLastYearA = lastYearABalance; + } + } + if (provisionalBalanceLastYearB === 0 && lastYearABalance >= 0 && unspecifiedZevClassReduction >= lastYearABalance) { + unspecifiedZevClassLastYearA = lastYearABalance; + } + // Reduce current year's B credits first then current year's A. + remainingUnspecifiedReduction = unspecifiedZevClassReduction - (unspecifiedZevClassLastYearA + unspecifiedZevClassLastYearB); + + if (provisionalBalanceCurrentYearB >= 0 && provisionalBalanceCurrentYearB >= remainingUnspecifiedReduction) { + unspecifiedZevClassCurrentYearB = remainingUnspecifiedReduction; + } + + if (provisionalBalanceCurrentYearB === 0 && currentYearABalance >= 0 && remainingUnspecifiedReduction >= currentYearABalance) { + unspecifiedZevClassCurrentYearA = currentYearABalance; + } + + if (provisionalBalanceCurrentYearB > 0 && provisionalBalanceCurrentYearB < remainingUnspecifiedReduction) { + unspecifiedZevClassCurrentYearB = provisionalBalanceCurrentYearB; + const unspecifieldBalance = unspecifiedZevClassReduction - (unspecifiedZevClassLastYearA + unspecifiedZevClassLastYearB + unspecifiedZevClassCurrentYearB); + if (unspecifieldBalance > 0 && currentYearABalance > 0 && currentYearABalance >= unspecifieldBalance) { + unspecifiedZevClassCurrentYearA = unspecifieldBalance; + } + if (unspecifieldBalance > 0 && currentYearABalance > 0 && currentYearABalance < unspecifieldBalance) { + unspecifiedZevClassCurrentYearA = unspecifieldBalance - currentYearABalance; + } + } + } + + const ratioBalance = unspecifiedZevClassReduction + - (unspecifiedZevClassLastYearA + + unspecifiedZevClassLastYearB + + unspecifiedZevClassCurrentYearB + + unspecifiedZevClassCurrentYearA); + if (ratioBalance > 0) { + unspecifiedCreditDeficit = ratioBalance; + } + + setCreditReductionSelection(radioId); + + setUnspecifiedReductions({ + currentYearA: unspecifiedZevClassCurrentYearA, + currentYearB: unspecifiedZevClassCurrentYearB, + lastYearA: unspecifiedZevClassLastYearA, + lastYearB: unspecifiedZevClassLastYearB, + }); + + setCreditBalance({ + A: (currentYearABalance - unspecifiedZevClassCurrentYearA), + B: (provisionalBalanceCurrentYearB - (unspecifiedZevClassCurrentYearB)), + creditADeficit: tempCreditADeficit, + unspecifiedCreditDeficit, + }); + }; + const creditAReduction = (supplierClass, classAReduction, provisionalBalance) => { const zevClassACreditReduction = classAReduction; Object.keys(provisionalBalance).forEach((each) => { @@ -129,10 +311,12 @@ const ComplianceObligationContainer = (props) => { }); } }; + const unspecifiedCreditReduction = (event, paramReduction) => { const { provisionalBalance } = reportDetails; const { lastYearABalance, currentYearABalance, creditADeficit } = remainingABalance; const { id: radioId } = event.target; + const unspecifiedZevClassReduction = paramReduction; let unspecifiedZevClassCurrentYearA = 0; let unspecifiedZevClassCurrentYearB = 0; @@ -173,6 +357,7 @@ const ComplianceObligationContainer = (props) => { } // Reduce current year's A credits first then current year's B. remainingUnspecifiedReduction = unspecifiedZevClassReduction - (unspecifiedZevClassLastYearA + unspecifiedZevClassLastYearB); + if (currentYearABalance > 0 && currentYearABalance >= remainingUnspecifiedReduction) { unspecifiedZevClassCurrentYearA = remainingUnspecifiedReduction; } @@ -214,12 +399,14 @@ const ComplianceObligationContainer = (props) => { } // Reduce current year's B credits first then current year's A. remainingUnspecifiedReduction = unspecifiedZevClassReduction - (unspecifiedZevClassLastYearA + unspecifiedZevClassLastYearB); - if (provisionalBalanceCurrentYearB > 0 && provisionalBalanceCurrentYearB >= remainingUnspecifiedReduction) { + + if (provisionalBalanceCurrentYearB >= 0 && provisionalBalanceCurrentYearB >= remainingUnspecifiedReduction) { unspecifiedZevClassCurrentYearB = remainingUnspecifiedReduction; } if (provisionalBalanceCurrentYearB === 0 && currentYearABalance >= 0 && remainingUnspecifiedReduction >= currentYearABalance) { unspecifiedZevClassCurrentYearA = currentYearABalance; } + if (provisionalBalanceCurrentYearB > 0 && provisionalBalanceCurrentYearB < remainingUnspecifiedReduction) { unspecifiedZevClassCurrentYearB = provisionalBalanceCurrentYearB; const unspecifieldBalance = unspecifiedZevClassReduction - (unspecifiedZevClassLastYearA + unspecifiedZevClassLastYearB + unspecifiedZevClassCurrentYearB); @@ -231,6 +418,7 @@ const ComplianceObligationContainer = (props) => { } } } + const ratioBalance = unspecifiedZevClassReduction - (unspecifiedZevClassLastYearA + unspecifiedZevClassLastYearB @@ -239,12 +427,16 @@ const ComplianceObligationContainer = (props) => { if (ratioBalance > 0) { unspecifiedCreditDeficit = ratioBalance; } + + setCreditReductionSelection(radioId); + setUnspecifiedReductions({ currentYearA: unspecifiedZevClassCurrentYearA, currentYearB: unspecifiedZevClassCurrentYearB, lastYearA: unspecifiedZevClassLastYearA, lastYearB: unspecifiedZevClassLastYearB, }); + setCreditBalance({ A: (currentYearABalance - unspecifiedZevClassCurrentYearA), B: (provisionalBalanceCurrentYearB - (unspecifiedZevClassCurrentYearB)), @@ -253,16 +445,6 @@ const ComplianceObligationContainer = (props) => { }); }; - const handleOffsetChange = (event) => { - const { id, value } = event.target; - const year = id.split('-')[0]; - const creditClass = id.split('-')[1]; - if (Object.keys(offsetNumbers).includes(year)) { - const yearTotal = offsetNumbers[year]; - const newYearTotal = { ...yearTotal, [creditClass]: parseFloat(value) }; - setOffsetNumbers({ ...offsetNumbers, [year]: newYearTotal }); - } - }; const handleSave = () => { const reportDetailsArray = []; Object.keys(reportDetails).forEach((each) => { @@ -289,15 +471,63 @@ const ComplianceObligationContainer = (props) => { } }); }); + + // zev class A reductions current year + reportDetailsArray.push({ + category: 'ClassAReduction', + year: reportYear, + a: zevClassAReduction.currentYearA, + b: 0, + }); + + // zev class A reductions previous year + reportDetailsArray.push({ + category: 'ClassAReduction', + year: reportYear - 1, + a: zevClassAReduction.lastYearA, + b: 0, + }); + + // unspecified balance current year + reportDetailsArray.push({ + category: 'UnspecifiedClassCreditReduction', + year: reportYear, + a: unspecifiedReductions.currentYearA, + b: unspecifiedReductions.currentYearB, + }); + + // unspecified balance previous year + reportDetailsArray.push({ + category: 'UnspecifiedClassCreditReduction', + year: reportYear - 1, + a: unspecifiedReductions.lastYearA, + b: unspecifiedReductions.lastYearB, + }); + + reportDetailsArray.push({ + category: 'ProvisionalBalanceAfterCreditReduction', + year: reportYear, + a: creditBalance.A, + b: creditBalance.B, + }); + + reportDetailsArray.push({ + category: 'CreditDeficit', + year: reportYear, + a: creditBalance.creditADeficit, + b: creditBalance.unspecifiedCreditDeficit, + }); + const data = { reportId: id, - sales: sales, - offset: offsetNumbers, + sales, creditActivity: reportDetailsArray, confirmations: checkboxes, + creditReductionSelection, }; - axios.post(ROUTES_COMPLIANCE.OBLIGATION, - data).then(() => { + axios.post( + ROUTES_COMPLIANCE.OBLIGATION, data, + ).then(() => { history.push(ROUTES_COMPLIANCE.REPORTS); history.replace(ROUTES_COMPLIANCE.REPORT_CREDIT_ACTIVITY.replace(':id', id)); }); @@ -311,13 +541,13 @@ const ComplianceObligationContainer = (props) => { axios.get(ROUTES_COMPLIANCE.REPORT_COMPLIANCE_DETAILS_BY_ID.replace(':id', id)), ]).then(axios.spread((reportDetailsResponse, ratioResponse, complianceResponse) => { const { - ldvSales, modelYearReportHistory, supplierClass, validationStatus, confirmations, modelYear, statuses: reportStatuses, + creditReductionSelection, } = reportDetailsResponse.data; setDetails({ complianceObligation: { @@ -333,15 +563,16 @@ const ComplianceObligationContainer = (props) => { } setStatuses(reportStatuses); - setSupplierClassInfo({ class: supplierClass, ldvSales }); - setSales(ldvSales); + const filteredRatio = ratioResponse.data.filter((data) => data.modelYear === modelYear.name)[0]; setRatios(filteredRatio); const classAReduction = ((filteredRatio.zevClassA / 100) * ldvSales); const complianceResponseDetails = complianceResponse.data.complianceObligation; - const { complianceOffset } = complianceResponse.data; + const { complianceOffset, ldvSales } = complianceResponse.data; + setSupplierClassInfo({ class: supplierClass, ldvSales }); + setSales(ldvSales); const creditBalanceStart = {}; const creditBalanceEnd = {}; const provisionalBalance = []; @@ -388,13 +619,13 @@ const ComplianceObligationContainer = (props) => { B: item.creditBValue, }); } - if (item.category === 'creditsIssuedSales') { + if (item.category === 'creditsIssuedSales' && item.issuedCredits) { item.issuedCredits.forEach((each) => { - creditsIssuedSales.push({ - modelYear: each.modelYear, - A: each.A, - B: each.B, - }); + creditsIssuedSales.push({ + modelYear: each.modelYear, + A: each.A, + B: each.B, + }); }); } if (item.category === 'pendingBalance') { @@ -439,8 +670,19 @@ const ComplianceObligationContainer = (props) => { }, }); + setCreditReductionSelection(creditReductionSelection); + creditAReduction(supplierClass, classAReduction, provisionalBalance); + calculateCreditReduction( + creditReductionSelection, + supplierClass, + classAReduction, + provisionalBalance, + ldvSales, + filteredRatio, + ); + axios.get(ROUTES_SIGNING_AUTHORITY_ASSERTIONS.LIST).then((assertionResponse) => { const filteredAssertions = assertionResponse.data.filter((data) => data.module === 'compliance_obligation'); setAssertions(filteredAssertions); @@ -470,7 +712,6 @@ const ComplianceObligationContainer = (props) => { details={details} disabledCheckboxes={disabledCheckboxes} handleCheckboxClick={handleCheckboxClick} - handleOffsetChange={handleOffsetChange} handleSave={handleSave} loading={loading} offsetNumbers={offsetNumbers} @@ -488,6 +729,7 @@ const ComplianceObligationContainer = (props) => { creditBalance={creditBalance} sales={sales} handleChangeSales={handleChangeSales} + creditReductionSelection={creditReductionSelection} /> ); diff --git a/frontend/src/compliance/ComplianceReportSummaryContainer.js b/frontend/src/compliance/ComplianceReportSummaryContainer.js index 5a05e37ab..0a9e99a46 100644 --- a/frontend/src/compliance/ComplianceReportSummaryContainer.js +++ b/frontend/src/compliance/ComplianceReportSummaryContainer.js @@ -8,7 +8,6 @@ import ROUTES_COMPLIANCE from '../app/routes/Compliance'; import CustomPropTypes from '../app/utilities/props'; import ComplianceReportTabs from './components/ComplianceReportTabs'; import ComplianceReportSummaryDetailsPage from './components/ComplianceReportSummaryDetailsPage'; -import formatNumeric from '../app/utilities/formatNumeric'; import ROUTES_SIGNING_AUTHORITY_ASSERTIONS from '../app/routes/SigningAuthorityAssertions'; const ComplianceReportSummaryContainer = (props) => { @@ -75,7 +74,6 @@ const ComplianceReportSummaryContainer = (props) => { validationStatus, confirmations, modelYear: reportModelYear, - ldvSales, } = reportDetailsResponse.data; // ALL STATUSES setConfirmationStatuses(statuses); @@ -102,13 +100,17 @@ const ComplianceReportSummaryContainer = (props) => { }, }); // CONSUMER SALES + let supplierClassText = ''; let { supplierClass } = reportDetailsResponse.data; if (supplierClass === 'M') { - supplierClass = 'Medium Volume Supplier'; + supplierClass = 'Medium'; + supplierClassText = 'Medium Volume Supplier'; } else if (supplierClass === 'L') { - supplierClass = 'Large Volume Supplier'; + supplierClass = 'Large'; + supplierClassText = 'Large Volume Supplier'; } else { - supplierClass = 'Small Volume Supplier'; + supplierClass = 'Small'; + supplierClassText = 'Small Volume Supplier'; } let pendingZevSales = 0; @@ -133,11 +135,14 @@ const ComplianceReportSummaryContainer = (props) => { const provisionalBalanceBeforeOffset = { A: 0, B: 0 }; const provisionalBalanceAfterOffset = { A: 0, B: 0 }; const pendingBalance = { A: 0, B: 0 }; + const creditDeficit = { A: 0, B: 0 }; + const provisionalBalanceAfterCreditReduction = { A: 0, B: 0 }; const transfersIn = { A: 0, B: 0 }; const transfersOut = { A: 0, B: 0 }; const creditsIssuedSales = { A: 0, B: 0 }; const complianceOffsetNumbers = { A: 0, B: 0 }; - const { complianceOffset } = creditActivityResponse.data; + const totalCreditReduction = { A: 0, B: 0 }; + const { complianceOffset, ldvSales} = creditActivityResponse.data; // OFFSET if (complianceOffset) { complianceOffset.forEach((item) => { @@ -163,15 +168,31 @@ const ComplianceReportSummaryContainer = (props) => { provisionalBalanceAfterOffset.A += aValue; provisionalBalanceAfterOffset.B += aValue; } + if (item.category === 'UnspecifiedClassCreditReduction') { + const aValue = parseFloat(item.creditAValue); + const bValue = parseFloat(item.creditBValue); + totalCreditReduction.A += aValue; + totalCreditReduction.B += bValue; + } + if (item.category === 'ClassAReduction') { + const aValue = parseFloat(item.creditAValue); + const bValue = parseFloat(item.creditBValue); + totalCreditReduction.A += aValue; + totalCreditReduction.B += bValue; + } + if (item.category === 'CreditDeficit') { + creditDeficit.A = item.creditAValue; + creditDeficit.B = item.creditBValue; + } + if (item.category === 'ProvisionalBalanceAfterCreditReduction') { + provisionalBalanceAfterCreditReduction.A = item.creditAValue; + provisionalBalanceAfterCreditReduction.B = item.creditBValue; + } if (item.category === 'pendingBalance') { const aValue = parseFloat(item.creditAValue); const bValue = parseFloat(item.creditBValue); pendingBalance.A += aValue; pendingBalance.B += bValue; - provisionalBalanceBeforeOffset.A += aValue; - provisionalBalanceBeforeOffset.B += bValue; - provisionalBalanceAfterOffset.A += aValue; - provisionalBalanceAfterOffset.B += bValue; if (pendingBalance.A > 0 || pendingBalance.B > 0) { setPendingBalanceExist(true); } @@ -202,7 +223,11 @@ const ComplianceReportSummaryContainer = (props) => { pendingBalance, provisionalBalanceBeforeOffset, provisionalBalanceAfterOffset, + provisionalBalanceAfterCreditReduction, supplierClass, + supplierClassText, + creditDeficit, + totalCreditReduction, ldvSales, transactions: { creditsIssuedSales, diff --git a/frontend/src/compliance/ComplianceReportsContainer.js b/frontend/src/compliance/ComplianceReportsContainer.js index 2a534590e..ee9e74b60 100644 --- a/frontend/src/compliance/ComplianceReportsContainer.js +++ b/frontend/src/compliance/ComplianceReportsContainer.js @@ -22,18 +22,15 @@ const ComplianceReportsContainer = (props) => { setLoading(showLoading); axios.get(ROUTES_COMPLIANCE.REPORTS).then((response) => { setData(response.data); - const filteredYears = availableYears.filter((year) => ( response.data.findIndex( (item) => parseInt(item.modelYear.name, 10) === parseInt(year, 10), ) < 0 )); - setAvailableYears(filteredYears); setLoading(false); }); }; - useEffect(() => { refreshList(true); }, []); diff --git a/frontend/src/compliance/SupplierInformationContainer.js b/frontend/src/compliance/SupplierInformationContainer.js index 143fc05f6..004ae59fc 100644 --- a/frontend/src/compliance/SupplierInformationContainer.js +++ b/frontend/src/compliance/SupplierInformationContainer.js @@ -59,7 +59,7 @@ const SupplierInformationContainer = (props) => { const data = { class: user.organization.supplierClass, averageLdvSales: details.organization.avgLdvSales, - previousLdvSales: details.organization.ldvSales, + ldvSales: details.organization.ldvSales, makes, modelYear, confirmations: checkboxes, @@ -146,7 +146,7 @@ const SupplierInformationContainer = (props) => { setMakes(currentMakes); } - const ldvSales = ldvSalesPrevious.sort((a, b) => ((a.modelYear < b.modelYear) ? 1 : -1)); + const ldvSales = ldvSalesPrevious.sort((a, b) => ((a.modelYear > b.modelYear) ? 1 : -1)); const supplierClassString = getClassDescriptions(supplierClass); setDetails({ supplierClassString, @@ -181,9 +181,9 @@ const SupplierInformationContainer = (props) => { return each; } }); - previousSales.sort((a, b) => ((a.modelYear < b.modelYear) ? 1 : -1)); + previousSales.sort((a, b) => ((a.modelYear > b.modelYear) ? 1 : -1)); const newOrg = { - ldvSales: previousSales, + ldvSales: previousSales.length >= 3 ? previousSales : [], avgLdvSales: user.organization.avgLdvSales, organizationAddress: user.organizationAddress, name: user.organization.name, diff --git a/frontend/src/compliance/components/AssessmentDetailsPage.js b/frontend/src/compliance/components/AssessmentDetailsPage.js index 7fe8f021f..871033351 100644 --- a/frontend/src/compliance/components/AssessmentDetailsPage.js +++ b/frontend/src/compliance/components/AssessmentDetailsPage.js @@ -1,10 +1,9 @@ /* eslint-disable react/no-array-index-key */ import React, { useState } from 'react'; import PropTypes from 'prop-types'; -import ReactQuill from 'react-quill'; +import parse from 'html-react-parser'; import Button from '../../app/components/Button'; import Loading from '../../app/components/Loading'; -import Modal from '../../app/components/Modal'; import history from '../../app/History'; import CustomPropTypes from '../../app/utilities/props'; import ROUTES_COMPLIANCE from '../../app/routes/Compliance'; @@ -28,41 +27,64 @@ const AssessmentDetailsPage = (props) => { loading, makes, modelYear, - radioSelection, radioDescriptions, - setPenalty, setRadioSelection, ratios, statuses, user, sales, + handleSubmit, + directorAction, + analystAction, + setDetails, } = props; - const { creditBalanceStart, pendingBalance, transactions, provisionalBalance, } = creditActivityDetails; + const assessmentDecision = details.assessment.decision && details.assessment.decision.description ? details.assessment.decision.description.replace(/{user.organization.name}/g, user.organization.name) : ''; const { creditsIssuedSales, transfersIn, transfersOut, } = transactions; const [showModal, setShowModal] = useState(false); const disabledInputs = false; const showDescription = (each) => ( -
+
= 0} onChange={(event) => { - setRadioSelection(each.id); + setDetails({ + ...details, + assessment: { ...details.assessment, decision: { description: each.description, id: each.id } }, + }); }} /> + {each.description + && ( + )}
); + let disabledRecommendBtn = false; + let recommendTooltip = ''; + + const pendingSalesExist = () => { + if (Object.keys(pendingBalance).length > 0) { + pendingBalance.forEach((each) => { + if (parseInt(each.A) > 0 || parseInt(each.B) > 0) { + disabledRecommendBtn = true; + recommendTooltip = 'There are credit applications that must be issued prior to recommending this assessment.'; + } + }); + } + }; if (loading) { return ; } @@ -74,28 +96,9 @@ const AssessmentDetailsPage = (props) => { const leftoverReduction = ((ratios.complianceRatio / 100) * details.ldvSales) - ((ratios.zevClassA / 100) * details.ldvSales); - const modal = ( - { setShowModal(false); }} - handleSubmit={() => { setShowModal(false); handleCancelConfirmation(); }} - modalClass="w-75" - showModal={showModal} - confirmClass="button primary" - > -
-

- Do you want to edit this page? This action will allow you to make further changes to{' '} - this information, it will also query the database to retrieve any recent updates.{' '} - Your previous confirmation will be cleared. -

-
-
- ); - return (
+ {pendingSalesExist()}

{modelYear} Model Year Report

@@ -113,7 +116,31 @@ const AssessmentDetailsPage = (props) => { /> )}
+ {user.isGovernment + && (
+ {details.changelog.ldvChanges && ( + Object.keys(details.changelog.makesAdditions) + || details.changelog.ldvChanges > 0 + ) + && ( + <> +

Assessment Adjustments

+
+ The analyst made the following adjustments: + {details.changelog.makesAdditions + && ( +
    + {details.changelog.makesAdditions.map((addition) => ( +
  • added Make: {addition.make}
  • + ))} + {/* {details.analystChanges.ldvChanges.map((change) => ( */} +
  • changed the {details.changelog.ldvChanges.year} Model Year LDV Sales\Leases total from {formatNumeric(details.changelog.ldvChanges.notFromGov, 0)} to {formatNumeric(details.changelog.ldvChanges.fromGov, 0)}
  • +
+ )} +
+ + )} {details.idirComment && details.idirComment.length > 0 && user.isGovernment && ( {
+ )}
+ {!user.isGovernment && statuses.assessment.status === 'ASSESSED' && ( + + )} {user.isGovernment && (statuses.assessment.status === 'SUBMITTED' || statuses.assessment.status === 'UNSAVED') && (
-
+

Service Address

{details.organization.organizationAddress && details.organization.organizationAddress.map((address) => ( @@ -163,7 +202,7 @@ const AssessmentDetailsPage = (props) => { ) ))}
-
+

Records Address

{details.organization.organizationAddress && details.organization.organizationAddress.map((address) => ( @@ -211,6 +250,7 @@ const AssessmentDetailsPage = (props) => { sales={sales} />
+
@@ -225,26 +265,49 @@ const AssessmentDetailsPage = (props) => { B - - - - + + {details.assessment.inCompliance && details.assessment.inCompliance.prior + && ( + <> + {Object.keys(creditActivityDetails.creditBalanceStart).map((each) => ( + + + + + + ))} + + )} + {details.assessment.inCompliance && !details.assessment.inCompliance.prior + && ( + + + + + + )}
- - {creditBalanceStart.A} - - {creditBalanceStart.B} -
•     {each} Credits: + {creditActivityDetails.creditBalanceStart[each].A || 0} + + {creditActivityDetails.creditBalanceStart[each].B || 0} +
•     Credit Deficit: + ({details.assessment.deficit.prior.a}) + + ({details.assessment.deficit.prior.b}) +
-

- Credit Activity -

-
- - - {Object.keys(creditsIssuedSales).length > 0 + + {(creditsIssuedSales || transfersIn || transfersOut) && ( + <> +

+ Credit Activity +

+
+
+ + {Object.keys(creditsIssuedSales).length > 0 && ( { negativeValue={false} /> )} - {/* {Object.keys(creditsIssuedInitiative).length > 0 + {/* {Object.keys(creditsIssuedInitiative).length > 0 && ( { negativeValue={false} /> )} */} - {Object.keys(transfersIn).length > 0 + {Object.keys(transfersIn).length > 0 && ( { /> )} - {Object.keys(transfersOut).length > 0 + {Object.keys(transfersOut).length > 0 && ( { negativeValue={false} /> )} - -
-
+ + +
+ + )}
@@ -360,19 +425,22 @@ const AssessmentDetailsPage = (props) => { - - + {user.isGovernment + && ( + + - + - - + + + )}
- Do you want to use ZEV Class A or B credits first for your unspecified ZEV class reduction? -
+ Do you want to use ZEV Class A or B credits first for your unspecified ZEV class reduction? + - - + + - -
+ +
•     2019 Credits: @@ -384,20 +452,6 @@ const AssessmentDetailsPage = (props) => {
- {/* */}
@@ -413,82 +467,158 @@ const AssessmentDetailsPage = (props) => { B - - - - - + {details.assessment.inCompliance && details.assessment.inCompliance.report + && ( + + {Object.keys(creditActivityDetails.creditBalanceEnd).map((each) => ( + <> + + + + + ))} + + + )} + {details.assessment.inCompliance && !details.assessment.inCompliance.report + && ( + + + + + + )}
•     {modelYear} Credits: - 977.76 - - 0 -
•     {each} Credits: + {creditActivityDetails.creditBalanceEnd[each].A || 0} + + {creditActivityDetails.creditBalanceEnd[each].B || 0} +
•     Credit Deficit: + ({details.assessment.deficit.report.a}) + + ({details.assessment.deficit.report.b}) +
- -

Analyst Recommended Director Assessment

-
-
-
-
- {radioDescriptions.map((each) => ( - (each.displayOrder === 0) - && showDescription(each) - ))} -
-    {details.organization.name} has not complied with section 10 (2) of the - Zero-Emission Vehicles Act for the {modelYear} adjustment period. + {!user.isGovernment && details.assessment && details.assessment.decision && details.assessment.decision.description + && ( + <> +

Director Assessment

+
+
+
+
+
The Director has assessed that {assessmentDecision} ${details.assessment.assessmentPenalty} CAD
+ {details.bceidComment + &&
{parse(details.bceidComment.comment)}
} +
+
- {radioDescriptions.map((each) => ( - (each.displayOrder > 0) +
+ + )} + {user.isGovernment + && ( + <> +

Analyst Recommended Director Assessment

+
+
+
+
+ {radioDescriptions.map((each) => ( + (each.displayOrder === 0) + && showDescription(each) + ))} +
+    {details.organization.name} has not complied with section 10 (2) of the + Zero-Emission Vehicles Act for the {modelYear} adjustment period. +
+ {radioDescriptions.map((each) => ( + (each.displayOrder > 0) && showDescription(each) - ))} -
- - -
-
-
-
+
+ + )}
+ {directorAction + && ( + <> - {/* + + + +
- {modal}
-
); }; @@ -507,6 +637,7 @@ AssessmentDetailsPage.propTypes = { user: CustomPropTypes.user.isRequired, modelYear: PropTypes.number.isRequired, statuses: PropTypes.shape().isRequired, - sales: PropTypes.number.isRequired, + sales: PropTypes.number, + handleSubmit: PropTypes.func.isRequired, }; export default AssessmentDetailsPage; diff --git a/frontend/src/compliance/components/AssessmentEditPage.js b/frontend/src/compliance/components/AssessmentEditPage.js index 1d762db6a..323ceebe7 100644 --- a/frontend/src/compliance/components/AssessmentEditPage.js +++ b/frontend/src/compliance/components/AssessmentEditPage.js @@ -1,5 +1,6 @@ import React from 'react'; import PropTypes from 'prop-types'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import CustomPropTypes from '../../app/utilities/props'; import ComplianceReportAlert from './ComplianceReportAlert'; @@ -10,6 +11,8 @@ import ConsumerLDVSales from './ConsumerLDVSales'; import getTotalReduction from '../../app/utilities/getTotalReduction'; import getClassACredits from '../../app/utilities/getClassAReduction'; import getUnspecifiedClassReduction from '../../app/utilities/getUnspecifiedClassReduction'; +import TextInput from '../../app/components/TextInput'; +import FormDropdown from '../../credits/components/FormDropdown'; const AssessmentEditPage = (props) => { const { @@ -28,6 +31,11 @@ const AssessmentEditPage = (props) => { ratios, sales, supplierMakes, + years, + adjustments, + handleChangeAdjustment, + handleDeleteAdjustment, + addAdjustment, } = props; if (loading) { @@ -119,6 +127,119 @@ const AssessmentEditPage = (props) => { />
+
+

Assessment Credit Adjustment

+
+ {adjustments && adjustments.map((adjustment, index) => ( +
+
+ Credits +
+
+
+ { + handleChangeAdjustment(event.target.value, 'type', index); + }} + /> + +
+
+ { + handleChangeAdjustment(event.target.value, 'type', index); + }} + /> + +
+
+
+
+ { + handleChangeAdjustment(event.target.value, 'creditClass', index); + }} + /> + +
+
+ { + handleChangeAdjustment(event.target.value, 'creditClass', index); + }} + /> + +
+
+ { + handleChangeAdjustment(event.target.value, 'modelYear', index); + }} + fieldName="modelYear" + accessor={(year) => year.name} + selectedOption={adjustment.modelYear || '--'} + labelClassname="mr-2 d-inline-block" + inputClassname="d-inline-block" + rowClassname="mr-5 d-inline-block align-middle" + /> + { + handleChangeAdjustment(event.target.value, 'quantity', index); + }} + labelSize="mr-2 col-form-label d-inline-block align-middle" + inputSize="d-inline-block align-middle transfer-input-width" + mandatory + rowSize="mr-5 d-inline-block align-middle" + num + /> + +
+ ))} + + +
+
{actionbar} @@ -149,5 +270,10 @@ AssessmentEditPage.propTypes = { make: PropTypes.string.isRequired, sales: PropTypes.shape().isRequired, ratios: PropTypes.shape().isRequired, + years: PropTypes.arrayOf(PropTypes.shape()).isRequired, + adjustments: PropTypes.arrayOf(PropTypes.shape()).isRequired, + handleChangeAdjustment: PropTypes.func.isRequired, + handleDeleteAdjustment: PropTypes.func.isRequired, + addAdjustment: PropTypes.func.isRequired, }; export default AssessmentEditPage; diff --git a/frontend/src/compliance/components/ComplianceObligationAmountsTable.js b/frontend/src/compliance/components/ComplianceObligationAmountsTable.js index 0e2e46cb6..e325d8b40 100644 --- a/frontend/src/compliance/components/ComplianceObligationAmountsTable.js +++ b/frontend/src/compliance/components/ComplianceObligationAmountsTable.js @@ -17,8 +17,8 @@ const ComplianceObligationAmountsTable = (props) => { handleChangeSales, sales, statuses, + user, } = props; - return (

Compliance Obligation

@@ -28,16 +28,22 @@ const ComplianceObligationAmountsTable = (props) => { - @@ -52,7 +58,7 @@ const ComplianceObligationAmountsTable = (props) => { - + + )} - {/* {offsetNumbers && Object.keys(offsetNumbers).map((year) => ( - - - - - - ))} */}
- {reportYear} Model Year LDV Consumer Sales\Leases Total: + + {reportYear} Model Year LDV Sales: + {page === 'obligation' + && ( + disabled={['SAVED', 'UNSAVED'].indexOf(statuses.complianceObligation.status) < 0} + /> + )} + {page === 'assessment' + && (sales || 0)}
Large Volume Supplier Class A Ratio: {ratios.zevClassA} %ZEV Class A Credit Reduction:• ZEV Class A Credit Reduction: {getClassAReduction( sales, @@ -64,7 +70,7 @@ const ComplianceObligationAmountsTable = (props) => { )}
- Unspecified ZEV Class Credit Reduction:• Unspecified ZEV Class Credit Reduction: {getUnspecifiedClassReduction( getTotalReduction(sales, ratios.complianceRatio), diff --git a/frontend/src/compliance/components/ComplianceObligationDetailsPage.js b/frontend/src/compliance/components/ComplianceObligationDetailsPage.js index 53eae775c..1c7a8f2b3 100644 --- a/frontend/src/compliance/components/ComplianceObligationDetailsPage.js +++ b/frontend/src/compliance/components/ComplianceObligationDetailsPage.js @@ -21,7 +21,6 @@ const ComplianceObligationDetailsPage = (props) => { details, handleCancelConfirmation, handleCheckboxClick, - handleOffsetChange, handleSave, loading, offsetNumbers, @@ -39,6 +38,7 @@ const ComplianceObligationDetailsPage = (props) => { creditBalance, sales, handleChangeSales, + creditReductionSelection, } = props; const [showModal, setShowModal] = useState(false); let disabledCheckboxes = propsDisabledCheckboxes; @@ -122,6 +122,8 @@ const ComplianceObligationDetailsPage = (props) => { sales={sales} handleChangeSales={handleChangeSales} statuses={statuses} + user={user} + page="obligation" />

Credit Activity

@@ -138,7 +140,6 @@ const ComplianceObligationDetailsPage = (props) => { offsetNumbers={offsetNumbers} unspecifiedCreditReduction={unspecifiedCreditReduction} supplierClassInfo={supplierClassInfo} - handleOffsetChange={handleOffsetChange} user={user} zevClassAReduction={zevClassAReduction} unspecifiedReductions={unspecifiedReductions} @@ -146,6 +147,7 @@ const ComplianceObligationDetailsPage = (props) => { totalReduction={totalReduction} reportYear={reportYear} creditBalance={creditBalance} + creditReductionSelection={creditReductionSelection} />
{ totalReduction, user, statuses, + creditReductionSelection, } = props; return ( @@ -78,6 +79,7 @@ const ComplianceObligationReductionOffsetTable = (props) => {
{ {
- •     {year} Credits - - { handleOffsetChange(event); }} - type="number" - /> - - { handleOffsetChange(event); }} - type="number" - /> -
diff --git a/frontend/src/compliance/components/ComplianceReportAlert.js b/frontend/src/compliance/components/ComplianceReportAlert.js index 443c278ed..d2f0418a0 100644 --- a/frontend/src/compliance/components/ComplianceReportAlert.js +++ b/frontend/src/compliance/components/ComplianceReportAlert.js @@ -14,7 +14,6 @@ const ComplianceReportAlert = (props) => { const { history, validationStatus, } = report; - let message = ''; let title; let classname; @@ -88,6 +87,16 @@ const ComplianceReportAlert = (props) => { message = ` Model year report signed and submitted ${date} by ${userName}. Pending analyst review and Director assessment.`; classname = 'alert-warning'; break; + case 'RECOMMENDED': + title = 'Recommended'; + message = `Model Year Report recommended ${date} by ${userName}, pending Director assessment. Signed and submitted ${confirmedBy.date} by ${confirmedBy.user}`; + classname = 'alert-primary'; + break; + case 'ASSESSED': + title = 'Assessed'; + message = `Model Year Report Assessed ${date} by the Director`; + classname = 'alert-success'; + break; default: title = ''; } diff --git a/frontend/src/compliance/components/ComplianceReportTabs.js b/frontend/src/compliance/components/ComplianceReportTabs.js index b9fabc2aa..d6394044c 100644 --- a/frontend/src/compliance/components/ComplianceReportTabs.js +++ b/frontend/src/compliance/components/ComplianceReportTabs.js @@ -7,10 +7,11 @@ import ROUTES_COMPLIANCE from '../../app/routes/Compliance'; const ComplianceReportTabs = (props) => { const { active, reportStatuses, user } = props; const { id } = useParams(); - const disableOtherTabs = reportStatuses.supplierInformation && reportStatuses.supplierInformation.status === 'UNSAVED'; - const disableAssessment = (reportStatuses.reportSummary && reportStatuses.reportSummary.status !== 'SUBMITTED') - || (reportStatuses.assessment && reportStatuses.assessment.status === 'SUBMITTED' && !user.isGovernment); + const disableAssessment = (reportStatuses.reportSummary && ( + reportStatuses.reportSummary.status !== 'SUBMITTED' && reportStatuses.assessment.status !== 'ASSESSED' + ) && user.isGovernment) + || (reportStatuses.assessment && reportStatuses.assessment.status !== 'ASSESSED' && !user.isGovernment); return (
    { if (row && row.original && user) { return { onClick: () => { - const { id } = row.original; - history.push(ROUTES_COMPLIANCE.REPORT_SUPPLIER_INFORMATION.replace(/:id/g, id)); + const { id, validationStatus } = row.original; + if (validationStatus === 'ASSESSED' || user.isGovernment) { + history.push(ROUTES_COMPLIANCE.REPORT_ASSESSMENT.replace(/:id/g, id)); + } else { + history.push(ROUTES_COMPLIANCE.REPORT_SUPPLIER_INFORMATION.replace(/:id/g, id)); + } }, className: 'clickable', }; diff --git a/frontend/src/compliance/components/ConsumerSalesDetailsPage.js b/frontend/src/compliance/components/ConsumerSalesDetailsPage.js index 0931e1b3d..c78b96a5d 100644 --- a/frontend/src/compliance/components/ConsumerSalesDetailsPage.js +++ b/frontend/src/compliance/components/ConsumerSalesDetailsPage.js @@ -53,6 +53,16 @@ const ConsumerSalesDetailsPage = (props) => { ); + let enableEditBtnForAnalyst = false; + const pendingSalesExist = () => { + if (Object.keys(vehicles).length > 0) { + vehicles.forEach((each) => { + if (each.pendingSales > 0) { + enableEditBtnForAnalyst = true; + } + }) + } + } assertions.forEach((assertion) => { if (checkboxes.indexOf(assertion.id) >= 0) { @@ -62,6 +72,7 @@ const ConsumerSalesDetailsPage = (props) => { return (
    + {pendingSalesExist()}

    {modelYear} Model Year Report

    @@ -84,7 +95,9 @@ const ConsumerSalesDetailsPage = (props) => {
    - {!user.isGovernment && statuses.consumerSales.status === 'CONFIRMED' && ( + {(!user.isGovernment || + (user.hasPermission('RECOMMEND_COMPLIANCE_REPORT') && enableEditBtnForAnalyst)) && + statuses.consumerSales.status === 'CONFIRMED' && ( )}

    Supplier Information

    -
    -

    Legal Name:

    - {details.organization.name} +
    +
    +
    + +

    Legal Name:

    +
    + + {details.organization.name} + +
    +
    -
    -

    Service Address

    - {details.organization.organizationAddress - && details.organization.organizationAddress.map((address) => ( - address.addressType.addressType === 'Service' && ( -
    - {address.representativeName && ( -
    {address.representativeName}
    - )} -
    {address.addressLine1}
    -
    {address.city} {address.state} {address.country}
    -
    {address.postalCode}
    -
    - ) - ))} -
    -
    -

    Records Address

    - {details.organization.organizationAddress - && details.organization.organizationAddress.map((address) => ( - address.addressType.addressType === 'Records' && ( -
    - {address.representativeName && ( -
    {address.representativeName}
    - )} -
    {address.addressLine1}
    -
    {address.city} {address.state} {address.country}
    -
    {address.postalCode}
    -
    - ) - ))} -
    + {details.organization.organizationAddress + && details.organization.organizationAddress.length > 0 && ( + <> +
    +

    Service Address

    + {details.organization.organizationAddress.map((address) => ( + address.addressType.addressType === 'Service' && ( +
    + {address.representativeName && ( +
    {address.representativeName}
    + )} +
    {address.addressLine1}
    +
    {address.city} {address.state} {address.country}
    +
    {address.postalCode}
    +
    + ) + ))} +
    + +
    +

    Records Address

    + {details.organization.organizationAddress.map((address) => ( + address.addressType.addressType === 'Records' && ( +
    + {address.representativeName && ( +
    {address.representativeName}
    + )} +
    {address.addressLine1}
    +
    {address.city} {address.state} {address.country}
    +
    {address.postalCode}
    +
    + ) + ))} +
    + + )}
    @@ -157,28 +169,27 @@ const SupplierInformationDetailsPage = (props) => {

    3 Year Average LDV Sales:

    - {FormatNumeric(details.organization.avgLdvSales)} + {FormatNumeric(details.organization.avgLdvSales, 0)}
    + {details.organization.ldvSales && details.organization.ldvSales.length > 0 && (
    -
    - {details.organization.ldvSales - && ( -
    +
    +
    {details.organization.ldvSales.map((yearSale) => (
    ))}
    - )}
    + )}
    If there is an error in any of the information above, please contact: ZEVRegulation@gov.bc.ca @@ -293,6 +304,7 @@ SupplierInformationDetailsPage.propTypes = { details: PropTypes.shape({ organization: PropTypes.shape(), supplierInformation: PropTypes.shape(), + supplierClassString: PropTypes.shape(), }).isRequired, handleCancelConfirmation: PropTypes.func.isRequired, handleChangeMake: PropTypes.func.isRequired, diff --git a/frontend/src/credits/components/CreditRequestAlert.js b/frontend/src/credits/components/CreditRequestAlert.js index f36328ab1..7c75184ab 100644 --- a/frontend/src/credits/components/CreditRequestAlert.js +++ b/frontend/src/credits/components/CreditRequestAlert.js @@ -68,9 +68,9 @@ const CreditRequestAlert = (props) => { classname = 'alert-success'; icon = 'check-circle'; if (isGovernment || excelUploadMessage === '') { - message = `CA-${id} Credits issued ${moment(statusFilter('VALIDATED').createTimestamp).format('MMM D, YYYY')} by ${statusFilter('VALIDATED').createUser.displayName}.`; + message = `CA-${id} issued ${moment(statusFilter('VALIDATED').createTimestamp).format('MMM D, YYYY')} by ${statusFilter('VALIDATED').createUser.displayName}.`; } else { - message = `CA-${id} Credits issued ${moment(statusFilter('VALIDATED').createTimestamp).format('MMM D, YYYY')} by Government of B.C.`; + message = `CA-${id} issued ${moment(statusFilter('VALIDATED').createTimestamp).format('MMM D, YYYY')} by Government of B.C.`; historyMessage = `${excelUploadMessage}. Application submitted to Government of B.C. ${moment(statusFilter('SUBMITTED').createTimestamp).format('MMM D, YYYY')} by ${statusFilter('SUBMITTED').createUser.displayName}`; } break; diff --git a/frontend/src/organizations/VehicleSupplierDetailsContainer.js b/frontend/src/organizations/VehicleSupplierDetailsContainer.js index c7c98ee3d..a32723f51 100644 --- a/frontend/src/organizations/VehicleSupplierDetailsContainer.js +++ b/frontend/src/organizations/VehicleSupplierDetailsContainer.js @@ -77,6 +77,17 @@ const VehicleSupplierDetailsContainer = (props) => { }); }; + const handleDeleteSale = (sale) => { + axios + .put(ROUTES_ORGANIZATIONS.LDV_SALES.replace(/:id/gi, id), { + id: sale.id + }) + .then(() => { + History.push(ROUTES_ORGANIZATIONS.LIST); + History.replace(ROUTES_ORGANIZATIONS.DETAILS.replace(/:id/gi, id)); + }); + }; + const handleSubmit = (event) => { event.preventDefault(); @@ -104,6 +115,7 @@ const VehicleSupplierDetailsContainer = (props) => { handleSubmit={handleSubmit} selectedModelYear={fields.modelYear} inputLDVSales={fields.ldvSales} + handleDeleteSale={handleDeleteSale} />
    ); diff --git a/frontend/src/organizations/components/VehicleSupplierDetailsPage.js b/frontend/src/organizations/components/VehicleSupplierDetailsPage.js index 666a4ac7b..bed0aef7f 100644 --- a/frontend/src/organizations/components/VehicleSupplierDetailsPage.js +++ b/frontend/src/organizations/components/VehicleSupplierDetailsPage.js @@ -20,6 +20,7 @@ const VehicleSupplierDetailsPage = (props) => { locationState, modelYears, selectedModelYear, + handleDeleteSale, } = props; const { organizationAddress } = details; @@ -47,8 +48,8 @@ const VehicleSupplierDetailsPage = (props) => { {(details.isActive) ? 'Actively supplying vehicles in British Columbia' : 'Not actively supplying vehicles in British Columbia'}
    -
    -
    +
    +

    Service Address

    {organizationAddress && organizationAddress.map((address) => ( @@ -64,7 +65,7 @@ const VehicleSupplierDetailsPage = (props) => { ) ))}
    -
    +

    Records Address

    {organizationAddress && organizationAddress.map((address) => ( @@ -139,13 +140,24 @@ const VehicleSupplierDetailsPage = (props) => {
      {ldvSales.map((sale) => ( -
    • -
      +
    • +
      {sale.modelYear} Model Year:
      -
      +
      {formatNumeric(sale.ldvSales, 0)}
      + {!details.hasSubmittedReport && ( +
      + +
      )}
    • ))}
    diff --git a/frontend/webpack.config.js b/frontend/webpack.config.js index 3bcfc5a5b..581378f8c 100644 --- a/frontend/webpack.config.js +++ b/frontend/webpack.config.js @@ -87,6 +87,8 @@ const config = { }, devServer: { historyApiFallback: true, + host: 'localhost', + port: 3000, }, devtool: 'source-map', plugins: [ diff --git a/keycloak/Dockerfile b/keycloak/Dockerfile new file mode 100644 index 000000000..ab839176b --- /dev/null +++ b/keycloak/Dockerfile @@ -0,0 +1,12 @@ +FROM jboss/keycloak:latest + +COPY realm.json /tmp/realm.json +COPY themes/bcgov-with-login-form /opt/jboss/keycloak/themes/bcgov-with-login-form + +ENV KEYCLOAK_USER admin +ENV KEYCLOAK_PASSWORD admin + +ENV KEYCLOAK_IMPORT /tmp/realm.json + +EXPOSE 8443 +EXPOSE 8888 diff --git a/keycloak/_realm.json b/keycloak/_realm.json new file mode 100644 index 000000000..0d1241d9a --- /dev/null +++ b/keycloak/_realm.json @@ -0,0 +1,1939 @@ +{ + "id": "zeva", + "realm": "zeva", + "displayName": "Zero Emission Reporting Online", + "notBefore": 1579136334, + "revokeRefreshToken": true, + "refreshTokenMaxReuse": 0, + "accessTokenLifespan": 300, + "accessTokenLifespanForImplicitFlow": 900, + "ssoSessionIdleTimeout": 1800, + "ssoSessionMaxLifespan": 36000, + "offlineSessionIdleTimeout": 2592000, + "offlineSessionMaxLifespanEnabled": false, + "offlineSessionMaxLifespan": 5184000, + "accessCodeLifespan": 60, + "accessCodeLifespanUserAction": 300, + "accessCodeLifespanLogin": 1800, + "actionTokenGeneratedByAdminLifespan": 43200, + "actionTokenGeneratedByUserLifespan": 300, + "enabled": true, + "sslRequired": "none", + "registrationAllowed": false, + "registrationEmailAsUsername": false, + "rememberMe": false, + "verifyEmail": false, + "loginWithEmailAllowed": false, + "duplicateEmailsAllowed": true, + "resetPasswordAllowed": false, + "editUsernameAllowed": false, + "bruteForceProtected": false, + "permanentLockout": false, + "maxFailureWaitSeconds": 900, + "minimumQuickLoginWaitSeconds": 60, + "waitIncrementSeconds": 60, + "quickLoginCheckMilliSeconds": 1000, + "maxDeltaTimeSeconds": 43200, + "failureFactor": 30, + "defaultRoles": [ + "uma_authorization", + "offline_access" + ], + "requiredCredentials": [ + "password" + ], + "otpPolicyType": "totp", + "otpPolicyAlgorithm": "HmacSHA1", + "otpPolicyInitialCounter": 0, + "otpPolicyDigits": 6, + "otpPolicyLookAheadWindow": 1, + "otpPolicyPeriod": 30, + "otpSupportedApplications": [ + "FreeOTP", + "Google Authenticator" + ], + "scopeMappings": [ + { + "clientScope": "offline_access", + "roles": [ + "offline_access" + ] + } + ], + "clientScopeMappings": { + "realm-management": [ + { + "client": "zeva-app-sa", + "roles": [ + "realm-admin", + "manage-users" + ] + } + ] + }, + "clients": [ + { + "id": "860858fa-e856-4d66-9426-5fc6a3a388b4", + "clientId": "realm-management", + "name": "${client_realm-management}", + "surrogateAuthRequired": false, + "enabled": true, + "clientAuthenticatorType": "client-secret", + "secret": "**********", + "redirectUris": [], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": true, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "authorizationServicesEnabled": true, + "publicClient": false, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": {}, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "defaultClientScopes": [ + "web-origins", + "role_list", + "profile", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ], + "authorizationSettings": { + "allowRemoteResourceManagement": false, + "policyEnforcementMode": "ENFORCING", + "resources": [], + "policies": [], + "scopes": [ + { + "id": "2c62c1dd-a2ab-4d29-9a55-e6a4ccda406a", + "name": "manage" + }, + { + "id": "8c6f1760-69a0-4d46-a885-8614cf05a2e6", + "name": "view" + }, + { + "id": "6386fdee-9b8f-4e29-aaa5-9acf98459e4e", + "name": "map-roles" + }, + { + "id": "70e8c1e8-e27c-4e2b-bdc2-0e58f40b1232", + "name": "map-roles-client-scope" + }, + { + "id": "d29fc221-294b-4d24-9718-088e7c282ae4", + "name": "map-roles-composite" + }, + { + "id": "e354ee98-86b5-4129-96c7-5125f902c80d", + "name": "configure" + }, + { + "id": "b0005515-f852-4392-9f67-27ee290059d2", + "name": "token-exchange" + }, + { + "id": "9ee0657f-bc84-43a1-a8f6-b84d61cd9d5c", + "name": "manage-members" + }, + { + "id": "f2f2d6e9-feac-4f3f-b293-6d4e0089fe0a", + "name": "manage-membership" + }, + { + "id": "0d7ff6bf-ef41-4de0-b32b-d5bc9d725de2", + "name": "view-members" + } + ] + } + }, + { + "id": "2b11efae-ed6a-4ddf-a277-7937646759f5", + "clientId": "zeva-app", + "surrogateAuthRequired": false, + "enabled": true, + "clientAuthenticatorType": "client-secret", + "secret": "**********", + "redirectUris": [ + "http://localhost:5001/*", + "http://localhost:10000/*", + "http://localhost:3000/*", + "http://localhost/*" + ], + "webOrigins": [ + "+" + ], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": true, + "directAccessGrantsEnabled": true, + "serviceAccountsEnabled": false, + "publicClient": true, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": { + "saml.assertion.signature": "false", + "saml.force.post.binding": "false", + "saml.multivalued.roles": "false", + "saml.encrypt": "false", + "saml.server.signature": "false", + "saml.server.signature.keyinfo.ext": "false", + "exclude.session.state.from.auth.response": "false", + "saml_force_name_id_format": "false", + "saml.client.signature": "false", + "tls.client.certificate.bound.access.tokens": "false", + "saml.authnstatement": "false", + "display.on.consent.screen": "false", + "saml.onetimeuse.condition": "false" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": true, + "nodeReRegistrationTimeout": -1, + "protocolMappers": [ + { + "id": "d83d1bfc-eb77-401b-8252-db1994e889ff", + "name": "user_id", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "user_id", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "user_id", + "jsonType.label": "String" + } + } + ], + "defaultClientScopes": [ + "web-origins", + "profile", + "roles", + "good-service", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "9375ad75-0d27-42cd-90b4-49ac27fbf700", + "clientId": "broker", + "name": "${client_broker}", + "surrogateAuthRequired": false, + "enabled": true, + "clientAuthenticatorType": "client-secret", + "secret": "**********", + "redirectUris": [], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": false, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": {}, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "defaultClientScopes": [ + "web-origins", + "profile", + "roles", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "202107d7-1f6e-457e-86fc-e5abc31be537", + "clientId": "account", + "name": "${client_account}", + "baseUrl": "/auth/realms/zeva/account", + "surrogateAuthRequired": false, + "enabled": true, + "clientAuthenticatorType": "client-secret", + "secret": "**********", + "defaultRoles": [ + "view-profile", + "manage-account" + ], + "redirectUris": [ + "/auth/realms/zeva/account/*" + ], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": false, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": {}, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "defaultClientScopes": [ + "web-origins", + "profile", + "roles", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "a618ff03-1fe3-407e-ab04-12a79282648f", + "clientId": "zeva-app-sa", + "surrogateAuthRequired": false, + "enabled": true, + "clientAuthenticatorType": "client-secret", + "secret": "**********", + "redirectUris": [], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": false, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": true, + "publicClient": false, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": { + "saml.assertion.signature": "false", + "saml.force.post.binding": "false", + "saml.multivalued.roles": "false", + "saml.encrypt": "false", + "saml.server.signature": "false", + "saml.server.signature.keyinfo.ext": "false", + "exclude.session.state.from.auth.response": "false", + "saml_force_name_id_format": "false", + "saml.client.signature": "false", + "tls.client.certificate.bound.access.tokens": "false", + "saml.authnstatement": "false", + "display.on.consent.screen": "false", + "saml.onetimeuse.condition": "false" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": -1, + "protocolMappers": [ + { + "id": "7e4841a8-0f51-4a0f-a0c3-9adb499ffbe6", + "name": "Client Host", + "protocol": "openid-connect", + "protocolMapper": "oidc-usersessionmodel-note-mapper", + "consentRequired": false, + "config": { + "user.session.note": "clientHost", + "userinfo.token.claim": "true", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "clientHost", + "jsonType.label": "String" + } + }, + { + "id": "b129b2c5-8a63-4aae-8256-6464b656bf6e", + "name": "Client IP Address", + "protocol": "openid-connect", + "protocolMapper": "oidc-usersessionmodel-note-mapper", + "consentRequired": false, + "config": { + "user.session.note": "clientAddress", + "userinfo.token.claim": "true", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "clientAddress", + "jsonType.label": "String" + } + }, + { + "id": "abbfe4c8-5d9c-4217-b356-579e4eeeab83", + "name": "Client ID", + "protocol": "openid-connect", + "protocolMapper": "oidc-usersessionmodel-note-mapper", + "consentRequired": false, + "config": { + "user.session.note": "clientId", + "userinfo.token.claim": "true", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "clientId", + "jsonType.label": "String" + } + } + ], + "defaultClientScopes": [ + "web-origins", + "profile", + "roles", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "c1751808-82c8-440a-9b08-6536f032bdd3", + "clientId": "security-admin-console", + "name": "${client_security-admin-console}", + "baseUrl": "/auth/admin/zeva/console/index.html", + "surrogateAuthRequired": false, + "enabled": true, + "clientAuthenticatorType": "client-secret", + "secret": "**********", + "redirectUris": [ + "/auth/admin/zeva/console/*" + ], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": true, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": {}, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "protocolMappers": [ + { + "id": "e1a47b30-17ed-4c82-8233-cef8fedc655f", + "name": "locale", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "locale", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "locale", + "jsonType.label": "String" + } + } + ], + "defaultClientScopes": [ + "web-origins", + "profile", + "roles", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "a5780d88-56e6-40e2-99b9-db407c0c2b0b", + "clientId": "admin-cli", + "name": "${client_admin-cli}", + "surrogateAuthRequired": false, + "enabled": true, + "clientAuthenticatorType": "client-secret", + "secret": "**********", + "redirectUris": [], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": false, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": true, + "serviceAccountsEnabled": false, + "publicClient": true, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": {}, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "defaultClientScopes": [ + "web-origins", + "profile", + "roles", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + } + ], + "clientScopes": [ + { + "id": "d4f1f8cd-4066-420c-a6c5-323c37ba78b5", + "name": "address", + "description": "OpenID Connect built-in scope: address", + "protocol": "openid-connect", + "attributes": { + "consent.screen.text": "${addressScopeConsentText}", + "display.on.consent.screen": "true" + }, + "protocolMappers": [ + { + "id": "c3c0cc42-58da-422a-8ac1-68419883348e", + "name": "address", + "protocol": "openid-connect", + "protocolMapper": "oidc-address-mapper", + "consentRequired": false, + "config": { + "user.attribute.formatted": "formatted", + "user.attribute.country": "country", + "user.attribute.postal_code": "postal_code", + "userinfo.token.claim": "true", + "user.attribute.street": "street", + "id.token.claim": "true", + "user.attribute.region": "region", + "access.token.claim": "true", + "user.attribute.locality": "locality" + } + } + ] + }, + { + "id": "5ab3e8ad-9849-4dea-a125-1005033b0643", + "name": "email", + "description": "OpenID Connect built-in scope: email", + "protocol": "openid-connect", + "attributes": { + "consent.screen.text": "${emailScopeConsentText}", + "display.on.consent.screen": "true" + }, + "protocolMappers": [ + { + "id": "40323d72-c0db-4648-9abf-0f19771176e3", + "name": "email", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "email", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "email", + "jsonType.label": "String" + } + }, + { + "id": "d36bc1a0-ed12-45f4-97ed-1ca7f016a2d8", + "name": "email verified", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "emailVerified", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "email_verified", + "jsonType.label": "boolean" + } + } + ] + }, + { + "id": "c0bb653e-4a0c-4bb1-8ad8-3fe9cc0c870f", + "name": "offline_access", + "description": "OpenID Connect built-in scope: offline_access", + "protocol": "openid-connect", + "attributes": { + "consent.screen.text": "${offlineAccessScopeConsentText}", + "display.on.consent.screen": "true" + } + }, + { + "id": "32670b31-8edd-4a1c-98ab-16232c577662", + "name": "phone", + "description": "OpenID Connect built-in scope: phone", + "protocol": "openid-connect", + "attributes": { + "consent.screen.text": "${phoneScopeConsentText}", + "display.on.consent.screen": "true" + }, + "protocolMappers": [ + { + "id": "dbf5be8e-4a4e-4c82-a3ea-7658208c4331", + "name": "phone number verified", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "phoneNumberVerified", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "phone_number_verified", + "jsonType.label": "boolean" + } + }, + { + "id": "be40f19b-8edd-47ab-8f3f-d393a35f13a5", + "name": "phone number", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "phoneNumber", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "phone_number", + "jsonType.label": "String" + } + } + ] + }, + { + "id": "0cae39e6-5d3a-4be4-bd1a-8ab09faa540a", + "name": "profile", + "description": "OpenID Connect built-in scope: profile", + "protocol": "openid-connect", + "attributes": { + "consent.screen.text": "${profileScopeConsentText}", + "display.on.consent.screen": "true" + }, + "protocolMappers": [ + { + "id": "4e557a61-1878-4fa3-bd90-5a287ddf845d", + "name": "picture", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "picture", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "picture", + "jsonType.label": "String" + } + }, + { + "id": "a7a80903-0f99-46a2-96b3-62561ae4617a", + "name": "zoneinfo", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "zoneinfo", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "zoneinfo", + "jsonType.label": "String" + } + }, + { + "id": "a9031c8e-f0a5-4675-8519-aa438c96e704", + "name": "locale", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "locale", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "locale", + "jsonType.label": "String" + } + }, + { + "id": "bcb270ef-536a-47c4-9546-31e9db29c143", + "name": "nickname", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "nickname", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "nickname", + "jsonType.label": "String" + } + }, + { + "id": "fe7b0315-3030-4c68-8b2c-0a05d3c1c103", + "name": "birthdate", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "birthdate", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "birthdate", + "jsonType.label": "String" + } + }, + { + "id": "56a94964-26e2-4b25-96db-5f08dd69cee3", + "name": "family name", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "lastName", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "family_name", + "jsonType.label": "String" + } + }, + { + "id": "fbbe67c3-85c6-4c06-8970-c4fad45ec385", + "name": "given name", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "firstName", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "given_name", + "jsonType.label": "String" + } + }, + { + "id": "6fe6e896-d6dd-427a-a96d-d77f5f800e16", + "name": "full name", + "protocol": "openid-connect", + "protocolMapper": "oidc-full-name-mapper", + "consentRequired": false, + "config": { + "id.token.claim": "true", + "access.token.claim": "true", + "userinfo.token.claim": "true" + } + }, + { + "id": "4e85ff85-b3a7-41c4-af67-a4f4ee913d5c", + "name": "gender", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "gender", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "gender", + "jsonType.label": "String" + } + }, + { + "id": "4a071796-85d5-46f0-96f3-28f474e52782", + "name": "profile", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "profile", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "profile", + "jsonType.label": "String" + } + }, + { + "id": "49128f28-f7ca-4dc3-89ec-847c8d06ccad", + "name": "middle name", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "middleName", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "middle_name", + "jsonType.label": "String" + } + }, + { + "id": "9e7ac7b6-f494-4a2b-bf6e-d7bc9d96888e", + "name": "website", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "website", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "website", + "jsonType.label": "String" + } + }, + { + "id": "0416e668-349f-4012-b83d-5ca8b4433e14", + "name": "username", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "username", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "preferred_username", + "jsonType.label": "String" + } + }, + { + "id": "74507992-a6ac-46f7-b4ef-2b81507885b0", + "name": "updated at", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "updatedAt", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "updated_at", + "jsonType.label": "String" + } + } + ] + }, + { + "id": "e702392b-36c7-429f-8532-e0b4888522e4", + "name": "roles", + "description": "OpenID Connect scope for add user roles to the access token", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "false", + "display.on.consent.screen": "true", + "consent.screen.text": "${rolesScopeConsentText}" + }, + "protocolMappers": [ + { + "id": "5ca8209a-64db-469f-af30-67df735f3b03", + "name": "realm roles", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-realm-role-mapper", + "consentRequired": false, + "config": { + "user.attribute": "foo", + "access.token.claim": "true", + "claim.name": "realm_access.roles", + "jsonType.label": "String", + "multivalued": "true" + } + }, + { + "id": "ac6d26db-89d0-4b1c-9df6-a983261b200b", + "name": "client roles", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-client-role-mapper", + "consentRequired": false, + "config": { + "user.attribute": "foo", + "access.token.claim": "true", + "claim.name": "resource_access.${client_id}.roles", + "jsonType.label": "String", + "multivalued": "true" + } + }, + { + "id": "dc021cdd-6b5a-4635-8e0b-6e249c1297f5", + "name": "audience resolve", + "protocol": "openid-connect", + "protocolMapper": "oidc-audience-resolve-mapper", + "consentRequired": false, + "config": {} + } + ] + }, + { + "id": "957049dd-c41d-4b9b-9388-e2b3cf071167", + "name": "microprofile-jwt", + "description": "Microprofile - JWT built-in scope", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "false" + }, + "protocolMappers": [ + { + "id": "e3ed9587-fcf9-443b-af37-e0a43db6ca44", + "name": "groups", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-realm-role-mapper", + "consentRequired": false, + "config": { + "multivalued": "true", + "user.attribute": "foo", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "groups", + "jsonType.label": "String" + } + }, + { + "id": "9513127f-d2f4-43ef-bcee-4a238208c823", + "name": "upn", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "username", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "upn", + "jsonType.label": "String" + } + } + ] + }, + { + "id": "5be78f8d-0fb5-4759-8d2d-ee37729c2e46", + "name": "web-origins", + "description": "OpenID Connect scope for add allowed web origins to the access token", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "false", + "display.on.consent.screen": "false", + "consent.screen.text": "" + }, + "protocolMappers": [ + { + "id": "d98cd745-7d7a-4f56-bb0b-e82b8fddea11", + "name": "allowed web origins", + "protocol": "openid-connect", + "protocolMapper": "oidc-allowed-origins-mapper", + "consentRequired": false, + "config": {} + } + ] + }, + { + "id": "957049dd-c41d-4b9b-9388-e2b3cf071167", + "name": "microprofile-jwt", + "description": "Microprofile - JWT built-in scope", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "false" + }, + "protocolMappers": [ + { + "id": "e3ed9587-fcf9-443b-af37-e0a43db6ca44", + "name": "groups", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-realm-role-mapper", + "consentRequired": false, + "config": { + "multivalued": "true", + "user.attribute": "foo", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "groups", + "jsonType.label": "String" + } + }, + { + "id": "9513127f-d2f4-43ef-bcee-4a238208c823", + "name": "upn", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "username", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "upn", + "jsonType.label": "String" + } + } + ] + }, + { + "id": "83700d5f-d33b-4aba-8be9-ee673d6dfa27", + "name": "good-service", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "true" + }, + "protocolMappers": [ + { + "id": "942451aa-b498-4b70-97e9-b48ac4308541", + "name": "zeva-audience", + "protocol": "openid-connect", + "protocolMapper": "oidc-audience-mapper", + "consentRequired": false, + "config": { + "included.client.audience": "zeva-app", + "id.token.claim": "false", + "access.token.claim": "true" + } + } + ] + }, + { + "id": "3a690104-6014-4329-92c4-e1082a24161b", + "name": "role_list", + "description": "SAML role list", + "protocol": "saml", + "attributes": { + "consent.screen.text": "${samlRoleListScopeConsentText}", + "display.on.consent.screen": "true" + }, + "protocolMappers": [ + { + "id": "913238c0-887c-48c2-9b81-214d2b6bc93a", + "name": "role list", + "protocol": "saml", + "protocolMapper": "saml-role-list-mapper", + "consentRequired": false, + "config": { + "single": "false", + "attribute.nameformat": "Basic", + "attribute.name": "Role" + } + } + ] + } + ], + "defaultDefaultClientScopes": [ + "profile", + "role_list", + "email", + "web-origins", + "roles" + ], + "defaultOptionalClientScopes": [ + "phone", + "microprofile-jwt", + "offline_access", + "address" + ], + "browserSecurityHeaders": { + "xContentTypeOptions": "nosniff", + "xRobotsTag": "none", + "xFrameOptions": "SAMEORIGIN", + "xXSSProtection": "1; mode=block", + "contentSecurityPolicy": "frame-src 'self'; frame-ancestors 'self'; object-src 'none';", + "strictTransportSecurity": "max-age=31536000; includeSubDomains" + }, + "smtpServer": {}, + "eventsEnabled": false, + "eventsListeners": [ + "jboss-logging" + ], + "enabledEventTypes": [], + "adminEventsEnabled": false, + "adminEventsDetailsEnabled": false, + "components": { + "org.keycloak.services.clientregistration.policy.ClientRegistrationPolicy": [ + { + "id": "7f6d5c35-7e6b-4889-bb61-6c41149ee571", + "name": "Allowed Client Scopes", + "providerId": "allowed-client-templates", + "subType": "authenticated", + "subComponents": {}, + "config": { + "allow-default-scopes": [ + "true" + ] + } + }, + { + "id": "f6f45a50-626b-49d9-8c84-d3b0587b5f58", + "name": "Allowed Protocol Mapper Types", + "providerId": "allowed-protocol-mappers", + "subType": "anonymous", + "subComponents": {}, + "config": { + "allowed-protocol-mapper-types": [ + "saml-user-attribute-mapper", + "oidc-sha256-pairwise-sub-mapper", + "saml-role-list-mapper", + "oidc-address-mapper", + "oidc-usermodel-attribute-mapper", + "saml-user-property-mapper", + "oidc-usermodel-property-mapper", + "oidc-full-name-mapper" + ] + } + }, + { + "id": "95d70534-9339-4847-85fd-5768d960c72e", + "name": "Allowed Protocol Mapper Types", + "providerId": "allowed-protocol-mappers", + "subType": "authenticated", + "subComponents": {}, + "config": { + "allowed-protocol-mapper-types": [ + "oidc-sha256-pairwise-sub-mapper", + "saml-role-list-mapper", + "saml-user-property-mapper", + "saml-user-attribute-mapper", + "oidc-usermodel-property-mapper", + "oidc-full-name-mapper", + "oidc-address-mapper", + "oidc-usermodel-attribute-mapper" + ] + } + }, + { + "id": "8afb3119-b0f7-42a4-a0e8-c1fe96b00d4d", + "name": "Max Clients Limit", + "providerId": "max-clients", + "subType": "anonymous", + "subComponents": {}, + "config": { + "max-clients": [ + "200" + ] + } + }, + { + "id": "b758cb9d-0f82-4c02-b6a8-1a7e90dcfa60", + "name": "Allowed Client Scopes", + "providerId": "allowed-client-templates", + "subType": "anonymous", + "subComponents": {}, + "config": { + "allow-default-scopes": [ + "true" + ] + } + }, + { + "id": "e8f696ed-eef9-4ca7-8f90-b0d09fa597fc", + "name": "Consent Required", + "providerId": "consent-required", + "subType": "anonymous", + "subComponents": {}, + "config": {} + }, + { + "id": "d0a919c9-2877-4e0e-ab58-875979d185e9", + "name": "Trusted Hosts", + "providerId": "trusted-hosts", + "subType": "anonymous", + "subComponents": {}, + "config": { + "host-sending-registration-request-must-match": [ + "true" + ], + "client-uris-must-match": [ + "true" + ] + } + }, + { + "id": "3a3d0c1e-71f6-4faf-a757-58067debcc83", + "name": "Full Scope Disabled", + "providerId": "scope", + "subType": "anonymous", + "subComponents": {}, + "config": {} + } + ], + "org.keycloak.keys.KeyProvider": [ + { + "id": "eca7ad38-7562-4c2f-a6f4-77eaf3b63b54", + "name": "aes-generated", + "providerId": "aes-generated", + "subComponents": {}, + "config": { + "priority": [ + "100" + ] + } + }, + { + "id": "0496e38f-fa05-462b-b899-5d487a2264fd", + "name": "rsa-generated", + "providerId": "rsa-generated", + "subComponents": {}, + "config": { + "priority": [ + "100" + ] + } + }, + { + "id": "49ee2b36-6b51-4ebf-adcd-d31bda0c2e10", + "name": "hmac-generated", + "providerId": "hmac-generated", + "subComponents": {}, + "config": { + "priority": [ + "100" + ] + } + } + ] + }, + "internationalizationEnabled": false, + "supportedLocales": [], + "authenticationFlows": [ + { + "id": "766798c7-a298-4385-9bcb-8f3e7cf7da65", + "alias": "Handle Existing Account", + "description": "Handle what to do if there is existing account with same email/username like authenticated identity provider", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "idp-confirm-link", + "requirement": "REQUIRED", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "idp-email-verification", + "requirement": "ALTERNATIVE", + "priority": 20, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "requirement": "ALTERNATIVE", + "priority": 30, + "flowAlias": "Verify Existing Account by Re-authentication", + "userSetupAllowed": false, + "autheticatorFlow": true + } + ] + }, + { + "id": "8ba0165a-7e88-4489-bc05-7d9d421eb0ff", + "alias": "Verify Existing Account by Re-authentication", + "description": "Reauthentication of existing account", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "idp-username-password-form", + "requirement": "REQUIRED", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "auth-otp-form", + "requirement": "OPTIONAL", + "priority": 20, + "userSetupAllowed": false, + "autheticatorFlow": false + } + ] + }, + { + "id": "848a9121-7b38-4470-a3c8-e8f8c7098a31", + "alias": "browser", + "description": "browser based authentication", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "auth-cookie", + "requirement": "ALTERNATIVE", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "auth-spnego", + "requirement": "DISABLED", + "priority": 20, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "identity-provider-redirector", + "requirement": "ALTERNATIVE", + "priority": 25, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "requirement": "ALTERNATIVE", + "priority": 30, + "flowAlias": "forms", + "userSetupAllowed": false, + "autheticatorFlow": true + } + ] + }, + { + "id": "f9cfa64c-0949-465f-b98f-ac7e516799a7", + "alias": "clients", + "description": "Base authentication for clients", + "providerId": "client-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "client-secret", + "requirement": "ALTERNATIVE", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "client-jwt", + "requirement": "ALTERNATIVE", + "priority": 20, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "client-secret-jwt", + "requirement": "ALTERNATIVE", + "priority": 30, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "client-x509", + "requirement": "ALTERNATIVE", + "priority": 40, + "userSetupAllowed": false, + "autheticatorFlow": false + } + ] + }, + { + "id": "2c3aa8de-b5a6-4fa6-906c-5428d8c65b2e", + "alias": "direct grant", + "description": "OpenID Connect Resource Owner Grant", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "direct-grant-validate-username", + "requirement": "REQUIRED", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "direct-grant-validate-password", + "requirement": "REQUIRED", + "priority": 20, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "direct-grant-validate-otp", + "requirement": "OPTIONAL", + "priority": 30, + "userSetupAllowed": false, + "autheticatorFlow": false + } + ] + }, + { + "id": "a1e66999-129d-4708-a666-fac5211893ab", + "alias": "docker auth", + "description": "Used by Docker clients to authenticate against the IDP", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "docker-http-basic-authenticator", + "requirement": "REQUIRED", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + } + ] + }, + { + "id": "6afc993f-41c6-4276-9c18-af968264b7a1", + "alias": "first broker login", + "description": "Actions taken after first broker login with identity provider account, which is not yet linked to any Keycloak account", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticatorConfig": "review profile config", + "authenticator": "idp-review-profile", + "requirement": "REQUIRED", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticatorConfig": "create unique user config", + "authenticator": "idp-create-user-if-unique", + "requirement": "ALTERNATIVE", + "priority": 20, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "requirement": "ALTERNATIVE", + "priority": 30, + "flowAlias": "Handle Existing Account", + "userSetupAllowed": false, + "autheticatorFlow": true + } + ] + }, + { + "id": "706621dd-396b-4897-84ff-0f6ce16fafc2", + "alias": "forms", + "description": "Username, password, otp and other auth forms.", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "auth-username-password-form", + "requirement": "REQUIRED", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "auth-otp-form", + "requirement": "OPTIONAL", + "priority": 20, + "userSetupAllowed": false, + "autheticatorFlow": false + } + ] + }, + { + "id": "2407d42d-18a1-4247-8837-4231cc4ee800", + "alias": "registration", + "description": "registration flow", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "registration-page-form", + "requirement": "REQUIRED", + "priority": 10, + "flowAlias": "registration form", + "userSetupAllowed": false, + "autheticatorFlow": true + } + ] + }, + { + "id": "73395a6a-2922-49f2-b986-9406b52a461b", + "alias": "registration form", + "description": "registration form", + "providerId": "form-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "registration-user-creation", + "requirement": "REQUIRED", + "priority": 20, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "registration-profile-action", + "requirement": "REQUIRED", + "priority": 40, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "registration-password-action", + "requirement": "REQUIRED", + "priority": 50, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "registration-recaptcha-action", + "requirement": "DISABLED", + "priority": 60, + "userSetupAllowed": false, + "autheticatorFlow": false + } + ] + }, + { + "id": "9cfc9409-afcd-4601-8a6c-4cb48817eb5d", + "alias": "reset credentials", + "description": "Reset credentials for a user if they forgot their password or something", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "reset-credentials-choose-user", + "requirement": "REQUIRED", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "reset-credential-email", + "requirement": "REQUIRED", + "priority": 20, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "reset-password", + "requirement": "REQUIRED", + "priority": 30, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "reset-otp", + "requirement": "OPTIONAL", + "priority": 40, + "userSetupAllowed": false, + "autheticatorFlow": false + } + ] + }, + { + "id": "209920f8-9f10-40d0-839e-1efdb9dcfdc0", + "alias": "saml ecp", + "description": "SAML ECP Profile Authentication Flow", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "http-basic-authenticator", + "requirement": "REQUIRED", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + } + ] + } + ], + "authenticatorConfig": [ + { + "id": "91bb2efa-1901-45a2-aa3f-b84bd2394b98", + "alias": "create unique user config", + "config": { + "require.password.update.after.registration": "false" + } + }, + { + "id": "cd6670e9-8e79-497e-9e80-dc2df2ada00a", + "alias": "review profile config", + "config": { + "update.profile.on.first.login": "missing" + } + } + ], + "requiredActions": [ + { + "alias": "CONFIGURE_TOTP", + "name": "Configure OTP", + "providerId": "CONFIGURE_TOTP", + "enabled": true, + "defaultAction": false, + "priority": 0, + "config": {} + }, + { + "alias": "UPDATE_PASSWORD", + "name": "Update Password", + "providerId": "UPDATE_PASSWORD", + "enabled": true, + "defaultAction": false, + "priority": 0, + "config": {} + }, + { + "alias": "UPDATE_PROFILE", + "name": "Update Profile", + "providerId": "UPDATE_PROFILE", + "enabled": true, + "defaultAction": false, + "priority": 0, + "config": {} + }, + { + "alias": "VERIFY_EMAIL", + "name": "Verify Email", + "providerId": "VERIFY_EMAIL", + "enabled": true, + "defaultAction": false, + "priority": 0, + "config": {} + }, + { + "alias": "terms_and_conditions", + "name": "Terms and Conditions", + "providerId": "terms_and_conditions", + "enabled": false, + "defaultAction": false, + "priority": 0, + "config": {} + } + ], + "browserFlow": "browser", + "registrationFlow": "registration", + "directGrantFlow": "direct grant", + "resetCredentialsFlow": "reset credentials", + "clientAuthenticationFlow": "clients", + "dockerAuthenticationFlow": "docker auth", + "attributes": { + "_browser_header.xXSSProtection": "1; mode=block", + "_browser_header.xFrameOptions": "SAMEORIGIN", + "_browser_header.strictTransportSecurity": "max-age=31536000; includeSubDomains", + "permanentLockout": "false", + "quickLoginCheckMilliSeconds": "1000", + "displayName": "Zero Emission Reporting Online", + "_browser_header.xRobotsTag": "none", + "maxFailureWaitSeconds": "900", + "minimumQuickLoginWaitSeconds": "60", + "failureFactor": "30", + "actionTokenGeneratedByUserLifespan": "300", + "maxDeltaTimeSeconds": "43200", + "_browser_header.xContentTypeOptions": "nosniff", + "offlineSessionMaxLifespan": "5184000", + "actionTokenGeneratedByAdminLifespan": "43200", + "bruteForceProtected": "false", + "_browser_header.contentSecurityPolicy": "frame-src 'self'; frame-ancestors 'self'; object-src 'none';", + "waitIncrementSeconds": "60", + "offlineSessionMaxLifespanEnabled": "false" + }, + "keycloakVersion": "13.0.1", + "userManagedAccessAllowed": false, + "users" : [{ + "id": "c92e4f6e-20a0-4dbc-89f7-de1022c337b6", + "createdTimestamp": 1539122262773, + "username": "analyst", + "enabled": true, + "totp": false, + "emailVerified": true, + "email": "analyst@test.com", + "attributes": { + "user_id": [ "analyst" ] + }, + "credentials": [ { + "type": "password", + "hashedSaltedValue": "sulY0QYZPvq5J7MC2CgnLDww4pgv4esFlK77qRs0BRgsSlWPzy5KrSpCz1p0i0QaD93+kcoDOEtUg2VKo1HIvQ==", + "salt": "NFKrKh9lAgNQ9UxiTXbESg==", + "hashIterations": 27500, + "counter": 0, + "algorithm": "pbkdf2-sha256", + "digits": 0, + "period": 0, + "createdDate": 1539122410575, + "config": { } + } ], + "disableableCredentialTypes": [ "password" ], + "requiredActions": [ ], + "realmRoles": [ "offline_access", "uma_authorization" ], + "clientRoles": { + "account": [ "manage-account", "view-profile" ] + }, + "notBefore": 0, + "groups": [ ] + }, { + "id": "3e2a5290-37dd-47a9-9a30-26cdef92f3f6", + "createdTimestamp": 1539122309425, + "username": "director", + "enabled": true, + "totp": false, + "emailVerified": true, + "email": "director@test.com", + "attributes": { + "user_id": [ "director" ] + }, + "credentials": [ { + "type": "password", + "hashedSaltedValue": "VwoRitLQ+aEW3pqfXOXaaeQk+po/Y5D9ylYw4P1hkUxQY/QV5ryZ3o8LKGEt5Ks8b7WmZX62C/FybtMHV9GWAg==", + "salt": "U89vfHGW3yKF0j4J/9GsMw==", + "hashIterations": 27500, + "counter": 0, + "algorithm": "pbkdf2-sha256", + "digits": 0, + "period": 0, + "createdDate": 1539122325302, + "config": { } + } ], + "disableableCredentialTypes": [ "password" ], + "requiredActions": [ ], + "realmRoles": [ "offline_access", "uma_authorization" ], + "clientRoles": { + "account": [ "manage-account", "view-profile" ] + }, + "notBefore": 0, + "groups": [ ] + }, { + "id": "e8053de2-57a6-43e4-908c-e0860ce9b5bd", + "createdTimestamp": 1539104835577, + "username": "service-account-zeva-app-sa", + "enabled": true, + "totp": false, + "emailVerified": false, + "email": "service-account-zeva-app-sa@placeholder.org", + "serviceAccountClientId": "zeva-app-sa", + "credentials": [ ], + "disableableCredentialTypes": [ ], + "requiredActions": [ ], + "realmRoles": [ "offline_access", "uma_authorization" ], + "clientRoles": { + "realm-management": [ "view-users", "query-realms", "manage-realm", "query-users", "manage-users", "impersonation", "create-client", "manage-authorization", "query-clients", "realm-admin", "manage-events", "manage-clients", "manage-identity-providers" ], + "broker": [ "read-token" ], + "account": [ "view-profile", "manage-account" ] + }, + "notBefore": 0, + "groups": [ ] + }, { + "id": "b35a4df0-9a27-4083-91f3-867c5587b9dd", + "createdTimestamp": 1618609023302, + "username": "vs1", + "enabled": true, + "totp": false, + "emailVerified": true, + "email": "vs1@test.com", + "attributes": { + "user_id": ["RTAN"] + }, + "credentials": [ { + "type": "password", + "hashedSaltedValue": "9Jtp9yu+2Jm6ilsfsA0ILx2nBjJwfLMiRuZH4L6iauBoK0ycdLGklYWeoGjsIv9pPLW+N9kpN4bt3Q22qKyxhw==", + "salt": "fJBroDLZCvzlyBSIvGG+ug==", + "hashIterations": 27500, + "counter": 0, + "algorithm": "pbkdf2-sha256", + "digits": 0, + "period": 0, + "createdDate": 1618609030632, + "config": { } + } ], + "disableableCredentialTypes": [ "password" ], + "requiredActions": [ ], + "realmRoles": [ "offline_access", "uma_authorization" ], + "clientRoles": { + "account": [ "manage-account", "view-profile" ] + }, + "notBefore": 0, + "groups": [ ] + }, { + "id": "227a3260-515d-44f2-b524-f1a0ea1a74ca", + "createdTimestamp": 1618609052240, + "username": "vs2", + "enabled": true, + "totp": false, + "emailVerified": true, + "email": "vs2@test.com", + "attributes": { + "user_id": ["EMHILLIE"] + }, + "credentials": [ { + "type": "password", + "hashedSaltedValue": "2L20yWpXAZuiI3qo08gmucO71kouZADdzfiYBJr7MHEy7cIemgkOu4GO2BxqqT5YWVzqRSa4RypZuF5W1+HDzw==", + "salt": "EEzUP3Rwo+/lfD6f2L8/rQ==", + "hashIterations": 27500, + "counter": 0, + "algorithm": "pbkdf2-sha256", + "digits": 0, + "period": 0, + "createdDate": 1618609061517, + "config": { } + } ], + "disableableCredentialTypes": [ "password" ], + "requiredActions": [ ], + "realmRoles": [ "offline_access", "uma_authorization" ], + "clientRoles": { + "account": [ "manage-account", "view-profile" ] + }, + "notBefore": 0, + "groups": [ ] + }, { + "id": "b3ee8ba0-10b3-40b8-87a0-f0d9e01eaadf", + "createdTimestamp": 1618609072915, + "username": "vs3", + "enabled": true, + "totp": false, + "emailVerified": true, + "email": "vs3@test.com", + "attributes": { + "user_id": ["NXGREWAL"] + }, + "credentials": [ { + "type": "password", + "hashedSaltedValue": "2zDW+zj89PeTMMO8tn2E0hohurDuoMFkXu7Se1R9XXS7UMrcaw1x24OWzbHkzYjzFwJnSkbZbQHmh+rsnaFevQ==", + "salt": "uSdIIDdfUW32urB3eKvxEw==", + "hashIterations": 27500, + "counter": 0, + "algorithm": "pbkdf2-sha256", + "digits": 0, + "period": 0, + "createdDate": 1618609084226, + "config": { } + } ], + "disableableCredentialTypes": [ "password" ], + "requiredActions": [ ], + "realmRoles": [ "offline_access", "uma_authorization" ], + "clientRoles": { + "account": [ "manage-account", "view-profile" ] + }, + "notBefore": 0, + "groups": [ ] + }, { + "id": "a1d80847-e210-4f57-bbd8-aeba45ddb1a5", + "createdTimestamp": 1542660526892, + "username": "zevaadmin", + "enabled": true, + "totp": false, + "emailVerified": true, + "email": "zevaadmin@test.com", + "attributes": { + "user_id": [ "JADONALD" ] + }, + "credentials": [ { + "type": "password", + "hashedSaltedValue": "2zDW+zj89PeTMMO8tn2E0hohurDuoMFkXu7Se1R9XXS7UMrcaw1x24OWzbHkzYjzFwJnSkbZbQHmh+rsnaFevQ==", + "salt": "uSdIIDdfUW32urB3eKvxEw==", + "hashIterations": 27500, + "counter": 0, + "algorithm": "pbkdf2-sha256", + "digits": 0, + "period": 0, + "createdDate": 1542660558719, + "config": { } + } ], + "disableableCredentialTypes": [ "password" ], + "requiredActions": [ ], + "realmRoles": [ "offline_access", "uma_authorization" ], + "clientRoles": { + "account": [ "manage-account", "view-profile" ] + }, + "notBefore": 0, + "groups": [ ] + }, { + "id": "9a82df94-e9db-4e03-b470-cbce3d81da00", + "createdTimestamp": 1623855752077, + "username": "service-account-realm-management", + "enabled": true, + "totp": false, + "emailVerified": false, + "serviceAccountClientId": "realm-management", + "disableableCredentialTypes": [], + "requiredActions": [], + "realmRoles": [ + "default-roles-zeva" + ], + "clientRoles": { + "realm-management": [ + "uma_protection" + ] + }, + "notBefore": 0, + "groups": [] + }, { + "id": "e8053de2-57a6-43e4-908c-e0860ce9b5bd", + "createdTimestamp": 1539104835577, + "username": "service-account-zeva-app-sa", + "enabled": true, + "totp": false, + "emailVerified": false, + "email": "service-account-zeva-app-sa@placeholder.org", + "serviceAccountClientId": "zeva-app-sa", + "disableableCredentialTypes": [], + "requiredActions": [], + "realmRoles": [ + "offline_access", + "uma_authorization" + ], + "clientRoles": { + "realm-management": [ + "query-clients", + "query-users", + "manage-clients", + "impersonation", + "view-users", + "query-realms", + "manage-realm", + "manage-events", + "manage-identity-providers", + "realm-admin", + "create-client", + "manage-users", + "manage-authorization" + ], + "broker": [ + "read-token" + ], + "account": [ + "view-profile", + "manage-account" + ] + }, + "notBefore": 0, + "groups": [] + }] +} \ No newline at end of file diff --git a/keycloak/realm.json b/keycloak/realm.json new file mode 100644 index 000000000..1b6a34983 --- /dev/null +++ b/keycloak/realm.json @@ -0,0 +1,1707 @@ +{ + "id": "zeva", + "realm": "zeva", + "displayName": "Zero Emission Reporting Online", + "notBefore": 1579136334, + "revokeRefreshToken": true, + "refreshTokenMaxReuse": 0, + "accessTokenLifespan": 300, + "accessTokenLifespanForImplicitFlow": 900, + "ssoSessionIdleTimeout": 1800, + "ssoSessionMaxLifespan": 36000, + "offlineSessionIdleTimeout": 2592000, + "offlineSessionMaxLifespanEnabled": false, + "offlineSessionMaxLifespan": 5184000, + "accessCodeLifespan": 60, + "accessCodeLifespanUserAction": 300, + "accessCodeLifespanLogin": 1800, + "actionTokenGeneratedByAdminLifespan": 43200, + "actionTokenGeneratedByUserLifespan": 300, + "enabled": true, + "sslRequired": "none", + "registrationAllowed": false, + "registrationEmailAsUsername": false, + "rememberMe": false, + "verifyEmail": false, + "loginWithEmailAllowed": false, + "duplicateEmailsAllowed": true, + "resetPasswordAllowed": false, + "editUsernameAllowed": false, + "bruteForceProtected": false, + "permanentLockout": false, + "maxFailureWaitSeconds": 900, + "minimumQuickLoginWaitSeconds": 60, + "waitIncrementSeconds": 60, + "quickLoginCheckMilliSeconds": 1000, + "maxDeltaTimeSeconds": 43200, + "failureFactor": 30, + "defaultRoles": [ + "uma_authorization", + "offline_access" + ], + "requiredCredentials": [ + "password" + ], + "otpPolicyType": "totp", + "otpPolicyAlgorithm": "HmacSHA1", + "otpPolicyInitialCounter": 0, + "otpPolicyDigits": 6, + "otpPolicyLookAheadWindow": 1, + "otpPolicyPeriod": 30, + "otpSupportedApplications": [ + "FreeOTP", + "Google Authenticator" + ], + "scopeMappings": [ + { + "clientScope": "offline_access", + "roles": [ + "offline_access" + ] + } + ], + "clientScopeMappings": { + "realm-management": [ + { + "client": "zeva-app-sa", + "roles": [ + "realm-admin", + "manage-users" + ] + } + ] + }, + "clients": [ + { + "id": "860858fa-e856-4d66-9426-5fc6a3a388b4", + "clientId": "realm-management", + "name": "${client_realm-management}", + "surrogateAuthRequired": false, + "enabled": true, + "clientAuthenticatorType": "client-secret", + "secret": "**********", + "redirectUris": [], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": true, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "authorizationServicesEnabled": true, + "publicClient": false, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": {}, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "defaultClientScopes": [ + "role_list", + "profile", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access" + ], + "authorizationSettings": { + "allowRemoteResourceManagement": false, + "policyEnforcementMode": "ENFORCING", + "resources": [], + "policies": [], + "scopes": [ + { + "id": "2c62c1dd-a2ab-4d29-9a55-e6a4ccda406a", + "name": "manage" + }, + { + "id": "8c6f1760-69a0-4d46-a885-8614cf05a2e6", + "name": "view" + }, + { + "id": "6386fdee-9b8f-4e29-aaa5-9acf98459e4e", + "name": "map-roles" + }, + { + "id": "70e8c1e8-e27c-4e2b-bdc2-0e58f40b1232", + "name": "map-roles-client-scope" + }, + { + "id": "d29fc221-294b-4d24-9718-088e7c282ae4", + "name": "map-roles-composite" + }, + { + "id": "e354ee98-86b5-4129-96c7-5125f902c80d", + "name": "configure" + }, + { + "id": "b0005515-f852-4392-9f67-27ee290059d2", + "name": "token-exchange" + }, + { + "id": "9ee0657f-bc84-43a1-a8f6-b84d61cd9d5c", + "name": "manage-members" + }, + { + "id": "f2f2d6e9-feac-4f3f-b293-6d4e0089fe0a", + "name": "manage-membership" + }, + { + "id": "0d7ff6bf-ef41-4de0-b32b-d5bc9d725de2", + "name": "view-members" + } + ] + } + }, + { + "id": "2b11efae-ed6a-4ddf-a277-7937646759f5", + "clientId": "zeva-app", + "surrogateAuthRequired": false, + "enabled": true, + "clientAuthenticatorType": "client-secret", + "secret": "**********", + "redirectUris": [ + "http://localhost:5001/*", + "http://localhost:10000/*", + "http://localhost:3000/*", + "http://localhost/*" + ], + "webOrigins": [ + "+" + ], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": true, + "directAccessGrantsEnabled": true, + "serviceAccountsEnabled": false, + "publicClient": true, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": { + "saml.assertion.signature": "false", + "saml.force.post.binding": "false", + "saml.multivalued.roles": "false", + "saml.encrypt": "false", + "saml.server.signature": "false", + "saml.server.signature.keyinfo.ext": "false", + "exclude.session.state.from.auth.response": "false", + "saml_force_name_id_format": "false", + "saml.client.signature": "false", + "tls.client.certificate.bound.access.tokens": "false", + "saml.authnstatement": "false", + "display.on.consent.screen": "false", + "saml.onetimeuse.condition": "false" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": true, + "nodeReRegistrationTimeout": -1, + "protocolMappers": [ + { + "id": "d83d1bfc-eb77-401b-8252-db1994e889ff", + "name": "user_id", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "user_id", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "user_id", + "jsonType.label": "String" + } + } + ], + "defaultClientScopes": [ + "role_list", + "profile", + "email", + "good-service" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access" + ] + }, + { + "id": "9375ad75-0d27-42cd-90b4-49ac27fbf700", + "clientId": "broker", + "name": "${client_broker}", + "surrogateAuthRequired": false, + "enabled": true, + "clientAuthenticatorType": "client-secret", + "secret": "**********", + "redirectUris": [], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": false, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": {}, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "defaultClientScopes": [ + "role_list", + "profile", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access" + ] + }, + { + "id": "202107d7-1f6e-457e-86fc-e5abc31be537", + "clientId": "account", + "name": "${client_account}", + "baseUrl": "/auth/realms/zeva/account", + "surrogateAuthRequired": false, + "enabled": true, + "clientAuthenticatorType": "client-secret", + "secret": "**********", + "defaultRoles": [ + "view-profile", + "manage-account" + ], + "redirectUris": [ + "/auth/realms/zeva/account/*" + ], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": false, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": {}, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "defaultClientScopes": [ + "role_list", + "profile", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access" + ] + }, + { + "id": "a618ff03-1fe3-407e-ab04-12a79282648f", + "clientId": "zeva-app-sa", + "surrogateAuthRequired": false, + "enabled": true, + "clientAuthenticatorType": "client-secret", + "secret": "**********", + "redirectUris": [], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": false, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": true, + "publicClient": false, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": { + "saml.assertion.signature": "false", + "saml.force.post.binding": "false", + "saml.multivalued.roles": "false", + "saml.encrypt": "false", + "saml.server.signature": "false", + "saml.server.signature.keyinfo.ext": "false", + "exclude.session.state.from.auth.response": "false", + "saml_force_name_id_format": "false", + "saml.client.signature": "false", + "tls.client.certificate.bound.access.tokens": "false", + "saml.authnstatement": "false", + "display.on.consent.screen": "false", + "saml.onetimeuse.condition": "false" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": -1, + "protocolMappers": [ + { + "id": "7e4841a8-0f51-4a0f-a0c3-9adb499ffbe6", + "name": "Client Host", + "protocol": "openid-connect", + "protocolMapper": "oidc-usersessionmodel-note-mapper", + "consentRequired": false, + "config": { + "user.session.note": "clientHost", + "userinfo.token.claim": "true", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "clientHost", + "jsonType.label": "String" + } + }, + { + "id": "b129b2c5-8a63-4aae-8256-6464b656bf6e", + "name": "Client IP Address", + "protocol": "openid-connect", + "protocolMapper": "oidc-usersessionmodel-note-mapper", + "consentRequired": false, + "config": { + "user.session.note": "clientAddress", + "userinfo.token.claim": "true", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "clientAddress", + "jsonType.label": "String" + } + }, + { + "id": "abbfe4c8-5d9c-4217-b356-579e4eeeab83", + "name": "Client ID", + "protocol": "openid-connect", + "protocolMapper": "oidc-usersessionmodel-note-mapper", + "consentRequired": false, + "config": { + "user.session.note": "clientId", + "userinfo.token.claim": "true", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "clientId", + "jsonType.label": "String" + } + } + ], + "defaultClientScopes": [ + "role_list", + "profile", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access" + ] + }, + { + "id": "c1751808-82c8-440a-9b08-6536f032bdd3", + "clientId": "security-admin-console", + "name": "${client_security-admin-console}", + "baseUrl": "/auth/admin/zeva/console/index.html", + "surrogateAuthRequired": false, + "enabled": true, + "clientAuthenticatorType": "client-secret", + "secret": "**********", + "redirectUris": [ + "/auth/admin/zeva/console/*" + ], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": true, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": {}, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "protocolMappers": [ + { + "id": "e1a47b30-17ed-4c82-8233-cef8fedc655f", + "name": "locale", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "locale", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "locale", + "jsonType.label": "String" + } + } + ], + "defaultClientScopes": [ + "role_list", + "profile", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access" + ] + }, + { + "id": "a5780d88-56e6-40e2-99b9-db407c0c2b0b", + "clientId": "admin-cli", + "name": "${client_admin-cli}", + "surrogateAuthRequired": false, + "enabled": true, + "clientAuthenticatorType": "client-secret", + "secret": "**********", + "redirectUris": [], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": false, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": true, + "serviceAccountsEnabled": false, + "publicClient": true, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": {}, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "defaultClientScopes": [ + "role_list", + "profile", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access" + ] + } + ], + "clientScopes": [ + { + "id": "d4f1f8cd-4066-420c-a6c5-323c37ba78b5", + "name": "address", + "description": "OpenID Connect built-in scope: address", + "protocol": "openid-connect", + "attributes": { + "consent.screen.text": "${addressScopeConsentText}", + "display.on.consent.screen": "true" + }, + "protocolMappers": [ + { + "id": "c3c0cc42-58da-422a-8ac1-68419883348e", + "name": "address", + "protocol": "openid-connect", + "protocolMapper": "oidc-address-mapper", + "consentRequired": false, + "config": { + "user.attribute.formatted": "formatted", + "user.attribute.country": "country", + "user.attribute.postal_code": "postal_code", + "userinfo.token.claim": "true", + "user.attribute.street": "street", + "id.token.claim": "true", + "user.attribute.region": "region", + "access.token.claim": "true", + "user.attribute.locality": "locality" + } + } + ] + }, + { + "id": "5ab3e8ad-9849-4dea-a125-1005033b0643", + "name": "email", + "description": "OpenID Connect built-in scope: email", + "protocol": "openid-connect", + "attributes": { + "consent.screen.text": "${emailScopeConsentText}", + "display.on.consent.screen": "true" + }, + "protocolMappers": [ + { + "id": "40323d72-c0db-4648-9abf-0f19771176e3", + "name": "email", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "email", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "email", + "jsonType.label": "String" + } + }, + { + "id": "d36bc1a0-ed12-45f4-97ed-1ca7f016a2d8", + "name": "email verified", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "emailVerified", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "email_verified", + "jsonType.label": "boolean" + } + } + ] + }, + { + "id": "c0bb653e-4a0c-4bb1-8ad8-3fe9cc0c870f", + "name": "offline_access", + "description": "OpenID Connect built-in scope: offline_access", + "protocol": "openid-connect", + "attributes": { + "consent.screen.text": "${offlineAccessScopeConsentText}", + "display.on.consent.screen": "true" + } + }, + { + "id": "32670b31-8edd-4a1c-98ab-16232c577662", + "name": "phone", + "description": "OpenID Connect built-in scope: phone", + "protocol": "openid-connect", + "attributes": { + "consent.screen.text": "${phoneScopeConsentText}", + "display.on.consent.screen": "true" + }, + "protocolMappers": [ + { + "id": "dbf5be8e-4a4e-4c82-a3ea-7658208c4331", + "name": "phone number verified", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "phoneNumberVerified", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "phone_number_verified", + "jsonType.label": "boolean" + } + }, + { + "id": "be40f19b-8edd-47ab-8f3f-d393a35f13a5", + "name": "phone number", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "phoneNumber", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "phone_number", + "jsonType.label": "String" + } + } + ] + }, + { + "id": "0cae39e6-5d3a-4be4-bd1a-8ab09faa540a", + "name": "profile", + "description": "OpenID Connect built-in scope: profile", + "protocol": "openid-connect", + "attributes": { + "consent.screen.text": "${profileScopeConsentText}", + "display.on.consent.screen": "true" + }, + "protocolMappers": [ + { + "id": "4e557a61-1878-4fa3-bd90-5a287ddf845d", + "name": "picture", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "picture", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "picture", + "jsonType.label": "String" + } + }, + { + "id": "a7a80903-0f99-46a2-96b3-62561ae4617a", + "name": "zoneinfo", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "zoneinfo", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "zoneinfo", + "jsonType.label": "String" + } + }, + { + "id": "a9031c8e-f0a5-4675-8519-aa438c96e704", + "name": "locale", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "locale", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "locale", + "jsonType.label": "String" + } + }, + { + "id": "bcb270ef-536a-47c4-9546-31e9db29c143", + "name": "nickname", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "nickname", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "nickname", + "jsonType.label": "String" + } + }, + { + "id": "fe7b0315-3030-4c68-8b2c-0a05d3c1c103", + "name": "birthdate", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "birthdate", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "birthdate", + "jsonType.label": "String" + } + }, + { + "id": "56a94964-26e2-4b25-96db-5f08dd69cee3", + "name": "family name", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "lastName", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "family_name", + "jsonType.label": "String" + } + }, + { + "id": "fbbe67c3-85c6-4c06-8970-c4fad45ec385", + "name": "given name", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "firstName", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "given_name", + "jsonType.label": "String" + } + }, + { + "id": "6fe6e896-d6dd-427a-a96d-d77f5f800e16", + "name": "full name", + "protocol": "openid-connect", + "protocolMapper": "oidc-full-name-mapper", + "consentRequired": false, + "config": { + "id.token.claim": "true", + "access.token.claim": "true", + "userinfo.token.claim": "true" + } + }, + { + "id": "4e85ff85-b3a7-41c4-af67-a4f4ee913d5c", + "name": "gender", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "gender", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "gender", + "jsonType.label": "String" + } + }, + { + "id": "4a071796-85d5-46f0-96f3-28f474e52782", + "name": "profile", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "profile", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "profile", + "jsonType.label": "String" + } + }, + { + "id": "49128f28-f7ca-4dc3-89ec-847c8d06ccad", + "name": "middle name", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "middleName", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "middle_name", + "jsonType.label": "String" + } + }, + { + "id": "9e7ac7b6-f494-4a2b-bf6e-d7bc9d96888e", + "name": "website", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "website", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "website", + "jsonType.label": "String" + } + }, + { + "id": "0416e668-349f-4012-b83d-5ca8b4433e14", + "name": "username", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "username", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "preferred_username", + "jsonType.label": "String" + } + }, + { + "id": "74507992-a6ac-46f7-b4ef-2b81507885b0", + "name": "updated at", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "updatedAt", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "updated_at", + "jsonType.label": "String" + } + } + ] + }, + { + "id": "3a690104-6014-4329-92c4-e1082a24161b", + "name": "role_list", + "description": "SAML role list", + "protocol": "saml", + "attributes": { + "consent.screen.text": "${samlRoleListScopeConsentText}", + "display.on.consent.screen": "true" + }, + "protocolMappers": [ + { + "id": "913238c0-887c-48c2-9b81-214d2b6bc93a", + "name": "role list", + "protocol": "saml", + "protocolMapper": "saml-role-list-mapper", + "consentRequired": false, + "config": { + "single": "false", + "attribute.nameformat": "Basic", + "attribute.name": "Role" + } + } + ] + }, + { + "id": "83700d5f-d33b-4aba-8be9-ee673d6dfa27", + "name": "good-service", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "true" + }, + "protocolMappers": [ + { + "id": "942451aa-b498-4b70-97e9-b48ac4308541", + "name": "zeva-audience", + "protocol": "openid-connect", + "protocolMapper": "oidc-audience-mapper", + "consentRequired": false, + "config": { + "included.client.audience": "zeva-app", + "id.token.claim": "false", + "access.token.claim": "true" + } + } + ] + } + ], + "defaultDefaultClientScopes": [ + "role_list", + "profile", + "email" + ], + "defaultOptionalClientScopes": [ + "offline_access", + "address", + "phone" + ], + "browserSecurityHeaders": { + "xContentTypeOptions": "nosniff", + "xRobotsTag": "none", + "xFrameOptions": "SAMEORIGIN", + "xXSSProtection": "1; mode=block", + "contentSecurityPolicy": "frame-src 'self'; frame-ancestors 'self'; object-src 'none';", + "strictTransportSecurity": "max-age=31536000; includeSubDomains" + }, + "smtpServer": {}, + "eventsEnabled": false, + "eventsListeners": [ + "jboss-logging" + ], + "enabledEventTypes": [], + "adminEventsEnabled": false, + "adminEventsDetailsEnabled": false, + "components": { + "org.keycloak.services.clientregistration.policy.ClientRegistrationPolicy": [ + { + "id": "7f6d5c35-7e6b-4889-bb61-6c41149ee571", + "name": "Allowed Client Scopes", + "providerId": "allowed-client-templates", + "subType": "authenticated", + "subComponents": {}, + "config": { + "allow-default-scopes": [ + "true" + ] + } + }, + { + "id": "f6f45a50-626b-49d9-8c84-d3b0587b5f58", + "name": "Allowed Protocol Mapper Types", + "providerId": "allowed-protocol-mappers", + "subType": "anonymous", + "subComponents": {}, + "config": { + "allowed-protocol-mapper-types": [ + "saml-user-attribute-mapper", + "oidc-sha256-pairwise-sub-mapper", + "saml-role-list-mapper", + "oidc-address-mapper", + "oidc-usermodel-attribute-mapper", + "saml-user-property-mapper", + "oidc-usermodel-property-mapper", + "oidc-full-name-mapper" + ] + } + }, + { + "id": "95d70534-9339-4847-85fd-5768d960c72e", + "name": "Allowed Protocol Mapper Types", + "providerId": "allowed-protocol-mappers", + "subType": "authenticated", + "subComponents": {}, + "config": { + "allowed-protocol-mapper-types": [ + "oidc-sha256-pairwise-sub-mapper", + "saml-role-list-mapper", + "saml-user-property-mapper", + "saml-user-attribute-mapper", + "oidc-usermodel-property-mapper", + "oidc-full-name-mapper", + "oidc-address-mapper", + "oidc-usermodel-attribute-mapper" + ] + } + }, + { + "id": "8afb3119-b0f7-42a4-a0e8-c1fe96b00d4d", + "name": "Max Clients Limit", + "providerId": "max-clients", + "subType": "anonymous", + "subComponents": {}, + "config": { + "max-clients": [ + "200" + ] + } + }, + { + "id": "b758cb9d-0f82-4c02-b6a8-1a7e90dcfa60", + "name": "Allowed Client Scopes", + "providerId": "allowed-client-templates", + "subType": "anonymous", + "subComponents": {}, + "config": { + "allow-default-scopes": [ + "true" + ] + } + }, + { + "id": "e8f696ed-eef9-4ca7-8f90-b0d09fa597fc", + "name": "Consent Required", + "providerId": "consent-required", + "subType": "anonymous", + "subComponents": {}, + "config": {} + }, + { + "id": "d0a919c9-2877-4e0e-ab58-875979d185e9", + "name": "Trusted Hosts", + "providerId": "trusted-hosts", + "subType": "anonymous", + "subComponents": {}, + "config": { + "host-sending-registration-request-must-match": [ + "true" + ], + "client-uris-must-match": [ + "true" + ] + } + }, + { + "id": "3a3d0c1e-71f6-4faf-a757-58067debcc83", + "name": "Full Scope Disabled", + "providerId": "scope", + "subType": "anonymous", + "subComponents": {}, + "config": {} + } + ], + "org.keycloak.keys.KeyProvider": [ + { + "id": "eca7ad38-7562-4c2f-a6f4-77eaf3b63b54", + "name": "aes-generated", + "providerId": "aes-generated", + "subComponents": {}, + "config": { + "priority": [ + "100" + ] + } + }, + { + "id": "0496e38f-fa05-462b-b899-5d487a2264fd", + "name": "rsa-generated", + "providerId": "rsa-generated", + "subComponents": {}, + "config": { + "priority": [ + "100" + ] + } + }, + { + "id": "49ee2b36-6b51-4ebf-adcd-d31bda0c2e10", + "name": "hmac-generated", + "providerId": "hmac-generated", + "subComponents": {}, + "config": { + "priority": [ + "100" + ] + } + } + ] + }, + "internationalizationEnabled": false, + "supportedLocales": [], + "authenticationFlows": [ + { + "id": "766798c7-a298-4385-9bcb-8f3e7cf7da65", + "alias": "Handle Existing Account", + "description": "Handle what to do if there is existing account with same email/username like authenticated identity provider", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "idp-confirm-link", + "requirement": "REQUIRED", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "idp-email-verification", + "requirement": "ALTERNATIVE", + "priority": 20, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "requirement": "ALTERNATIVE", + "priority": 30, + "flowAlias": "Verify Existing Account by Re-authentication", + "userSetupAllowed": false, + "autheticatorFlow": true + } + ] + }, + { + "id": "8ba0165a-7e88-4489-bc05-7d9d421eb0ff", + "alias": "Verify Existing Account by Re-authentication", + "description": "Reauthentication of existing account", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "idp-username-password-form", + "requirement": "REQUIRED", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "auth-otp-form", + "requirement": "OPTIONAL", + "priority": 20, + "userSetupAllowed": false, + "autheticatorFlow": false + } + ] + }, + { + "id": "848a9121-7b38-4470-a3c8-e8f8c7098a31", + "alias": "browser", + "description": "browser based authentication", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "auth-cookie", + "requirement": "ALTERNATIVE", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "auth-spnego", + "requirement": "DISABLED", + "priority": 20, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "identity-provider-redirector", + "requirement": "ALTERNATIVE", + "priority": 25, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "requirement": "ALTERNATIVE", + "priority": 30, + "flowAlias": "forms", + "userSetupAllowed": false, + "autheticatorFlow": true + } + ] + }, + { + "id": "f9cfa64c-0949-465f-b98f-ac7e516799a7", + "alias": "clients", + "description": "Base authentication for clients", + "providerId": "client-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "client-secret", + "requirement": "ALTERNATIVE", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "client-jwt", + "requirement": "ALTERNATIVE", + "priority": 20, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "client-secret-jwt", + "requirement": "ALTERNATIVE", + "priority": 30, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "client-x509", + "requirement": "ALTERNATIVE", + "priority": 40, + "userSetupAllowed": false, + "autheticatorFlow": false + } + ] + }, + { + "id": "2c3aa8de-b5a6-4fa6-906c-5428d8c65b2e", + "alias": "direct grant", + "description": "OpenID Connect Resource Owner Grant", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "direct-grant-validate-username", + "requirement": "REQUIRED", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "direct-grant-validate-password", + "requirement": "REQUIRED", + "priority": 20, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "direct-grant-validate-otp", + "requirement": "OPTIONAL", + "priority": 30, + "userSetupAllowed": false, + "autheticatorFlow": false + } + ] + }, + { + "id": "a1e66999-129d-4708-a666-fac5211893ab", + "alias": "docker auth", + "description": "Used by Docker clients to authenticate against the IDP", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "docker-http-basic-authenticator", + "requirement": "REQUIRED", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + } + ] + }, + { + "id": "6afc993f-41c6-4276-9c18-af968264b7a1", + "alias": "first broker login", + "description": "Actions taken after first broker login with identity provider account, which is not yet linked to any Keycloak account", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticatorConfig": "review profile config", + "authenticator": "idp-review-profile", + "requirement": "REQUIRED", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticatorConfig": "create unique user config", + "authenticator": "idp-create-user-if-unique", + "requirement": "ALTERNATIVE", + "priority": 20, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "requirement": "ALTERNATIVE", + "priority": 30, + "flowAlias": "Handle Existing Account", + "userSetupAllowed": false, + "autheticatorFlow": true + } + ] + }, + { + "id": "706621dd-396b-4897-84ff-0f6ce16fafc2", + "alias": "forms", + "description": "Username, password, otp and other auth forms.", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "auth-username-password-form", + "requirement": "REQUIRED", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "auth-otp-form", + "requirement": "OPTIONAL", + "priority": 20, + "userSetupAllowed": false, + "autheticatorFlow": false + } + ] + }, + { + "id": "2407d42d-18a1-4247-8837-4231cc4ee800", + "alias": "registration", + "description": "registration flow", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "registration-page-form", + "requirement": "REQUIRED", + "priority": 10, + "flowAlias": "registration form", + "userSetupAllowed": false, + "autheticatorFlow": true + } + ] + }, + { + "id": "73395a6a-2922-49f2-b986-9406b52a461b", + "alias": "registration form", + "description": "registration form", + "providerId": "form-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "registration-user-creation", + "requirement": "REQUIRED", + "priority": 20, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "registration-profile-action", + "requirement": "REQUIRED", + "priority": 40, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "registration-password-action", + "requirement": "REQUIRED", + "priority": 50, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "registration-recaptcha-action", + "requirement": "DISABLED", + "priority": 60, + "userSetupAllowed": false, + "autheticatorFlow": false + } + ] + }, + { + "id": "9cfc9409-afcd-4601-8a6c-4cb48817eb5d", + "alias": "reset credentials", + "description": "Reset credentials for a user if they forgot their password or something", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "reset-credentials-choose-user", + "requirement": "REQUIRED", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "reset-credential-email", + "requirement": "REQUIRED", + "priority": 20, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "reset-password", + "requirement": "REQUIRED", + "priority": 30, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "reset-otp", + "requirement": "OPTIONAL", + "priority": 40, + "userSetupAllowed": false, + "autheticatorFlow": false + } + ] + }, + { + "id": "209920f8-9f10-40d0-839e-1efdb9dcfdc0", + "alias": "saml ecp", + "description": "SAML ECP Profile Authentication Flow", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "http-basic-authenticator", + "requirement": "REQUIRED", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + } + ] + } + ], + "authenticatorConfig": [ + { + "id": "91bb2efa-1901-45a2-aa3f-b84bd2394b98", + "alias": "create unique user config", + "config": { + "require.password.update.after.registration": "false" + } + }, + { + "id": "cd6670e9-8e79-497e-9e80-dc2df2ada00a", + "alias": "review profile config", + "config": { + "update.profile.on.first.login": "missing" + } + } + ], + "requiredActions": [ + { + "alias": "CONFIGURE_TOTP", + "name": "Configure OTP", + "providerId": "CONFIGURE_TOTP", + "enabled": true, + "defaultAction": false, + "priority": 0, + "config": {} + }, + { + "alias": "UPDATE_PASSWORD", + "name": "Update Password", + "providerId": "UPDATE_PASSWORD", + "enabled": true, + "defaultAction": false, + "priority": 0, + "config": {} + }, + { + "alias": "UPDATE_PROFILE", + "name": "Update Profile", + "providerId": "UPDATE_PROFILE", + "enabled": true, + "defaultAction": false, + "priority": 0, + "config": {} + }, + { + "alias": "VERIFY_EMAIL", + "name": "Verify Email", + "providerId": "VERIFY_EMAIL", + "enabled": true, + "defaultAction": false, + "priority": 0, + "config": {} + }, + { + "alias": "terms_and_conditions", + "name": "Terms and Conditions", + "providerId": "terms_and_conditions", + "enabled": false, + "defaultAction": false, + "priority": 0, + "config": {} + } + ], + "browserFlow": "browser", + "registrationFlow": "registration", + "directGrantFlow": "direct grant", + "resetCredentialsFlow": "reset credentials", + "clientAuthenticationFlow": "clients", + "dockerAuthenticationFlow": "docker auth", + "attributes": { + "_browser_header.xXSSProtection": "1; mode=block", + "_browser_header.xFrameOptions": "SAMEORIGIN", + "_browser_header.strictTransportSecurity": "max-age=31536000; includeSubDomains", + "permanentLockout": "false", + "quickLoginCheckMilliSeconds": "1000", + "displayName": "Zero Emission Reporting Online", + "_browser_header.xRobotsTag": "none", + "maxFailureWaitSeconds": "900", + "minimumQuickLoginWaitSeconds": "60", + "failureFactor": "30", + "actionTokenGeneratedByUserLifespan": "300", + "maxDeltaTimeSeconds": "43200", + "_browser_header.xContentTypeOptions": "nosniff", + "offlineSessionMaxLifespan": "5184000", + "actionTokenGeneratedByAdminLifespan": "43200", + "bruteForceProtected": "false", + "_browser_header.contentSecurityPolicy": "frame-src 'self'; frame-ancestors 'self'; object-src 'none';", + "waitIncrementSeconds": "60", + "offlineSessionMaxLifespanEnabled": "false" + }, + "keycloakVersion": "4.3.0.Final", + "userManagedAccessAllowed": false, + "users" : [{ + "id": "c92e4f6e-20a0-4dbc-89f7-de1022c337b6", + "createdTimestamp": 1539122262773, + "username": "analyst", + "enabled": true, + "totp": false, + "emailVerified": true, + "email": "analyst@test.com", + "attributes": { + "user_id": [ "analyst" ] + }, + "credentials": [ { + "type": "password", + "hashedSaltedValue": "sulY0QYZPvq5J7MC2CgnLDww4pgv4esFlK77qRs0BRgsSlWPzy5KrSpCz1p0i0QaD93+kcoDOEtUg2VKo1HIvQ==", + "salt": "NFKrKh9lAgNQ9UxiTXbESg==", + "hashIterations": 27500, + "counter": 0, + "algorithm": "pbkdf2-sha256", + "digits": 0, + "period": 0, + "createdDate": 1539122410575, + "config": { } + } ], + "disableableCredentialTypes": [ "password" ], + "requiredActions": [ ], + "realmRoles": [ "offline_access", "uma_authorization" ], + "clientRoles": { + "account": [ "manage-account", "view-profile" ] + }, + "notBefore": 0, + "groups": [ ] + }, { + "id": "3e2a5290-37dd-47a9-9a30-26cdef92f3f6", + "createdTimestamp": 1539122309425, + "username": "director", + "enabled": true, + "totp": false, + "emailVerified": true, + "email": "director@test.com", + "attributes": { + "user_id": [ "director" ] + }, + "credentials": [ { + "type": "password", + "hashedSaltedValue": "VwoRitLQ+aEW3pqfXOXaaeQk+po/Y5D9ylYw4P1hkUxQY/QV5ryZ3o8LKGEt5Ks8b7WmZX62C/FybtMHV9GWAg==", + "salt": "U89vfHGW3yKF0j4J/9GsMw==", + "hashIterations": 27500, + "counter": 0, + "algorithm": "pbkdf2-sha256", + "digits": 0, + "period": 0, + "createdDate": 1539122325302, + "config": { } + } ], + "disableableCredentialTypes": [ "password" ], + "requiredActions": [ ], + "realmRoles": [ "offline_access", "uma_authorization" ], + "clientRoles": { + "account": [ "manage-account", "view-profile" ] + }, + "notBefore": 0, + "groups": [ ] + }, { + "id": "e8053de2-57a6-43e4-908c-e0860ce9b5bd", + "createdTimestamp": 1539104835577, + "username": "service-account-zeva-app-sa", + "enabled": true, + "totp": false, + "emailVerified": false, + "email": "service-account-zeva-app-sa@placeholder.org", + "serviceAccountClientId": "zeva-app-sa", + "credentials": [ ], + "disableableCredentialTypes": [ ], + "requiredActions": [ ], + "realmRoles": [ "offline_access", "uma_authorization" ], + "clientRoles": { + "realm-management": [ "view-users", "query-realms", "manage-realm", "query-users", "manage-users", "impersonation", "create-client", "manage-authorization", "query-clients", "realm-admin", "manage-events", "manage-clients", "manage-identity-providers" ], + "broker": [ "read-token" ], + "account": [ "view-profile", "manage-account" ] + }, + "notBefore": 0, + "groups": [ ] + }, { + "id": "b35a4df0-9a27-4083-91f3-867c5587b9dd", + "createdTimestamp": 1618609023302, + "username": "vs1", + "enabled": true, + "totp": false, + "emailVerified": true, + "email": "vs1@test.com", + "attributes": { + "user_id": ["RTAN"] + }, + "credentials": [ { + "type": "password", + "hashedSaltedValue": "9Jtp9yu+2Jm6ilsfsA0ILx2nBjJwfLMiRuZH4L6iauBoK0ycdLGklYWeoGjsIv9pPLW+N9kpN4bt3Q22qKyxhw==", + "salt": "fJBroDLZCvzlyBSIvGG+ug==", + "hashIterations": 27500, + "counter": 0, + "algorithm": "pbkdf2-sha256", + "digits": 0, + "period": 0, + "createdDate": 1618609030632, + "config": { } + } ], + "disableableCredentialTypes": [ "password" ], + "requiredActions": [ ], + "realmRoles": [ "offline_access", "uma_authorization" ], + "clientRoles": { + "account": [ "manage-account", "view-profile" ] + }, + "notBefore": 0, + "groups": [ ] + }, { + "id": "227a3260-515d-44f2-b524-f1a0ea1a74ca", + "createdTimestamp": 1618609052240, + "username": "vs2", + "enabled": true, + "totp": false, + "emailVerified": true, + "email": "vs2@test.com", + "attributes": { + "user_id": ["EMHILLIE"] + }, + "credentials": [ { + "type": "password", + "hashedSaltedValue": "2L20yWpXAZuiI3qo08gmucO71kouZADdzfiYBJr7MHEy7cIemgkOu4GO2BxqqT5YWVzqRSa4RypZuF5W1+HDzw==", + "salt": "EEzUP3Rwo+/lfD6f2L8/rQ==", + "hashIterations": 27500, + "counter": 0, + "algorithm": "pbkdf2-sha256", + "digits": 0, + "period": 0, + "createdDate": 1618609061517, + "config": { } + } ], + "disableableCredentialTypes": [ "password" ], + "requiredActions": [ ], + "realmRoles": [ "offline_access", "uma_authorization" ], + "clientRoles": { + "account": [ "manage-account", "view-profile" ] + }, + "notBefore": 0, + "groups": [ ] + }, { + "id": "b3ee8ba0-10b3-40b8-87a0-f0d9e01eaadf", + "createdTimestamp": 1618609072915, + "username": "vs3", + "enabled": true, + "totp": false, + "emailVerified": true, + "email": "vs3@test.com", + "attributes": { + "user_id": ["NXGREWAL"] + }, + "credentials": [ { + "type": "password", + "hashedSaltedValue": "2zDW+zj89PeTMMO8tn2E0hohurDuoMFkXu7Se1R9XXS7UMrcaw1x24OWzbHkzYjzFwJnSkbZbQHmh+rsnaFevQ==", + "salt": "uSdIIDdfUW32urB3eKvxEw==", + "hashIterations": 27500, + "counter": 0, + "algorithm": "pbkdf2-sha256", + "digits": 0, + "period": 0, + "createdDate": 1618609084226, + "config": { } + } ], + "disableableCredentialTypes": [ "password" ], + "requiredActions": [ ], + "realmRoles": [ "offline_access", "uma_authorization" ], + "clientRoles": { + "account": [ "manage-account", "view-profile" ] + }, + "notBefore": 0, + "groups": [ ] + }, { + "id": "a1d80847-e210-4f57-bbd8-aeba45ddb1a5", + "createdTimestamp": 1542660526892, + "username": "zevaadmin", + "enabled": true, + "totp": false, + "emailVerified": true, + "email": "zevaadmin@test.com", + "attributes": { + "user_id": [ "JADONALD" ] + }, + "credentials": [ { + "type": "password", + "hashedSaltedValue": "2zDW+zj89PeTMMO8tn2E0hohurDuoMFkXu7Se1R9XXS7UMrcaw1x24OWzbHkzYjzFwJnSkbZbQHmh+rsnaFevQ==", + "salt": "uSdIIDdfUW32urB3eKvxEw==", + "hashIterations": 27500, + "counter": 0, + "algorithm": "pbkdf2-sha256", + "digits": 0, + "period": 0, + "createdDate": 1542660558719, + "config": { } + } ], + "disableableCredentialTypes": [ "password" ], + "requiredActions": [ ], + "realmRoles": [ "offline_access", "uma_authorization" ], + "clientRoles": { + "account": [ "manage-account", "view-profile" ] + }, + "notBefore": 0, + "groups": [ ] + }] +} \ No newline at end of file diff --git a/keycloak/themes/bcgov-with-login-form/login/login.ftl b/keycloak/themes/bcgov-with-login-form/login/login.ftl new file mode 100644 index 000000000..9c9a8e9bd --- /dev/null +++ b/keycloak/themes/bcgov-with-login-form/login/login.ftl @@ -0,0 +1,87 @@ +<#import "template.ftl" as layout> +<@layout.registrationLayout displayInfo=social.displayInfo displayWide=(realm.password && social.providers??); section> + <#if section = "form"> +
    +
    class="has-social-accounts"> + <#if realm.password> +
    +
    + <#if usernameEditDisabled??> + ${msg("usernameOrEmail")}<#else>${msg("email")}" /> + <#else> + ${msg("usernameOrEmail")}<#else>${msg("email")}" /> + +
    + +
    + +
    + +
    +
    +
    + <#if realm.rememberMe && !usernameEditDisabled??> +
    + +
    + +
    + <#if realm.resetPasswordAllowed> + ${msg("doForgotPassword")} + +
    +
    +
    + +
    + +
    +
    +
    + +
    + + <#if realm.password && social.providers??> +
    +
    + <#assign oidc_providers = [] /> + <#assign social_media_providers = [] /> + <#list social.providers as p> + <#if p.providerId == "oidc"> + <#assign oidc_providers = oidc_providers + [p] /> + <#else> + <#assign social_media_providers = social_media_providers + [p] /> + + + <#list oidc_providers as p> + + + +
    +
    + +
    + <#elseif section = "info"> + <#if realm.password && realm.registrationAllowed && !usernameEditDisabled??> +
    + ${msg("noAccount")} ${msg("doRegister")} +
    + + + + diff --git a/keycloak/themes/bcgov-with-login-form/login/resources/css/login.css b/keycloak/themes/bcgov-with-login-form/login/resources/css/login.css new file mode 100644 index 000000000..82619dadb --- /dev/null +++ b/keycloak/themes/bcgov-with-login-form/login/resources/css/login.css @@ -0,0 +1,323 @@ +@font-face { + font-family: "MyriadWebPro"; + src: url("../font/MyriadWebPro.ttf"); +} + +.ie-specific { + color: #494949; + font-size: 1.2rem; + font-weight: bold; +} + +.ie-specific .realm-name { + text-transform: uppercase; +} + +.kc-content { + align-self: center; + display: flex; + margin-top: -15%; + max-height: 100%; + max-height: calc(100% + 15%); + overflow: hidden; + width: 100%; +} + +.kc-content.has-login-form { + max-height: calc(100% + 10%); + margin-top: -5%; +} + +.kc-content-wrapper { + display: block; + padding: 1rem 5rem; + width: 100%; +} + +.kc-content-wrapper .alert { + display: table; + font-size: 1.4rem; + margin: 0 auto 1rem; +} + +.kc-content-wrapper .alert.alert-error, +.kc-content-wrapper .alert.alert-error > .pficon-error-circle-o:before { + color: #a12622; +} + +.login-tfrs { + height: 100%; +} + +.login-tfrs body { + background-image: url('../img/bg.jpg'); + background-position-x: left; + background-position-y: center; + background-repeat: no-repeat; + background-size: cover; + height: 100%; + margin: 0; + min-height: 100%; + padding: 0; + position: relative; + width: 100%; +} + +.login-tfrs .card-tfrs { + align-content: center; + bottom: 0; + display: flex; + justify-content: center; + overflow: hidden; + position: absolute; + top: 15%; + width: 100%; +} + +.login-tfrs .login-tfrs-page { + background-color: rgb(255, 255, 255); + background-color: rgba(255, 255, 255, 0.8); + bottom: 0; + height: 100%; + left: 0; + min-width: 40rem; + position: absolute; + top: 0; +} + +.login-tfrs-brand { + background-image: url('../img/BCID_H_rgb_pos.png'); + background-position-x: center; + background-position-y: center; + background-repeat: no-repeat; + background-size: contain; + height: 15%; + margin: 1rem auto; + padding: 0; +} + +.login-tfrs-page-header { + height: 15%; + margin: 1rem 0; + padding: 0 5rem; + position: fixed; + width: 100%; +} + +.login-tfrs-page-header .login-tfrs-page-header-text { + color: #ffffff; + font-family: "MyriadWebPro", sans-serif; + font-size: 3.5rem; + line-height: normal; + position: relative; + top: 50%; + transform: translateY(-50%); + -webkit-transform: translateY(-50%); + -moz-transform: translateY(-50%); + -ms-transform: translateY(-50%); + -o-transform: translateY(-50%); +} + +.login-tfrs-signup { + margin-top: 2rem; + text-align: center; +} + +.login-tfrs-social .oidc:not(:first-child) { + border-top: 1px solid #fcba19; +} + +.login-tfrs-social .social-links { + display: flex; + flex-wrap: wrap; + justify-content: space-evenly; +} + +.login-tfrs-social .social-links .social-links-header { + color: #494949; + text-align: center; + width: 100%; +} + +.login-tfrs-social-link { + display: inline-block; + min-width: 11rem; + padding: 0.5rem; +} + +.login-tfrs-social-link .zocial { + width: 100%; +} + +.login-tfrs-social-link .gitlab, +.login-tfrs-social-link .openshift-v3 { + padding: 0; +} + +.zocial { + background-color: #bcbec5; +} + +.zocial.microsoft { + background-color: #0052a4; +} + +.zocial.microsoft:before { + content: "\f15d"; +} + +#kc-form-wrapper.has-social-accounts { + border-bottom: 1px solid #fcba19; + margin-bottom: 2.5rem; + padding-bottom: 2.5rem; +} + +#kc-form-wrapper.has-social-accounts #kc-form-buttons { + margin-bottom: 0; +} + +#kc-form-login input#kc-login { + background-color: #002663; + border-radius: 0.7rem; + color: #ffffff; + display: block; + font-size: 2rem; + margin-left: auto; + padding: 1rem; + width: 100%; +} + +#kc-form-login input#kc-login:hover { + background-color: #38598a; +} + +#kc-form-login input.input-field { + color: #494949; + display: block; + font-size: 2rem; + height: auto; + padding: 0.5rem; + width: 100%; +} + +#kc-info-message .instruction { + color: #494949; + font-size: 1.5rem; + text-align: center; +} + +#zocial-bceid { + background-color: #002663; + border-radius: 0.7rem; + color: #ffffff; + display: block; + font-size: 2.5rem; + margin: 2.5rem auto; + padding: 1rem; + text-align: center; + text-decoration: none; + width: 100%; +} + +#zocial-bceid:hover { + background-color: #38598a; +} + +#zocial-bceid .text { + display: inline-block; + vertical-align: middle; +} + +#zocial-bceid .display-name { + background-image: url('../img/bceid.png'); + background-position-x: left; + background-position-y: center; + background-repeat: no-repeat; + background-size: contain; + color: transparent; + display: inline-block; + height: 2.5rem; + vertical-align: middle; + width: 10rem; +} + +#zocial-idir { + background-color: #ffffff; + border: 1px solid #002663; + border-radius: 0.7rem; + color: #002663; + display: block; + font-size: 2.5rem; + margin: 2.5rem auto; + padding: 1rem; + text-align: center; + text-decoration: none; + width: 100%; +} + +#zocial-idir:hover { + background-color: #38598a; + color: #ffffff; +} + +#zocial-idir .display-name { + font-weight: bold; +} + +@media (min-width: 992px) { + .login-tfrs .login-tfrs-page { + width: 55rem; + } + + .login-tfrs-page-header { + padding-left: 60rem; + } +} + +@media (min-width: 769px) and (max-width: 992px) { + .login-tfrs .login-tfrs-page { + width: 40rem; + } + + .login-tfrs-page-header { + padding-left: 45rem; + } +} + +@media (max-width: 768px) { + #kc-form-login input.input-field, #kc-form-login input#kc-login { + font-size: 4vw; + } + + .login-tfrs .login-tfrs-page { + min-width: 30rem; + width: 100%; + } + + .login-tfrs-page .login-tfrs-brand { + background-image: url('../img/BCID_V_rgb_pos.png'); + background-position-x: 3rem; + background-position-y: center; + background-size: contain; + } + + .login-tfrs-page-header { + z-index: 999; + } + + .login-tfrs-page-header .login-tfrs-page-header-text { + color: #002663; + font-size: 4.5vw; + padding-left: 10rem; + text-align: center; + } + + #zocial-bceid, #zocial-idir { + font-size: 4vw; + } + + #zocial-bceid .display-name { + height: 100%; + width: 20vw; + } +} \ No newline at end of file diff --git a/keycloak/themes/bcgov-with-login-form/login/resources/font/MyriadWebPro.ttf b/keycloak/themes/bcgov-with-login-form/login/resources/font/MyriadWebPro.ttf new file mode 100644 index 000000000..efcd3dcf5 Binary files /dev/null and b/keycloak/themes/bcgov-with-login-form/login/resources/font/MyriadWebPro.ttf differ diff --git a/keycloak/themes/bcgov-with-login-form/login/resources/img/BCID_H_rgb_pos.png b/keycloak/themes/bcgov-with-login-form/login/resources/img/BCID_H_rgb_pos.png new file mode 100644 index 000000000..07e41768f Binary files /dev/null and b/keycloak/themes/bcgov-with-login-form/login/resources/img/BCID_H_rgb_pos.png differ diff --git a/keycloak/themes/bcgov-with-login-form/login/resources/img/BCID_V_rgb_pos.png b/keycloak/themes/bcgov-with-login-form/login/resources/img/BCID_V_rgb_pos.png new file mode 100644 index 000000000..98a8be507 Binary files /dev/null and b/keycloak/themes/bcgov-with-login-form/login/resources/img/BCID_V_rgb_pos.png differ diff --git a/keycloak/themes/bcgov-with-login-form/login/resources/img/bceid.png b/keycloak/themes/bcgov-with-login-form/login/resources/img/bceid.png new file mode 100644 index 000000000..5ea1cd2fe Binary files /dev/null and b/keycloak/themes/bcgov-with-login-form/login/resources/img/bceid.png differ diff --git a/keycloak/themes/bcgov-with-login-form/login/resources/img/bg.jpg b/keycloak/themes/bcgov-with-login-form/login/resources/img/bg.jpg new file mode 100644 index 000000000..49f1576b6 Binary files /dev/null and b/keycloak/themes/bcgov-with-login-form/login/resources/img/bg.jpg differ diff --git a/keycloak/themes/bcgov-with-login-form/login/template.ftl b/keycloak/themes/bcgov-with-login-form/login/template.ftl new file mode 100644 index 000000000..9e25b9c5c --- /dev/null +++ b/keycloak/themes/bcgov-with-login-form/login/template.ftl @@ -0,0 +1,83 @@ +<#macro registrationLayout bodyClass="" displayInfo=false displayMessage=true displayWide=false> + + + + + + + + + + + <#if properties.meta?has_content> + <#list properties.meta?split(' ') as meta> + + + + ${msg("loginTitle",(realm.displayName!''))} + + <#if properties.styles?has_content> + <#list properties.styles?split(' ') as style> + + + + + <#if properties.scripts?has_content> + <#list properties.scripts?split(' ') as script> + + + + + <#if scripts??> + <#list scripts as script> + + + + + + +
    +
    ${kcSanitize(msg("loginTitleHtml",(realm.displayNameHtml!'')))?no_esc}
    +
    +
    + +
    + +
    +
    + + + diff --git a/keycloak/themes/bcgov-with-login-form/login/theme.properties b/keycloak/themes/bcgov-with-login-form/login/theme.properties new file mode 100644 index 000000000..e1a44a63b --- /dev/null +++ b/keycloak/themes/bcgov-with-login-form/login/theme.properties @@ -0,0 +1,68 @@ +parent=base +import=common/keycloak + +styles=node_modules/patternfly/dist/css/patternfly.css node_modules/patternfly/dist/css/patternfly-additions.css lib/zocial/zocial.css css/login.css +meta=viewport==width=device-width,initial-scale=1 + +kcHtmlClass=login-tfrs +kcLoginClass=login-tfrs-page + +kcLogoClass=login-tfrs-brand + +kcContainerClass=container-fluid +kcContentClass=kc-content +kcContentWrapperClass=kc-content-wrapper + +kcHeaderClass=login-tfrs-page-header +kcHeaderWrapperClass=login-tfrs-page-header-text +kcFeedbackAreaClass=col-md-12 +kcLocaleClass=col-xs-12 col-sm-1 +kcAlertIconClasserror=pficon pficon-error-circle-o + +kcFormAreaClass=col-sm-10 col-sm-offset-1 col-md-8 col-md-offset-2 col-lg-8 col-lg-offset-2 +kcFormCardClass=card-tfrs +kcFormCardAccountClass=login-pf-accounts +kcFormSocialAccountClass=login-tfrs-social-section +kcFormSocialAccountContentClass= +kcFormSocialAccountListClass=login-tfrs-social list-unstyled login-tfrs-social-all +kcFormSocialAccountDoubleListClass=login-tfrs-social-double-col +kcFormSocialAccountListLinkClass=login-tfrs-social-link col-xs-6 col-sm-6 col-md-4 +kcFormHeaderClass=login-tfrs-header + +kcFeedbackErrorIcon=pficon pficon-error-circle-o +kcFeedbackWarningIcon=pficon pficon-warning-triangle-o +kcFeedbackSuccessIcon=pficon pficon-ok +kcFeedbackInfoIcon=pficon pficon-info + + +kcFormClass=form-horizontal +kcFormGroupClass=form-group +kcFormGroupErrorClass=has-error +kcLabelClass=control-label +kcLabelWrapperClass=col-xs-12 col-sm-12 col-md-12 col-lg-12 +kcInputClass=input-field +kcInputWrapperClass=col-xs-12 col-sm-12 col-md-12 col-lg-12 +kcFormOptionsClass=col-xs-12 col-sm-12 col-md-12 col-lg-12 +kcFormButtonsClass=col-xs-12 col-sm-12 col-md-12 col-lg-12 +kcFormSettingClass=login-tfrs-settings +kcTextareaClass=form-control +kcSignUpClass=login-tfrs-signup + + +kcInfoAreaClass=col-xs-12 col-sm-4 col-md-4 col-lg-5 details + +##### css classes for form buttons +# main class used for all buttons +kcButtonClass=btn +# classes defining priority of the button - primary or default (there is typically only one priority button for the form) +kcButtonPrimaryClass=btn-primary +kcButtonDefaultClass=btn-default +# classes defining size of the button +kcButtonLargeClass=btn-lg +kcButtonBlockClass=btn-block + +##### css classes for input +kcInputLargeClass=input-lg + +##### css classes for form accessability +kcSrOnlyClass=sr-only diff --git a/mailslurper/Dockerfile b/mailslurper/Dockerfile new file mode 100644 index 000000000..7ee748e75 --- /dev/null +++ b/mailslurper/Dockerfile @@ -0,0 +1,5 @@ +FROM marcopas/docker-mailslurper + +COPY config.json /opt/mailslurper/config.json + +EXPOSE 2500 diff --git a/mailslurper/config.json b/mailslurper/config.json new file mode 100644 index 000000000..d250b999e --- /dev/null +++ b/mailslurper/config.json @@ -0,0 +1,19 @@ +{ + "wwwAddress": "0.0.0.0", + "wwwPort": 8081, + "serviceAddress": "0.0.0.0", + "servicePort": 8085, + "smtpAddress": "0.0.0.0", + "smtpPort": 2500, + "dbEngine": "SQLite", + "dbHost": "", + "dbPort": 0, + "dbDatabase": "./mailslurper.db", + "dbUserName": "", + "dbPassword": "", + "maxWorkers": 1000, + "autoStartBrowser": false, + "keyFile": "", + "certFile": "", + "adminKeyFile": "" +} \ No newline at end of file diff --git a/minio.env b/minio.env new file mode 100644 index 000000000..30fc393ff --- /dev/null +++ b/minio.env @@ -0,0 +1,3 @@ +MINIO_ENDPOINT=minio:9000 +MINIO_ACCESS_KEY=H74MGVE2X8NSPDZTIE72 +MINIO_SECRET_KEY=FkeDXvgIvxPaOFxFVOfnDDY9E++T0Hq9EXmb83We \ No newline at end of file diff --git a/minio/minio_files/.minio.sys/buckets/.bloomcycle.bin b/minio/minio_files/.minio.sys/buckets/.bloomcycle.bin new file mode 100644 index 000000000..e2092971b Binary files /dev/null and b/minio/minio_files/.minio.sys/buckets/.bloomcycle.bin differ diff --git a/minio/minio_files/.minio.sys/buckets/.minio.sys/buckets/.bloomcycle.bin/fs.json b/minio/minio_files/.minio.sys/buckets/.minio.sys/buckets/.bloomcycle.bin/fs.json new file mode 100644 index 000000000..31b9ed5da --- /dev/null +++ b/minio/minio_files/.minio.sys/buckets/.minio.sys/buckets/.bloomcycle.bin/fs.json @@ -0,0 +1 @@ +{"version":"1.0.2","checksum":{"algorithm":"","blocksize":0,"hashes":null},"meta":{"etag":"aeeae86c98c7d1e9b99e715eb4e426d5"}} \ No newline at end of file diff --git a/minio/minio_files/.minio.sys/buckets/.minio.sys/buckets/.usage-cache.bin/fs.json b/minio/minio_files/.minio.sys/buckets/.minio.sys/buckets/.usage-cache.bin/fs.json new file mode 100644 index 000000000..83f095562 --- /dev/null +++ b/minio/minio_files/.minio.sys/buckets/.minio.sys/buckets/.usage-cache.bin/fs.json @@ -0,0 +1 @@ +{"version":"1.0.2","checksum":{"algorithm":"","blocksize":0,"hashes":null},"meta":{"etag":"5de6ab31e4e8c8c63583f5b8c804c730"}} \ No newline at end of file diff --git a/minio/minio_files/.minio.sys/buckets/.minio.sys/buckets/.usage.json/fs.json b/minio/minio_files/.minio.sys/buckets/.minio.sys/buckets/.usage.json/fs.json new file mode 100644 index 000000000..4e407e8c3 --- /dev/null +++ b/minio/minio_files/.minio.sys/buckets/.minio.sys/buckets/.usage.json/fs.json @@ -0,0 +1 @@ +{"version":"1.0.2","checksum":{"algorithm":"","blocksize":0,"hashes":null},"meta":{"etag":"f1804e36cf7f0a3ef5e87c4becf539f8"}} \ No newline at end of file diff --git a/minio/minio_files/.minio.sys/buckets/.minio.sys/buckets/tfrs/.usage-cache.bin/fs.json b/minio/minio_files/.minio.sys/buckets/.minio.sys/buckets/tfrs/.usage-cache.bin/fs.json new file mode 100644 index 000000000..c3e87e86a --- /dev/null +++ b/minio/minio_files/.minio.sys/buckets/.minio.sys/buckets/tfrs/.usage-cache.bin/fs.json @@ -0,0 +1 @@ +{"version":"1.0.2","checksum":{"algorithm":"","blocksize":0,"hashes":null},"meta":{"etag":"d74b13ffd26004e16b0eee2857e430d6"}} \ No newline at end of file diff --git a/minio/minio_files/.minio.sys/buckets/.minio.sys/buckets/zeva/.usage-cache.bin/fs.json b/minio/minio_files/.minio.sys/buckets/.minio.sys/buckets/zeva/.usage-cache.bin/fs.json new file mode 100644 index 000000000..053714f06 --- /dev/null +++ b/minio/minio_files/.minio.sys/buckets/.minio.sys/buckets/zeva/.usage-cache.bin/fs.json @@ -0,0 +1 @@ +{"version":"1.0.2","checksum":{"algorithm":"","blocksize":0,"hashes":null},"meta":{"etag":"34c1109a830d2b8f45f93e721aef0c5d"}} \ No newline at end of file diff --git a/minio/minio_files/.minio.sys/buckets/.tracker.bin b/minio/minio_files/.minio.sys/buckets/.tracker.bin new file mode 100755 index 000000000..9e3242d66 Binary files /dev/null and b/minio/minio_files/.minio.sys/buckets/.tracker.bin differ diff --git a/minio/minio_files/.minio.sys/buckets/.usage-cache.bin b/minio/minio_files/.minio.sys/buckets/.usage-cache.bin new file mode 100644 index 000000000..13a84fff9 Binary files /dev/null and b/minio/minio_files/.minio.sys/buckets/.usage-cache.bin differ diff --git a/minio/minio_files/.minio.sys/buckets/.usage.json b/minio/minio_files/.minio.sys/buckets/.usage.json new file mode 100644 index 000000000..f6bb1ccfc --- /dev/null +++ b/minio/minio_files/.minio.sys/buckets/.usage.json @@ -0,0 +1 @@ +{"lastUpdate":"2021-06-16T16:24:15.073065163Z","objectsCount":0,"objectsTotalSize":0,"objectsPendingReplicationTotalSize":0,"objectsFailedReplicationTotalSize":0,"objectsReplicatedTotalSize":0,"objectsReplicaTotalSize":0,"objectsPendingReplicationCount":0,"objectsFailedReplicationCount":0,"bucketsCount":2,"bucketsUsageInfo":{"zeva":{"size":0,"objectsPendingReplicationTotalSize":0,"objectsFailedReplicationTotalSize":0,"objectsReplicatedTotalSize":0,"objectReplicaTotalSize":0,"objectsPendingReplicationCount":0,"objectsFailedReplicationCount":0,"objectsCount":0,"objectsSizesHistogram":{"BETWEEN_1024_B_AND_1_MB":0,"BETWEEN_10_MB_AND_64_MB":0,"BETWEEN_128_MB_AND_512_MB":0,"BETWEEN_1_MB_AND_10_MB":0,"BETWEEN_64_MB_AND_128_MB":0,"GREATER_THAN_512_MB":0,"LESS_THAN_1024_B":0}}},"bucketsSizes":null} \ No newline at end of file diff --git a/minio/minio_files/.minio.sys/buckets/zeva/.metadata.bin b/minio/minio_files/.minio.sys/buckets/zeva/.metadata.bin new file mode 100644 index 000000000..faefbfd7a Binary files /dev/null and b/minio/minio_files/.minio.sys/buckets/zeva/.metadata.bin differ diff --git a/minio/minio_files/.minio.sys/buckets/zeva/.usage-cache.bin b/minio/minio_files/.minio.sys/buckets/zeva/.usage-cache.bin new file mode 100644 index 000000000..57661c54d Binary files /dev/null and b/minio/minio_files/.minio.sys/buckets/zeva/.usage-cache.bin differ diff --git a/minio/minio_files/.minio.sys/config/config.json b/minio/minio_files/.minio.sys/config/config.json new file mode 100644 index 000000000..b5c22c0e5 --- /dev/null +++ b/minio/minio_files/.minio.sys/config/config.json @@ -0,0 +1 @@ +{"api":{"_":[{"key":"requests_max","value":"0"},{"key":"requests_deadline","value":"10s"},{"key":"cluster_deadline","value":"10s"},{"key":"cors_allow_origin","value":"*"},{"key":"remote_transport_deadline","value":"2h"},{"key":"list_quorum","value":"strict"},{"key":"extend_list_cache_life","value":"0s"},{"key":"replication_workers","value":"100"}]},"audit_webhook":{"_":[{"key":"enable","value":"off"},{"key":"endpoint","value":""},{"key":"auth_token","value":""},{"key":"client_cert","value":""},{"key":"client_key","value":""}]},"cache":{"_":[{"key":"drives","value":""},{"key":"exclude","value":""},{"key":"expiry","value":"90"},{"key":"quota","value":"80"},{"key":"after","value":"0"},{"key":"watermark_low","value":"70"},{"key":"watermark_high","value":"80"},{"key":"range","value":"on"},{"key":"commit","value":"writethrough"}]},"compression":{"_":[{"key":"enable","value":"off"},{"key":"allow_encryption","value":"off"},{"key":"extensions","value":".txt,.log,.csv,.json,.tar,.xml,.bin"},{"key":"mime_types","value":"text/*,application/json,application/xml,binary/octet-stream"}]},"credentials":{"_":[{"key":"access_key","value":"minioadmin"},{"key":"secret_key","value":"minioadmin"}]},"etcd":{"_":[{"key":"endpoints","value":""},{"key":"path_prefix","value":""},{"key":"coredns_path","value":"/skydns"},{"key":"client_cert","value":""},{"key":"client_cert_key","value":""}]},"heal":{"_":[{"key":"bitrotscan","value":"off"},{"key":"max_sleep","value":"1s"},{"key":"max_io","value":"10"}]},"identity_ldap":{"_":[{"key":"server_addr","value":""},{"key":"username_format","value":""},{"key":"user_dn_search_base_dn","value":""},{"key":"user_dn_search_filter","value":""},{"key":"group_search_filter","value":""},{"key":"group_search_base_dn","value":""},{"key":"sts_expiry","value":"1h"},{"key":"tls_skip_verify","value":"off"},{"key":"server_insecure","value":"off"},{"key":"server_starttls","value":"off"},{"key":"lookup_bind_dn","value":""},{"key":"lookup_bind_password","value":""}]},"identity_openid":{"_":[{"key":"config_url","value":""},{"key":"client_id","value":""},{"key":"claim_name","value":"policy"},{"key":"claim_prefix","value":""},{"key":"scopes","value":""},{"key":"jwks_url","value":""}]},"kms_kes":{"_":[{"key":"endpoint","value":""},{"key":"key_name","value":""},{"key":"cert_file","value":""},{"key":"key_file","value":""},{"key":"capath","value":""}]},"kms_vault":{"_":[{"key":"endpoint","value":""},{"key":"key_name","value":""},{"key":"auth_type","value":"approle"},{"key":"auth_approle_id","value":""},{"key":"auth_approle_secret","value":""},{"key":"capath","value":""},{"key":"key_version","value":""},{"key":"namespace","value":""}]},"logger_webhook":{"_":[{"key":"enable","value":"off"},{"key":"endpoint","value":""},{"key":"auth_token","value":""}]},"notify_amqp":{"_":[{"key":"enable","value":"off"},{"key":"url","value":""},{"key":"exchange","value":""},{"key":"exchange_type","value":""},{"key":"routing_key","value":""},{"key":"mandatory","value":"off"},{"key":"durable","value":"off"},{"key":"no_wait","value":"off"},{"key":"internal","value":"off"},{"key":"auto_deleted","value":"off"},{"key":"delivery_mode","value":"0"},{"key":"queue_limit","value":"0"},{"key":"queue_dir","value":""}]},"notify_elasticsearch":{"_":[{"key":"enable","value":"off"},{"key":"url","value":""},{"key":"format","value":"namespace"},{"key":"index","value":""},{"key":"queue_dir","value":""},{"key":"queue_limit","value":"0"},{"key":"username","value":""},{"key":"password","value":""}]},"notify_kafka":{"_":[{"key":"enable","value":"off"},{"key":"topic","value":""},{"key":"brokers","value":""},{"key":"sasl_username","value":""},{"key":"sasl_password","value":""},{"key":"sasl_mechanism","value":"plain"},{"key":"client_tls_cert","value":""},{"key":"client_tls_key","value":""},{"key":"tls_client_auth","value":"0"},{"key":"sasl","value":"off"},{"key":"tls","value":"off"},{"key":"tls_skip_verify","value":"off"},{"key":"queue_limit","value":"0"},{"key":"queue_dir","value":""},{"key":"version","value":""}]},"notify_mqtt":{"_":[{"key":"enable","value":"off"},{"key":"broker","value":""},{"key":"topic","value":""},{"key":"password","value":""},{"key":"username","value":""},{"key":"qos","value":"0"},{"key":"keep_alive_interval","value":"0s"},{"key":"reconnect_interval","value":"0s"},{"key":"queue_dir","value":""},{"key":"queue_limit","value":"0"}]},"notify_mysql":{"_":[{"key":"enable","value":"off"},{"key":"format","value":"namespace"},{"key":"dsn_string","value":""},{"key":"table","value":""},{"key":"queue_dir","value":""},{"key":"queue_limit","value":"0"},{"key":"max_open_connections","value":"2"}]},"notify_nats":{"_":[{"key":"enable","value":"off"},{"key":"address","value":""},{"key":"subject","value":""},{"key":"username","value":""},{"key":"password","value":""},{"key":"token","value":""},{"key":"tls","value":"off"},{"key":"tls_skip_verify","value":"off"},{"key":"cert_authority","value":""},{"key":"client_cert","value":""},{"key":"client_key","value":""},{"key":"ping_interval","value":"0"},{"key":"streaming","value":"off"},{"key":"streaming_async","value":"off"},{"key":"streaming_max_pub_acks_in_flight","value":"0"},{"key":"streaming_cluster_id","value":""},{"key":"queue_dir","value":""},{"key":"queue_limit","value":"0"}]},"notify_nsq":{"_":[{"key":"enable","value":"off"},{"key":"nsqd_address","value":""},{"key":"topic","value":""},{"key":"tls","value":"off"},{"key":"tls_skip_verify","value":"off"},{"key":"queue_dir","value":""},{"key":"queue_limit","value":"0"}]},"notify_postgres":{"_":[{"key":"enable","value":"off"},{"key":"format","value":"namespace"},{"key":"connection_string","value":""},{"key":"table","value":""},{"key":"queue_dir","value":""},{"key":"queue_limit","value":"0"},{"key":"max_open_connections","value":"2"}]},"notify_redis":{"_":[{"key":"enable","value":"off"},{"key":"format","value":"namespace"},{"key":"address","value":""},{"key":"key","value":""},{"key":"password","value":""},{"key":"queue_dir","value":""},{"key":"queue_limit","value":"0"}]},"notify_webhook":{"_":[{"key":"enable","value":"off"},{"key":"endpoint","value":""},{"key":"auth_token","value":""},{"key":"queue_limit","value":"0"},{"key":"queue_dir","value":""},{"key":"client_cert","value":""},{"key":"client_key","value":""}]},"policy_opa":{"_":[{"key":"url","value":""},{"key":"auth_token","value":""}]},"region":{"_":[{"key":"name","value":""}]},"scanner":{"_":[{"key":"delay","value":"10"},{"key":"max_wait","value":"15s"}]},"storage_class":{"_":null}} \ No newline at end of file diff --git a/minio/minio_files/.minio.sys/config/iam/format.json b/minio/minio_files/.minio.sys/config/iam/format.json new file mode 100644 index 000000000..491d73446 --- /dev/null +++ b/minio/minio_files/.minio.sys/config/iam/format.json @@ -0,0 +1 @@ +{"version":1} \ No newline at end of file diff --git a/minio/minio_files/.minio.sys/format.json b/minio/minio_files/.minio.sys/format.json new file mode 100644 index 000000000..7d0990e9c --- /dev/null +++ b/minio/minio_files/.minio.sys/format.json @@ -0,0 +1 @@ +{"version":"1","format":"fs","id":"b06ad6f4-47ef-426b-9bfc-b2d9ff9b827c","fs":{"version":"2"}} \ No newline at end of file diff --git a/rabbitmq.env b/rabbitmq.env new file mode 100644 index 000000000..cb87aa48c --- /dev/null +++ b/rabbitmq.env @@ -0,0 +1,5 @@ +RABBITMQ_VHOST=/zeva +RABBITMQ_USER=rabbitmq +RABBITMQ_PASSWORD=rabbitmq +RABBITMQ_HOST=rabbitmq +RABBITMQ_PORT=5672