Commit Graph
517 Commits
Author SHA1 Message Date
Herculino Trotta e2f26b3629 fix(sharing): enforce object-level authorization outside the rules app
The rules endpoints were one instance of a pattern repeated across every
SharedObject-backed app. Route the rest through the same helper.

DCA entries were the worst case and are strictly wider than the reported
rules bug: DCAEntry has an unscoped default manager, so filtering by
strategy__id alone reached entries on strategies the caller could not see
at all. No public or shared strategy was needed -- only a guessable
integer. strategy_entry_add/edit/delete now resolve through the parent
strategy with via="strategy".

Every SharedObject delete view carried an inverted condition:

    if obj.owner != request.user and request.user in obj.shared_with.all():
        obj.shared_with.remove(request.user)
    else:
        obj.delete()

An object its owner had made public matched neither branch's intent and
fell through to delete(), so any authenticated user could destroy it.
Confirmed reachable for accounts, account groups, categories, tags,
entities and DCA strategies. The owner now deletes, a shared user revokes
only their own access, and anyone else gets a 403.

The API viewsets had no object-level check at all. DjangoModelPermissions
gated them shut for ordinary users, who hold no model permissions, so this
was not reachable in a default install -- but the check belongs there
regardless, and a user granted change_account in the admin could write any
visible account. SharedObjectPermission adds it for the SharedObject
viewsets, leaving reads to SharedObjectManager.

account_toggle_untracked only flips the calling user's own row in the
untracked_by m2m, so it takes READ rather than EDIT.

Refs GHSA-83g9-vjqf-2j5q
2026-09-01 23:15:41 -03:00
Herculino Trotta 039ad225d3 test(rules): cover object-level authorization for transaction rules
Regression tests for GHSA-83g9-vjqf-2j5q, one per endpoint the advisory
named plus the delete and view paths found alongside them: a non-owner
gets 403 on every mutation of a public or shared rule, and the object is
asserted unchanged afterwards.

Also covers the parts that are easy to regress in the other direction:
shared users keep read access, a shared user deleting only revokes their
own access, unowned rules stay claimable, and children of invisible rules
answer 404 rather than 403.

SharedObjectPredicateParityTests asserts is_visible_to agrees with
SharedObjectManager across every owner/visibility/shared combination.
The manager builds a Q and the predicate tests an instance, so they
cannot share an implementation and can otherwise drift apart.
2026-09-01 21:43:13 -03:00
Herculino Trotta 18d4ab7d11 fix(rules): enforce object-level authorization on rule endpoints
SharedObjectManager scopes querysets to what a user may see, which
includes other people's public and shared-with-them objects. Several
mutating rule endpoints treated that visibility as permission to write.

Generalise get_owned_object_or_403 into get_shared_object_or_error, which
takes an explicit access level instead of inferring one:

- READ requires the object to be visible; denial is 404 so the response
  does not confirm that an id exists.
- EDIT requires ownership; denial is 403, but only after the visibility
  check, so 403 never leaks the existence of an invisible object. This
  matters for TransactionRuleAction, whose manager is unscoped.

The previous owner_path resolved to a User and discarded the object, so
it could not express visibility at all. via= now points at the governing
SharedObject and is resolved with a plain getattr, so an unresolvable
path raises instead of silently granting access.

is_visible_to/is_editable_by replace is_accessible_by, which was never
called and tested visibility == "shared", a value that does not exist in
Visibility.

Also fixes two further holes in the same module:

- transaction_rule_delete fell through to delete() whenever the caller
  was not in shared_with, so any user could delete a public rule. Now
  only the owner deletes; a shared user revokes their own access.
- transaction_rule_view was read-only but is now explicitly READ, so
  rules shared with a user stay viewable.

The activate/deactivate control is hidden for rules the user does not
own, instead of rendering a button that always fails.

Refs GHSA-83g9-vjqf-2j5q
2026-09-01 21:25:46 -03:00
Moshe Levi 68a9286ce5 Fix BOLA on transaction-rule endpoints via get_owned_object_or_403
Add apps.common.functions.get_owned_object_or_403, an ownership-enforcing
variant of get_object_or_404, and apply it across every rules handler that
resolved a TransactionRule / (UpdateOrCreate)TransactionRuleAction from a URL id
without an owner check. Mirrors the check in transaction_rule_edit
(obj.owner and obj.owner != request.user); objects with no owner remain
accessible, preserving existing behaviour. Nested ownership (actions owned via
their parent rule) is handled with owner_path='rule.owner'.
2026-08-31 20:14:40 +03:00
Herculino Trotta cd48edddab feat(transactions): add htmx filter presets 2026-08-16 01:40:11 -03:00
Herculino Trotta 9e135560fe feat(transactions): add filter preset model 2026-08-16 01:40:03 -03:00
Herculino Trotta 8e09f3e8d8 feat(net-worth): add consolidated chart lines 2026-08-15 14:36:13 -03:00
Herculino Trotta ea3e9fd68f feat(monthly): show asset accounts/currencies on specific sumaries 2026-08-15 01:30:57 -03:00
Herculino Trotta 2ab6621205 Merge pull request #581 from obervinov/fix/recurring-tags-scheduler
fix: recurring tags and entities dropped when generated for another user
2026-08-13 00:00:48 -03:00
obervinov 11a6b3faa6 fix(transactions): recurring tags and entities dropped when generated for another user 2026-08-12 22:33:01 +04:00
Herculino Trotta 743951a862 feat(automatic-exchange-rates): Add Yahoo Finance as a provider via yfinance 2026-07-18 15:42:51 -03:00
Herculino Trotta 5a80a3b1d3 feat: replace mp3 sounds with cuelume; deprecate volume setting 2026-07-17 21:28:13 -03:00
obervinovandClaude Opus 4.8 ca14f77f41 test: cover demo-mode block on revoked-token delete
Parity with the existing demo-mode tests for token create/revoke.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 00:00:10 +04:00
obervinovandClaude Opus 4.8 0fb37a59fa feat: add delete button for revoked API tokens
Revoked tokens previously stayed in the list with no way to remove them.
Adds a delete action (hard delete, scoped to the owner, gated behind
demo mode) shown on revoked rows, alongside the existing revoke action on
active ones.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-29 23:55:38 +04:00
Herculino Trotta 106d721279 feat: add demo mode tests to ensure API is disabled on it 2026-06-27 18:02:31 -03:00
Herculino Trotta d0e9c05283 feat: disable oauth and token creation while on demo mode 2026-06-27 18:02:03 -03:00
Herculino Trotta 7f5a91c11f fix: wrong Guam timezone string 2026-06-27 17:56:24 -03:00
Herculino Trotta 009a7038c8 style: improve api token box look 2026-06-27 17:56:05 -03:00
4273c541c5 Add API tokens and OAuth2 client support for external integrations
- Personal API tokens (model, user-settings UI, admin, management command,
  DRF auth class) for non-interactive API access from automations like n8n.
  Raw token shown once; only a SHA-256 hash is stored; last_used_at writes
  are throttled.
- OAuth2 authorization server via django-oauth-toolkit with authorization
  server metadata and optional, off-by-default Dynamic Client Registration
  (RFC 7591), so remote OAuth/MCP clients can authenticate and self-register.
- Tests for token auth, DCR gating and the management commands, plus
  .env.example and README documentation.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 19:15:31 +04:00
Herculino Trotta 6a19381672 feat(transactions): add attachments 2026-06-06 04:33:06 -03:00
Herculino Trotta d72ff3cdf5 fix(rules): allow category expressions to clear categories 2026-05-02 16:16:27 -03:00
Herculino Trotta 63c69e5c6a test(api): expect unauthorized for anonymous requests 2026-05-02 16:16:08 -03:00
Herculino Trotta 78171183cc test(currencies): avoid test discovery collision 2026-05-02 16:15:48 -03:00
Herculino Trotta 34a2b6bfd4 fix(procrastinate): close Django connections around jobs 2026-05-02 16:15:26 -03:00
Herculino Trotta 5527389196 fix: deduplication breaks when given m2m fields 2026-04-18 14:47:19 +00:00
Herculino Trotta 3fd0fba1b8 Merge pull request #513 from pawelaugustyn/feat/default-account
feat: default account for new transactions
2026-02-15 23:24:08 -03:00
Herculino Trotta c787565c04 refactor: move help_text to model definition 2026-02-15 23:22:52 -03:00
Herculino Trotta 0413921dbe fix: migrations set default as 0 instead of null 2026-02-15 23:22:10 -03:00
pawelaugustyn 9ecf8279b4 feat: default account for new transactions 2026-02-15 22:59:18 +01:00
Herculino Trotta 86cf625158 Merge pull request #518 from eitchtee/dev
feat(auth): trust OIDC connections and automatically connect them with local accounts
2026-02-15 14:43:33 -03:00
Herculino Trotta ea097ab6f0 feat(auth): trust OIDC connections and connect them with local accounts 2026-02-15 14:41:45 -03:00
Herculino Trotta 163f2f4e5b Merge pull request #504 from eitchtee/dev
feat: add QIF import
2026-01-25 16:54:02 -03:00
Herculino Trotta d5bbad7887 feat: add QIF import 2026-01-25 16:46:56 -03:00
Herculino Trotta 49cac0588e add tests and fix missing get_queryset 2026-01-11 12:20:27 +01:00
icovada 3b2b6d6473 Query all DCA Strategies 2026-01-11 12:19:57 +01:00
icovada db30bcbeb7 Remove filtering function superseesed by search_fields 2026-01-11 12:19:57 +01:00
icovada a122733a47 Enable filtering and sorting on all API views 2026-01-11 12:19:30 +01:00
Herculino Trotta d756286135 Merge pull request #496 from eitchtee/dev
feat(automatic-exchange-rate): track and display unsuccessful runs
2026-01-10 17:49:44 -03:00
Herculino Trotta 96318f003d Merge branch 'main' into rest_filtering 2026-01-10 17:43:45 -03:00
Herculino Trotta 1a0412264a add tests and fix missing get_queryset 2026-01-10 17:42:37 -03:00
icovada c015b78cd6 Apply CustomNumberPagination to all API views 2026-01-10 17:14:53 +00:00
Herculino Trotta 50e5492ea1 feat(automatic-exchange-rate): track unsuccessful runs 2026-01-10 14:10:21 -03:00
Herculino Trotta c83b1bf2d6 Merge pull request #495 from eitchtee/dev
feat: add late section to monthly and all views (w/ default ordering)
2026-01-10 02:53:30 -03:00
Herculino Trotta b074ef7929 feat: add late section to monthly and all views (w/ default ordering) 2026-01-10 02:52:46 -03:00
Herculino Trotta 0a03745ce6 Merge pull request #493 from eitchtee/dev
fix(dca): strategy api endpoint returns nothing
2026-01-09 23:53:04 -03:00
Herculino Trotta ff4bd79634 fix(dca): strategy api endpoint returns nothing 2026-01-09 23:51:31 -03:00
Herculino Trotta 48e43ac031 Merge pull request #492 from eitchtee/dev
fix(transactions): empty internal_id raises duplicate error when editing via django admin
2026-01-09 23:27:01 -03:00
Herculino Trotta dd6a390e6b fix(transactions): empty internal_id raises duplicate error when editing via django admin 2026-01-09 23:25:13 -03:00
icovada 0c961a8250 Query all DCA Strategies 2026-01-08 22:51:50 +01:00
icovada e28c651973 Remove filtering function superseesed by search_fields 2026-01-08 22:51:50 +01:00