Skip to content

Commit b19ff34

Browse files
committed
[FIX] Alias listing and SSO.
1 parent 842ee62 commit b19ff34

File tree

3 files changed

+257
-185
lines changed

3 files changed

+257
-185
lines changed

functions/sso.php

Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
<?php
2+
/**
3+
* SSO Functions for sMultisite
4+
*
5+
* This file contains SSO-related functions that are loaded only when needed.
6+
* Functions are defined here to avoid redefining them on every plugin call.
7+
*/
8+
9+
/**
10+
* Base64url-safe encode.
11+
*
12+
* @param string $s Raw bytes
13+
* @return string Base64url string without '='
14+
*/
15+
if (!function_exists('ms_b64u')) {
16+
function ms_b64u(string $s): string { return rtrim(strtr(base64_encode($s), '+/', '-_'), '='); }
17+
}
18+
19+
/**
20+
* JSON → base64url-safe encode.
21+
*
22+
* @param array $a Data to encode as JSON
23+
* @return string Base64url-encoded JSON
24+
*/
25+
if (!function_exists('ms_b64u_json')) {
26+
function ms_b64u_json(array $a): string { return ms_b64u(json_encode($a, JSON_UNESCAPED_SLASHES)); }
27+
}
28+
29+
/**
30+
* Obtain a shared secret used for signing tokens (HS256).
31+
*
32+
* Order of precedence:
33+
* 1) SMULTI_SSO_SECRET from environment (recommended: same across domains).
34+
* 2) If absent — read/write core/storage/ms_sso/secret.key (shared FS).
35+
* 3) Derive the final key by HMAC with SESSION_COOKIE_NAME as "info"/salt.
36+
*
37+
* @return string Raw bytes (string) to be used as HMAC key.
38+
*/
39+
if (!function_exists('ms_sso_secret')) {
40+
function ms_sso_secret(): string {
41+
static $sec;
42+
if ($sec) return $sec;
43+
44+
$base = getenv('SMULTI_SSO_SECRET');
45+
if (!$base || strlen($base) < 32) {
46+
$dir = rtrim(EVO_CORE_PATH ?? __DIR__, '/') . '/storage/ms_sso';
47+
$file = $dir . '/secret.key';
48+
if (!is_dir($dir)) @mkdir($dir, 0775, true);
49+
if (is_file($file)) {
50+
$base = trim((string)@file_get_contents($file));
51+
} else {
52+
$base = bin2hex(random_bytes(32));
53+
@file_put_contents($file, $base, LOCK_EX);
54+
}
55+
}
56+
57+
$cookieName = defined('SESSION_COOKIE_NAME') ? SESSION_COOKIE_NAME : session_name();
58+
// HKDF-like derivation: bind to cookie name (makes cross-install reuse safer).
59+
$derived = hash_hmac('sha256', $cookieName, $base, true);
60+
return $sec = $derived;
61+
}
62+
}
63+
64+
/**
65+
* Create a signed token (JWT-like, HS256).
66+
*
67+
* @param array $claims Custom fields (e.g., mode, sid, host)
68+
* @param int $ttl Token TTL (seconds)
69+
* @return string JWT string "header.payload.signature"
70+
*/
71+
if (!function_exists('ms_sso_token_make')) {
72+
function ms_sso_token_make(array $claims, int $ttl = 180): string {
73+
$now = time();
74+
$payload = $claims + ['iat' => $now, 'nbf' => $now - 5, 'exp' => $now + $ttl];
75+
$head = ['alg' => 'HS256', 'typ' => 'JWT'];
76+
$seg = ms_b64u_json($head) . '.' . ms_b64u_json($payload);
77+
$sig = ms_b64u(hash_hmac('sha256', $seg, ms_sso_secret(), true));
78+
return $seg . '.' . $sig;
79+
}
80+
}
81+
82+
/**
83+
* Parse and validate token.
84+
*
85+
* @param string $jwt Incoming JWT
86+
* @return array|null Payload if valid; null otherwise
87+
*/
88+
if (!function_exists('ms_sso_token_parse')) {
89+
function ms_sso_token_parse(string $jwt): ?array {
90+
if (!preg_match('~^[A-Za-z0-9\-_]+\.[A-Za-z0-9\-_]+\.[A-Za-z0-9\-_]+$~', $jwt)) return null;
91+
[$h, $p, $s] = explode('.', $jwt, 3);
92+
$calc = ms_b64u(hash_hmac('sha256', "$h.$p", ms_sso_secret(), true));
93+
if (!hash_equals($calc, $s)) return null;
94+
$payload = json_decode(base64_decode(strtr($p, '-_', '+/')), true);
95+
if (!$payload || !is_array($payload)) return null;
96+
$now = time();
97+
if (isset($payload['nbf']) && $payload['nbf'] > $now) return null;
98+
if (isset($payload['exp']) && $payload['exp'] < $now) return null;
99+
return $payload;
100+
}
101+
}
102+
103+
/**
104+
* Directory for SSO "run plans".
105+
*
106+
* @return string Absolute path
107+
*/
108+
if (!function_exists('ms_run_store_dir')) {
109+
function ms_run_store_dir(): string {
110+
$base = rtrim(EVO_CORE_PATH ?? __DIR__, '/') . '/storage/ms_sso/runs';
111+
if (!is_dir($base)) @mkdir($base, 0775, true);
112+
return $base;
113+
}
114+
}
115+
116+
/**
117+
* Persist run plan with TTL.
118+
*
119+
* Structure: ['home' => host, 'steps' => [['host' => ..., 'code' => ...], ... ]]
120+
*
121+
* @param string $id Run identifier
122+
* @param array $payload Run payload
123+
* @param int $ttl Lifetime in seconds
124+
* @return void
125+
*/
126+
if (!function_exists('ms_run_put')) {
127+
function ms_run_put(string $id, array $payload, int $ttl = 300): void {
128+
$path = ms_run_store_dir() . '/' . preg_replace('~[^A-Za-z0-9_\-]~', '', $id) . '.json';
129+
$data = ['exp' => time() + $ttl, 'data' => $payload];
130+
file_put_contents($path, json_encode($data, JSON_UNESCAPED_SLASHES), LOCK_EX);
131+
}
132+
}
133+
134+
/**
135+
* Get run plan or null if missing/expired.
136+
*
137+
* @param string $id Run identifier
138+
* @return array|null
139+
*/
140+
if (!function_exists('ms_run_get')) {
141+
function ms_run_get(string $id): ?array {
142+
$path = ms_run_store_dir() . '/' . preg_replace('~[^A-Za-z0-9_\-]~', '', $id) . '.json';
143+
if (!is_file($path)) return null;
144+
$raw = @file_get_contents($path);
145+
$obj = $raw ? json_decode($raw, true) : null;
146+
if (!$obj || empty($obj['exp']) || $obj['exp'] < time()) { @unlink($path); return null; }
147+
return $obj['data'] ?? null;
148+
}
149+
}
150+
151+
/**
152+
* Touch/extend TTL for existing run.
153+
*
154+
* @param string $id Run identifier
155+
* @param int $ttl New TTL
156+
* @return void
157+
*/
158+
if (!function_exists('ms_run_touch')) {
159+
function ms_run_touch(string $id, int $ttl = 300): void {
160+
$path = ms_run_store_dir() . '/' . preg_replace('~[^A-Za-z0-9_\-]~', '', $id) . '.json';
161+
if (!is_file($path)) return;
162+
$raw = @file_get_contents($path);
163+
$obj = $raw ? json_decode($raw, true) : null;
164+
if (!$obj) return;
165+
$obj['exp'] = time() + $ttl;
166+
file_put_contents($path, json_encode($obj, JSON_UNESCAPED_SLASHES), LOCK_EX);
167+
}
168+
}
169+
170+
/**
171+
* Delete run plan after completion.
172+
*
173+
* @param string $id Run identifier
174+
* @return void
175+
*/
176+
if (!function_exists('ms_run_del')) {
177+
function ms_run_del(string $id): void {
178+
$path = ms_run_store_dir() . '/' . preg_replace('~[^A-Za-z0-9_\-]~', '', $id) . '.json';
179+
@unlink($path);
180+
}
181+
}
182+
183+
/**
184+
* Load SSO functions if not already loaded
185+
*/
186+
if (!function_exists('ms_sso_load_functions')) {
187+
function ms_sso_load_functions(): void {
188+
static $loaded = false;
189+
if (!$loaded) {
190+
$ssoFile = __DIR__ . '/sso.php';
191+
if (file_exists($ssoFile)) {
192+
require_once $ssoFile;
193+
$loaded = true;
194+
}
195+
}
196+
}
197+
}

0 commit comments

Comments
 (0)