""" Copyright 2019 de la Dirección General de Sistemas Informáticos – Secretaría Legal y Técnica - Nación. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see http://www.gnu.org/licenses/ """ import base64 from django.core.exceptions import ValidationError from django.utils.translation import gettext as _ from rest_framework import status from rest_framework.views import APIView from rest_framework.response import Response from web3.exceptions import CannotHandleRequest from raven.contrib.django.raven_compat.models import client from rest_framework.schemas import ManualSchema import coreschema,coreapi from app.managers import TimestampManager from app.utils import Utils from TsaApi.local_settings import TEMPORARY_OTS_PREFIX, PERMANENT_OTS_PREFIX, CONTRACTS class Stamp(APIView): """ [POST] Permite realizar un stamp de un archivo Parámetros recibidos: [Content-Type:application/json] - file_hash: El hash del archivo encodeado en sha256 Devuelve un OTS para poder verificar en el futuro que el archivo fue incluido a la Blockchain Ejemplo: { "file_hash": "1957db7fe23e4be1740ddeb941ddda7ae0a6b782e536a9e00b5aa82db1e84547" } """ schema = ManualSchema(fields=[ coreapi.Field( name='file_hash', required=True, location='form', schema=coreschema.String(), description='El hash del archivo encodeado en sha256', ), ]) def post(self, request): try: if not request.data.get('file_hash'): raise ValidationError('file_hash') file_hash = request.data.get('file_hash') ots_hash = Utils.get_ots_hash(file_hash) tx_hash = TimestampManager.stamp(ots_hash, file_hash) #Al OTS se le agrega la transacción para poder verificar luego si está pendiente de subida ots = Utils.get_temporary_ots(ots_hash, tx_hash.hex()) return Response({_('status'): _('success'), _('temporary_rd'): base64.b64encode(ots.encode('utf-8')).decode('utf-8')}, status=status.HTTP_200_OK) except ValidationError as e: return Response({_('status'): _('failure'), _('messages'): _('parameter_missing') % e.message}, status=status.HTTP_400_BAD_REQUEST) except CannotHandleRequest: return Response({_('status'): _('failure'), _('messages'): _('could_not_connect')}, status=status.HTTP_503_SERVICE_UNAVAILABLE) except Exception as e: client.captureException() return Response({_('status'): _('failure'), _('messages'): _('operation_failed')}, status=status.HTTP_500_INTERNAL_SERVER_ERROR) class Verify(APIView): """ [POST] Permite verificar que un archivo fue subido a la Blockchain Parámetros recibidos: [Content-Type:application/json] - file_hash: El hash del archivo original encodeado en sha256 - rd: Recibo digital recibido como prueba al momento de realizar el stamp Devuelve número de bloque, fecha y hora de subida a la Blockchain Ejemplo: { "file_hash": "1957db7fe23e4be1740ddeb941ddda7ae0a6b782e536a9e00b5aa82db1e84547", "rd": "NzNkYzA5OGJkODlmZjdlMjc4OGFjMzJlNmU2ODdiOTdmODdiMTBjMWIyNzg5OTFlMDNkN2E2YWVkMDk3ODJkZTAxLTB4NGM2ZmNiNDBhMmUyZGVjYzc2YWQzMjM3MDU2NzZjMjljYWE1MmIyYjZkMDdiMDIzYjBhY2EzOWYzZGIxYmRlZg==" } """ schema = ManualSchema(fields=[ coreapi.Field( name='file_hash', required=True, location='form', schema=coreschema.String(), description='El hash del archivo encodeado en sha256', ), coreapi.Field( name='rd', required=True, location='form', schema=coreschema.String(), description='El recibo digital recibido al hacer el stamp del archivo encodeado en sha256', ) ]) def post(self, request): try: if not request.data.get('file_hash'): raise ValidationError('file_hash') if not request.data.get('rd'): raise ValidationError('rd') original_file_hash = request.data.get('file_hash') base64_ots = request.data.get('rd') ots = base64.b64decode(base64_ots).decode('utf-8') if ots[:2] == PERMANENT_OTS_PREFIX: ots_version, file_hash, ots_hash, tx_hash, block_number = ots.split('-') method_name, args = Utils.decode_contract_call(CONTRACTS['01']['abi'], TimestampManager.get_transaction(tx_hash).input) if args[0].decode('utf-8') == ots_hash and args[1].decode('utf-8') == file_hash: block = TimestampManager.get_block(int(block_number)) return Response({_('status'): _('success'), _('permanent_rd'): base64.b64encode(Utils.get_permanent_ots(original_file_hash, ots_hash, tx_hash, block.number).encode('utf-8')).decode('utf-8'), _('messages'): _('file_uploaded') % ( file_hash, str(block.number), str(Utils.datetime_from_timestamp(block.timestamp)))}, status=status.HTTP_200_OK) else: return Response({_('status'): _('failure'), _('messages'): _('file_not_found')}, status=status.HTTP_404_NOT_FOUND) else: ots_version, ots_hash, tx_hash = ots.split('-') transaction = TimestampManager.get_transaction(tx_hash) contract_version = ots_hash[-2:] if TimestampManager.verify(contract_version, ots_hash, original_file_hash): if (TimestampManager.get_last_block_number() - transaction.blockNumber) < Utils.required_block_difference(TimestampManager.get_signers_count()): return Response({_('status'): _('pending'), _('messages'): _('transaction_pending')}, status=status.HTTP_200_OK) else: block = TimestampManager.get_block(TimestampManager.get_block_number(contract_version, ots_hash)) return Response({_('status'): _('success'), _('permanent_rd'): base64.b64encode(Utils.get_permanent_ots(original_file_hash, ots_hash, tx_hash, block.number).encode('utf-8')).decode('utf-8'), _('messages'): _('file_uploaded') % (original_file_hash, str(block.number), str( Utils.datetime_from_timestamp(block.timestamp)))}, status=status.HTTP_200_OK) else: try: if transaction and not transaction.blockNumber: return Response({_('status'): _('pending'), _('messages'): _('transaction_pending')}, status=status.HTTP_200_OK) except ValueError: pass return Response({_('status'): _('failure'), _('messages'): _('file_not_found')},status=status.HTTP_404_NOT_FOUND) except ValidationError as e: return Response({_('status'): _('failure'), _('messages'): _('parameter_missing') % e.message}, status=status.HTTP_400_BAD_REQUEST) except CannotHandleRequest: return Response({_('status'): _('failure'), _('messages'): _('could_not_connect')}, status=status.HTTP_503_SERVICE_UNAVAILABLE) except Exception as e: client.captureException() return Response({_('status'): _('failure'), _('messages'): _('operation_failed')}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)