Yrityskohtainen IP-rajoitus kirjautumiseen
Lisätty allowed_ips kenttä yrityksiin. Tyhjä = ei rajoitusta, muuten vain listatut IP:t/CIDR-alueet pääsevät kirjautumaan. Superadmin ohittaa aina IP-tarkistuksen (backdoor). Tarkistus tehdään login, check_auth ja company_switch -endpointeissa. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
88
api.php
88
api.php
@@ -130,6 +130,37 @@ function getClientIp(): string {
|
||||
return $_SERVER['HTTP_X_FORWARDED_FOR'] ?? $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0';
|
||||
}
|
||||
|
||||
/**
|
||||
* Tarkista onko IP sallittujen listalla.
|
||||
* Tyhjä lista = ei rajoitusta (kaikki sallittu).
|
||||
* Tukee yksittäisiä IP-osoitteita ja CIDR-alueita (esim. 192.168.1.0/24).
|
||||
*/
|
||||
function isIpAllowed(string $ip, string $allowedIps): bool {
|
||||
$allowedIps = trim($allowedIps);
|
||||
if ($allowedIps === '') return true; // ei rajoitusta
|
||||
$entries = preg_split('/[\s,]+/', $allowedIps, -1, PREG_SPLIT_NO_EMPTY);
|
||||
$ipLong = ip2long($ip);
|
||||
if ($ipLong === false) return false;
|
||||
foreach ($entries as $entry) {
|
||||
$entry = trim($entry);
|
||||
if ($entry === '') continue;
|
||||
if (strpos($entry, '/') !== false) {
|
||||
// CIDR-alue (esim. 192.168.1.0/24)
|
||||
[$subnet, $bits] = explode('/', $entry, 2);
|
||||
$bits = (int)$bits;
|
||||
if ($bits < 0 || $bits > 32) continue;
|
||||
$subnetLong = ip2long($subnet);
|
||||
if ($subnetLong === false) continue;
|
||||
$mask = $bits === 0 ? 0 : (~0 << (32 - $bits));
|
||||
if (($ipLong & $mask) === ($subnetLong & $mask)) return true;
|
||||
} else {
|
||||
// Yksittäinen IP
|
||||
if (ip2long($entry) === $ipLong) return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function normalizeAddress(string $addr): string {
|
||||
$addr = strtolower(trim($addr));
|
||||
$addr = preg_replace('/\s+/', ' ', $addr);
|
||||
@@ -1115,6 +1146,28 @@ switch ($action) {
|
||||
echo json_encode(['error' => 'Sinulla ei ole oikeutta kirjautua tälle sivustolle.']);
|
||||
break;
|
||||
}
|
||||
// IP-rajoitus: superadmin ohittaa aina
|
||||
$allCompanies = dbLoadCompanies();
|
||||
if ($u['role'] !== 'superadmin') {
|
||||
$allowedCompanies = [];
|
||||
foreach ($userCompanies as $ucId) {
|
||||
foreach ($allCompanies as $comp) {
|
||||
if ($comp['id'] === $ucId) {
|
||||
if (isIpAllowed($ip, $comp['allowed_ips'] ?? '')) {
|
||||
$allowedCompanies[] = $ucId;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (empty($allowedCompanies)) {
|
||||
dbRecordLoginAttempt($ip);
|
||||
http_response_code(403);
|
||||
echo json_encode(['error' => 'IP-osoitteesi ei ole sallittu.']);
|
||||
break;
|
||||
}
|
||||
$userCompanies = $allowedCompanies;
|
||||
}
|
||||
session_regenerate_id(true);
|
||||
$_SESSION['user_id'] = $u['id'];
|
||||
$_SESSION['username'] = $u['username'];
|
||||
@@ -1128,7 +1181,6 @@ switch ($action) {
|
||||
$_SESSION['company_id'] = !empty($userCompanies) ? $userCompanies[0] : '';
|
||||
}
|
||||
// Hae yritysten nimet
|
||||
$allCompanies = dbLoadCompanies();
|
||||
$companyList = [];
|
||||
foreach ($allCompanies as $comp) {
|
||||
if (in_array($comp['id'], $userCompanies)) {
|
||||
@@ -1167,15 +1219,32 @@ switch ($action) {
|
||||
$_SESSION['company_id'] = !empty($_SESSION['companies']) ? $_SESSION['companies'][0] : '';
|
||||
}
|
||||
}
|
||||
// Hae yritysten nimet
|
||||
// Hae yritysten nimet + IP-rajoitus
|
||||
$userCompanyIds = $_SESSION['companies'] ?? [];
|
||||
$allCompanies = dbLoadCompanies();
|
||||
$ip = getClientIp();
|
||||
$companyList = [];
|
||||
foreach ($allCompanies as $comp) {
|
||||
if (in_array($comp['id'], $userCompanyIds)) {
|
||||
// IP-rajoitus: superadmin ohittaa aina
|
||||
if (($_SESSION['role'] ?? '') !== 'superadmin' && !isIpAllowed($ip, $comp['allowed_ips'] ?? '')) {
|
||||
continue;
|
||||
}
|
||||
$companyList[] = ['id' => $comp['id'], 'nimi' => $comp['nimi']];
|
||||
}
|
||||
}
|
||||
// Jos IP-rajoitus poistaa kaikki yritykset (ei superadmin) → kirjaa ulos
|
||||
if (empty($companyList) && ($_SESSION['role'] ?? '') !== 'superadmin') {
|
||||
session_destroy();
|
||||
echo json_encode(['authenticated' => false]);
|
||||
break;
|
||||
}
|
||||
// Päivitä session companies IP-suodatuksen mukaan
|
||||
$allowedIds = array_column($companyList, 'id');
|
||||
$_SESSION['companies'] = $allowedIds;
|
||||
if (!in_array($_SESSION['company_id'] ?? '', $allowedIds) && !empty($allowedIds)) {
|
||||
$_SESSION['company_id'] = $allowedIds[0];
|
||||
}
|
||||
// Hae allekirjoitukset (oletus generoituna jos omaa ei ole)
|
||||
$userSignatures = $u ? buildSignaturesWithDefaults($u, $u['companies'] ?? []) : [];
|
||||
// Brändäystiedot domain-pohjaisesti (sama kuin branding-endpoint)
|
||||
@@ -2914,6 +2983,7 @@ switch ($action) {
|
||||
if (isset($input['enabled_modules']) && is_array($input['enabled_modules'])) {
|
||||
$c['enabled_modules'] = array_values($input['enabled_modules']);
|
||||
}
|
||||
if (isset($input['allowed_ips'])) $c['allowed_ips'] = trim($input['allowed_ips']);
|
||||
dbSaveCompany($c);
|
||||
$found = true;
|
||||
echo json_encode($c);
|
||||
@@ -2957,6 +3027,20 @@ switch ($action) {
|
||||
echo json_encode(['error' => 'Ei oikeutta tähän yritykseen']);
|
||||
break;
|
||||
}
|
||||
// IP-rajoitus yritystä vaihdettaessa (superadmin ohittaa)
|
||||
if (($_SESSION['role'] ?? '') !== 'superadmin') {
|
||||
$companies = dbLoadCompanies();
|
||||
foreach ($companies as $comp) {
|
||||
if ($comp['id'] === $companyId) {
|
||||
if (!isIpAllowed(getClientIp(), $comp['allowed_ips'] ?? '')) {
|
||||
http_response_code(403);
|
||||
echo json_encode(['error' => 'IP-osoitteesi ei ole sallittu tälle yritykselle.']);
|
||||
break 2;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
$_SESSION['company_id'] = $companyId;
|
||||
echo json_encode(['success' => true, 'company_id' => $companyId]);
|
||||
break;
|
||||
|
||||
Reference in New Issue
Block a user