Documentazione Tecnica SSO LinkedIn per Masticationpedia

Questa pagina descrive la struttura tecnica per integrare il login Single Sign-On (SSO) tramite LinkedIn, con creazione automatica dell’utente su MediaWiki.

Tutti i file sono caricati nella cartella: /oauth/

📁 1. linkedin-login.php

<?php
// linkedin-login.php

$client_id = 'TUO_CLIENT_ID';
$redirect_uri = 'https://staging.masticationpedia.org/oauth/linkedin-callback.php';
$scope = 'openid email profile';
$state = bin2hex(random_bytes(16));

$url = "https://www.linkedin.com/oauth/v2/authorization?response_type=code"
     . "&client_id={$client_id}"
     . "&redirect_uri=" . urlencode($redirect_uri)
     . "&state={$state}"
     . "&scope=" . urlencode($scope);

header("Location: $url");
exit;

Funzione: Genera l'URL di richiesta login e consensi a LinkedIn. Avvia il flusso OAuth2.


📁 2. linkedin-callback.php

<?php
// linkedin-callback.php

ini_set('display_errors', 1);
error_reporting(E_ALL);
$log = __DIR__ . '/linkedin_callback.log';
function log_debug($msg) {
    global $log;
    file_put_contents($log, "[".date('Y-m-d H:i:s')."] $msg\n", FILE_APPEND);
}

if (!isset($_GET['code'], $_GET['state'])) exit('Errore: code/state mancanti');
$code = $_GET['code'];

$token_url = 'https://www.linkedin.com/oauth/v2/accessToken';
$post = [
    'grant_type' => 'authorization_code',
    'code' => $code,
    'redirect_uri' => 'https://staging.masticationpedia.org/oauth/linkedin-callback.php',
    'client_id' => 'TUO_CLIENT_ID',
    'client_secret' => 'TUO_CLIENT_SECRET'
];
$ch = curl_init($token_url);
curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_POSTFIELDS => http_build_query($post),
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_SSL_VERIFYPEER => false
]);
$resp = curl_exec($ch);
curl_close($ch);
$data = json_decode($resp, true);
$token = $data['access_token'] ?? '';
if (!$token) exit('Errore token');

$ch2 = curl_init('https://api.linkedin.com/v2/userinfo');
curl_setopt_array($ch2, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => ["Authorization: Bearer $token"],
    CURLOPT_SSL_VERIFYPEER => false
]);
$userinfo = curl_exec($ch2);
curl_close($ch2);
$user = json_decode($userinfo, true);
$sub = $user['sub'] ?? '';
$name = $user['name'] ?? '';
$email = $user['email'] ?? '';

if (!$sub || !$name) exit('Errore dati incompleti');

$ch3 = curl_init('https://staging.masticationpedia.org/oauth/create_mw_user_direct.php');
curl_setopt_array($ch3, [
    CURLOPT_POST => true,
    CURLOPT_POSTFIELDS => http_build_query([
        'sub' => $sub,
        'name' => $name,
        'email' => $email
    ]),
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_SSL_VERIFYPEER => false
]);
curl_exec($ch3);
curl_close($ch3);

header("Location: https://staging.masticationpedia.org/wiki/Main_Page");
exit;
?>

Funzione: Scambia il code con un token, ottiene i dati da LinkedIn e li invia al file che crea l'utente.


📁 3. create_mw_user_direct.php

<?php
use MediaWiki\MediaWikiServices;

ini_set('display_errors', 1);
error_reporting(E_ALL);
$log = __DIR__ . '/mw_user_creation.log';
function log_debug($m) {
    global $log;
    file_put_contents($log, "[".date('Y-m-d H:i:s')."] $m\n", FILE_APPEND);
}

if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
    http_response_code(405);
    exit('❌ Metodo non consentito.');
}

$sub = $_POST['sub']   ?? 'no-sub';
$name = $_POST['name'] ?? 'Anon';
$email = $_POST['email'] ?? 'noemail@mpedia';

file_put_contents(__DIR__.'/incoming_user_debug.json',
    json_encode(['ts'=>date('c'),'sub'=>$sub,'name'=>$name,'email'=>$email], JSON_PRETTY_PRINT)."\n", FILE_APPEND);

if (!$sub || !$name) {
    log_debug("Missing user data");
    http_response_code(400);
    exit;
}

require_once __DIR__ . '/../includes/WebStart.php';
$services = MediaWikiServices::getInstance();
$userFactory = $services->getUserFactory();
$userLookup = $services->getUserLookup();

$username = substr(preg_replace('/[^A-Za-z0-9]/','_',$name . '_' . substr($sub,0,6)), 0, 50);

$userObj = \User::newFromName($username);
if ($userObj && $userObj->getId() !== 0) {
    log_debug("User exists: $username");
    exit;
}

$userObj = $userFactory->newFromName($username);
$userObj->addToDatabase();
$userObj->setEmail($email);
$userObj->setRealName($name);
$userObj->setToken();
$userObj->saveSettings();

$services->getDBLoadBalancerFactory()->commitMasterChanges(__METHOD__);
$entry = new \ManualLogEntry('newusers','create');
$entry->setPerformer($userObj);
$entry->setTarget($userObj->getUserPage());
$entry->setComment('Created via LinkedIn SSO');
$id = $entry->insert();
$entry->publish($id);

log_debug("User created: $username");
?>

Funzione: Riceve i dati utente da LinkedIn e crea direttamente un account MediaWiki completo, visibile in Special:Users.


✅ Riassunto finale

  • **3 file**: `linkedin-login.php`, `linkedin-callback.php`, `create_mw_user_direct.php`
  • **Cartella consigliata**: `/oauth/`
  • **Log consigliati**:
 * `linkedin_callback.log`
 * `incoming_user_debug.json`
 * `mw_user_creation.log`

🧪 Debug e test

1. Inserisci correttamente i tuoi `client_id` e `client_secret` nei file. 2. Collega il pulsante o link di login LinkedIn alla pagina:

  /oauth/linkedin-login.php

3. Verifica nei log se la creazione dell’utente avviene correttamente.

4. Controlla la pagina Special:Users per vedere se l’utente è stato registrato.


🔐 Sicurezza finale

Dopo la messa in produzione:

  • Rimuovere le righe `ini_set('display_errors', 1)` e `log_debug(...)`
  • Proteggere l'accesso alla cartella `/oauth/` se non strettamente necessario.
  • Salvare backup regolari di `incoming_user_debug.json` e log.

Documentazione tecnica completa - SSO LinkedIn su MediaWiki (Masticationpedia)

🧠 Obiettivo del sistema SSO LinkedIn

Consentire agli utenti di Masticationpedia di accedere direttamente al sito tramite il proprio profilo LinkedIn, senza registrazione manuale. L’utente viene autenticato tramite OAuth2 (OpenID Connect) e, se non esiste già, viene creato automaticamente nel database MediaWiki e visibile su `Special:Users`.

✅ Risultati ottenuti

Al momento attuale (luglio 2025), il sistema ha raggiunto diversi traguardi fondamentali:

  • Il pulsante di login LinkedIn apre correttamente la finestra di autorizzazione.
  • LinkedIn restituisce il `code` e lo scambio con `access_token` funziona perfettamente.
  • I dati dell’utente (sub, nome, email) vengono ricevuti correttamente.
  • L’utente teoricamente viene creato tramite `create_mw_user_direct.php`, con salvataggio previsto su `Special:Users`.
  • I log di debug sono ben strutturati (incoming\_user\_debug.json, mw\_user\_creation.log)

❌ Problema attuale (blocco finale)

Nonostante il flusso OAuth appaia corretto:

1. **L’utente non compare in `Special:Users`**, né risulta registrato in modo persistente da MediaWiki.

2. I file di debug non vengono più scritti (es: `incoming_user_debug.json` è vuoto), anche se fino a poco prima funzionavano.

3. In alcuni tentativi recenti, **LinkedIn ha bloccato il flusso** reindirizzando su una pagina interna di errore: `challenge_global_internal_error`.

🧪 Possibili cause analizzate

🔹 1. LinkedIn blocca il flusso con errore interno ===

  • Dopo numerosi tentativi ravvicinati, LinkedIn ha mostrato CAPTCHA e poi un errore generico con redirect su `/checkpoint/lg/login?errorKey=challenge_global_internal_error`.
  • È possibile che il sistema LinkedIn consideri i tentativi come "anormali" da parte dello stesso IP server.
  • Tuttavia, il login classico sul sito LinkedIn funziona, quindi l'account **non è bannato**.

🔹 2. Il file `create_mw_user_direct.php` non viene nemmeno raggiunto ===

  • Potrebbe essere dovuto a un errore nella `curl_exec` finale nel callback.
  • Oppure a **permessi Apache/PHP** sulla cartella `/oauth/`.
  • Nessuna scrittura su `incoming_user_debug.json` è un forte indizio che l'esecuzione si ferma prima.

🔹 3. La fase di scrittura su MediaWiki fallisce silenziosamente

  • Anche quando tutto sembra completarsi, l’utente non è visibile.
  • MediaWiki potrebbe accettare la richiesta ma non completare il commit nel database.

🧰 Diagnostica svolta

  • Test del singolo script `create_mw_user_direct.php` → funzionante in locale.
  • Uso di `file_put_contents()` in vari punti → successivamente non più visibili.
  • Logging attivo con timestamp → ora assente.
  • Permessi `755` per i file PHP confermati.

📌 Prossimi tentativi suggeriti ==

1. **Verificare i permessi su tutta la cartella `/oauth/`**, specialmente se `www-data` ha accesso in scrittura.

2. **Inserire `var_dump()` o `echo` diagnostici in cima ai file** per vedere se si attivano realmente.

3. **Ripristinare uno script minimo** che scriva sempre qualcosa, anche senza passaggi LinkedIn, per confermare che la `curl` arrivi.

4. **Pulire cache LinkedIn** o aspettare 24-48h dopo l’errore `challenge_global_internal_error`.

5. **Verificare la variabile `client_id` e `client_secret`**: in uno degli ultimi tentativi era errata e ha mandato in confusione il flusso.

🧩 Conclusione provvisoria

Il sistema è quasi completo. La parte più difficile (flusso OAuth, access token, lettura dati utente) è superata con successo.

Resta da risolvere:

  • La corretta chiamata finale a `create_mw_user_direct.php`
  • La scrittura reale nel DB MediaWiki
  • Il logging per capire il vero punto di blocco.

🔐 Nota finale

Si raccomanda di:

Una volta ripristinata la scrittura JSON o il log, potremo eseguire un nuovo ciclo completo e definitivo.

Pagina privata generata da ChatGPT su richiesta del fondatore di Masticationpedia, Gianni Frisardi.

Appendice

<?php
// linkedin-login.php

$client_id = 'TUO_CLIENT_ID';
$redirect_uri = 'https://staging.masticationpedia.org/oauth/linkedin-callback.php';
$scope = 'openid email profile';
$state = bin2hex(random_bytes(16));

$url = "https://www.linkedin.com/oauth/v2/authorization?response_type=code"
     . "&client_id={$client_id}"
     . "&redirect_uri=" . urlencode($redirect_uri)
     . "&state={$state}"
     . "&scope=" . urlencode($scope);

header("Location: $url");
exit;