Summary
Theme File Editor is a tool for trusted administrators. Saving a template means writing PHP that the server runs, so anyone who can save a file here can run any code as the web server user. The plugin's security model limits who can do that and which files they can touch; it does not sandbox the code.
Who has access
The plugin registers two ACL resources, ThemeFileEditor_Index (the pages) and ThemeFileEditor_Api (the JSON API), allows them to the super role and denies them to admin (in Classic, Admin inherits Super User's rights). Contributors and Researchers get nothing. Omeka checks the rule before every page and API action, and each controller checks it again. Users without access get Omeka's Forbidden page (HTTP 403), or the login page when they are not signed in, and do not see Theme Files under Appearance.
To give another role access (only to users you would also give shell access), a plugin of your own can add a theme_file_editor_roles filter, which returns the roles allowed besides Super User:
class MySitePlugin extends Omeka_Plugin_AbstractPlugin
{
protected $_filters = ['theme_file_editor_roles'];
public function filterThemeFileEditorRoles($roles)
{
$roles[] = 'admin';
return $roles;
}
}
Filters are registered when plugins load, before the ACL is built, so the order of the plugins does not matter. Unknown roles are ignored.
Which themes can be changed
A theme's files can be changed only when all of these hold (see Copying a Theme):
- the theme is the site's own (made with Copy Theme) or a Super User ticked it under Settings > Editable themes;
- its folder is a real folder of the themes folder, not a link to a folder elsewhere (which other sites may share);
- the web server can write to it.
Otherwise its files open read-only and every change is refused, with the reason.
What can be read
Browsing, opening and searching are limited to:
| Source | Folder |
|---|---|
| A theme | themes/<theme>/ |
| Omeka views | application/views/scripts/ |
| A plugin's views | plugins/<Plugin>/views/public/ and views/shared/ |
Theme and plugin names must match ^[A-Za-z0-9_-]+$ (and a theme must be one of the themes folder's), before any path is built. Hidden files are not listed. Only templates (.php), stylesheets (.css), scripts (.js), the editor's backups, and text files (.ini, .scss, .sass, .less, .md, .txt, .json, .svg, .xml, .html) can be opened, and files larger than Largest file are refused.
Symbolic links are followed only when they point inside the theme's or plugin's own folder, the Omeka installation, or the folder that holds the themes folder (where the Theme Engine lives); others are not listed. A link must lead to a file with the same extension as its name, and a file reached through a link out of the source folder opens only if it is a template, stylesheet or script, read-only.
Path checks
Every relative path from the browser goes through the same checks:
- Backslashes become slashes.
- Absolute paths (
/…,C:…) and NUL bytes are refused, before any trimming, so/etc/passwdis never read asetc/passwd. - Empty,
.and..segments are refused. - The path is joined to the source folder and resolved with
realpath(), and the real path must lie inside the source folder or the link rules above.
A refusal is answered with HTTP 403 by the API, or shown as an error on the page.
What can be written
| Operation | Writes | Checks |
|---|---|---|
| Save | An existing template, stylesheet or script of the theme | The theme is editable; the real path of the file lies inside the theme's real folder (a link to a file outside the theme, such as a Theme Engine template, is never written through); the file exists (saving never creates files); size limit; syntax check of .php; rate limit. |
| Rollback | The revision's file | As for a save, without the syntax check or size limit. |
| Copy to Theme | A template, stylesheet or script at the override path in the theme | The theme is editable; the source is a core or plugin view of those kinds; the target, or the nearest existing folder on its way, resolves inside the theme's real folder; replacing a file needs an explicit confirmation. Missing folders are made with mode 0755. custom.php and functions.php are never copied. |
| Copy Theme | A new folder themes/<name>/ |
The source is one of the themes; the name matches ^[A-Za-z0-9_-]{1,30}$ and no theme has it, in any case; the themes folder is writable. Links are replaced by copies of their targets (when they point where reading may) or left out; the copy is built in a hidden temporary folder and renamed. Then the copy's theme.ini title is set. |
| Delete a copy | Removes themes/<name>/ |
Only a theme made with Copy Theme, whose folder is a real folder, not the public theme and used by no exhibit. Links inside are removed, never followed. |
| Backup | <file>.<timestamp>.bak next to the file |
Only when asked, for a file being saved (or replaced by Copy to Theme). |
| Delete a backup | Removes a `*.php | css |
theme.ini, config.ini and every file that is not a template, stylesheet or script are never written. A mistake in theme.ini or config.ini breaks the Themes page and the theme's configuration form; Copy Theme sets the copy's title itself. Nothing outside the themes folder is ever written, and the plugin never changes file ownership or permissions.
Other safeguards
- CSRF: every POST to the JSON API carries the session's CSRF token (Omeka's own, the one its forms use) in the
X-CSRF-Tokenheader or acsrffield; without a valid one the request is refused (HTTP 403). The Settings form uses the same token. - No form filtering of code: the API takes JSON bodies, so Omeka's HTML filtering of form posts never alters templates.
- Syntax check: PHP's parser checks every
.phpfile before it is saved, thenphp -lwhereexec()is allowed (a temporary file, deleted afterwards). An error blocks the save unless the user saves anyway. - Size limit on opening and saving, and a rate limit on saves per user and minute.
- History before change: the current content of a file is stored before the editor changes it, when the history does not have it, so every change can be undone.
- Audit: every change fires the
audit_trail_loghook, recorded by the Audit Trail plugin when installed, with who did it, and the plugin's own hooks (see Hooks and Integration). Revisions record the user. - OPcache: a saved file is invalidated in OPcache, so the change shows at once.
What it does not do
- The JSON API has no key-based authentication: it relies on the admin session, the ACL and the CSRF token.
- No content filtering: a template can hold any PHP.
- The admin theme (
admin/themes/) and plugin files are never edited; plugin and core views are read-only and reach a theme only through Copy to Theme. - Backups are not deleted automatically, and revisions are pruned per file only when that file is saved.