470 lines
16 KiB
PHP
470 lines
16 KiB
PHP
<?php
|
|
/**
|
|
* API Backend pour la vérification faciale
|
|
* Gère la validation des tokens et la comparaison des visages
|
|
*/
|
|
|
|
// header('Content-Type: application/json');
|
|
header('Content-Type: text/html; charset=UTF-8');
|
|
// require_once 'config.php';
|
|
// require_once 'database.php';
|
|
// require_once "Assure.php";
|
|
|
|
|
|
class FacialVerificationAPI {
|
|
// private $db;
|
|
private $assure_api;
|
|
private $maxAttempts = 3;
|
|
|
|
// public function __construct($db) {
|
|
public function __construct() {
|
|
// $this->db = $db;
|
|
// $assure_api = new Assure();
|
|
$assure_api = $_SESSION['assure'];
|
|
$maxAttempts = $assure_api->get_nbTentativeBiometrie($_SESSION['codeEntite']);
|
|
}
|
|
|
|
/**
|
|
* Valide un token de vérification
|
|
*/
|
|
public function validateToken($token) {
|
|
|
|
// $request = $assure_api->valider_token();
|
|
// var_dump($request);
|
|
return [
|
|
'success' => false,
|
|
'message' => 'Test KANE'
|
|
];
|
|
/*
|
|
try {
|
|
$request = $assure_api->valider_token();
|
|
|
|
var_dump($request);
|
|
|
|
// exit();
|
|
|
|
if (!$request) {
|
|
return [
|
|
'success' => false,
|
|
'message' => 'Lien expiré ou invalide'
|
|
];
|
|
}
|
|
|
|
return [
|
|
'success' => true,
|
|
'message' => 'Token valide',
|
|
'assure' => [
|
|
'nom' => $request['nom'],
|
|
'prenoms' => $request['prenoms']
|
|
]
|
|
];
|
|
|
|
} catch (Exception $e) {
|
|
error_log("Erreur validateToken: " . $e->getMessage());
|
|
return [
|
|
'success' => false,
|
|
'message' => 'Erreur serveur'
|
|
];
|
|
}
|
|
*/
|
|
}
|
|
|
|
/**
|
|
* Compare deux visages avec l'API de reconnaissance faciale
|
|
* Utilisez Azure Face API, AWS Rekognition, ou une solution locale
|
|
*/
|
|
|
|
/*
|
|
private function compareFaces($referenceImagePath, $capturedImageBase64) {
|
|
// Option 1: Azure Face API (Recommandé)
|
|
return $this->compareWithAzureFaceAPI($referenceImagePath, $capturedImageBase64);
|
|
|
|
// Option 2: AWS Rekognition
|
|
// return $this->compareWithAWSRekognition($referenceImagePath, $capturedImageBase64);
|
|
|
|
// Option 3: Solution locale avec OpenCV/dlib (avancé)
|
|
// return $this->compareWithLocalFaceRecognition($referenceImagePath, $capturedImageBase64);
|
|
}
|
|
*/
|
|
|
|
/**
|
|
* Comparaison avec Azure Face API
|
|
*/
|
|
|
|
/*
|
|
private function compareWithAzureFaceAPI($referenceImagePath, $capturedImageBase64) {
|
|
$endpoint = AZURE_FACE_ENDPOINT; // Ex: https://your-resource.cognitiveservices.azure.com
|
|
$apiKey = AZURE_FACE_API_KEY;
|
|
|
|
try {
|
|
// 1. Détecter le visage dans l'image de référence
|
|
$referenceImageData = base64_encode(file_get_contents($referenceImagePath));
|
|
$referenceFaceId = $this->detectFaceAzure($referenceImageData, $endpoint, $apiKey);
|
|
|
|
if (!$referenceFaceId) {
|
|
return [
|
|
'match' => false,
|
|
'confidence' => 0,
|
|
'error' => 'Aucun visage détecté dans la photo de référence'
|
|
];
|
|
}
|
|
|
|
// 2. Détecter le visage dans l'image capturée
|
|
$capturedImageData = explode(',', $capturedImageBase64)[1]; // Enlever le préfixe data:image
|
|
$capturedFaceId = $this->detectFaceAzure($capturedImageData, $endpoint, $apiKey);
|
|
|
|
if (!$capturedFaceId) {
|
|
return [
|
|
'match' => false,
|
|
'confidence' => 0,
|
|
'error' => 'Aucun visage détecté dans votre photo'
|
|
];
|
|
}
|
|
|
|
// 3. Comparer les deux visages
|
|
$verifyUrl = $endpoint . '/face/v1.0/verify';
|
|
|
|
$data = [
|
|
'faceId1' => $referenceFaceId,
|
|
'faceId2' => $capturedFaceId
|
|
];
|
|
|
|
$ch = curl_init($verifyUrl);
|
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
|
curl_setopt($ch, CURLOPT_POST, true);
|
|
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
|
|
curl_setopt($ch, CURLOPT_HTTPHEADER, [
|
|
'Content-Type: application/json',
|
|
'Ocp-Apim-Subscription-Key: ' . $apiKey
|
|
]);
|
|
|
|
$response = curl_exec($ch);
|
|
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
|
curl_close($ch);
|
|
|
|
if ($httpCode !== 200) {
|
|
throw new Exception("Azure API error: " . $response);
|
|
}
|
|
|
|
$result = json_decode($response, true);
|
|
|
|
return [
|
|
'match' => $result['isIdentical'],
|
|
'confidence' => round($result['confidence'] * 100, 2),
|
|
'error' => null
|
|
];
|
|
|
|
} catch (Exception $e) {
|
|
error_log("Erreur Azure Face API: " . $e->getMessage());
|
|
return [
|
|
'match' => false,
|
|
'confidence' => 0,
|
|
'error' => 'Erreur lors de la vérification faciale'
|
|
];
|
|
}
|
|
}
|
|
*/
|
|
|
|
/**
|
|
* Détecte un visage avec Azure Face API et retourne le faceId
|
|
*/
|
|
|
|
/*
|
|
private function detectFaceAzure($imageBase64, $endpoint, $apiKey) {
|
|
$detectUrl = $endpoint . '/face/v1.0/detect?returnFaceId=true';
|
|
|
|
$ch = curl_init($detectUrl);
|
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
|
curl_setopt($ch, CURLOPT_POST, true);
|
|
curl_setopt($ch, CURLOPT_POSTFIELDS, base64_decode($imageBase64));
|
|
curl_setopt($ch, CURLOPT_HTTPHEADER, [
|
|
'Content-Type: application/octet-stream',
|
|
'Ocp-Apim-Subscription-Key: ' . $apiKey
|
|
]);
|
|
|
|
$response = curl_exec($ch);
|
|
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
|
curl_close($ch);
|
|
|
|
if ($httpCode !== 200) {
|
|
return null;
|
|
}
|
|
|
|
$faces = json_decode($response, true);
|
|
|
|
if (empty($faces)) {
|
|
return null;
|
|
}
|
|
|
|
return $faces[0]['faceId'];
|
|
}
|
|
*/
|
|
|
|
/**
|
|
* Comparaison avec AWS Rekognition (Alternative)
|
|
*/
|
|
|
|
/*
|
|
private function compareWithAWSRekognition($referenceImagePath, $capturedImageBase64) {
|
|
require_once 'vendor/autoload.php'; // AWS SDK
|
|
|
|
try {
|
|
$rekognitionClient = new Aws\Rekognition\RekognitionClient([
|
|
'version' => 'latest',
|
|
'region' => AWS_REGION,
|
|
'credentials' => [
|
|
'key' => AWS_ACCESS_KEY_ID,
|
|
'secret' => AWS_SECRET_ACCESS_KEY
|
|
]
|
|
]);
|
|
|
|
$referenceImageData = file_get_contents($referenceImagePath);
|
|
$capturedImageData = base64_decode(explode(',', $capturedImageBase64)[1]);
|
|
|
|
$result = $rekognitionClient->compareFaces([
|
|
'SourceImage' => ['Bytes' => $referenceImageData],
|
|
'TargetImage' => ['Bytes' => $capturedImageData],
|
|
'SimilarityThreshold' => 80
|
|
]);
|
|
|
|
if (empty($result['FaceMatches'])) {
|
|
return [
|
|
'match' => false,
|
|
'confidence' => 0,
|
|
'error' => 'Les visages ne correspondent pas'
|
|
];
|
|
}
|
|
|
|
$similarity = $result['FaceMatches'][0]['Similarity'];
|
|
|
|
return [
|
|
'match' => $similarity >= 80,
|
|
'confidence' => round($similarity, 2),
|
|
'error' => null
|
|
];
|
|
|
|
} catch (Exception $e) {
|
|
error_log("Erreur AWS Rekognition: " . $e->getMessage());
|
|
return [
|
|
'match' => false,
|
|
'confidence' => 0,
|
|
'error' => 'Erreur lors de la vérification faciale'
|
|
];
|
|
}
|
|
}
|
|
*/
|
|
|
|
/**
|
|
* Enregistre la photo capturée
|
|
*/
|
|
/*
|
|
private function saveCapturedImage($assureId, $imageBase64) {
|
|
$uploadDir = 'uploads/facial_verification/';
|
|
|
|
if (!file_exists($uploadDir)) {
|
|
mkdir($uploadDir, 0755, true);
|
|
}
|
|
|
|
$imageData = explode(',', $imageBase64)[1];
|
|
$imageData = base64_decode($imageData);
|
|
|
|
$filename = $uploadDir . $assureId . '_' . time() . '.jpg';
|
|
file_put_contents($filename, $imageData);
|
|
|
|
return $filename;
|
|
}
|
|
*/
|
|
|
|
/**
|
|
* Met à jour le statut de la vérification
|
|
*/
|
|
|
|
/*
|
|
private function updateVerificationStatus($token, $status, $matchResult = null, $capturedPhotoPath = null) {
|
|
$sql = "UPDATE facial_verification_requests
|
|
SET status = ?,
|
|
verified_at = NOW(),
|
|
match_confidence = ?,
|
|
captured_photo_path = ?,
|
|
attempts = attempts + 1
|
|
WHERE verification_token = ?";
|
|
|
|
$stmt = $this->db->prepare($sql);
|
|
$stmt->execute([
|
|
$status,
|
|
$matchResult ? $matchResult['confidence'] : null,
|
|
$capturedPhotoPath,
|
|
$token
|
|
]);
|
|
}
|
|
*/
|
|
|
|
/**
|
|
* Crée une session d'autorisation pour l'accès aux prestations
|
|
*/
|
|
|
|
/*
|
|
private function createAuthorizationSession($assureId, $verificationRequestId) {
|
|
$sessionToken = bin2hex(random_bytes(32));
|
|
$expiresAt = date('Y-m-d H:i:s', time() + 3600); // 1 heure
|
|
|
|
$sql = "INSERT INTO prestation_authorization_sessions
|
|
(assure_id, verification_request_id, session_token, expires_at, status)
|
|
VALUES (?, ?, ?, ?, 'active')";
|
|
|
|
$stmt = $this->db->prepare($sql);
|
|
$stmt->execute([$assureId, $verificationRequestId, $sessionToken, $expiresAt]);
|
|
|
|
return $sessionToken;
|
|
}
|
|
*/
|
|
|
|
/**
|
|
* Vérifie le visage capturé
|
|
*/
|
|
|
|
/*
|
|
public function verifyFace($token, $capturedImageBase64) {
|
|
try {
|
|
// 1. Récupérer les infos de la demande
|
|
$sql = "SELECT vr.*, a.photo_reference_path, a.id as assure_id
|
|
FROM facial_verification_requests vr
|
|
JOIN assures a ON vr.assure_id = a.id
|
|
WHERE vr.verification_token = ?
|
|
AND vr.status = 'pending'
|
|
AND vr.expires_at > NOW()";
|
|
|
|
$stmt = $this->db->prepare($sql);
|
|
$stmt->execute([$token]);
|
|
$request = $stmt->fetch(PDO::FETCH_ASSOC);
|
|
|
|
if (!$request) {
|
|
return [
|
|
'success' => false,
|
|
'match' => false,
|
|
'message' => 'Demande expirée ou invalide'
|
|
];
|
|
}
|
|
|
|
// 2. Vérifier le nombre de tentatives
|
|
if ($request['attempts'] >= $this->maxAttempts) {
|
|
$this->updateVerificationStatus($token, 'failed');
|
|
return [
|
|
'success' => false,
|
|
'match' => false,
|
|
'message' => 'Nombre maximum de tentatives atteint'
|
|
];
|
|
}
|
|
|
|
// 3. Enregistrer la photo capturée
|
|
$capturedPhotoPath = $this->saveCapturedImage($request['assure_id'], $capturedImageBase64);
|
|
|
|
// 4. Comparer les visages
|
|
$comparisonResult = $this->compareFaces(
|
|
$request['photo_reference_path'],
|
|
$capturedImageBase64
|
|
);
|
|
|
|
if ($comparisonResult['error']) {
|
|
$this->updateVerificationStatus($token, 'error', $comparisonResult, $capturedPhotoPath);
|
|
return [
|
|
'success' => false,
|
|
'match' => false,
|
|
'message' => $comparisonResult['error'],
|
|
'attempts_remaining' => $this->maxAttempts - ($request['attempts'] + 1)
|
|
];
|
|
}
|
|
|
|
// 5. Seuil de confiance minimum (ex: 80%)
|
|
$confidenceThreshold = 80;
|
|
$isMatch = $comparisonResult['match'] && $comparisonResult['confidence'] >= $confidenceThreshold;
|
|
|
|
if ($isMatch) {
|
|
// Succès: créer une session d'autorisation
|
|
$this->updateVerificationStatus($token, 'verified', $comparisonResult, $capturedPhotoPath);
|
|
$sessionToken = $this->createAuthorizationSession($request['assure_id'], $request['id']);
|
|
|
|
return [
|
|
'success' => true,
|
|
'match' => true,
|
|
'confidence' => $comparisonResult['confidence'],
|
|
'message' => 'Identité vérifiée avec succès',
|
|
'session_token' => $sessionToken,
|
|
'redirect_url' => 'saisie_prestations.php?token=' . $sessionToken
|
|
];
|
|
} else {
|
|
// Échec de correspondance
|
|
$attemptsRemaining = $this->maxAttempts - ($request['attempts'] + 1);
|
|
|
|
if ($attemptsRemaining > 0) {
|
|
$this->updateVerificationStatus($token, 'pending', $comparisonResult, $capturedPhotoPath);
|
|
return [
|
|
'success' => false,
|
|
'match' => false,
|
|
'confidence' => $comparisonResult['confidence'],
|
|
'message' => 'Votre visage ne correspond pas',
|
|
'attempts_remaining' => $attemptsRemaining
|
|
];
|
|
} else {
|
|
$this->updateVerificationStatus($token, 'failed', $comparisonResult, $capturedPhotoPath);
|
|
return [
|
|
'success' => false,
|
|
'match' => false,
|
|
'confidence' => $comparisonResult['confidence'],
|
|
'message' => 'Vérification échouée. Nombre maximum de tentatives atteint.',
|
|
'attempts_remaining' => 0
|
|
];
|
|
}
|
|
}
|
|
|
|
} catch (Exception $e) {
|
|
error_log("Erreur verifyFace: " . $e->getMessage());
|
|
return [
|
|
'success' => false,
|
|
'match' => false,
|
|
'message' => 'Erreur lors de la vérification: ' . $e->getMessage()
|
|
];
|
|
}
|
|
}
|
|
*/
|
|
}
|
|
|
|
// Traiter la requête
|
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|
$input = json_decode(file_get_contents('php://input'), true);
|
|
$action = $input['action'] ?? null;
|
|
|
|
// $api = new FacialVerificationAPI($db->getConnection());
|
|
$api = new FacialVerificationAPI();
|
|
|
|
// var_dump($api);
|
|
// exit;
|
|
|
|
switch ($action) {
|
|
case 'validate_token':
|
|
$token = $input['token'] ?? null;
|
|
if (!$token) {
|
|
echo json_encode(['success' => false, 'message' => 'Token requis']);
|
|
exit;
|
|
}
|
|
echo json_encode($api->validateToken($token));
|
|
break;
|
|
|
|
case 'verify_face':
|
|
$token = $input['token'] ?? null;
|
|
$image = $input['image'] ?? null;
|
|
|
|
if (!$token || !$image) {
|
|
echo json_encode(['success' => false, 'message' => 'Token et image requis']);
|
|
exit;
|
|
}
|
|
echo json_encode($api->verifyFace($token, $image));
|
|
break;
|
|
|
|
default:
|
|
echo json_encode(['success' => false, 'message' => 'Action invalide']);
|
|
}
|
|
} else {
|
|
echo json_encode(['success' => false, 'message' => 'Méthode non autorisée']);
|
|
}
|