Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,17 @@ All notable changes to **Tiger Core** (`webtigers/tiger-core`). Format follows

## [Unreleased]

## [1.35.0] — 2026-10-21

### Changed
- **TIGER-335 migration (batch 4)** — `_isAdmin()` → `_isAllowed('<method>')` across seven modules whose
admin services are wildcard-privilege grants: **sms** (TIGER-352), **seo** (TIGER-353), **mcp**
(TIGER-348), **register** (TIGER-347, incl. the `_isAdmin('Register_AdminController')` cross-resource
form), **media** (TIGER-341), **blog** (TIGER-342), **cms** (TIGER-337). Behavior-preserving.
**Audit note:** these modules' `guest` grants were confirmed to sit on genuinely public *controllers*
(sms inbound webhook, seo sitemap/robots/llms, mcp server, register verify, blog/cms index) — **not**
on the admin services carrying the in-method checks, so there was no F5 hole here (no acl.ini change).

## [1.34.0] — 2026-10-21

### Changed
Expand Down
2 changes: 1 addition & 1 deletion library/Tiger/Version.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,5 @@
class Tiger_Version
{
/** Current Tiger Core version. Keep in lockstep with the git tag cut for a release. */
const VERSION = '1.34.0';
const VERSION = '1.35.0';
}
12 changes: 6 additions & 6 deletions modules/blog/services/Post.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ class Blog_Service_Post extends Tiger_Service_Service
*/
public function datatable(array $params): void
{
if (!$this->_isAdmin()) { $this->_error('core.api.error.not_allowed'); return; }
if (!$this->_isAllowed('datatable')) { $this->_error('core.api.error.not_allowed'); return; }

$dt = $this->_dtParams($params);
$model = new Blog_Model_Post();
Expand All @@ -35,8 +35,8 @@ public function datatable(array $params): void
'limit' => $dt['length'],
]);

$canEdit = $this->_isAdmin(static::class, 'save');
$canDelete = $this->_isAdmin(static::class, 'delete');
$canEdit = $this->_isAllowed('save');
$canDelete = $this->_isAllowed('delete');

$rows = [];
foreach ($data['rows'] as $r) {
Expand Down Expand Up @@ -69,7 +69,7 @@ public function datatable(array $params): void
*/
public function save(array $params): void
{
if (!$this->_isAdmin()) { $this->_error('core.api.error.not_allowed'); return; }
if (!$this->_isAllowed('save')) { $this->_error('core.api.error.not_allowed'); return; }

$form = new Blog_Form_Post();
if (!$form->isValid($params)) { $this->_formErrors($form); return; }
Expand Down Expand Up @@ -138,7 +138,7 @@ public function save(array $params): void
*/
public function delete(array $params): void
{
if (!$this->_isAdmin()) { $this->_error('core.api.error.not_allowed'); return; }
if (!$this->_isAllowed('delete')) { $this->_error('core.api.error.not_allowed'); return; }
$id = !empty($params['post_id']) ? (string) $params['post_id'] : '';
if ($id === '') { $this->_error('core.api.error.general'); return; }

Expand All @@ -158,7 +158,7 @@ public function delete(array $params): void
*/
public function restore(array $params): void
{
if (!$this->_isAdmin()) { $this->_error('core.api.error.not_allowed'); return; }
if (!$this->_isAllowed('restore')) { $this->_error('core.api.error.not_allowed'); return; }
$id = !empty($params['post_id']) ? (string) $params['post_id'] : '';
$version = isset($params['version']) ? (int) $params['version'] : 0;
if ($id === '' || $version < 1) { $this->_error('core.api.error.general'); return; }
Expand Down
2 changes: 1 addition & 1 deletion modules/blog/services/Taxonomy.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ class Blog_Service_Taxonomy extends Tiger_Service_Service
*/
public function listTerms(array $params): void
{
if (!$this->_isAdmin()) { $this->_error('core.api.error.not_allowed'); return; }
if (!$this->_isAllowed('listTerms')) { $this->_error('core.api.error.not_allowed'); return; }

$vocab = ($params['vocabulary'] ?? '') === Blog_Model_Taxonomy::VOCAB_CATEGORY
? Blog_Model_Taxonomy::VOCAB_CATEGORY : Blog_Model_Taxonomy::VOCAB_TAG;
Expand Down
18 changes: 9 additions & 9 deletions modules/cms/services/Menu.php
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,11 @@ protected function _menuOrgId()
*/
public function datatable(array $params): void
{
if (!$this->_isAdmin()) { $this->_error('core.api.error.not_allowed'); return; }
if (!$this->_isAllowed('datatable')) { $this->_error('core.api.error.not_allowed'); return; }

$dt = $this->_dtParams($params);
$canEdit = $this->_isAdmin(static::class, 'save');
$canDelete = $this->_isAdmin(static::class, 'deleteMenu');
$canEdit = $this->_isAllowed('save');
$canDelete = $this->_isAllowed('deleteMenu');

// Theme display names (all installed) so a menu shows its origin theme even after deactivation.
$active = Tiger_Theme::active(); // {key, name, dir}
Expand Down Expand Up @@ -158,7 +158,7 @@ protected function _countNodes(array $nodes): int
*/
public function save(array $params): void
{
if (!$this->_isAdmin()) { $this->_error('core.api.error.not_allowed'); return; }
if (!$this->_isAllowed('save')) { $this->_error('core.api.error.not_allowed'); return; }

$menuKey = trim((string) ($params['menu_key'] ?? ''));
if ($menuKey === '') { $this->_error('cms.menu.key_required'); return; }
Expand Down Expand Up @@ -218,7 +218,7 @@ public function save(array $params): void
*/
public function delete(array $params): void
{
if (!$this->_isAdmin()) { $this->_error('core.api.error.not_allowed'); return; }
if (!$this->_isAllowed('delete')) { $this->_error('core.api.error.not_allowed'); return; }
$id = (string) ($params['menu_id'] ?? '');
if ($id === '') { $this->_error('core.api.error.general'); return; }
$menuKey = (string) ($params['menu_key'] ?? '');
Expand All @@ -243,7 +243,7 @@ public function delete(array $params): void
*/
public function deleteMenu(array $params): void
{
if (!$this->_isAdmin()) { $this->_error('core.api.error.not_allowed'); return; }
if (!$this->_isAllowed('deleteMenu')) { $this->_error('core.api.error.not_allowed'); return; }
$menuKey = (string) ($params['menu_key'] ?? '');
$orgId = $this->_scopedOrgId($params);
if ($orgId === null) { $this->_error('core.api.error.not_allowed'); return; }
Expand All @@ -267,7 +267,7 @@ public function deleteMenu(array $params): void
*/
public function reorder(array $params): void
{
if (!$this->_isAdmin()) { $this->_error('core.api.error.not_allowed'); return; }
if (!$this->_isAllowed('reorder')) { $this->_error('core.api.error.not_allowed'); return; }
$menuKey = (string) ($params['menu_key'] ?? '');
$orgId = $this->_scopedOrgId($params);
if ($orgId === null) { $this->_error('core.api.error.not_allowed'); return; }
Expand Down Expand Up @@ -305,7 +305,7 @@ public function reorder(array $params): void
*/
public function importFromTheme(array $params): void
{
if (!$this->_isAdmin()) { $this->_error('core.api.error.not_allowed'); return; }
if (!$this->_isAllowed('importFromTheme')) { $this->_error('core.api.error.not_allowed'); return; }

$themeMenus = Tiger_Theme_Menus::all();
if (!$themeMenus) { $this->_error('cms.menu.import_none'); return; }
Expand Down Expand Up @@ -362,7 +362,7 @@ protected function _importNodes(Tiger_Model_Menu $model, array $nodes, string $m
*/
public function revertToTheme(array $params): void
{
if (!$this->_isAdmin()) { $this->_error('core.api.error.not_allowed'); return; }
if (!$this->_isAllowed('revertToTheme')) { $this->_error('core.api.error.not_allowed'); return; }
$menuKey = (string) ($params['menu_key'] ?? '');
$orgId = $this->_scopedOrgId($params);
if ($orgId === null) { $this->_error('core.api.error.not_allowed'); return; }
Expand Down
22 changes: 11 additions & 11 deletions modules/cms/services/Page.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ class Cms_Service_Page extends Tiger_Service_Service
*/
public function datatable(array $params): void
{
if (!$this->_isAdmin()) { $this->_error('core.api.error.not_allowed'); return; }
if (!$this->_isAllowed('datatable')) { $this->_error('core.api.error.not_allowed'); return; }

$dt = $this->_dtParams($params);

Expand All @@ -48,8 +48,8 @@ public function datatable(array $params): void

// Server-authoritative control permissions (per-caller, privilege-checked so
// tightening save/delete in the ACL flows straight through to the buttons).
$canEdit = $this->_isAdmin(static::class, 'save');
$canDelete = $this->_isAdmin(static::class, 'delete');
$canEdit = $this->_isAllowed('save');
$canDelete = $this->_isAllowed('delete');

$rows = [];
foreach ($data['rows'] as $r) {
Expand Down Expand Up @@ -83,7 +83,7 @@ public function datatable(array $params): void
*/
public function themeTemplates(array $params): void
{
if (!$this->_isAdmin()) { $this->_error('core.api.error.not_allowed'); return; }
if (!$this->_isAllowed('themeTemplates')) { $this->_error('core.api.error.not_allowed'); return; }

$dt = $this->_dtParams($params);

Expand Down Expand Up @@ -137,7 +137,7 @@ public function themeTemplates(array $params): void
// Paginate.
$page = array_slice($rows, (int) $dt['start'], ((int) $dt['length'] > 0) ? (int) $dt['length'] : null);

$canEdit = $this->_isAdmin(static::class, 'save');
$canEdit = $this->_isAllowed('save');
foreach ($page as &$r) { $r['can_edit'] = $canEdit; }
unset($r);

Expand Down Expand Up @@ -172,7 +172,7 @@ protected function _forkedIndex(): array
*/
public function forkTheme(array $params): void
{
if (!$this->_isAdmin()) { $this->_error('core.api.error.not_allowed'); return; }
if (!$this->_isAllowed('forkTheme')) { $this->_error('core.api.error.not_allowed'); return; }

$kind = (string) ($params['kind'] ?? 'page');
if (!in_array($kind, ['page', 'layout', 'partial'], true)) { $kind = 'page'; }
Expand Down Expand Up @@ -264,7 +264,7 @@ public function forkTheme(array $params): void
*/
public function save(array $params): void
{
if (!$this->_isAdmin()) { $this->_error('core.api.error.not_allowed'); return; }
if (!$this->_isAllowed('save')) { $this->_error('core.api.error.not_allowed'); return; }

$form = new Cms_Form_Page();
if (!$form->isValid($params)) { $this->_formErrors($form); return; }
Expand Down Expand Up @@ -354,7 +354,7 @@ public function save(array $params): void
*/
public function saveDesign(array $params): void
{
if (!$this->_isAdmin()) { $this->_error('core.api.error.not_allowed'); return; }
if (!$this->_isAllowed('saveDesign')) { $this->_error('core.api.error.not_allowed'); return; }

$pageId = trim((string) ($params['page_id'] ?? ''));
if ($pageId === '') { $this->_error('core.api.error.general'); return; }
Expand Down Expand Up @@ -423,7 +423,7 @@ public function saveDesign(array $params): void
*/
public function saveBlock(array $params): void
{
if (!$this->_isAdmin()) { $this->_error('core.api.error.not_allowed'); return; }
if (!$this->_isAllowed('saveBlock')) { $this->_error('core.api.error.not_allowed'); return; }

$name = trim((string) ($params['name'] ?? ''));
if ($name === '') { $this->_error('cms.block.name_required'); return; }
Expand Down Expand Up @@ -478,7 +478,7 @@ protected function _uniqueKey(Tiger_Model_Page $pm, string $base): string
*/
public function delete(array $params): void
{
if (!$this->_isAdmin()) { $this->_error('core.api.error.not_allowed'); return; }
if (!$this->_isAllowed('delete')) { $this->_error('core.api.error.not_allowed'); return; }
$id = !empty($params['page_id']) ? (string) $params['page_id'] : '';
if ($id === '') { $this->_error('core.api.error.general'); return; }

Expand All @@ -498,7 +498,7 @@ public function delete(array $params): void
*/
public function restore(array $params): void
{
if (!$this->_isAdmin()) { $this->_error('core.api.error.not_allowed'); return; }
if (!$this->_isAllowed('restore')) { $this->_error('core.api.error.not_allowed'); return; }
$id = !empty($params['page_id']) ? (string) $params['page_id'] : '';
$version = isset($params['version']) ? (int) $params['version'] : 0;
if ($id === '' || $version < 1) { $this->_error('core.api.error.general'); return; }
Expand Down
2 changes: 1 addition & 1 deletion modules/cms/services/Paths.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ class Cms_Service_Paths extends Tiger_Service_Service
*/
public function search(array $params): void
{
if (!$this->_isAdmin()) { $this->_error('core.api.error.not_allowed'); return; }
if (!$this->_isAllowed('search')) { $this->_error('core.api.error.not_allowed'); return; }

$q = trim((string) ($params['q'] ?? ''));
$advanced = !empty($params['advanced']) && (string) $params['advanced'] !== '0';
Expand Down
2 changes: 1 addition & 1 deletion modules/cms/services/Settings.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ class Cms_Service_Settings extends Tiger_Service_Service
*/
public function save(array $params): void
{
if (!$this->_isAdmin()) { $this->_error('core.api.error.not_allowed'); return; }
if (!$this->_isAllowed('save')) { $this->_error('core.api.error.not_allowed'); return; }

$form = new Cms_Form_Settings();
if (!$form->isValid($params)) { $this->_formErrors($form); return; }
Expand Down
8 changes: 4 additions & 4 deletions modules/mcp/services/Settings.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ class Mcp_Service_Settings extends Tiger_Service_Service
*/
public function save(array $params): void
{
if (!$this->_isAdmin()) { $this->_error('core.api.error.not_allowed'); return; }
if (!$this->_isAllowed('save')) { $this->_error('core.api.error.not_allowed'); return; }

$on = !empty($params['enabled']) && $params['enabled'] !== '0' && $params['enabled'] !== 'false';
try {
Expand All @@ -39,7 +39,7 @@ public function save(array $params): void
*/
public function mintToken(array $params): void
{
if (!$this->_isAdmin()) { $this->_error('core.api.error.not_allowed'); return; }
if (!$this->_isAllowed('mintToken')) { $this->_error('core.api.error.not_allowed'); return; }
$identity = Zend_Auth::getInstance()->getIdentity();
$userId = $identity->user_id ?? null;
if ($userId === null) { $this->_error('core.api.error.login_required'); return; }
Expand All @@ -62,7 +62,7 @@ public function mintToken(array $params): void
/** List the caller's MCP tokens with their scope (prefix + timestamps + modules/read_only/org_scoped). */
public function tokens(array $params): void
{
if (!$this->_isAdmin()) { $this->_error('core.api.error.not_allowed'); return; }
if (!$this->_isAllowed('tokens')) { $this->_error('core.api.error.not_allowed'); return; }
$userId = Zend_Auth::getInstance()->getIdentity()->user_id ?? null;
if ($userId === null) { $this->_error('core.api.error.login_required'); return; }

Expand All @@ -80,7 +80,7 @@ public function tokens(array $params): void
/** Revoke one of the caller's tokens + drop its MCP policy. */
public function revokeToken(array $params): void
{
if (!$this->_isAdmin()) { $this->_error('core.api.error.not_allowed'); return; }
if (!$this->_isAllowed('revokeToken')) { $this->_error('core.api.error.not_allowed'); return; }
$userId = Zend_Auth::getInstance()->getIdentity()->user_id ?? null;
$id = (string) ($params['credential_id'] ?? '');
if ($userId === null || $id === '') { $this->_error('core.api.error.general'); return; }
Expand Down
12 changes: 6 additions & 6 deletions modules/media/services/Media.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ class Media_Service_Media extends Tiger_Service_Service
*/
public function upload(array $params): void
{
if (!$this->_isAdmin()) { $this->_error('core.api.error.not_allowed'); return; }
if (!$this->_isAllowed('upload')) { $this->_error('core.api.error.not_allowed'); return; }

$file = isset($_FILES['file']) ? $_FILES['file'] : null;
if (!$file || !is_uploaded_file((string) $file['tmp_name']) || $file['error'] !== UPLOAD_ERR_OK) {
Expand Down Expand Up @@ -126,7 +126,7 @@ public function upload(array $params): void
*/
public function datatable(array $params): void
{
if (!$this->_isAdmin()) { $this->_error('core.api.error.not_allowed'); return; }
if (!$this->_isAllowed('datatable')) { $this->_error('core.api.error.not_allowed'); return; }

$dt = $this->_dtParams($params);
$kinds = [Tiger_Model_Media::KIND_IMAGE, Tiger_Model_Media::KIND_DOCUMENT, Tiger_Model_Media::KIND_PDF,
Expand Down Expand Up @@ -173,7 +173,7 @@ public function datatable(array $params): void
])
: ['rows' => [], 'total' => 0, 'filtered' => 0];

$canDelete = $this->_isAdmin(static::class, 'delete');
$canDelete = $this->_isAllowed('delete');
$rows = [];
foreach ($staticSlice as $e) { $rows[] = $this->_presentStatic($e); }
if ($need > 0) {
Expand All @@ -197,7 +197,7 @@ public function datatable(array $params): void
*/
public function copyToLibrary(array $params): void
{
if (!$this->_isAdmin()) { $this->_error('core.api.error.not_allowed'); return; }
if (!$this->_isAllowed('copyToLibrary')) { $this->_error('core.api.error.not_allowed'); return; }

$src = Tiger_Media_Manifest::file((string) ($params['media_id'] ?? $params['ref'] ?? ''));
if ($src === '') { $this->_error('media.error.static_missing'); return; }
Expand Down Expand Up @@ -306,7 +306,7 @@ protected function _presentStatic(array $e)
*/
public function update(array $params): void
{
if (!$this->_isAdmin()) { $this->_error('core.api.error.not_allowed'); return; }
if (!$this->_isAllowed('update')) { $this->_error('core.api.error.not_allowed'); return; }
$id = (string) ($params['media_id'] ?? '');
$model = new Tiger_Model_Media();
// Ownership, not just role: the ACL says this caller is an admin, not that this row is theirs.
Expand Down Expand Up @@ -338,7 +338,7 @@ public function update(array $params): void
*/
public function delete(array $params): void
{
if (!$this->_isAdmin()) { $this->_error('core.api.error.not_allowed'); return; }
if (!$this->_isAllowed('delete')) { $this->_error('core.api.error.not_allowed'); return; }
$id = (string) ($params['media_id'] ?? '');
$model = new Tiger_Model_Media();
// Ownership FIRST — below this point the stored bytes are destroyed, which is not undoable by
Expand Down
8 changes: 4 additions & 4 deletions modules/media/services/Settings.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ class Media_Service_Settings extends Tiger_Service_Service
*/
public function save(array $params): void
{
if (!$this->_isAdmin()) { $this->_error('core.api.error.not_allowed'); return; }
if (!$this->_isAllowed('save')) { $this->_error('core.api.error.not_allowed'); return; }

$form = new Media_Form_Settings();
if (!$form->isValid($params)) { $this->_formErrors($form); return; }
Expand Down Expand Up @@ -71,7 +71,7 @@ public function save(array $params): void
*/
public function migrateDisk(array $params): void
{
if (!$this->_isAdmin()) { $this->_error('core.api.error.not_allowed'); return; }
if (!$this->_isAllowed('migrateDisk')) { $this->_error('core.api.error.not_allowed'); return; }

$to = preg_replace('/[^a-zA-Z0-9_-]/', '', (string) ($params['to'] ?? ''));
$move = (string) ($params['move'] ?? '') === '1';
Expand Down Expand Up @@ -105,7 +105,7 @@ public function migrateDisk(array $params): void
*/
public function saveStorage(array $params): void
{
if (!$this->_isAdmin()) { $this->_error('core.api.error.not_allowed'); return; }
if (!$this->_isAllowed('saveStorage')) { $this->_error('core.api.error.not_allowed'); return; }

$adapter = $this->_storageAdapter($params);
if ($adapter === null) { $this->_error('media.storage.bad_adapter'); return; }
Expand Down Expand Up @@ -157,7 +157,7 @@ public function saveStorage(array $params): void
*/
public function testConnection(array $params): void
{
if (!$this->_isAdmin()) { $this->_error('core.api.error.not_allowed'); return; }
if (!$this->_isAllowed('testConnection')) { $this->_error('core.api.error.not_allowed'); return; }

$settings = $this->_storageSettings($params);
if ($settings === null) { $this->_error('media.storage.bad_adapter'); return; }
Expand Down
Loading
Loading