Hilfe
abbrechen
Suchergebnisse werden angezeigt für 
Stattdessen suchen nach 
Meintest du: 

Rest Api ohne Phototan-App nutzen können

truefalse10
Autor
2 Beiträge

Hallo zusammen:)

Ich habe mir eine kleine App für meine persöhnliche Zwecken geplannt.
Die App wollte ich über ein Cron-Job in meinem Server laufen lassen, die extrahierte Daten wollte ich in einem MySql Datenbank speichern.

Ich bin die Steps der Api-Doku durchgegangen und da ist mir aufgefallen, dass ich zwichen Step 2.4 und Step 2.5 auf meinem Handy Photo-Tan App aufmachen soll und da "Login zum personlichen Bereich" einmal bestätigen muss. 

Somit wurde meine App über ein Cron Job natürlich nie lauffähig sein.

Was ich so grob erreichen möchte wäre aktueller Kontostand und alle Umsatze (Ein- und Ausgaben) des aktuellen Tags, diese Infos hätte ich gerne mit meinem Tech-Stack Symfony 6.2 (PHP 8.1) und MariaDB Mysql umsetzen wollen.

Kann mar mir bitte irgendwas dazu empfehlen und bzw genau sagen ob ich irgendwas übersehe und es doch möglich ohne Phototan App ist?

Viele Grüße
Alex

2 ANTWORTEN

Tarulia
Mentor ★
1.112 Beiträge

Ich bin jetzt kein Experte in PSD2-Angelegenheiten, aber ich vermute, dass das im Sinne der "starken Kundenauthentifizierung" nicht gehen wird, da 2 Faktoren gefordert werden und der API Key nur einer wäre.

 

Vielleicht weiß @SMT_Service da ja mehr 🙂

truefalse10
Autor
2 Beiträge

Hey Tarulia:)
Ich habs tatsächlich hinbekommen, wie man das alles umsetzen kann (PHP und MySql):

<?php

declare(strict_types=1);

namespace App\Service;

use App\Entity\AccessResponse;
use App\Entity\User;
use App\Repository\AccessResponseRepository;
use App\Repository\UserRepository;
use GuzzleHttp\Client;

class RefreshTokenService
{
public const API_URL = 'https://api.comdirect.de';

private Client $client;

public function __construct(
private readonly AccessResponseRepository $accessResponseRepository,
private readonly UserRepository $userRepository,
)
{
$this->client = new Client();
}

public function generateAccessToken(): void
{
$sessionId = $this->generateSessionId();
$requestId = $this->generateRequestId();

$users = $this->userRepository->findAll();

$accessResponses = $this->initAccessAndRefreshTokens($users);

$sessionUuids = $this->getSessionUuid(
$users,
$accessResponses,
$sessionId,
$requestId
);

$tanIds = $this->activateSession(
$users,
$accessResponses,
$sessionUuids,
$sessionId,
$requestId,
);

$this->activateTans(
$users,
$accessResponses,
$sessionId,
$sessionUuids,
$requestId,
$tanIds
);

$this->getFinalAccessTokens(
$users,
$accessResponses,
$sessionId,
$requestId,
);
}

/**
* @param User[] $users
* @return AccessResponse[]
*/
private function initAccessAndRefreshTokens(array $users😞 array
{
$accessReponses = [];

foreach ($users as $user) {
$response = $this->client->post(
self::API_URL . '/oauth/token',
[
'headers' => [
'Accept' => 'application/json',
'Content-Type' => 'application/x-www-form-urlencoded'
],
'form_params' => [
'client_id' => $user->getComdirectApiKey(),
'client_secret' => $user->getComdirectApiSecret(),
'username' => $user->getComdirectUsername(),
'password' => $user->getComdirectPassword(),
'grant_type' => 'password',
]
]
);

/**
* @var array{
* access_token: string,
* refresh_token: string,
* } $responseBody
*/
$responseBody = json_decode($response->getBody()->getContents(), true);
$accessToken = $responseBody['access_token'];
$refreshToken = $responseBody['refresh_token'];

$accessResponse = (new AccessResponse())
->setAccessToken($accessToken)
->setRefreshToken($refreshToken)
->setUser($user);

$this->accessResponseRepository->save($accessResponse);

$accessReponses[$user->getId()] = $accessResponse;
}

return $accessReponses;
}

/**
* @param User[] $users
* @param AccessResponse[] $accessResponses
* @return string[]
*/
private function getSessionUuid(
array $users,
array $accessResponses,
string $sessionId,
string $requestId,
😞 array
{
$identifiers = [];

foreach ($users as $user) {
$response = $this->client->get(
self::API_URL . '/api/session/clients/user/v1/sessions',
[
'headers' => [
'Accept' => 'application/json',
'Authorization' => 'Bearer ' . $accessResponses[$user->getId()]->getAccessToken(),
'Content-Type' => 'application/json',
'x-http-request-info' =>
sprintf(
'{"clientRequestId":{"sessionId":"%s","requestId":"%s"}}',
$sessionId,
$requestId
),
]
]
);

/** @var mixed[] $responseData */
$responseData = json_decode($response->getBody()->getContents(), true);

/**
* @var array{
* identifier: string
* } $responseDataFirstElement
*/
$responseDataFirstElement = reset($responseData);
$identifiers[$user->getId()] = $responseDataFirstElement['identifier'];
}

return $identifiers;
}

/**
* @param User[] $users
* @param AccessResponse[] $accessResponses
* @param string[] $sessionUuids
* @return string[]
*/
private function activateSession(
array $users,
array $accessResponses,
array $sessionUuids,
string $sessionId,
string $requestId,
😞 array
{
$result = [];

foreach ($users as $user) {
$sessionUuid = $sessionUuids[$user->getId()];

$response = $this->client->post(
self::API_URL . '/api/session/clients/user/v1/sessions/' . $sessionUuid . '/validate',
[
'headers' => [
'Accept' => 'application/json',
'Authorization' => 'Bearer ' . $accessResponses[$user->getId()]->getAccessToken(),
'Content-Type' => 'application/json',
'x-http-request-info' =>
sprintf(
'{"clientRequestId":{"sessionId":"%s","requestId":"%s"}}', $sessionId, $requestId
),
],
'json' => [
'identifier' => $sessionUuid,
'sessionTanActive' => true,
'activated2FA' => true
]
]
);

$authenticationInfo = $response->getHeaders()['x-once-authentication-info'];

/**
* @var array{
* id: string
* } $responseData
*/
$responseData = json_decode($authenticationInfo[0], true);

$result[$user->getId()] = $responseData['id'];
}

return $result;
}

/**
* @param User[] $users
* @param AccessResponse[] $accessResponses
* @param string $sessionId
* @param string[] $sessionUuids
* @param string $requestId
* @param string[] $tanIds
*/
private function activateTans(
array $users,
array $accessResponses,
string $sessionId,
array $sessionUuids,
string $requestId,
array $tanIds
😞 void
{
sleep(10); //pause to activate photo tan app
foreach ($users as $user) {
$this->client->patch(
self::API_URL . '/api/session/clients/user/v1/sessions/' . $sessionUuids[$user->getId()],
[
'headers' => [
'Accept' => 'application/json',
'Authorization' => 'Bearer ' . $accessResponses[$user->getId()]->getAccessToken(),
'x-http-request-info' =>
sprintf(
'{"clientRequestId":{"sessionId":"%s","requestId":"%s"}}', $sessionId, $requestId
),
'Content-Type' => 'application/json',
'x-once-authentication-info' => sprintf(
'{"id":"%s"}', $tanIds[$user->getId()]
)
],
'json' => [
'identifier' => $sessionUuids[$user->getId()],
'sessionTanActive' => true,
'activated2FA' => true
]
]
);
}
}

/**
* @param User[] $users
* @param AccessResponse[] $accessResponses
*/
private function getFinalAccessTokens(
array $users,
array $accessResponses,
string $sessionId,
string $requestId
😞 void
{
foreach ($users as $user) {
$response = $this->client->post(
self::API_URL . '/oauth/token',
[
'headers' => [
'Accept' => 'application/json',
'Content-Type' => 'application/x-www-form-urlencoded'
],
'form_params' => [
'client_id' => $user->getComdirectApiKey(),
'client_secret' => $user->getComdirectApiSecret(),
'grant_type' => 'cd_secondary',
'token' => $accessResponses[$user->getId()]->getAccessToken()
]
]
);

/**
* @var array{
* access_token: string,
* token_type: string,
* refresh_token: string,
* expires_in: int,
* scope: string,
* kdnr: string,
* bpid: string,
* kontaktId: string,
* } $responseData
*/
$responseData = json_decode($response->getBody()->getContents(), true);

$accessResponse = new AccessResponse();
$accessResponse
->setAccessToken($responseData['access_token'])
->setTokenType($responseData['token_type'])
->setRefreshToken($responseData['refresh_token'])
->setExpiresIn($responseData['expires_in'])
->setScope($responseData['scope'])
->setKdnr($responseData['kdnr'])
->setBpid((string)$responseData['bpid'])
->setKontaktId((string)$responseData['kontaktId'])
->setSessionId($sessionId)
->setReguestId($requestId)
->setCreatedAt(new \DateTimeImmutable())
->setUser($user);

$this->accessResponseRepository->save($accessResponse);
}
}

private function generateSessionId(): string
{
$sessionId = '';
for ($i = 0; $i <= 31; $i++) {
$sessionId .= rand(0, 1);
}
return $sessionId;
}

private function generateRequestId(): string
{
return (new \DateTime())->format('HHmm') . rand(0, 9);
}

/**
* @param User[] $users
*/
public function refreshAccessToken(array $users😞 void
{
foreach ($users as $user) {
/** @var AccessResponse $actualToken */
$actualToken = $this->accessResponseRepository->findOneBy([
'user' => $user
], [
'id' => 'DESC'
]);

$response = $this->client->post(
self::API_URL . '/oauth/token',
[
'headers' => [
'Accept' => 'application/json',
'Content-Type' => 'application/x-www-form-urlencoded'
],
'form_params' => [
'client_id' => $user->getComdirectApiKey(),
'client_secret' => $user->getComdirectApiSecret(),
'grant_type' => 'refresh_token',
'refresh_token' => $actualToken->getRefreshToken(),
]
],
);

$responseData = $response->getBody()->getContents();

/**
* @var array{
* access_token: string,
* refresh_token: string,
* token_type: string,
* expires_in: int,
* scope: string,
* } $responseDataAsArray
*/
$responseDataAsArray = json_decode($responseData, true);

$refreshedToken = (new AccessResponse())
->setAccessToken($responseDataAsArray['access_token'])
->setRefreshToken($responseDataAsArray['refresh_token'])
->setTokenType($responseDataAsArray['token_type'] . ' (refreshed via token with id) ' . $actualToken->getId())
->setExpiresIn($responseDataAsArray['expires_in'])
->setScope($responseDataAsArray['scope'])
->setBpid('')
->setKdnr('')
->setSessionId($actualToken->getSessionId())
->setReguestId($actualToken->getReguestId())
->setKontaktId('')
->setUser($user)
->setCreatedAt(new \DateTimeImmutable());

$this->accessResponseRepository->save($refreshedToken);
}
}
}

Nachdem ich activateSession aufrufe gibt es die Funktion aktivateTans -> da muss man innerhalb von 10 Sekunden schaffen können Push Tan in App zu bestätigen, danach hab ich ein Cron was einfach aktuellen Token aus initialen Bank Response abholt und refresht da der Token nur 599 Sekunden gültig ist. Cron habe ich auf alle 5 Minuten eingestellt und da läuft alles einwandfrei (siehe dazu refreshAccessToken funktion). Ich hoffe mit dem Code kann jemand was anfangen, ich kann auch mehr zeigen falls Interesse da ist (inklusive Docker Umgebung und Rest von Funktionen und Features die ich umgesetzt habe). Wenn Interesse da ist gerne melden, ich mach dann ein neues Repo auf und teile die gerne weiter:)

@SMT_Service Ich hab auch paar coole Ideen und Vorschläge für Erweiterung von existierenden API, in meinem Projekt habe ich einen Telegram Bot geschrieben welcher mir dann Benachrichtigungen über neue Transaktionen schickt (manchmal 40 Minuten früher als die aus der Bank). Gerne können wir das jederzeit näher besprechen!

Beste Grüße
Alexey Bereznyak