1
0
Fork 0
mirror of https://github.com/retspen/webvirtcloud synced 2026-11-08 11:16:47 +00:00
Commit graph

61 commits

Author SHA1 Message Date
catborise
8063dcec66 feat(admin): the user list shows how many API tokens each user has
Which users can reach the API with a token was seen only by opening each
user. The user list now has an API tokens column: the number of the
user's tokens that still work (expired ones are left out; they are
deleted only at the user's next token), linked to the user's token tab.
One query counts them for all users.
2026-10-25 20:44:59 +03:00
catborise
b24746c1cb fix(logs): blocking a blocked user writes no row; one upload submit path
Follow-ups from the review of the audit log layers:

- Blocking a user who is already blocked, or unblocking one who is
  not, changes nothing and now writes no row, like any other edit
  that changes nothing.
- write_chunk takes the request instead of the user and an optional
  request; the view always had one, so the branch only tests used
  is gone and every chunk 0 goes through the same audited submit.
- Tests: a busy clone creates no job and its row carries the page's
  message; a later chunk that fails writes no submit row; libvirt
  refusing a network or pool delete is a failed row.
2026-10-23 14:23:32 +03:00
catborise
e04adec87b feat(logs): changes to users, groups, owners, flavors and settings are logged
The Logs page now records the panel's own records too: users (add,
change with the changed fields, delete, block, unblock, an admin's
password set, a user's own password change including the first-login
one, own profile edits), groups with their members and permissions,
VM owners from the accounts pages and from the VM page, OTP QR code
mails, flavors from web and API, app settings and the theme,
removed-VM records and SSH keys. No row carries a password or a key.

- A user is created in one transaction: when a VM named in
  NEW_USER_DEFAULT_INSTANCES is missing, no half-made user stays.
- The OTP mail checks the email address before it creates a device;
  without one it writes a failed row and makes no device.
- The VM page's owner rows read "VM web-1 owner bob: add" and
  "...: remove" instead of "Add owner: bob" and "Delete owner: 17"; a
  second owner refused by the single-owner setting is a failed row.
  Owner rows keep the VM's host and name, so the VM's log tab shows
  them.
- A setting or theme posted with its current value is no change and
  writes no row; the old free-text setting rows are gone.

Host-side objects (networks, storage, interfaces, secrets, filters)
follow in the next layer.
2026-10-23 11:10:24 +03:00
catborise
8c0fbff8f7 feat(logs): admin changes and logins are recorded on the Logs page
The Logs page shows who added, changed or deleted a compute, from web
or API, and every login, failed login, lockout and logout, with the
client address and a Result column (Done, Failed, Unknown for older
rows). A failed change is recorded with its reason and the error still
reaches the user; a refused one is recorded too.

- logs.audit.write saves each row in its own savepoint and never
  raises, so a log write cannot break or undo the action it records.
  The audit() context manager writes one row when the change ends.
- Logs gains success and ip; user, host and instance are wider and
  date is indexed. The page orders by date then id.
- A compute edit names the changed fields; an edit that changes
  nothing writes no row, and an empty password field is no change.
- A compute's VMs leaving the panel get a system row each: owners
  kept on the Removed VMs page, moved to another compute, restored,
  left without owners, or the removed record expired.
- Job rows carry the result. A job's log entry that cannot be
  computed is logged and skipped instead of rolling back the finish
  (test_service.py changes this on purpose).
- Logins are recorded by signal receivers; CustomLoginView is removed
  and the plain LoginView is used. A wrong OTP code is a failed login,
  counted by django-axes toward the lockout.

Panel records and host-side objects follow in the next layers.
2026-10-23 10:30:48 +03:00
catborise
fbe93d9aee fix(templates): each delete page says what deleting its object does
The shared delete page asked only whether to delete the object. Each
view now gives its consequence: a deleted compute's VMs keep running
but the panel forgets them and their owners, a deleted user's VMs
stay, a group's members keep their accounts, a VM's owner loses only
the access, a removed VM's record no longer brings its owners back,
an SSH key stays on the VMs that have it, a flavor only goes as a
size template.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-22 11:15:00 +03:00
catborise
676bcfeb09 fix(templates): forms and detail lists fit a phone screen
Column offsets without a breakpoint applied below it, where the column
is already full width, so the change password, compute, user, VM
create and VM settings forms ran past the right edge on a phone. Each
offset now starts at its column's breakpoint. The interface and network
detail lists were rows directly inside rows and sit in a column now;
the compute form loses a Bootstrap 3 thumbnail padding that broke its
gutters, and the add-rule dialog's hint is no longer cut off. The page
test checks both grid rules on every page.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-21 13:19:14 +03:00
catborise
e324e79fcb fix(templates): a wide table scrolls in its own box on a narrow screen
Twenty tables had no .table-responsive wrapper, so on a phone the user
list, compute list, VM lists, network and network filter pages scrolled
sideways as a whole. Each table now scrolls inside its box, as the
other ten already did. The page test walks the network page too, and
checks that every table on every page has the wrapper.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-21 12:57:26 +03:00
catborise
64057f8d05 fix(templates): icon-only buttons, selects and delete pages have accessible names
An accessibility scan (axe-core, WCAG 2 A/AA) of every page found 87
elements without a name. Icon-only buttons and links (the navbar's
administration menu on every page, the add/create buttons, the flavor
delete and back buttons) get a title, which is also their tooltip; the
labels of the language, architecture, chipset and filter selects point
at them; the memory bar is named. The shared delete confirmation page
had an empty <title> and heading, as no view passes "title": it says
"Delete <object>". The power-off form's aria-label was misspelled.
2026-10-19 16:15:06 +03:00
catborise
3a4488d34f fix(templates): pages parse without markup errors, element IDs are unique
Rendered through every URL on libvirt's test driver, the pages had
duplicate IDs and markup browsers repaired silently:

- Each flavor modal's "Advanced" button toggled #flav-add-collapse, the
  first modal's section; every modal now has its own. The flavor and
  template copies of the custom tab's control IDs, and the template
  tab's unused images input, are gone.
- The VM page's action icons and the XML editors were styled by
  repeated IDs; they use classes.
- Modal forms opened inside modal-body and closed after modal-footer;
  they wrap both. Stray end tags and duplicate attributes are removed,
  paragraphs no longer hold modals or rows, table heads have a row, and
  the create page closes its row and column.
- html lang is the active language instead of "en".

A test walks every URL as a superuser and fails on a repeated ID. The
console page still has no doctype: its full-height layout relies on
quirks mode and needs its own check.
2026-10-19 07:49:25 +03:00
catborise
c55f52f025 test: each app keeps its tests in a tests package
131 test modules sat next to the code they test; instances alone had
51 of them beside views.py and utils.py. They move into a
tests/ package per app, and the JavaScript checks into
webvirtcloud/tests/js. Each non-empty tests.py becomes tests/test_views.py;
the empty generated stubs are removed.

Imports of the app's own modules become absolute, paths built from
__file__ go one level up, the live helper is instances.tests.livetest,
and coverage omits */tests/*. The suite discovers the same 960 tests as
before.
2026-10-12 15:17:30 +03:00
catborise
4ae3fb2d53 feat(admin): list and remove the owners kept for vanished VMs
A VM that disappears from its compute keeps its owners in a tombstone
until INSTANCE_OWNERSHIP_RETENTION_DAYS pass, but nothing showed them.
Superusers now see them on the "Removed VMs" admin page (name, UUID,
owners, removed, expires) and can remove one, so a VM deleted on
purpose no longer hands its owners to a later VM with the same UUID.
2026-10-08 19:07:02 +03:00
catborise
c2fea2aa39 feat(accounts): failed logins lock a user for the address they come from
Nothing limited failed logins: a password could be guessed at the speed
the server answers. django-axes (8.3.1, Django 5.2, Python 3.10-3.13)
now counts them.

- A user is locked for the address the failed logins come from
  (username and IP together), so an attacker cannot lock someone out
  from elsewhere and users behind one NAT address do not share a lock.
  A successful login resets the count. axes sits in front of every
  authentication: the login page with or without OTP, LDAP and the API.
- The number of failed logins (default 5) and the lock time in minutes
  (default 15) are on the Settings page (LOGIN_FAILURE_LIMIT,
  LOGIN_LOCKOUT_MINUTES, added by migration appsettings.0014) and apply
  at once; an invalid value falls back to the default. Unlock with
  "manage.py axes_reset_username <username>".
- Behind the bundled nginx every request comes from 127.0.0.1; the
  client address is read from X-Real-IP, which nginx sets, but only for
  requests from a loopback address or from LOGIN_TRUSTED_PROXIES, so a
  forged header does not escape a lock.

settings.py.template gets the app, middleware, backend and AXES_*
settings; README tells existing installations what to copy. Tests that
call client.login now pass a request, which the axes backend needs.
The new tests fail with axes disabled.
2026-10-06 11:05:03 +03:00
catborise
940545c2c4 fix(admin): saving a user returns to ?next= only on this site
The user edit page redirected to its ?next= parameter after saving,
whatever it held: a link to the edit page with next=https://other.site
sent the administrator who saved the form to that site (CodeQL
py/url-redirection). The account page links there with a next of this
site.

next is now followed only when url_has_allowed_host_and_scheme accepts
it for this host, as user_instance_delete already does; otherwise the
user list. The test fails on the previous code for an absolute URL, a
scheme-relative one and a backslash variant.
2026-10-06 08:00:13 +03:00
catborise
2295673203 fix: every icon of the templates comes from the local icon cache
django-bootstrap-icons serves icons from static/icon_cache and fetches a
missing one from the CDN while the page renders; a server without
internet access shows an error text instead of the icon.

- copy (Clone on the instance lists), envelope-open ("Lost OTP?" page)
  and save (VM settings, network page) were not cached; they are now,
  fetched with django-bootstrap-icons itself.
- The user and group lists asked for 'exclamation-triangle ' with a
  trailing space, an icon that does not exist; the warning icon never
  showed.

A test checks that every {% bs_icon %} in the templates has its cache
file; it fails on the previous files.
2026-10-05 20:20:57 +03:00
catborise
405e6f1445 fix(admin): a new user's password reaches the database only hashed
The admin page that creates a user saved the form first, which wrote
the user with the raw password in the password column, and hashed it
with a second save afterwards. The raw password was in the database,
and in its journal and any copy taken meanwhile, and stayed there if
anything failed between the two writes.

- UserCreateForm hashes the password before the first save; groups
  and permissions are saved as before.
- The form applies AUTH_PASSWORD_VALIDATORS, after the user fields are
  set (so the similarity check sees the new username), as the password
  change forms already did. The settings template configures no
  validators, so nothing changes unless an installation sets them.

The tests record the SQL parameters of the request: the previous code
wrote the raw password in the INSERT; it also accepted passwords that
configured validators reject.
2026-10-05 18:39:33 +03:00
catborise
99dabbdcec fix(accounts): migrate no longer overrides the change-password permission
SHOW_PROFILE_EDIT_PASSWORD was replaced in 2020 by the change_password
permission, settable per user or group. A post_migrate hook applied the
old setting to all users so existing installs kept their behavior, and
asked to remove the setting afterwards; but the settings template still
set it to True. Every migrate, so every upgrade, therefore granted the
permission to all users again and undid an admin's per-user or
per-group choice (with False it would have revoked it from everyone).

- Migration accounts.0008 applies the setting one last time, if a
  settings.py still has it; the post_migrate hook is gone.
- New users get the permission when they are created (any path,
  including LDAP); the admin user form ticks it and saves the admin's
  choice afterwards.
- The template no longer sets SHOW_PROFILE_EDIT_PASSWORD; a check
  (accounts.W001) asks to remove it from existing settings, and the
  README upgrade notes say so.

The test that removes the permission and runs migrate fails on the
previous code. An upgrade from master with users was simulated: the
setting is applied once, and a permission removed afterwards stays
removed through the next migrate.
2026-10-04 20:26:53 +03:00
catborise
b04f656d2b ci: replace super-linter with ruff
Super-linter ran from the unpinned :latest image and, because of
branches-ignore: [master], never on pull requests to master. Ruff now
checks syntax errors and pyflakes rules (E9, F) on every push and PR.

Fixes the existing findings: unused imports and variables, an unused
duplicate import in instances/tests.py, and the star import in
accounts/views.py. settings-dev.py keeps its star import of the base
settings.
2026-10-04 01:08:11 +03:00
catborise
6dabd69445 fix(accounts): first-login password change is stored, not in the session
The forced change of the generated admin password was a session flag
set at login when the password still matched data/admin_password. A
new session after the file was gone bypassed it.

- UserAttributes.must_change_password (migration 0007) holds it; the
  admin created with a generated password gets it, and a password
  change clears it. ForcePasswordChangeMiddleware reads it.
- A system check (accounts.E001) stops the server unless the middleware
  follows AuthenticationMiddleware; the README has the settings change.
- If the generated password cannot be stored privately, provisioning
  stops without printing it and without creating the admin (one
  transaction for the user and its attributes).
- The admin is provisioned on any real migrate run while no user
  exists, so running migrate again after a failure creates it; flush
  (plan=None) and rollbacks of accounts are skipped.
- The post_migrate receivers check the migration state for the models
  they use, so a targeted migrate on a fresh database (e.g. migrate
  appsettings) no longer fails with 'no such table'.
2026-10-04 01:08:11 +03:00
catborise
cf1cde376b fix: require POST for user block/unblock and OTP email, flag console cookie
- user_block, user_unblock and admin_email_otp changed state on GET, so an
  <img> tag could lock a user out or trigger OTP emails through an
  admin's session. They now require POST; the buttons are CSRF forms.
- The console token cookie is set with httponly, SameSite=Lax and, on
  HTTPS, secure. Only novncd reads it; the page gets the token directly.
2026-10-02 22:44:00 +03:00
catborise
708b986e8e feat(security): apply Codex review hardening to console tokens and superuser authorization 2026-09-27 11:30:03 +03:00
catborise
c31ed28042 test: expand unit test coverage across admin, accounts, vrtManager, logs, appsettings, and datasource 2026-09-25 11:26:53 +03:00
catborise
58e215f755
Merge pull request #609 from catborise/master
show last login info for users
2023-10-06 16:31:10 +03:00
catborise
e47d909e41 show last login time of users 2023-10-06 16:15:35 +03:00
Amélie Krejčí
1b2b3a3bce
Get UserAttributes object using get_or_create
This is done to automatically create the UserAttributes object in case LDAP User Backend didn't create it.
2023-09-23 09:52:48 +02:00
catborise
b47ec2e8f9 bootstrap icon conversion fixes 2023-08-11 09:40:38 +03:00
Emre Serdengeçti
1cbdf76df6
logger updates (#31)
* Log for failed login attempts

* Logger configuration for logging to file

* interface fixes

* login log fix, added logged in too

* bootstrap icons setup

* font-awesome icons replaced with bootstrap icons

* replaced i-tags with django_bootstrap_icons

* removed icons library from project

* bug fix

---------

Co-authored-by: catborise <catborise@gmail.com>
2023-08-09 09:20:18 +03:00
cserma
ad9f1db643 added filter for logs 2023-04-13 12:25:42 +03:00
catborise
a1eab70e2d reorg imports and black format 2022-11-02 16:05:41 +03:00
catborise
217e106c8b format python code with black 2022-11-02 08:54:35 +03:00
catborise
97acbd9843 fix typo error 2022-09-05 15:36:37 +03:00
Info-IIG
de2dce7573 Added Technicians group 2022-06-14 15:10:33 +02:00
catborise
073b7b6717
Bootstrap5 (#17)
* Bootstrap5 migration
2021-07-07 14:12:38 +03:00
catborise
d9c23291de rearrange search functionality 2021-06-10 15:17:53 +03:00
catborise
610efd5520 add host name for logs beside others 2021-05-31 11:39:09 +03:00
catborise
508e3609be lint with black python. convert f style strings to old one. some small fixes 2020-11-05 12:34:31 +03:00
Real-Gecko
5a19f0c949 OTP improvements 2020-10-19 14:26:08 +06:00
Real-Gecko
01043ba8a8 Templates cleanup 2020-10-15 20:18:45 +06:00
Real-Gecko
1b3fd9d05e replaced ugettext_lazy with gettext_lazy 2020-10-14 18:27:57 +06:00
Real-Gecko
5172a9f619 Accounts app improvements and tests 2020-10-14 14:25:05 +03:00
catborise
922a8e62d1 fix some linter warnings 2020-09-25 15:57:17 +03:00
catborise
372ba5a0f2 fix create user error 2020-08-21 20:53:16 +03:00
catborise
de63d9746d add change other user password ability to superusers 2020-07-23 13:16:39 +03:00
Real-Gecko
1a3cc36ada Moved admin/common/form.html to project templates 2020-06-16 18:04:37 +06:00
Real-Gecko
2951d0b035 Cleaned up UserAttributes model 2020-06-16 11:26:58 +06:00
Real-Gecko
68f7376d15 Moved confirm_delete.html to project templates 2020-06-15 16:30:27 +06:00
Real-Gecko
354163fa1d Added pagination to logs list template 2020-06-15 15:48:32 +06:00
Real-Gecko
e4e86ed576 replaced django-fa with django-icons 2020-06-15 14:46:13 +06:00
catborise
b7fc8a5fea Language change activation. Fix typos and add new translations 2020-06-10 15:16:58 +03:00
catborise
7bb6bd310b update locale files and TR translations. 2020-06-10 11:15:14 +03:00
catborise
21fcb8cd64 workaround for django 2.2 bug related with '0011_update_proxy_permissions' migration 2020-06-09 11:44:29 +03:00