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.