Cap´ıtulo 5
5.1 Servidor do operador
Figura 5.1: M´odulos que constituem o prot´otipo do servidor do operador.
O prot´otipo do servidor do operador foi implementado, em Python, com os m´odulos apre-sentados na Figura 5.1. A descri¸c˜ao do prot´otipo come¸car´a por incidir sobre a comunica¸c˜ao entre a framework Web Django e o complemento da framework Django REST com a base
Figura 5.2: Modelo de dados do servidor.
de dados MySQL e, mais `a frente, ser´a descrita a rela¸c˜ao com os m´odulos “hedera-sdk-py” e
“starkbank-ecdsa”.
Antes de descrever o funcionamento deste servidor, ´e necess´ario ter em conta o conceito de modelos (models),ModelSerializers e views.
Inicialmente foram definidos os modelos que permitem organizar a estrutura da base de dados MySQL, onde um modelo representa uma tabela e um campo de um modelo representa uma coluna dessa tabela. O modelo de dados do servidor est´a representado na Figura 5.2, onde se encontram 9 tabelas diferentes em que: a tabelaHederaAccount cont´em uma lista de IDs de contas Hedera Hashgraph pertencentes ao operador que se encarregaram de receber e/ou enviar transa¸c˜oes para contas Hedera Hashgraph de passageiros; a tabelaStop cont´em informa¸c˜oes acerca de cada uma das paragens pertencentes `a rota de um ve´ıculo, com o seu ID, nome, custo da viagem partindo da mesma etimestampcom o hor´ario previsto da chegada do ve´ıculo (por motivos de simplifica¸c˜ao foi simulado apenas um ve´ıculo); a tabelaUBKeycont´em os IDs e aspublic keys correspondentes a todas as unidades de bordo (cada uma pertencente a um ve´ıculo), de forma a desencriptar informa¸c˜ao gerada pelas mesmas; e a CustomUser salvaguarda a informa¸c˜ao dos passageiros que se registam no sistema. Durante uma viagem, os passageiros ficam associados `as tabelasTravellingPassenger ePendingRefund quando efetuam check-ins, enquanto que as tabelas FinishedTransaction e FinishedTrip recebem uma nova entrada cada vez que um passageiro efetua umcheck-out. Por fim, a tabelaSession faz parte da autentica¸c˜ao de passageiros no sistema, explorado mais `a frente.
Um ModelSerializer ´e uma ferramenta que permite facilitar a comunica¸c˜ao entre mode-los/tabelas e os requests HTTP, que permitem o acesso de dispositivos externos, como o smartphone de um passageiro, aos dados do servidor. As views, no caso deste servidor, s˜ao fun¸c˜oes que processam requests HTTP (POST/GET) e fazem o return de objetos de uma classe Response. Esta funcionalidade permite-lhes servir de intermedi´arios entre o acesso de dispositivos externos ao servidor e os pr´oprios ModelSerializers, al´em de permitirem ao ser-vidor interagir diretamente com a sua base de dados. A cada classe que cont´em fun¸c˜oesview foi designado um URL para permitir a comunica¸c˜ao da aplica¸c˜ao dos passageiros atrav´es de HTTPrequests.
5.1.1 Registo do utilizador
Para registar um passageiro na base de dados do servidor, a aplica¸c˜ao do passageiro realiza um POST HTTP com a informa¸c˜ao de registo (nome de utilizador, email, palavra-passe, n´umero de telem´ovel e ID da sua conta Hedera Hashgraph) que, caso seja enviado com sucesso, alcan¸ca o servidor sob o formato:
"POST /api/register/ HTTP/1.1" 200
Assim, o servidor acede a uma fun¸c˜aopost() da classe com este URL (neste caso a “Regis-terAPI”), passa a informa¸c˜ao recebida pelo ModelSerializer “RegisterSerializer”, que valida a informa¸c˜ao recebida, converte a informa¸c˜ao num modelo “CustomUser” e guarda o novo utilizador na base de dados. Caso o registo tenha sido bem sucedido, o servidor envia a seguinte resposta:
{"response":"Registration Successfull"}
De salientar ainda que o modelo “CustomUser” foi adicionado ao servidor com recurso `a subclasse AbstractUser da framework Django, pela necessidade de complementar o modelo User com os campos: n´umero de telem´ovel e ID da conta Hedera Hashgraph.
5.1.2 Autentica¸c˜ao do utilizador
Com o registo conclu´ıdo, o passo seguinte ´e a autentica¸c˜ao dos utilizadores registados na base de dados para permitir que efetuem o login nas respetivas aplica¸c˜oes. Esta classe recebe da aplica¸c˜ao um POST HTTP com o nome de utilizador e respetiva palavra-passe. A informa¸c˜ao passa peloSerializer AuthTokenSerializer proveniente da subclasse ObtainAuth-Token da framework Django REST, verifica se a informa¸c˜ao est´a de acordo com o modelo User e, caso a informa¸c˜ao seja v´alida, estes campos passam a ser argumentos de uma fun¸c˜ao authenticate():
username = serializer.validated_data[’username’]
password = serializer.validated_data[’password’]
user = authenticate(username=username, password=password)
Esta fun¸c˜ao verifica se as credenciais fazem parte de algum utilizador registado no servidor e, caso isso se verifique, a fun¸c˜ao devolve o objeto User correspondente. O request HTTP proveniente da aplica¸c˜ao do utilizador (POST ’/api-token-auth/’) e o objeto User, passam a ser argumentos de uma fun¸c˜aologin(), que cria uma key para a sess˜ao ativa (“sessionid”):
login(request,user)
data[’sessionid’] = request.session.session_key
terminando com uma resposta com o formato do seguinte exemplo:
{"response":"Login Successfull",
"sessionid":"oavj31em72waamdf1ags96hl5xeo183c"}.
Esta String de 32 caracteres ASCII aleat´orios ´e guardada na base de dados do servidor e associada ao ID do passageiro, com uma validade de 14 dias (na tabela Session). Esta permite restringir o acesso `as restantes classes apenas a quem est´a a fazer requests a partir de um login ativo, sendo transmitida no cabe¸calho de todos osrequests HTTP enviados pela aplica¸c˜ao do passageiro. O processo de logout ´e um desses exemplos, tendo em conta que come¸ca por identificar a validade da “sessionid” recebida e, s´o no caso de esta ser v´alida, procede `a elimina¸c˜ao dessa sess˜ao da base de dados (session.delete()), terminando com a resposta:
{"response":"Logout Successfull"}
O utilizador s´o volta a ter uma sess˜ao ativa (“sessionid” atribu´ıdo ao seu ID na base de dados) assim que efetuar um novo login.
5.1.3 Autentica¸c˜ao dos beacons
Tal como foi referido na arquitetura descrita no Cap´ıtulo 4, uma das fun¸c˜oes do servidor do operador ´e desencriptar as assinaturas digitais dos an´uncios de paragem recebidos pela aplica¸c˜ao dos passageiros de modo a validar a sua autenticidade e evitar situa¸c˜oes despoofing ehijacking.
Antes de dar in´ıcio `a desencripta¸c˜ao de uma mensagem, ´e necess´ario que a tabelaUBKey da base de dados tenha o ID da Unidade de Bordo que encriptou essa mensagem associado `a correspondentepublic key, bem como o ID da paragem onde a mensagem foi encriptada asso-ciado ao nome dessa paragem (tabela Stop). No caso da mensagem encriptada apresentada no cap´ıtulo 4, a base de dados teria uma entrada na tabelaStop com o ID da paragem “00A1”
associado ao nome dessa paragem e, na tabelaUBKey, o ID da UB “000018” associado a uma public key como a seguinte:
---BEGIN PUBLIC
KEY---MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAEO3radhXVo6wzk2f8QbH0fHCnQsKW1uhC OZqOoPVNaTJN+p1lvT+3zWRKXC3F4SEm2+Bn1JGQIDYI9bdQWqsGlA==
---END PUBLIC
KEY---Dado que a desencripta¸c˜ao ECDSA, com parˆametros secp256k1, ´e necess´aria apenas para an´uncios de paragem, foi criada uma classe “AnnouncementsAPI”, em que a sua fun¸c˜aopost() visa desencriptar qualquer an´uncio de paragem recebido pelo servidor e, atrav´es de umaflag
“status”, o servidor determina se est´a na presen¸ca de uma situa¸c˜ao de check-in, paragem interm´edia ou check-out. Para isso, a classe recebe POSTs HTTP, que contˆem cinco campos distintos: o ID da sess˜ao, o timestamp do smartphone do passageiro, a flag, a mensagem com os dados desencriptados do an´uncio de paragem e a assinatura digital (com os dados encriptados).
Essencialmente, o processo de valida¸c˜ao do an´uncio consiste em identificar o ID da UB incorporado na mensagem, identificar a public key associada e, atrav´es dela, desencrip-tar a assinatura digital. Para isso, foi usada o m´odulo “starkbank-ecdsa”, que converte as Strings da assinatura digital e public key em “ellipticcurve.signature.Signature” e “el-lipticcurve.publicKey.PublicKey”, respetivamente, que passam a ser argumentos da fun¸c˜ao
“Ecdsa.verify()” junto da mensagem desencriptada:
Ecdsa.verify(message, signature, pubkey)
Caso a desencripta¸c˜ao da assinatura digital com a public key resulte numa mensagem igual
`
a sec¸c˜ao de dados, faz return de uma vari´avelboolean “True”, confirmando que os dados n˜ao sofreram altera¸c˜oes desde a sa´ıda da Unidade de Bordo at´e `a chegada ao servidor. O servidor verifica ainda se o an´uncio foi recebido pelo passageiro dentro de um intervalo de tempo vi´avel, para que este n˜ao seja prejudicado por uma r´eplica desse an´uncio de paragem, por exemplo no dia seguinte, por uma entidade maliciosa que pretenda que o utilizador transfira dinheiro sem estar a viajar (spoofing).
Este processo ´e realizado atrav´es da identifica¸c˜ao do ID da paragem representada na men-sagem, que na base de dados tem umtimestamp previsto associado `a chegada do transporte p´ublico a essa paragem, obt´em tamb´em otimestampprevisto da chegada `a paragem seguinte e calcula o tempo estimado entre elas. Caso a diferen¸ca entre o timestamp enviado pela aplica¸c˜ao do passageiro e otimestamp da cria¸c˜ao do an´uncio pela UB seja superior ao tempo estimado entre paragens, o an´uncio ´e descartado.
5.1.4 Check-in
A classe “AnnouncementsAPI”, quando recebe um POST request de um an´uncio de pa-ragem relativo a um check-in, procede `a valida¸c˜ao do an´uncio de paragem, guarda o nome da paragem atual com o timestamp correspondente e associa esta informa¸c˜ao ao nome do passageiro, no modelo “TravelingPassenger”. A fun¸c˜aopost() da classe, termina com oreturn do nome da paragem atual, nome da paragem seguinte, pre¸co a pagar pelo passageiro, o ID da conta Hedera Hashgraph da empresa de transporte p´ublico, para o qual o passageiro ter´a de enviar o montante, e uma mensagem que indica que o check-in foi bem sucedido, como demonstra o exemplo de resposta da Figura 5.3.
Figura 5.3: Resposta a umrequest HTTP de check-in.
O processo de check-in, al´em do request `a classe AnnouncementsAPI, tamb´em faz um POST dirigido `a classe PendingRefundAPI. A aplica¸c˜ao do passageiro, ap´os receber o va-lor da quantia a transferir atrav´es do elemento “price” representado na Figura 5.3, trans-fere o montante para a conta Hedera Hashgraph do operador de transporte p´ublico e faz um request HTTP para esta classe, para que o servidor guarde o pedido de reembolso com o nome do passageiro, a quantia paga pelo mesmo e o ID da transa¸c˜ao, por exemplo
“[email protected]”, na tabela PendingRefund.
5.1.5 Paragem interm´edia
No caso de uma paragem interm´edia, ap´os a valida¸c˜ao do an´uncio de paragem, o servidor apenas responde com os nomes das paragens atual e seguinte, junto de uma mensagem a confirmar que o pedido foi bem sucedido.
5.1.6 Check-out
Em rela¸c˜ao aos an´uncios de paragem em situa¸c˜oes de check-out, o servidor come¸ca o processo por apagar da base de dados o objeto da tabela TravelingPassenger que foi criado nocheck-in. Caso encontre um objeto em PendingRefund que corresponda a um pagamento efetuado pelo utilizador, d´a por terminada a viagem registando-a na tabela FinishedTrip, como demonstra o exemplo da Figura 5.4, com o nome do passageiro (ex.:“a0”), o custo da viagem e os nomes das paragens de check-in e check-out com os respetivos timestamps dos an´uncios. Esta tabela permite aos utilizadores, atrav´es de um request GET enviado `a classe AnnouncementsAPI, obterem os seus hist´oricos de viagens. Neste caso, o servidor usa o nome do utilizador para filtrar as entradas de viagens completadas pelo mesmo e responde ao pedido da seguinte forma:
history = FinishedTrip.objects.filter(user=user).
Figura 5.4: Registo de uma viagem completa na base de dados.
Figura 5.5: Registo das transa¸c˜oes de uma viagem completa na base de dados.
values(’stopnameCI’,’timestampCI’,’stopnameCO’,’timestampCO’,’cost’) return Response(history)
Caso o check-out n˜ao tenha sido efetuado na ´ultima paragem, o servidor d´a in´ıcio ao processo de reembolso da quantia que o passageiro pagou a mais, nocheck-in.
Tendo em conta que at´e ao momento a Hedera Hashgraph apenas disponibiliza SDKs1 nativos em Java, JavaScript e Go, para efetuar o reembolso neste servidor desenvolvido em Python, foi usado umwrapper em Python do Hedera Java SDK, chamado “hedera-sdk-py”.
Este wrapper efetua a transa¸c˜ao atrav´es da fun¸c˜ao TransferTransaction(), que tem como argumentos: o ID da conta que vai reembolsar (ID da conta Hedera Hashgraph da empresa de transporte p´ublico), o ID da conta que vai ser reembolsada (ID da conta do passageiro), a quantia a reembolsar (custo a partir da paragem decheck-out) eclient da Testnet, que d´a acesso `a conta Hedera Hashgraph da empresa de transporte p´ublico:
resp = TransferTransaction().
addHbarTransfer(operatorID, amount.negated()).
addHbarTransfer(passengerID, amount).execute(client)
Conclu´ıda a transa¸c˜ao, o servidor acede ao “status” contido no recibo da transa¸c˜ao:
receipt=resp.getReceipt(client) rstatus=receipt.status.toString() data[’response’] = rstatus
que no caso de ter sido bem sucedida, apresenta a String “SUCCESS”, que ´e inclu´ıda na resposta com o nome da paragem de check-out e nome da paragem seguinte, que no caso ´e um h´ıfen a representar que n˜ao existe.
Por fim, o servidor procede em apagar o objeto da tabela PendingRefund e guardar na tabela FinishedTransaction, os campos representados na Figura 5.5: nome do passageiro, a paragem de check-in, paragem de check-out, ID da transa¸c˜ao recebida, ID da transa¸c˜ao enviada e respetivas quantias transferidas, dando por terminado o processo de reembolso.
Figura 5.6: POSTs recebidos pelo servidor.
A Figura 5.6 representa os POSTs de uma viagem completa recebidos pelo servidor, no caso de um passageiro efetuar o seu login e dar in´ıcio a uma viagem com apenas 1 paragem interm´edia.