Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

24632 create upload document #3141

Open
wants to merge 2 commits into
base: feature-drs-integration
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions legal-api/src/legal_api/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,11 @@ class _Config(): # pylint: disable=too-few-public-methods
STAGE_1_DELAY = int(os.getenv('STAGE_1_DELAY', '42'))
STAGE_2_DELAY = int(os.getenv('STAGE_2_DELAY', '30'))

# Document Record Service Settings
DRS_BASE_URL = os.getenv('DRS_BASE_URL', '')
DRS_ACCOUNT_ID = os.getenv('DRS_ACCOUNT_ID', '')
DRS_X_API_KEY = os.getenv('DRS_X_API_KEY', '')

TESTING = False
DEBUG = False

Expand Down
17 changes: 17 additions & 0 deletions legal-api/src/legal_api/resources/v2/document.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

from legal_api.models import Document, Filing
from legal_api.services.minio import MinioService
from legal_api.services.document_record import DocumentRecordService
from legal_api.utils.auth import jwt


Expand Down Expand Up @@ -77,3 +78,19 @@ def get_minio_document(document_key: str):
return jsonify(
message=f'Error getting file {document_key}.'
), HTTPStatus.INTERNAL_SERVER_ERROR

@bp.route('/<string:document_class>/<string:document_type>', methods=['POST', 'OPTIONS'])
@cross_origin(origin='*')
@jwt.requires_auth
def upload_document(document_class: str, document_type: str):
"""Upload document file to Document Record Service."""

return DocumentRecordService.upload_document(document_class, document_type), HTTPStatus.OK

@bp.route('/drs/<string:document_service_id>', methods=['DELETE'])
@cross_origin(origin='*')
@jwt.requires_auth
def delete_document(document_service_id: str):
"""Delete document file from Document Record Service."""

return DocumentRecordService.delete_document(document_service_id), HTTPStatus.OK
1 change: 1 addition & 0 deletions legal-api/src/legal_api/services/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
from .furnishing_documents_service import FurnishingDocumentsService
from .involuntary_dissolution import InvoluntaryDissolutionService
from .minio import MinioService
from .document_record import DocumentRecordService
from .mras_service import MrasService
from .naics import NaicsService
from .namex import NameXService
Expand Down
125 changes: 125 additions & 0 deletions legal-api/src/legal_api/services/document_record.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
# Copyright © 2021 Province of British Columbia
#
# Licensed under the Apache License, Version 2.0 (the 'License');
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an 'AS IS' BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""This module is a wrapper for Document Record Service."""

import base64
from typing import Optional
import requests
from flask import current_app, request
from flask_babel import _

import PyPDF2

class DocumentRecordService:
"""Document Storage class."""


@staticmethod
def upload_document(document_class: str, document_type: str) -> dict:
"""Upload document to Docuemtn Record Service."""
query_params = request.args.to_dict()
file = request.files.get('file')
# Ensure file exists
if not file:
current_app.logger.debug('No file found in request.')
return {'data': 'File not provided'}
current_app.logger.debug(f'Upload file to document record service {file.filename}')

Check notice

Code scanning / SonarCloud

Logging should not be vulnerable to injection attacks Low

Change this code to not log user-controlled data. See more on SonarQube Cloud
DRS_BASE_URL = current_app.config.get('DRS_BASE_URL', '') # pylint: disable=invalid-name
url = f'{DRS_BASE_URL}documents/{document_class}/{document_type}'

# Validate file size and encryption status before submitting to DRS.
validation_error = DocumentRecordService.validate_pdf(file, request.content_length)
if validation_error:
return {
'error': validation_error
}

file_content = file.read()

try:
# Read and encode the file content as base64
file_content = file.read()
file_base64 = base64.b64encode(file_content).decode('utf-8')

response_body = requests.post(
url,
params=query_params,
json={
'filename': file.filename,
'content': file_base64,
'content_type': file.content_type,
},
headers={
'x-apikey': current_app.config.get('DRS_X_API_KEY', ''),
'Account-Id': current_app.config.get('DRS_ACCOUNT_ID', ''),
'Content-Type': 'application/pdf'
}
).json()

current_app.logger.debug(f'Upload file to document record service {response_body}')
return {
'documentServiceId': response_body['documentServiceId'],
'consumerDocumentId': response_body['consumerDocumentId'],
'consumerFilename': response_body['consumerFilename']
}
except Exception as e:
current_app.logger.debug(f"Error on uploading document {e}")
return {}

@staticmethod
def delete_document(document_service_id: str) -> dict:
"""Delete document from Document Record Service."""
DRS_BASE_URL = current_app.config.get('DRS_BASE_URL', '') # pylint: disable=invalid-name
url = f'{DRS_BASE_URL}documents/{document_service_id}'

try:
response = requests.patch(
url, json={ 'removed': True },
headers={
'x-apikey': current_app.config.get('DRS_X_API_KEY', ''),
'Account-Id': current_app.config.get('DRS_ACCOUNT_ID', ''),
}
).json()
current_app.logger.debug(f'Delete document from document record service {response}')
return response
except Exception as e:
current_app.logger.debug(f'Error on deleting document {e}')
return {}

@staticmethod
def validate_pdf(file, content_length) -> Optional[list]:
"""Validate the PDF file."""
msg = []
try:
pdf_reader = PyPDF2.PdfFileReader(file)

# Check that all pages in the pdf are letter size and able to be processed.
if any(x.mediaBox.getWidth() != 612 or x.mediaBox.getHeight() != 792 for x in pdf_reader.pages):
msg.append({'error': _('Document must be set to fit onto 8.5” x 11” letter-size paper.'),
'path': file.filename})
Comment on lines +107 to +110
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


if content_length > 30000000:
msg.append({'error': _('File exceeds maximum size.'), 'path': file.filename})

if pdf_reader.isEncrypted:
msg.append({'error': _('File must be unencrypted.'), 'path': file.filename})

except Exception as e:
msg.append({'error': _('Invalid file.'), 'path': file.filename})
current_app.logger.debug(e)

if msg:
return msg

return None
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,6 @@ def validate(filing_json: dict) -> Optional[Error]: # pylint: disable=too-many-
return msg # Cannot continue validation without legal_type

msg.extend(validate_business_in_colin(filing_json, filing_type))
msg.extend(validate_continuation_in_authorization(filing_json, filing_type))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We still need to validate if a file exist or not against this key

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay so I moved other validations to file submission.
I will add a validation to check if file exists.

msg.extend(_validate_foreign_jurisdiction(filing_json, filing_type, legal_type))
msg.extend(validate_name_request(filing_json, legal_type, filing_type))

Expand Down Expand Up @@ -126,10 +125,7 @@ def _validate_foreign_jurisdiction(filing_json: dict, filing_type: str, legal_ty
foreign_jurisdiction['country'] == 'CA' and
((region := foreign_jurisdiction.get('region')) and region == 'AB')):
affidavit_file_key_path = f'{foreign_jurisdiction_path}/affidavitFileKey'
if file_key := foreign_jurisdiction.get('affidavitFileKey'):
if err := validate_pdf(file_key, affidavit_file_key_path, False):
msg.extend(err)
else:
Comment on lines -129 to -132
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We still need to validate if a file exist or not against this key

if not foreign_jurisdiction.get('affidavitFileKey'):
msg.append({'error': 'Affidavit from the directors is required.', 'path': affidavit_file_key_path})
try:
# Check the incorporation date is in valid format
Expand Down