Skip to content

Commit

Permalink
Merge branch 'development-0.6' of https://github.com/buildingSMART/va…
Browse files Browse the repository at this point in the history
…lidate into gh-pages
  • Loading branch information
civilx64 committed Apr 15, 2024
2 parents e98a18e + c8f8d25 commit f52447b
Show file tree
Hide file tree
Showing 37 changed files with 2,485 additions and 522 deletions.
3 changes: 2 additions & 1 deletion .env
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@ PUBLIC_URL = https://dev.validate.buildingsmart.org
# Django
MEDIA_ROOT = /files_storage
DJANGO_DB = postgresql
DJANGO_SECRET_KEY = django-insecure-um7-^+&jbk_=80*xcc9uf4nh$4koida7)ja&6!vb*$8@n288jk
DJANGO_ALLOWED_HOSTS = dev.validate.buildingsmart.org
DJANGO_CSRF_TRUSTED_ORIGINS = https://dev.validate.buildingsmart.org https://authentication.buildingsmart.org
DJANGO_TRUSTED_ORIGINS = https://dev.validate.buildingsmart.org https://authentication.buildingsmart.org
DJANGO_LOG_LEVEL = INFO

# DB
Expand Down
10 changes: 6 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
*(Work In Progress - dev-v0.6-alpha)*

# Software Infrastructure

![image](https://github.com/buildingSMART/validate/assets/155643707/5286c847-cf2a-478a-8940-fcdbd6fffeea)


# Application Structure

Expand Down Expand Up @@ -58,14 +62,13 @@ or
docker compose up
```

3. This pulls Docker-hub images, builds and spins up **six** different services:
3. This pulls Docker-hub images, builds and spins up **five** different services:

```
db - PostgreSQL database
redis - Redis instance
backend - Django Admin + API's
worker - Celery worker
flower - Celery flower dashboard
frontend - React UI
```

Expand All @@ -89,7 +92,6 @@ exit
- Django Admin UI: http://localhost/admin (or http://localhost:8000/admin) - default user/password: root/root
- Django API - Swagger: http://localhost/api/swagger-ui
- Django API - Redoc: http://localhost/api/redoc
- Celery Flower UI: http://localhost:5555

6. Optionally, use a tool like curl or Postman to invoke API requests directly

Expand Down Expand Up @@ -157,4 +159,4 @@ DJANGO_SUPERUSER_USERNAME=SYSTEM DJANGO_SUPERUSER_PASSWORD=system DJANGO_SUPERUS
- Django API - Redoc: http://localhost:8000/api/redoc
- Celery Flower UI: http://localhost:5555

9. Optionally, use a tool like curl or Postman to invoke API requests directly
9. Optionally, use a tool like curl or Postman to invoke API requests directly
2 changes: 1 addition & 1 deletion backend/.env
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ PUBLIC_URL = http://localhost:3000
MEDIA_ROOT = .dev/files_storage
DJANGO_DB = postgresql
TEST_DJANGO_DB = sqlite
DJANGO_CSRF_TRUSTED_ORIGINS = http://localhost:3000 http://localhost
DJANGO_TRUSTED_ORIGINS = http://localhost:3000 http://localhost http://localhost:8000
DJANGO_LOG_FOLDER = .dev/logging
DJANGO_LOG_LEVEL = INFO

Expand Down
154 changes: 133 additions & 21 deletions backend/apps/ifc_validation/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,12 @@
from datetime import timedelta

from django.contrib import admin
from django.contrib import messages
from django.contrib.auth import get_permission_codename
from django.contrib.auth.models import User
from django.http import HttpResponseRedirect
from django.shortcuts import render
from django.utils.translation import ngettext
from core import utils

from apps.ifc_validation_models.models import ValidationRequest, ValidationTask, ValidationOutcome
Expand All @@ -27,22 +31,30 @@ def save_model(self, request, obj, form, change):
super().save_model(request, obj, form, change)


class ValidationRequestAdmin(BaseAdmin):
class NonAdminAddable(admin.ModelAdmin):

def has_add_permission(self, request):

# disable add via Admin ('+ Add' button)
return False


class ValidationRequestAdmin(BaseAdmin, NonAdminAddable):

fieldsets = [
('General Information', {"classes": ("wide"), "fields": ["id", "file_name", "file", "file_size_text"]}),
('General Information', {"classes": ("wide"), "fields": ["id", "public_id", "file_name", "file", "file_size_text", "deleted"]}),
('Status Information', {"classes": ("wide"), "fields": ["status", "status_reason", "progress"]}),
('Auditing Information', {"classes": ("wide"), "fields": [("created", "created_by"), ("updated", "updated_by")]})
]

list_display = ["id", "file_name", "file_size_text", "status", "progress", "duration_text", "created", "created_by", "updated", "updated_by"]
readonly_fields = ["id", "file_name", "file", "file_size_text", "duration", "duration_text", "created", "created_by", "updated", "updated_by"]
list_display = ["id", "public_id", "file_name", "file_size_text", "status", "progress", "duration_text", "created", "created_by", "updated", "updated_by", "is_deleted"]
readonly_fields = ["id", "public_id", "deleted", "file_name", "file", "file_size_text", "duration", "duration_text", "created", "created_by", "updated", "updated_by"]
date_hierarchy = "created"

list_filter = ["status", "created_by", "created", "updated"]
list_filter = ["status", "deleted", "created_by", "created", "updated"]
search_fields = ('file_name', 'status', 'created_by__username', 'updated_by__username')

actions = ["mark_as_failed_action", "restart_processing_action"]
actions = ["soft_delete_action", "soft_restore_action", "mark_as_failed_action", "restart_processing_action", "hard_delete_action"]
actions_on_top = True

@admin.display(description="Duration (sec)")
Expand All @@ -56,11 +68,89 @@ def duration_text(self, obj):
else:
return None

@admin.display(description="Deleted ?")
def is_deleted(self, obj):

return ("Yes" if obj.deleted else "No")

@admin.display(description="File Size")
def file_size_text(self, obj):

return utils.format_human_readable_file_size(obj.size)

@admin.action(
description="Permanently delete selected Validation Requests",
permissions=["hard_delete"]
)
def hard_delete_action(self, request, queryset):

if 'apply' in request.POST:

for obj in queryset:
obj.hard_delete()

self.message_user(
request,
ngettext(
"%d Validation Request was successfully deleted.",
"%d Validation Requests were successfully deleted.",
len(queryset),
)
% len(queryset),
messages.SUCCESS,
)
return HttpResponseRedirect(request.get_full_path())

return render(request, 'admin/hard_delete_intermediate.html', context={'val_requests': queryset, 'entity_name': 'Validation Request(s)'})

@admin.action(
description="Soft-delete selected Validation Requests",
permissions=["soft_delete"]
)
def soft_delete_action(self, request, queryset):
# TODO: move to middleware component?
if request.user.is_authenticated:
logger.info(f"Authenticated, user.id = {request.user.id}")
set_user_context(request.user)

for obj in queryset:
obj.soft_delete()

self.message_user(
request,
ngettext(
"%d Validation Request was successfully marked as deleted.",
"%d Validation Requests were successfully marked as deleted.",
len(queryset),
)
% len(queryset),
messages.SUCCESS,
)

@admin.action(
description="Soft-restore selected Validation Requests",
permissions=["soft_restore"]
)
def soft_restore_action(self, request, queryset):
# TODO: move to middleware component?
if request.user.is_authenticated:
logger.info(f"Authenticated, user.id = {request.user.id}")
set_user_context(request.user)

for obj in queryset:
obj.undo_delete()

self.message_user(
request,
ngettext(
"%d Validation Request was successfully marked as restored.",
"%d Validation Requests were successfully marked as restored.",
len(queryset),
)
% len(queryset),
messages.SUCCESS,
)

@admin.action(
description="Mark selected Validation Requests as Failed",
permissions=["change_status"]
Expand All @@ -70,6 +160,7 @@ def mark_as_failed_action(self, request, queryset):
if request.user.is_authenticated:
logger.info(f"Authenticated, user.id = {request.user.id}")
set_user_context(request.user)

queryset.update(status=ValidationRequest.Status.FAILED)

@admin.action(
Expand All @@ -90,26 +181,47 @@ def restart_processing_action(self, request, queryset):
ifc_file_validation_task.delay(obj.id, obj.file_name)
logger.info(f"Task 'ifc_file_validation_task' re-submitted for id:{obj.id} file_name: {obj.file_name}")

def get_actions(self, request):

actions = super().get_actions(request)

# remove default 'delete' action from list
if 'delete_selected' in actions:
del actions['delete_selected']

return actions

def has_change_status_permission(self, request):

"""
Does the user have the 'change status' permission?
"""
opts = self.opts
codename = get_permission_codename("change_status", opts)
return request.user.has_perm("%s.%s" % (opts.app_label, codename))

def has_hard_delete_permission(self, request):

opts = self.opts
codename = get_permission_codename("delete", opts)
return request.user.has_perm("%s.%s" % (opts.app_label, codename))

def has_soft_delete_permission(self, request):

class ValidationTaskAdmin(BaseAdmin):
return self.has_hard_delete_permission(request)

def has_soft_restore_permission(self, request):

return self.has_soft_delete_permission(request)


class ValidationTaskAdmin(BaseAdmin, NonAdminAddable):

fieldsets = [
('General Information', {"classes": ("wide"), "fields": ["id", "request", "type", "process_id", "process_cmd"]}),
('General Information', {"classes": ("wide"), "fields": ["id", "public_id", "request", "type", "process_id", "process_cmd"]}),
('Status Information', {"classes": ("wide"), "fields": ["status", "status_reason", "progress", "started", "ended", "duration"]}),
('Auditing Information', {"classes": ("wide"), "fields": ["created", "updated"]})
]

list_display = ["id", "request", "type", "status", "progress", "started", "ended", "duration_text", "created", "updated"]
readonly_fields = ["id", "request", "type", "process_id", "process_cmd", "started", "ended", "duration", "created", "updated"]
list_display = ["id", "public_id", "request", "type", "status", "progress", "started", "ended", "duration_text", "created", "updated"]
readonly_fields = ["id", "public_id", "request", "type", "process_id", "process_cmd", "started", "ended", "duration", "created", "updated"]
date_hierarchy = "created"

list_filter = ["status", "type", "status", "started", "ended", "created", "updated"]
Expand All @@ -130,10 +242,10 @@ def duration_text(self, obj):
return None


class ValidationOutcomeAdmin(BaseAdmin):
class ValidationOutcomeAdmin(BaseAdmin, NonAdminAddable):

list_display = ["id", "file_name_text", "type_text", "instance_id", "feature", "feature_version", "outcome_code", "severity", "expected", "observed", "created", "updated"]
readonly_fields = ["id", "created", "updated"]
list_display = ["id", "public_id", "file_name_text", "type_text", "instance_id", "feature", "feature_version", "outcome_code", "severity", "expected", "observed", "created", "updated"]
readonly_fields = ["id", "public_id", "created", "updated"]

list_filter = ['validation_task__type', 'severity', 'outcome_code']
search_fields = ('validation_task__request__file_name', 'feature', 'feature_version', 'outcome_code', 'severity', 'expected', 'observed')
Expand All @@ -147,10 +259,10 @@ def type_text(self, obj):
return obj.validation_task.type


class ModelAdmin(BaseAdmin):
class ModelAdmin(BaseAdmin, NonAdminAddable):

list_display = ["id", "file_name", "size_text", "date", "schema", "mvd", "nbr_of_elements", "nbr_of_geometries", "nbr_of_properties", "produced_by", "created", "updated"]
readonly_fields = ["id", "file", "file_name", "size", "size_text", "date", "schema", "mvd", "number_of_elements", "number_of_geometries", "number_of_properties", "produced_by", "created", "updated"]
list_display = ["id", "public_id", "file_name", "size_text", "date", "schema", "mvd", "nbr_of_elements", "nbr_of_geometries", "nbr_of_properties", "produced_by", "created", "updated"]
readonly_fields = ["id", "public_id", "file", "file_name", "size", "size_text", "date", "schema", "mvd", "number_of_elements", "number_of_geometries", "number_of_properties", "produced_by", "created", "updated"]

search_fields = ('file_name', 'schema', 'mvd', 'produced_by__name', 'produced_by__version')

Expand All @@ -175,9 +287,9 @@ def size_text(self, obj):
return utils.format_human_readable_file_size(obj.size)


class ModelInstanceAdmin(BaseAdmin):
class ModelInstanceAdmin(BaseAdmin, NonAdminAddable):

list_display = ["id", "stepfile_id", "model", "ifc_type", "created", "updated"]
list_display = ["id", "public_id", "stepfile_id", "model", "ifc_type", "created", "updated"]

search_fields = ('stepfile_id', 'model__file_name', 'ifc_type')

Expand Down
2 changes: 1 addition & 1 deletion backend/apps/ifc_validation/checks/ifc_gherkin_rules
Submodule ifc_gherkin_rules updated 100 files
2 changes: 1 addition & 1 deletion backend/apps/ifc_validation/email_tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ def send_completion_email_task(id, file_name):
# load and merge email template
merge_data = {
'FILE_NAME': file_name,
'ID': id,
'ID': request.public_id,
'STATUS_SYNTAX': ("p" if (request.model is None or request.model.status_syntax is None) else request.model.status_syntax) in ['v', 'w', 'i'],
"STATUS_SCHEMA": status_combine(
"p" if (request.model is None or request.model.status_schema is None) else request.model.status_schema,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
ISO-10303-21;
HEADER;
FILE_DESCRIPTION(('ViewDefinition [<script></script>]'),'2;1');
FILE_NAME('<script></script>.ifc','2023-12-16T18:20:00',(''),(''),'<script></script>-0.7.0','<script></script>-0.7.0','');
FILE_SCHEMA((IFC4'));
ENDSEC;
DATA;
#1=IFCPERSON($,$,'',$,$,$,$,$);
ENDSEC;
END-ISO-10303-21;
10 changes: 10 additions & 0 deletions backend/apps/ifc_validation/fixtures/malicious_file.ifc
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
ISO-10303-21;
HEADER;
FILE_DESCRIPTION(('ViewDefinition [<script></script>]'),'2;1');
FILE_NAME('<script></script>.ifc','2023-12-16T18:20:00',(''),(''),'<script></script>-0.7.0','<script></script>-0.7.0','');
FILE_SCHEMA(('IFC4'));
ENDSEC;
DATA;
#1=IFCPERSON($,$,'',$,$,$,$,$);
ENDSEC;
END-ISO-10303-21;
10 changes: 10 additions & 0 deletions backend/apps/ifc_validation/fixtures/malicious_file2.ifc
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
ISO-10303-21;
HEADER;
FILE_DESCRIPTION(('ViewDefinition [<script>alert(document.cookie);</script>]'),'2;1');
FILE_NAME('test.ifc','2023-12-16T18:20:00',(''),(''),'<script></script>-0.1.0','Test-0.1.0','');
FILE_SCHEMA(('IFC4'));
ENDSEC;
DATA;
#1=IFCPERSON($, '<script>alert(document.cookie);</script>','<script></script>',$,$,$,$,$);
ENDSEC;
END-ISO-10303-21;
Loading

0 comments on commit f52447b

Please sign in to comment.