3 Commits

Author SHA1 Message Date
dillard a5f5c5cdb7 Fix code-review findings: ajax error handling, translated comment, docs
- js/index.js: the occ-command ajax call used the wrong jQuery .fail()
  signature ((response, code) instead of (jqXHR, textStatus,
  errorThrown)), so any failure printed the literal string
  "[object Object]" instead of a useful message.
- js/index.js: the initial bootstrap call that builds the terminal had
  no .fail() handler at all - a failure there (expired session/CSRF
  token, network error, rejected admin check) left #app-content
  permanently blank with zero diagnostic information. This is exactly
  what made today's NC34 debugging session harder than it needed to
  be; now it shows the actual HTTP status/reason in the page.
- OccController.php: translated a leftover Dutch comment (missed by
  the earlier Russian->English pass since it wasn't Russian), and
  documented why requireAdmin() is intentional defense-in-depth rather
  than a misunderstanding of the framework: verified against
  Nextcloud's own SecurityMiddleware source that any controller method
  without @NoAdminRequired already rejects non-admins by default, so
  this check (and the original "security fix" commit that added it)
  was addressing a non-issue for THIS controller specifically.
  DbController's equivalent check is still genuinely load-bearing -
  that controller originally shipped with @PublicPage, which does
  bypass the default protection - so it gets a note explaining the
  distinction instead.
- README: added compatibility matrix rows for 0.4.0 (app id rename to
  extended_occweb) and 0.4.1 (these fixes), both verified on NC 34.0.4.

Deferred to when a test instance exists (tracked, not fixed here): no
documented migration path from the old occweb id to extended_occweb
for existing installs, OccController's constructor doing expensive
work before the (redundant) admin check runs, and js/jquery.js loading
unconditionally on NC30-33 where core still provides jQuery.

Version bump to 0.4.1.
2026-09-26 16:04:14 +02:00
dillard 221ea8a249 Shorten navigation menu label to "OCC Web" (was "OCCWeb")
Full app name stays "Extended OCC Web" for the App Store/apps
management page - only the top-nav menu entry needed a shorter,
readable label.
2026-09-26 15:55:10 +02:00
dillard 138ee88486 Rename app id from occweb to extended_occweb, prep for App Store
Nextcloud's app id "occweb" is effectively unavailable for a new store
listing: the original Adphi/occweb still owns that App Store slot
(stale, v0.2.3), and el-choco/occweb_v2 has since published a
same-lineage rewrite under its own id. Rather than fight over the
"occweb" name, publish this fork under its own id, matching the
<name> this app has always had: "Extended OCC Web".

Renames every place the literal app id is used: info.xml <id> and the
auto-generated navigation <route>, script()/style() registrations in
templates/index.php, the TemplateResponse app name in
OccController::index(), the /apps/... base URL in js/index.js, and the
'app' log context tag in DbController.php. The GitHub repo name and
its <website>/<bugs>/<repository>/<screenshot> links are unaffected -
those identify the repo, not the Nextcloud app.

Docs (README, INSTALL-AIO.md, install.sh) updated to clone into and
reference an "extended_occweb" directory, since Nextcloud requires the
app folder name to match its id - a plain `git clone` of this repo no
longer produces a correctly-named install directory on its own.

Version bump to 0.4.0 given the scope (breaking change for anyone who
already deployed under the old "occweb" id - requires disable+remove
of the old app and a fresh enable under the new id, not an in-place
upgrade).
2026-09-26 12:51:21 +02:00
8 changed files with 73 additions and 42 deletions
+7 -7
View File
@@ -27,14 +27,14 @@ tar -C "$STAGE" -cf /tmp/occweb-deploy.tar .
# 2. Copy it onto the Docker host, then into the container
scp /tmp/occweb-deploy.tar <docker-host>:/tmp/occweb-deploy.tar
ssh <docker-host> "docker cp /tmp/occweb-deploy.tar nextcloud-aio-nextcloud:/tmp/occweb-deploy.tar && \
docker exec nextcloud-aio-nextcloud sh -c 'mkdir -p /var/www/html/custom_apps/occweb && \
tar -xf /tmp/occweb-deploy.tar -C /var/www/html/custom_apps/occweb && rm /tmp/occweb-deploy.tar'"
docker exec nextcloud-aio-nextcloud sh -c 'mkdir -p /var/www/html/custom_apps/extended_occweb && \
tar -xf /tmp/occweb-deploy.tar -C /var/www/html/custom_apps/extended_occweb && rm /tmp/occweb-deploy.tar'"
# 3. Fix ownership - tar preserves the source uid/gid, not www-data
ssh <docker-host> "docker exec nextcloud-aio-nextcloud chown -R www-data:www-data /var/www/html/custom_apps/occweb"
ssh <docker-host> "docker exec nextcloud-aio-nextcloud chown -R www-data:www-data /var/www/html/custom_apps/extended_occweb"
# 4. Fresh install only (skip when updating an already-enabled app):
ssh <docker-host> "docker exec -u www-data nextcloud-aio-nextcloud php occ app:enable occweb"
ssh <docker-host> "docker exec -u www-data nextcloud-aio-nextcloud php occ app:enable extended_occweb"
```
> **Gotcha**: never copy a file into a container with `ssh host "docker exec container bash -c 'cat > file'"`
@@ -54,7 +54,7 @@ and Nextcloud puts the whole instance into a "needs upgrade" state until you syn
manually:
```bash
ssh <docker-host> "docker exec -u www-data nextcloud-aio-nextcloud php occ config:app:set occweb installed_version --value='X.Y.Z'"
ssh <docker-host> "docker exec -u www-data nextcloud-aio-nextcloud php occ config:app:set extended_occweb installed_version --value='X.Y.Z'"
```
(use the same `X.Y.Z` as the new `<version>`). Verify with `occ status` that
@@ -77,8 +77,8 @@ default path) — hence the manual PID lookup.
## Removing the app
```bash
ssh <docker-host> "docker exec -u www-data nextcloud-aio-nextcloud php occ app:disable occweb && \
docker exec nextcloud-aio-nextcloud rm -rf /var/www/html/custom_apps/occweb"
ssh <docker-host> "docker exec -u www-data nextcloud-aio-nextcloud php occ app:disable extended_occweb && \
docker exec nextcloud-aio-nextcloud rm -rf /var/www/html/custom_apps/extended_occweb"
```
## `max-version` policy
+11 -5
View File
@@ -39,10 +39,13 @@ path and deploy steps differ from the generic instructions below.
No build step required (plain PHP + vanilla JS). Clone straight into the
target server's `apps/` directory and run the install script:
Nextcloud requires the app's directory name to match its id (`extended_occweb`), so
clone into that name explicitly rather than the repo's own name:
```bash
cd /var/www/nextcloud/apps
git clone https://github.com/dillardblom/occweb.git
bash occweb/install.sh
git clone https://github.com/dillardblom/occweb.git extended_occweb
bash extended_occweb/install.sh
```
`install.sh` removes the dev/CI-only files that don't belong in a running
@@ -53,7 +56,7 @@ server user, and runs `occ app:enable`. It assumes Nextcloud lives at
values as arguments if that's not the case:
```bash
bash occweb/install.sh /path/to/nextcloud custom-web-user
bash extended_occweb/install.sh /path/to/nextcloud custom-web-user
```
`occ app:enable` takes care of registering the app's version correctly, so
@@ -102,7 +105,7 @@ So if your instance is pretty big, commands like `occ files:scan` will time out
## Deploying updates
After pulling/copying new code into `nextcloud/apps/occweb/`, restart PHP so
After pulling/copying new code into `nextcloud/apps/extended_occweb/`, restart PHP so
the changes actually take effect. `occ app:disable`/`app:enable` is **not**
enough — it does not clear PHP's opcode cache, so a stale, cached version of
the code can keep running (routes silently 404ing is a typical symptom).
@@ -124,7 +127,7 @@ CLI-upgrade wizard, even though nothing about Nextcloud core actually
changed. After bumping the version, sync it manually:
```bash
sudo -u www-data php /var/www/nextcloud/occ config:app:set occweb installed_version --value="X.Y.Z"
sudo -u www-data php /var/www/nextcloud/occ config:app:set extended_occweb installed_version --value="X.Y.Z"
```
(use the same `X.Y.Z` as the new `<version>`), then restart PHP as above.
@@ -143,6 +146,9 @@ was verified, so a rollback target is obvious if a newer major causes problems.
| 0.3.4 | `v0.3.4` | 34.0.4 | ❌ Blank terminal - global jQuery gone on NC34 (`jQuery is not defined`), then a 412 "CSRF check failed" on every ajax call once jQuery was vendored. Fixed in 0.3.5. |
| 0.3.5 | `v0.3.5-nc34` | 34.0.4 | ✅ Verified (nc01, 2026-09-26) - vendors its own jQuery + sends the CSRF `requesttoken` header explicitly on every ajax call |
| 0.3.5 | `v0.3.5-nc34` | 33.0.8 | ⚠️ Believed compatible (the 0.3.5 change is additive-only, nothing removed that NC30-33 depended on) but not re-verified - nc01 no longer runs NC33 to test against |
| 0.4.0 | (untagged) | 34.0.4 | ✅ Verified (nc01, 2026-09-26) - app id renamed `occweb` → `extended_occweb` (App Store prep), no functional changes to the 0.3.5 fixes otherwise |
| 0.4.1 | `v0.4.1-nc34` | 34.0.4 | ✅ Verified (nc01, 2026-09-26) - code review fixes: correct jQuery `.fail()` signature, added a missing `.fail()` handler on the initial terminal-load request |
| 0.4.1 | `v0.4.1-nc34` | 33.0.8 | ⚠️ Same caveat as 0.3.5/0.4.0 - not re-verified, no NC33 test environment available. Also unresolved: `js/jquery.js` now loads unconditionally on every supported version, even NC30-33 where core still provides jQuery - flagged in code review, needs an NC33 environment to confirm it's harmless there. |
If a future major breaks something, `git checkout v0.3.4` is the last version confirmed
working on NC33 and older.
+4 -4
View File
@@ -1,11 +1,11 @@
<?xml version="1.0"?>
<info xmlns:xsi= "http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="https://apps.nextcloud.com/schema/apps/info.xsd">
<id>occweb</id>
<id>extended_occweb</id>
<name>Extended OCC Web</name>
<summary>OCC Commands in a web terminal</summary>
<description><![CDATA[Run OCC Commands in a web terminal]]></description>
<version>0.3.5</version>
<version>0.4.1</version>
<licence>agpl</licence>
<author mail="dillard.blom@ensembia.com" >Dillard Blom</author>
<namespace>OCCWeb</namespace>
@@ -24,8 +24,8 @@
</dependencies>
<navigations>
<navigation role="admin">
<name>OCCWeb</name>
<route>occweb.occ.index</route>
<name>OCC Web</name>
<route>extended_occweb.occ.index</route>
<icon>app.svg</icon>
<type>link</type>
</navigation>
+5 -4
View File
@@ -1,10 +1,11 @@
#!/usr/bin/env bash
#
# Post-clone install script for occweb.
# Post-clone install script for occweb (Nextcloud app id: extended_occweb).
#
# Usage (from nextcloud/apps/):
# git clone https://github.com/dillardblom/occweb.git
# bash occweb/install.sh [nextcloud-root] [web-user]
# Usage (from nextcloud/apps/) - clone into "extended_occweb", not the repo's
# own name, since Nextcloud requires the app directory to match its id:
# git clone https://github.com/dillardblom/occweb.git extended_occweb
# bash extended_occweb/install.sh [nextcloud-root] [web-user]
#
# Defaults: nextcloud-root=/var/www/nextcloud, web-user=www-data
#
+13 -3
View File
@@ -12,7 +12,7 @@
var html = $('html');
html.scrollTop(html.prop('scrollHeight'));
}
var baseUrl = OC.generateUrl('/apps/occweb');
var baseUrl = OC.generateUrl('/apps/extended_occweb');
// Current terminal mode: 'occ' (regular occ commands) or 'sql'
// (running arbitrary SQL queries via /db/query).
@@ -387,8 +387,8 @@
data: JSON.stringify(occCommand)
}).done(function (response) {
term.echo('\n' + response).resume();
}).fail(function (response, code) {
term.echo('\n' + response).resume();
}).fail(function (xhr, status) {
term.echo('\n[[;#ff5555;]Request failed: ]' + $.terminal.escape_formatting(xhr.status + ' ' + xhr.statusText)).resume();
});
}
}, {
@@ -410,6 +410,16 @@
scrollToBottom()
}
});
}).fail(function (xhr, status) {
// Without this, a failure here (expired session/CSRF token, network
// error, a rejected admin check) leaves #app-content permanently
// empty with no visible error and nothing in the console pointing
// at the cause - exactly what happened during NC34 testing before
// this handler existed.
$('#app-content').text(
'Failed to load the terminal: ' + xhr.status + ' ' + xhr.statusText +
'. Try reloading the page; if that keeps happening, check the server logs.'
);
});
$('html').keypress(function(){
scrollToBottom()
+16 -11
View File
@@ -162,7 +162,12 @@ class DbController extends Controller
public function query()
{
// Check admin privileges
// Deliberate defense-in-depth (see the equivalent check in
// OccController::requireAdmin() for why it's kept even though
// Nextcloud's SecurityMiddleware already enforces this by default
// absent @NoAdminRequired) - this one is also the direct fix for
// this controller having shipped with @PublicPage originally,
// which did bypass that default protection entirely.
$user = $this->userSession->getUser();
if (!$user) {
return new JSONResponse(['error' => 'Not authenticated'], 401);
@@ -189,8 +194,8 @@ class DbController extends Controller
// Audit: log the fact that execution was attempted BEFORE any checks,
// so blocked/rejected queries are recorded in the log too, not just
// the ones that succeeded.
$this->logger->warning('[occweb] SQL submitted by {user}: {sql}', [
'app' => 'occweb',
$this->logger->warning('[extended_occweb] SQL submitted by {user}: {sql}', [
'app' => 'extended_occweb',
'user' => $user->getUID(),
'sql' => $sql,
]);
@@ -207,8 +212,8 @@ class DbController extends Controller
foreach ($queries as $query) {
$forbidden = $this->findForbiddenConstruct($query);
if ($forbidden !== null) {
$this->logger->error('[occweb] Blocked forbidden construct ({construct}) from {user}: {sql}', [
'app' => 'occweb',
$this->logger->error('[extended_occweb] Blocked forbidden construct ({construct}) from {user}: {sql}', [
'app' => 'extended_occweb',
'construct' => $forbidden,
'user' => $user->getUID(),
'sql' => $sql,
@@ -277,8 +282,8 @@ class DbController extends Controller
$this->db->beginTransaction();
$transactionStarted = true;
} catch (\Exception $e) {
$this->logger->warning('[occweb] beginTransaction() failed, proceeding without an explicit transaction: {error}', [
'app' => 'occweb',
$this->logger->warning('[extended_occweb] beginTransaction() failed, proceeding without an explicit transaction: {error}', [
'app' => 'extended_occweb',
'error' => $e->getMessage(),
]);
}
@@ -353,8 +358,8 @@ class DbController extends Controller
// permanently applied — we deliberately do NOT set
// rolledBack to true, since that would be untrue.
$rollbackFailed = true;
$this->logger->warning('[occweb] rollBack() failed after an error — earlier statements in this batch may already be permanently applied: {error}', [
'app' => 'occweb',
$this->logger->warning('[extended_occweb] rollBack() failed after an error — earlier statements in this batch may already be permanently applied: {error}', [
'app' => 'extended_occweb',
'error' => $rollbackError->getMessage(),
]);
}
@@ -371,8 +376,8 @@ class DbController extends Controller
// a DDL statement on MySQL/MariaDB) — the effects are
// already saved, this isn't an error in executing the batch
// itself.
$this->logger->info('[occweb] commit() had nothing to commit (likely auto-committed by a DDL statement): {error}', [
'app' => 'occweb',
$this->logger->info('[extended_occweb] commit() had nothing to commit (likely auto-committed by a DDL statement): {error}', [
'app' => 'extended_occweb',
'error' => $e->getMessage(),
]);
}
+12 -3
View File
@@ -50,8 +50,17 @@ class OccController extends Controller
private function requireAdmin(): ?JSONResponse
{
// Services via OC::$server zodat de constructor-signatuur ongewijzigd blijft
// en NC's DI-container (zonder application.php) de class kan resolven.
// Deliberate defense-in-depth, not a workaround for a missing framework
// check: Nextcloud's own SecurityMiddleware already rejects non-admins
// here by default (no method below has @NoAdminRequired), so this is
// redundant today. Kept anyway given what this app can do (arbitrary
// occ/SQL execution) - if the framework check ever regresses, this
// still holds. If you ever want to intentionally open one of these
// routes to non-admins, remove the call below AND add
// @NoAdminRequired, or this will keep silently blocking it.
//
// Services via OC::$server so the constructor signature stays unchanged
// and NC's DI container (without an application.php) can resolve the class.
$userSession = OC::$server->get(\OCP\IUserSession::class);
$user = $userSession->getUser();
if ($user === null) {
@@ -70,7 +79,7 @@ class OccController extends Controller
public function index()
{
if ($err = $this->requireAdmin()) return $err;
return new TemplateResponse('occweb', 'index');
return new TemplateResponse('extended_occweb', 'index');
}
/**
+5 -5
View File
@@ -3,11 +3,11 @@
// reliably present on NC34+ (see e.g. serverinfo needing to bundle its own) -
// term.js (jquery.terminal) and index.js both require jQuery to be loaded
// first, so ship and load our own copy rather than relying on core.
script('occweb', 'jquery');
script('occweb', 'index');
script('occweb', 'term');
script('occweb', 'unix_formatting');
style('occweb', 'style');
script('extended_occweb', 'jquery');
script('extended_occweb', 'index');
script('extended_occweb', 'term');
script('extended_occweb', 'unix_formatting');
style('extended_occweb', 'style');
?>
<div id="app" class="full-width">