Yrityskohtaiset käyttäjäroolit + IP-rajoitus bugikorjaus

- Lisää role-sarake user_companies-tauluun (admin/user per yritys)
- Migraatio: kopioi vanhat admin-roolit user_companies-tauluun, muuta globaali admin → user
- Päivitä dbSaveUser/dbLoadUsers/dbGetUser/dbGetUserByUsername käsittelemään company_roles
- isCompanyAdmin() tarkistaa nyt yrityskohtaisen roolin (session company_role)
- requireAdmin() käyttää isCompanyAdmin():ia
- requireCompany() tarkistaa IP-rajoituksen (siirretty login/check_auth:sta)
- Login ei enää estä kirjautumista IP:n perusteella, vaan merkitsee ip_blocked
- check_auth näyttää kaikki yritykset, IP-estetyt merkitään ip_blocked:lla
- company_switch palauttaa company_role ja päivittää session
- Frontend: käyttäjälomakkeessa yrityskohtaiset rooli-dropdownit (admin/käyttäjä)
- Frontend: yritysvaihto päivittää admin-näkyvyyden company_rolen mukaan
- Frontend: yritysvalitsimessa IP-estetyt yritykset näkyvät "(IP-rajoitus)" -tekstillä

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-11 20:45:18 +02:00
parent 4c128f5c71
commit 68c9075676
4 changed files with 194 additions and 89 deletions

122
api.php
View File

@@ -38,8 +38,7 @@ function requireAuth() {
function requireAdmin() {
requireAuth();
$role = $_SESSION['role'] ?? '';
if ($role !== 'admin' && $role !== 'superadmin') {
if (!isCompanyAdmin()) {
http_response_code(403);
echo json_encode(['error' => 'Vain ylläpitäjä voi tehdä tämän']);
exit;
@@ -60,8 +59,8 @@ function isSuperAdmin(): bool {
}
function isCompanyAdmin(): bool {
$role = $_SESSION['role'] ?? '';
return $role === 'admin' || $role === 'superadmin';
if (($_SESSION['role'] ?? '') === 'superadmin') return true;
return ($_SESSION['company_role'] ?? '') === 'admin';
}
function currentUser(): string {
@@ -103,6 +102,20 @@ function requireCompany(): string {
echo json_encode(['error' => 'Ei oikeutta tähän yritykseen']);
exit;
}
// IP-rajoitus: tarkista vasta kun yrityksen dataa käytetään (superadmin ohittaa)
if (($_SESSION['role'] ?? '') !== 'superadmin') {
$allCompanies = dbLoadCompanies();
foreach ($allCompanies 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.']);
exit;
}
break;
}
}
}
return $companyId;
}
@@ -1329,45 +1342,33 @@ switch ($action) {
echo json_encode(['error' => 'Sinulla ei ole oikeutta kirjautua tälle sivustolle.']);
break;
}
// IP-rajoitus: superadmin ohittaa aina
// IP-rajoitus EI enää estä kirjautumista — tarkistetaan vasta requireCompany():ssa
$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 (' . $ip . ') ei ole sallittu.']);
break;
}
$userCompanies = $allowedCompanies;
}
session_regenerate_id(true);
$_SESSION['user_id'] = $u['id'];
$_SESSION['username'] = $u['username'];
$_SESSION['nimi'] = $u['nimi'];
$_SESSION['role'] = $u['role'];
$_SESSION['companies'] = $userCompanies;
$_SESSION['company_roles'] = $u['company_roles'] ?? [];
// Jos domain matchaa ja käyttäjällä on oikeus -> käytä sitä
if ($domainCompanyId && in_array($domainCompanyId, $userCompanies)) {
$_SESSION['company_id'] = $domainCompanyId;
} else {
$_SESSION['company_id'] = !empty($userCompanies) ? $userCompanies[0] : '';
}
// Hae yritysten nimet
// Aseta aktiivisen yrityksen rooli
$_SESSION['company_role'] = $_SESSION['company_roles'][$_SESSION['company_id']] ?? 'user';
// Hae yritysten nimet + IP-status
$companyList = [];
foreach ($allCompanies as $comp) {
if (in_array($comp['id'], $userCompanies)) {
$companyList[] = ['id' => $comp['id'], 'nimi' => $comp['nimi']];
$entry = ['id' => $comp['id'], 'nimi' => $comp['nimi']];
// Merkitse IP-estetyt yritykset (superadmin ohittaa)
if ($u['role'] !== 'superadmin' && !isIpAllowed($ip, $comp['allowed_ips'] ?? '')) {
$entry['ip_blocked'] = true;
}
$companyList[] = $entry;
}
}
echo json_encode([
@@ -1375,6 +1376,7 @@ switch ($action) {
'username' => $u['username'],
'nimi' => $u['nimi'],
'role' => $u['role'],
'company_role' => $_SESSION['company_role'],
'companies' => $companyList,
'company_id' => $_SESSION['company_id'],
'signatures' => buildSignaturesWithDefaults($u, $u['companies'] ?? []),
@@ -1397,37 +1399,29 @@ switch ($action) {
$u = dbGetUser($_SESSION['user_id']);
if ($u) {
$_SESSION['companies'] = $u['companies'] ?? [];
$_SESSION['company_roles'] = $u['company_roles'] ?? [];
// Varmista aktiivinen yritys on sallittu
if (!in_array($_SESSION['company_id'] ?? '', $_SESSION['companies'])) {
$_SESSION['company_id'] = !empty($_SESSION['companies']) ? $_SESSION['companies'][0] : '';
}
// Päivitä aktiivisen yrityksen rooli
$_SESSION['company_role'] = $_SESSION['company_roles'][$_SESSION['company_id'] ?? ''] ?? 'user';
}
// Hae yritysten nimet + IP-rajoitus
// Hae yritysten nimet — EI suodata IP:n perusteella pois, vaan merkitään ip_blocked
$userCompanyIds = $_SESSION['companies'] ?? [];
$allCompanies = dbLoadCompanies();
$ip = getClientIp();
$companyList = [];
foreach ($allCompanies as $comp) {
if (in_array($comp['id'], $userCompanyIds)) {
// IP-rajoitus: superadmin ohittaa aina
$entry = ['id' => $comp['id'], 'nimi' => $comp['nimi']];
// Merkitse IP-estetyt yritykset (superadmin ohittaa)
if (($_SESSION['role'] ?? '') !== 'superadmin' && !isIpAllowed($ip, $comp['allowed_ips'] ?? '')) {
continue;
$entry['ip_blocked'] = true;
}
$companyList[] = ['id' => $comp['id'], 'nimi' => $comp['nimi']];
$companyList[] = $entry;
}
}
// 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)
@@ -1449,6 +1443,7 @@ switch ($action) {
'nimi' => $_SESSION['nimi'],
'email' => $u['email'] ?? '',
'role' => $_SESSION['role'],
'company_role' => $_SESSION['company_role'] ?? 'user',
'companies' => $companyList,
'company_id' => $_SESSION['company_id'] ?? '',
'signatures' => $userSignatures,
@@ -1549,7 +1544,8 @@ switch ($action) {
$nimi = trim($input['nimi'] ?? '');
$email = trim($input['email'] ?? '');
$isSA = ($_SESSION['role'] ?? '') === 'superadmin';
$validRoles = $isSA ? ['superadmin', 'admin', 'user'] : ['admin', 'user'];
// Globaali rooli: user tai superadmin (admin on nyt yrityskohtainen)
$validRoles = $isSA ? ['superadmin', 'user'] : ['user'];
$role = in_array($input['role'] ?? '', $validRoles) ? $input['role'] : 'user';
if (empty($username) || empty($password)) {
http_response_code(400);
@@ -1583,6 +1579,19 @@ switch ($action) {
$signatures[(string)$mbId] = (string)$sig;
}
}
// Yrityskohtaiset roolit
$companyRoles = [];
if ($isSA && isset($input['company_roles']) && is_array($input['company_roles'])) {
foreach ($input['company_roles'] as $cid => $crole) {
if (in_array($cid, $companies) && in_array($crole, ['admin', 'user'])) {
$companyRoles[$cid] = $crole;
}
}
} elseif (!$isSA) {
// Admin luo käyttäjiä vain omaan yritykseensä -> oletusrooli user
$myCompanyId = $_SESSION['company_id'] ?? '';
$companyRoles[$myCompanyId] = 'user';
}
$newUser = [
'id' => generateId(),
'username' => $username,
@@ -1591,6 +1600,7 @@ switch ($action) {
'email' => $email,
'role' => $role,
'companies' => $companies,
'company_roles' => $companyRoles,
'signatures' => $signatures,
'luotu' => date('Y-m-d H:i:s'),
];
@@ -1623,7 +1633,8 @@ switch ($action) {
if (isset($input['nimi'])) $u['nimi'] = trim($input['nimi']);
if (isset($input['email'])) $u['email'] = trim($input['email']);
if (isset($input['role'])) {
$validRoles = $isSA ? ['superadmin', 'admin', 'user'] : ['admin', 'user'];
// Globaali rooli: user tai superadmin (admin on nyt yrityskohtainen)
$validRoles = $isSA ? ['superadmin', 'user'] : ['user'];
// Admin ei voi muuttaa superadminia
if (!$isSA && ($u['role'] === 'superadmin')) {
// Älä muuta roolia
@@ -1636,6 +1647,16 @@ switch ($action) {
$validIds = array_column($allCompanies, 'id');
$u['companies'] = array_values(array_filter($input['companies'], fn($c) => in_array($c, $validIds)));
}
// Yrityskohtaiset roolit
if (isset($input['company_roles']) && is_array($input['company_roles'])) {
$companyRoles = $u['company_roles'] ?? [];
foreach ($input['company_roles'] as $cid => $crole) {
if (in_array($cid, $u['companies'] ?? []) && in_array($crole, ['admin', 'user'])) {
$companyRoles[$cid] = $crole;
}
}
$u['company_roles'] = $companyRoles;
}
if (!empty($input['password'])) {
$u['password_hash'] = password_hash($input['password'], PASSWORD_DEFAULT);
}
@@ -1652,12 +1673,15 @@ switch ($action) {
// Päivitä sessio jos muokattiin kirjautunutta käyttäjää
if ($u['id'] === $_SESSION['user_id']) {
$_SESSION['companies'] = $u['companies'] ?? [];
$_SESSION['company_roles'] = $u['company_roles'] ?? [];
if (!empty($u['companies']) && !in_array($_SESSION['company_id'] ?? '', $u['companies'])) {
$_SESSION['company_id'] = $u['companies'][0];
}
if (empty($u['companies'])) {
$_SESSION['company_id'] = '';
}
// Päivitä aktiivisen yrityksen rooli
$_SESSION['company_role'] = ($_SESSION['company_roles'][$_SESSION['company_id'] ?? '']) ?? 'user';
}
$safe = $u;
unset($safe['password_hash']);
@@ -3755,7 +3779,13 @@ switch ($action) {
}
}
$_SESSION['company_id'] = $companyId;
echo json_encode(['success' => true, 'company_id' => $companyId]);
// Päivitä aktiivisen yrityksen rooli
$_SESSION['company_role'] = ($_SESSION['company_roles'] ?? [])[$companyId] ?? 'user';
echo json_encode([
'success' => true,
'company_id' => $companyId,
'company_role' => $_SESSION['company_role'],
]);
break;
case 'company_config':