<?php

use Firebase\JWT\JWK;
use Firebase\JWT\JWT;

require_once __DIR__.'/vendor/autoload.php'; // For Firebase JWT
require_once __DIR__.'/../api/current/rcc_mysql.php';
require_once __DIR__.'/../api/current/login_db.php';
require_once __DIR__.'/../api/current/rcc_api.php';

const AZP_CONSTANT = '99045fe1-7639-4a75-9d4a-577b6ca3810f'; // constant from entra ID documents 99045fe1-7639-4a75-9d4a-577b6ca3810f

header('Content-Type: application/json');
if (($_SERVER['REQUEST_METHOD'] ?? '') !== 'POST') 
{
    http_response_code(405); 
    echo json_encode(['error'=>'method_not_allowed']); 
    exit;
}

// bearer token
$jwt = get_bearer_token();
if (!$jwt)
{
    http_response_code(401); 
    echo json_encode(['error' => 'missing_bearer']); 
    exit;
}

// grab payload and check for issuer data
[$header64Encoded, $payload64Encoded] = explode('.', $jwt); // seperate out the 3 parts of the jwt
$payload = json_decode(base64_decode(strtr($payload64Encoded, '-_', '+/')), true) ?: []; // base64 decode the payload to get the data
$issuer = rtrim((string)($payload['iss'] ?? ''), '/'); // issuer is our tenant id url
$audience = (string)($payload['aud'] ?? ''); // audience is our app id
$azp = (string)($payload['azp'] ?? ($payload['appid'] ?? '')); 

// Extract the GUID from the issuer URL (workforce or external)
$tenantId = null;
if (preg_match('~^https://login\.microsoftonline\.com/([a-f0-9-]{36})/v2\.0$~i', $issuer, $matches)) 
{
    $tenantId = $matches[1];
} 
elseif (preg_match('~^https://[^/]+\.ciamlogin\.com/([a-f0-9-]{36})/v2\.0$~i', $issuer, $matches)) 
{
    $tenantId = $matches[1];
}

if($tenantId === null)
{
    http_response_code(401); 
    echo json_encode(['error' => 'bad_issuer_format','iss'=>$issuer]); 
    exit;
}

// grab jwks for this issuer
$oidc = $issuer . '/.well-known/openid-configuration';
$oidcDoc = @json_decode(@file_get_contents($oidc), true);
$jwksUri = $oidcDoc['jwks_uri'] ?? null;
if (!$jwksUri) 
{
    http_response_code(401); 
    echo json_encode(['error'=>'no_jwks']); 
    exit; 
}

$cacheFile = sys_get_temp_dir() . '/jwks_' . md5($jwksUri) . '.json';
if (!file_exists($cacheFile) || filemtime($cacheFile) < time() - 60) 
{
    $jwksRaw = @file_get_contents($jwksUri);
    if ($jwksRaw) 
    {
      file_put_contents($cacheFile, $jwksRaw);
    }
}
$jwksData = json_decode(file_get_contents($cacheFile), true);

// Azure only sends the 'alg' key with the first key, so add it to any others that are missing it
if (!isset($jwksData['keys']) || count($jwksData['keys']) === 0) 
{
    http_response_code(401);
    echo json_encode(['error' => 'no_valid_jwk_keys']);
    exit;
}
else 
{
    foreach ($jwksData['keys'] as &$key) 
    {
        if (!isset($key['alg'])) 
        {
            $key['alg'] = 'RS256';
        }
    }
    unset($key); // break reference
}

$keys = JWK::parseKeySet($jwksData);

// check signature & expiration
try 
{ 
    $decoded = JWT::decode($jwt, $keys); 
}
catch (Throwable $error) 
{ 
    http_response_code(401); 
    echo json_encode(['error'=>'bad_sig','detail'=>$error->getMessage()]); 
    exit; 
}

// must be Entra's auth events platform
if ($azp !== AZP_CONSTANT) // constant from entra ID documents
{ 
    http_response_code(401); 
    echo json_encode(['error'=>'bad_caller','azp'=>$azp]); 
    exit;
}

// Connect to DB
$sql = rcc_mysql_init();
$isvname = rcc_get_isv_by_azure_tenant_and_app_id($sql, $tenantId, $audience);

// this verifies the app id, it comes through the token as the aud/audience value
if ($isvname === null)
{
    http_response_code(401);
    echo json_encode(['error'=>'bad_aud','aud'=>$audience]);
    exit;
}

/*
 *  $sql above is the CENTRAL connection (needed for the
 *  jwt_configurations lookup, which has no tenant to scope to yet).
 *  $isvname is already implicitly validated as a real, configured
 *  tenant by that lookup succeeding, so no separate existence check is
 *  needed here (unlike the now-retired login.php, which took an
 *  unvalidated isvname straight from the client - worth checking
 *  whether its planned authorize.php replacement has the same concern).
 *  Reconnect to that tenant's own database
 *  for everything below - see rcc_mysql_init()'s
 *  $GLOBALS['_rcc_tenant_id'] fallback.
 */
$GLOBALS['_rcc_tenant_id'] = $isvname;
$sql = rcc_mysql_init();

// everything verified, now parse request
$body = json_decode(file_get_contents('php://input'), true) ?: [];
$user = $body['data']['authenticationContext']['user'] ?? [];
$email  = $user['mail'] ?? null;   
$customerLines = '';

if(!$email)
{
    http_response_code(401); 
    echo json_encode(['error'=>'User_not_provisioned_email_not_found','user'=>$user]); 
    exit;
}

$email = $sql->real_escape_string($email); 
$select = rcc_web_select_init();
$select['fmt'] = "AND email = '".$email."'";
$users = rcc_mysql_get_sso_users($sql, $isvname, $select, "");
if ($users) 
{
    $user_row = $users->fetch_assoc();
    $customerLineResult = rcc_mysql_get_assigned_servers_customer_lines($sql, $user_row['user_id'], $isvname);
    if($customerLineResult)
    {
        while ($row = $customerLineResult->fetch_assoc())
        {
            $customerLines .= "CUSTOMER ".$row['server_id']." isv=".$isvname." server=".$row['servername']." port=443 password=".$row['pw'].";";
        }
        $customerLines = rtrim($customerLines, ';'); // Remove trailing semicolon
    }
} 
$sql->close();

$claims = [
    'rlm_licenses' => $customerLines,
];

// Respond with Entra schema
echo json_encode([
  'data' => [
    '@odata.type' => 'microsoft.graph.onTokenIssuanceStartResponseData',
    'actions' => [[
      '@odata.type' => 'microsoft.graph.tokenIssuanceStart.provideClaimsForToken',
      'claims' => $claims
    ]]
  ]
], JSON_UNESCAPED_SLASHES);
