Skip to content
Snippets Groups Projects
views.py 8.83 KiB
Newer Older
"""
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/
"""
Patricio Kumagae's avatar
Patricio Kumagae committed
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
Patricio Kumagae's avatar
Patricio Kumagae committed

from app.managers import TimestampManager
from app.utils import Utils
from TsaApi.local_settings import TEMPORARY_OTS_PREFIX, PERMANENT_OTS_PREFIX, CONTRACTS

Patricio Kumagae's avatar
Patricio Kumagae committed
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())
Patricio Kumagae's avatar
Patricio Kumagae committed

            return Response(
                {_('status'): _('success'), _('temporary_rd'): base64.b64encode(ots.encode('utf-8')).decode('utf-8')},
                status=status.HTTP_200_OK)
Patricio Kumagae's avatar
Patricio Kumagae committed

        except ValidationError as e:
            return Response({_('status'): _('failure'), _('messages'): _('parameter_missing') % e.message},
                            status=status.HTTP_400_BAD_REQUEST)
Patricio Kumagae's avatar
Patricio Kumagae committed
        except CannotHandleRequest:
            return Response({_('status'): _('failure'), _('messages'): _('could_not_connect')},
                            status=status.HTTP_503_SERVICE_UNAVAILABLE)
Patricio Kumagae's avatar
Patricio Kumagae committed
        except Exception as e:
            client.captureException()
            return Response({_('status'): _('failure'), _('messages'): _('operation_failed')},
                            status=status.HTTP_500_INTERNAL_SERVER_ERROR)
Patricio Kumagae's avatar
Patricio Kumagae committed


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
Patricio Kumagae's avatar
Patricio Kumagae committed

    Devuelve número de bloque, fecha y hora de subida a la Blockchain

    Ejemplo:
    {
      "file_hash": "1957db7fe23e4be1740ddeb941ddda7ae0a6b782e536a9e00b5aa82db1e84547",
      "rd": "NzNkYzA5OGJkODlmZjdlMjc4OGFjMzJlNmU2ODdiOTdmODdiMTBjMWIyNzg5OTFlMDNkN2E2YWVkMDk3ODJkZTAxLTB4NGM2ZmNiNDBhMmUyZGVjYzc2YWQzMjM3MDU2NzZjMjljYWE1MmIyYjZkMDdiMDIzYjBhY2EzOWYzZGIxYmRlZg=="
Patricio Kumagae's avatar
Patricio Kumagae committed
    }
    """

    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(
Patricio Kumagae's avatar
Patricio Kumagae committed
            name='rd',
Patricio Kumagae's avatar
Patricio Kumagae committed
            required=True,
            location='form',
            schema=coreschema.String(),
Patricio Kumagae's avatar
Patricio Kumagae committed
            description='El recibo digital recibido al hacer el stamp del archivo encodeado en sha256',
Patricio Kumagae's avatar
Patricio Kumagae committed
        )
    ])

    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')
Patricio Kumagae's avatar
Patricio Kumagae committed

            original_file_hash = request.data.get('file_hash')
            base64_ots = request.data.get('rd')
Patricio Kumagae's avatar
Patricio Kumagae committed

            ots = base64.b64decode(base64_ots).decode('utf-8')

            if ots[:2] == PERMANENT_OTS_PREFIX:
Patricio Kumagae's avatar
Patricio Kumagae committed

                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)
Patricio Kumagae's avatar
Patricio Kumagae committed

Patricio Kumagae's avatar
Patricio Kumagae committed
                if args[0].decode('utf-8') == ots_hash and args[1].decode('utf-8') == original_file_hash:
Patricio Kumagae's avatar
Patricio Kumagae committed

                    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'),
                                     _('attestation_time'): str(Utils.datetime_from_timestamp(block.timestamp)),
Patricio Kumagae's avatar
Patricio Kumagae committed
                                     _('messages'): _('file_uploaded') % (
                                         file_hash, str(block.number),
                                         str(Utils.datetime_from_timestamp(block.timestamp)))},
Patricio Kumagae's avatar
Patricio Kumagae committed
                                    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)

Patricio Kumagae's avatar
Patricio Kumagae committed
                contract_version = ots_hash[-2:]

                if TimestampManager.verify(contract_version, ots_hash, original_file_hash):
Patricio Kumagae's avatar
Patricio Kumagae committed

                    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:
Patricio Kumagae's avatar
Patricio Kumagae committed

                        block = TimestampManager.get_block(
                            TimestampManager.get_block_number(contract_version, ots_hash))
Patricio Kumagae's avatar
Patricio Kumagae committed

                        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'),
                                         _('attestation_time'): str(Utils.datetime_from_timestamp(block.timestamp)),
                                         _('messages'): _('file_uploaded') % (original_file_hash, str(block.number), str(
                                             Utils.datetime_from_timestamp(block.timestamp)))}, status=status.HTTP_200_OK)
Patricio Kumagae's avatar
Patricio Kumagae committed
                else:
                    try:
                        if transaction and not transaction.blockNumber:
                            return Response({_('status'): _('pending'), _('messages'): _('transaction_pending')},
                                            status=status.HTTP_200_OK)
Patricio Kumagae's avatar
Patricio Kumagae committed
                    except ValueError:
                        pass

                    return Response({_('status'): _('failure'), _('messages'): _('file_not_found')},
                                    status=status.HTTP_404_NOT_FOUND)
Patricio Kumagae's avatar
Patricio Kumagae committed

        except ValidationError as e:
            return Response({_('status'): _('failure'), _('messages'): _('parameter_missing') % e.message},
                            status=status.HTTP_400_BAD_REQUEST)
Patricio Kumagae's avatar
Patricio Kumagae committed
        except CannotHandleRequest:
            return Response({_('status'): _('failure'), _('messages'): _('could_not_connect')},
                            status=status.HTTP_503_SERVICE_UNAVAILABLE)
Patricio Kumagae's avatar
Patricio Kumagae committed
        except Exception as e:
            client.captureException()
            return Response({_('status'): _('failure'), _('messages'): _('operation_failed')},
                            status=status.HTTP_500_INTERNAL_SERVER_ERROR)