Files
Varaverk/Plugin/unraid/include/auth.php
T

286 lines
13 KiB
PHP

<?php
require_once __DIR__ . '/config.php';
// ── Config ────────────────────────────────────────────────────────────────────
function vv_auth_conf(): array {
$v = vv_conf_vars();
$host = strtoupper(vv_detect_host());
return [
'npm_url' => rtrim($v["{$host}_NPM_URL"] ?? 'http://localhost:81', '/'),
'npm_user' => $v["{$host}_NPM_USER"] ?? '',
'npm_pass' => $v["{$host}_NPM_PASS"] ?? '',
'lldap_url' => rtrim($v["{$host}_LLDAP_URL"] ?? 'http://localhost:17170', '/'),
'lldap_user' => $v["{$host}_LLDAP_USER"] ?? '',
'lldap_pass' => $v["{$host}_LLDAP_PASS"] ?? '',
'authelia_config' => $v["{$host}_AUTHELIA_CONFIG"] ?? '/mnt/user/appdata-Fallback/Critical-Data/Authelia/configuration.yml',
'authelia_container' => $v["{$host}_AUTHELIA_CONTAINER"] ?? 'Authelia',
'is_owner' => vv_is_owner(),
];
}
// ── NPM ───────────────────────────────────────────────────────────────────────
function vv_npm_token(): string {
if (!session_id()) session_start();
$conf = vv_auth_conf();
$cached = $_SESSION['vv_npm_token'] ?? '';
$expiry = $_SESSION['vv_npm_token_exp'] ?? 0;
if ($cached && time() < $expiry) return $cached;
$resp = vv_npm_raw('POST', '/api/tokens', [
'identity' => $conf['npm_user'],
'secret' => $conf['npm_pass'],
'expiry' => '1d',
], '', $conf);
$token = $resp['token'] ?? '';
if ($token) {
$_SESSION['vv_npm_token'] = $token;
$_SESSION['vv_npm_token_exp'] = time() + 82800;
}
return $token;
}
function vv_npm_raw(string $method, string $path, array $data, string $token, array $conf = []): array {
if (!$conf) $conf = vv_auth_conf();
$url = $conf['npm_url'] . $path;
$headers = ['Content-Type: application/json', 'Accept: application/json'];
if ($token) $headers[] = 'Authorization: Bearer ' . $token;
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 10,
CURLOPT_HTTPHEADER => $headers,
CURLOPT_CUSTOMREQUEST => $method,
]);
if ($data && in_array($method, ['POST', 'PUT'], true))
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
$body = curl_exec($ch);
curl_close($ch);
return json_decode($body ?: '{}', true) ?: [];
}
function vv_npm_req(string $method, string $path, array $data = []): array {
$token = vv_npm_token();
if (!$token) return ['_err' => 'NPM auth failed — check credentials in host conf'];
return vv_npm_raw($method, $path, $data, $token);
}
function vv_npm_list_proxies(): array {
$list = vv_npm_req('GET', '/api/nginx/proxy-hosts?expand=certificate');
if (!is_array($list) || isset($list['_err']))
return ['ok' => false, 'error' => $list['_err'] ?? 'Invalid response from NPM'];
return ['ok' => true, 'proxies' => $list];
}
function vv_npm_list_certs(): array {
$list = vv_npm_req('GET', '/api/nginx/certificates');
return is_array($list) ? $list : [];
}
function vv_npm_create_proxy(array $data): array {
$r = vv_npm_req('POST', '/api/nginx/proxy-hosts', $data);
return isset($r['id']) ? ['ok' => true, 'proxy' => $r] : ['ok' => false, 'error' => $r['error'] ?? ($r['_err'] ?? 'Create failed')];
}
function vv_npm_update_proxy(int $id, array $data): array {
$r = vv_npm_req('PUT', "/api/nginx/proxy-hosts/$id", $data);
return isset($r['id']) ? ['ok' => true, 'proxy' => $r] : ['ok' => false, 'error' => $r['error'] ?? ($r['_err'] ?? 'Update failed')];
}
function vv_npm_delete_proxy(int $id): array {
vv_npm_req('DELETE', "/api/nginx/proxy-hosts/$id");
return ['ok' => true];
}
function vv_npm_toggle_proxy(int $id, bool $enabled): array {
vv_npm_req('POST', "/api/nginx/proxy-hosts/$id/" . ($enabled ? 'enable' : 'disable'));
return ['ok' => true];
}
// ── lldap ─────────────────────────────────────────────────────────────────────
function vv_lldap_token(): string {
if (!session_id()) session_start();
$conf = vv_auth_conf();
$cached = $_SESSION['vv_lldap_token'] ?? '';
$expiry = $_SESSION['vv_lldap_token_exp'] ?? 0;
if ($cached && time() < $expiry) return $cached;
$ch = curl_init($conf['lldap_url'] . '/auth/simple/login');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 10,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode(['username' => $conf['lldap_user'], 'password' => $conf['lldap_pass']]),
CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
]);
$body = curl_exec($ch);
curl_close($ch);
$resp = json_decode($body ?: '{}', true) ?: [];
$token = $resp['token'] ?? '';
if ($token) {
$_SESSION['vv_lldap_token'] = $token;
$_SESSION['vv_lldap_token_exp'] = time() + 3500;
}
return $token;
}
function vv_lldap_gql(string $query, array $variables = []): array {
$conf = vv_auth_conf();
$token = vv_lldap_token();
if (!$token) return ['errors' => [['message' => 'lldap auth failed — check credentials']]];
$ch = curl_init($conf['lldap_url'] . '/api/graphql');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 10,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode(['query' => $query, 'variables' => $variables]),
CURLOPT_HTTPHEADER => ['Content-Type: application/json', 'Authorization: Bearer ' . $token],
]);
$body = curl_exec($ch);
curl_close($ch);
return json_decode($body ?: '{}', true) ?: [];
}
function vv_lldap_list_users(): array {
$r = vv_lldap_gql('query { listUsers { id displayName email creationDate groups { id displayName } } }');
if (isset($r['errors'])) return ['ok' => false, 'error' => $r['errors'][0]['message'] ?? 'Query failed'];
return ['ok' => true, 'users' => $r['data']['listUsers'] ?? []];
}
function vv_lldap_list_groups(): array {
$r = vv_lldap_gql('query { listGroups { id displayName users { id displayName } } }');
if (isset($r['errors'])) return ['ok' => false, 'error' => $r['errors'][0]['message'] ?? 'Query failed'];
return ['ok' => true, 'groups' => $r['data']['listGroups'] ?? []];
}
function vv_lldap_create_user(string $id, string $email, string $displayName, string $password): array {
$r = vv_lldap_gql(
'mutation CreateUser($user: CreateUserInput!) { createUser(user: $user) { id displayName email } }',
['user' => ['id' => $id, 'email' => $email, 'displayName' => $displayName]]
);
if (isset($r['errors'])) return ['ok' => false, 'error' => $r['errors'][0]['message'] ?? 'Create failed'];
if ($password) vv_lldap_set_password($id, $password);
return ['ok' => true, 'user' => $r['data']['createUser'] ?? []];
}
function vv_lldap_update_user(string $id, string $email, string $displayName): array {
$r = vv_lldap_gql(
'mutation UpdateUser($user: UpdateUserInput!) { updateUser(user: $user) { ok } }',
['user' => ['id' => $id, 'email' => $email, 'displayName' => $displayName]]
);
if (isset($r['errors'])) return ['ok' => false, 'error' => $r['errors'][0]['message'] ?? 'Update failed'];
return ['ok' => true];
}
function vv_lldap_delete_user(string $id): array {
$r = vv_lldap_gql(
'mutation DeleteUser($userId: String!) { deleteUser(userId: $userId) { ok } }',
['userId' => $id]
);
if (isset($r['errors'])) return ['ok' => false, 'error' => $r['errors'][0]['message'] ?? 'Delete failed'];
return ['ok' => true];
}
function vv_lldap_set_password(string $userId, string $password): array {
$r = vv_lldap_gql(
'mutation ChangePassword($userId: String!, $password: String!) { changeUserPassword(userId: $userId, password: $password) }',
['userId' => $userId, 'password' => $password]
);
if (isset($r['errors'])) return ['ok' => false, 'error' => $r['errors'][0]['message'] ?? 'Password change failed'];
return ['ok' => true];
}
function vv_lldap_create_group(string $name): array {
$r = vv_lldap_gql(
'mutation CreateGroup($name: String!) { createGroup(name: $name) { id displayName } }',
['name' => $name]
);
if (isset($r['errors'])) return ['ok' => false, 'error' => $r['errors'][0]['message'] ?? 'Create failed'];
return ['ok' => true, 'group' => $r['data']['createGroup'] ?? []];
}
function vv_lldap_delete_group(int $id): array {
$r = vv_lldap_gql(
'mutation DeleteGroup($groupId: Int!) { deleteGroup(groupId: $groupId) { ok } }',
['groupId' => $id]
);
if (isset($r['errors'])) return ['ok' => false, 'error' => $r['errors'][0]['message'] ?? 'Delete failed'];
return ['ok' => true];
}
function vv_lldap_add_to_group(string $userId, int $groupId): array {
$r = vv_lldap_gql(
'mutation AddUserToGroup($userId: String!, $groupId: Int!) { addUserToGroup(userId: $userId, groupId: $groupId) { ok } }',
['userId' => $userId, 'groupId' => $groupId]
);
if (isset($r['errors'])) return ['ok' => false, 'error' => $r['errors'][0]['message'] ?? 'Failed'];
return ['ok' => true];
}
function vv_lldap_remove_from_group(string $userId, int $groupId): array {
$r = vv_lldap_gql(
'mutation RemoveUserFromGroup($userId: String!, $groupId: Int!) { removeUserFromGroup(userId: $userId, groupId: $groupId) { ok } }',
['userId' => $userId, 'groupId' => $groupId]
);
if (isset($r['errors'])) return ['ok' => false, 'error' => $r['errors'][0]['message'] ?? 'Failed'];
return ['ok' => true];
}
// ── Authelia ──────────────────────────────────────────────────────────────────
function vv_authelia_read_rules(): array {
$conf = vv_auth_conf();
$file = $conf['authelia_config'];
if (!file_exists($file)) return ['ok' => false, 'error' => 'Config not found: ' . $file];
$py = "import yaml,json,sys\n"
. "d=yaml.safe_load(open(sys.argv[1]))\n"
. "ac=d.get('access_control',{})\n"
. "print(json.dumps({'default_policy':ac.get('default_policy','deny'),'rules':ac.get('rules',[])}))\n";
$tmp = '/tmp/vv_auth_rd_' . getmypid() . '.py';
file_put_contents($tmp, $py);
$out = shell_exec('python3 ' . escapeshellarg($tmp) . ' ' . escapeshellarg($file) . ' 2>/dev/null');
@unlink($tmp);
if (!$out) return ['ok' => false, 'error' => 'Parse failed — python3 with PyYAML required'];
$data = json_decode(trim($out), true);
if (!$data) return ['ok' => false, 'error' => 'Invalid YAML response'];
return ['ok' => true, 'default_policy' => $data['default_policy'], 'rules' => $data['rules']];
}
function vv_authelia_write_rules(array $rules, string $defaultPolicy): array {
$conf = vv_auth_conf();
$file = $conf['authelia_config'];
if (!file_exists($file)) return ['ok' => false, 'error' => 'Config not found: ' . $file];
$acJson = json_encode(['default_policy' => $defaultPolicy, 'rules' => $rules]);
$py = <<<'PYEOF'
import yaml, json, sys, re
config_file = sys.argv[1]
new_ac = json.loads(sys.argv[2])
with open(config_file, 'r') as f:
content = f.read()
new_block = yaml.dump({'access_control': new_ac}, default_flow_style=False, allow_unicode=True, sort_keys=False)
pattern = r'(?ms)^access_control:.*?(?=^[a-zA-Z#]|\Z)'
if re.search(pattern, content):
content = re.sub(pattern, new_block + '\n', content)
else:
content = content.rstrip('\n') + '\n\n' + new_block + '\n'
with open(config_file, 'w') as f:
f.write(content)
print('ok')
PYEOF;
$tmp = '/tmp/vv_auth_wr_' . getmypid() . '.py';
file_put_contents($tmp, $py);
$out = shell_exec('python3 ' . escapeshellarg($tmp) . ' ' . escapeshellarg($file) . ' ' . escapeshellarg($acJson) . ' 2>&1');
@unlink($tmp);
if (trim($out) !== 'ok') return ['ok' => false, 'error' => 'Write failed: ' . trim($out)];
shell_exec('docker restart ' . escapeshellarg($conf['authelia_container']) . ' >/dev/null 2>&1 &');
return ['ok' => true];
}