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
30 changes: 30 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,36 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

- Mail standard filter write endpoints (#116, #13 write slice):
`kasapi-cli mail filters add <mail-login> --filter <item> [--filter
<item>...]` and `… delete <mail-login>` wire
`add_mailstandardfilter` / `delete_mailstandardfilter`. Both are
gated by the #109 confirmation prompt: the KAS API has no
`update_mailstandardfilter` action, so `add` *replaces* the configured
filter chain wholesale (items previously set but missing from the new
`--filter` list are dropped), which is destructive to recover from
without a stored copy. Both honour `--dry-run` (#132) and emit a #131
audit record. Repeatable `--filter` items are joined with `;` on the
wire (the format the captured `add_mailstandardfilter` request
fixture uses); each item is either a bare filter id (e.g. `pdw`) or
`<filter-id>:<option>=<value>` (e.g. `spamc_move:move=Spam`). Items
must be non-empty and must not contain `;` themselves. `delete` takes
only `<mail-login>` and removes the whole chain in one shot — the KAS
API exposes no per-item delete — so its prompt verb is "remove all
standard filters of mail account" rather than the bare "delete" used
elsewhere, to make the all-at-once effect explicit. **Known API
quirk**: `delete_mailstandardfilter` sometimes surfaces an
envelope-level SOAP fault (an internal `sizeof()` PHP error) even
when the chain was in fact removed on the server; the fault is
surfaced verbatim, and `docs/usage/destructive-writes.md` documents
the verification path (`mail accounts get <login>` → `mail_spamfilter`).

- A new shared envelope-level fault fixture
`testdata/response_failed_internal_server_error.xml` captures the
generic PHP `sizeof()` runtime error wrapped in a SOAP-ENV:Server
fault. It is exercised by the soap fixture walker and by
`mailfilter.Client.Delete`'s "fault surfaced verbatim" test.

- Mail account write endpoints (#114, #13 write slice):
`kasapi-cli mail accounts add <address> --password <pw> [field flags]`,
`… update <mail-login> [field flags]` and
Expand Down
2 changes: 1 addition & 1 deletion docs/cli/kasapi-cli_mail.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ Inspect mail accounts and filters; inspect and manage forwards and mailing lists

* [kasapi-cli](kasapi-cli.md) - Command-line client for the All-Inkl KAS API
* [kasapi-cli mail accounts](kasapi-cli_mail_accounts.md) - Inspect and manage mail accounts (get/add/update/delete_mailaccount)
* [kasapi-cli mail filters](kasapi-cli_mail_filters.md) - Inspect mail standard filters (get_mailstandardfilter)
* [kasapi-cli mail filters](kasapi-cli_mail_filters.md) - Inspect and manage mail standard filters (get/add/delete_mailstandardfilter)
* [kasapi-cli mail forwards](kasapi-cli_mail_forwards.md) - Inspect and manage mail forwards (get/add/update/delete_mailforward)
* [kasapi-cli mail lists](kasapi-cli_mail_lists.md) - Inspect and manage mailing lists (get/add/update/delete_mailinglist)

4 changes: 3 additions & 1 deletion docs/cli/kasapi-cli_mail_filters.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
## kasapi-cli mail filters

Inspect mail standard filters (get_mailstandardfilter)
Inspect and manage mail standard filters (get/add/delete_mailstandardfilter)

### Options

Expand Down Expand Up @@ -29,5 +29,7 @@ Inspect mail standard filters (get_mailstandardfilter)
### SEE ALSO

* [kasapi-cli mail](kasapi-cli_mail.md) - Inspect mail accounts and filters; inspect and manage forwards and mailing lists
* [kasapi-cli mail filters add](kasapi-cli_mail_filters_add.md) - Set the standard-filter chain on a mail account (add_mailstandardfilter)
* [kasapi-cli mail filters delete](kasapi-cli_mail_filters_delete.md) - Remove every standard filter from a mail account (delete_mailstandardfilter)
* [kasapi-cli mail filters list](kasapi-cli_mail_filters_list.md) - List the available standard mail filters (get_mailstandardfilter)

46 changes: 46 additions & 0 deletions docs/cli/kasapi-cli_mail_filters_add.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
## kasapi-cli mail filters add

Set the standard-filter chain on a mail account (add_mailstandardfilter)

### Synopsis

Set the configured standard-filter chain on a mail account via
add_mailstandardfilter. Each --filter is one item of the chain, either a
bare filter id (e.g. "pdw") or "<filter-id>:<option>=<value>" (e.g.
"spamc_move:move=Spam"). Items are joined with ';' on the wire and the
chain replaces what was configured before — there is no per-item add.
Use "mail filters list" for the available filter ids.

```
kasapi-cli mail filters add <mail-login> --filter <item> [--filter <item>...] [flags]
```

### Options

```
--filter stringArray filter chain item (repeatable; replaces the full chain)
-h, --help help for add
```

### Options inherited from parent commands

```
--audit-log string append a JSON-Lines audit record for each write action to this file (also KAS_AUDIT_LOG); a logfmt line always goes to stderr regardless
--auth-data string KAS auth data (overrides config and KAS_AUTHDATA)
--auth-type string KAS auth strategy: 'plain' = send password on each KasApi call (no KasAuth, no 2FA support); 'session' = bootstrap via KasAuth and reuse the credential token. Overrides config and KAS_AUTHTYPE.
--config string path to the kasapi-cli config file (overrides the default location)
--dry-run preview a destructive command's KAS request (action + redacted parameters) and exit 0 without dispatching or prompting; honours --output
--login string KAS login (overrides config and KAS_LOGIN)
--otp string 2FA one-time PIN — sent to KasAuth as session_2fa during the credential-token bootstrap. Requires auth_type=session; the KAS API does not document 2FA on direct kas_auth_type=plain calls.
-o, --output string output format: json|yaml|table (default table)
--profile string profile to select from the config file (overrides default_profile)
--session-lifetime int session_lifetime in seconds passed to KasAuth (1..30000); 0 keeps the server default. Requires auth_type=session.
--session-update-lifetime string session_update_lifetime passed to KasAuth ('Y' = sliding window, 'N' = fixed). Empty omits the parameter. Requires auth_type=session.
-v, --verbose enable verbose logging on stderr
-y, --yes skip confirmation prompts on destructive operations
```

### SEE ALSO

* [kasapi-cli mail filters](kasapi-cli_mail_filters.md) - Inspect and manage mail standard filters (get/add/delete_mailstandardfilter)

48 changes: 48 additions & 0 deletions docs/cli/kasapi-cli_mail_filters_delete.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
## kasapi-cli mail filters delete

Remove every standard filter from a mail account (delete_mailstandardfilter)

### Synopsis

Remove every configured standard filter from a mail account via
delete_mailstandardfilter. The KAS action takes only the mail login and
drops the whole chain in one shot — there is no per-item delete.

Note: the API sometimes surfaces an envelope-level SOAP fault (an
internal "sizeof()" PHP error) even when the chain is in fact removed
on the server. The fault is surfaced verbatim; if you see it, verify
the actual outcome with "mail accounts get <login>" — the configured
chain is reported in the mail_spamfilter field.

```
kasapi-cli mail filters delete <mail-login> [flags]
```

### Options

```
-h, --help help for delete
```

### Options inherited from parent commands

```
--audit-log string append a JSON-Lines audit record for each write action to this file (also KAS_AUDIT_LOG); a logfmt line always goes to stderr regardless
--auth-data string KAS auth data (overrides config and KAS_AUTHDATA)
--auth-type string KAS auth strategy: 'plain' = send password on each KasApi call (no KasAuth, no 2FA support); 'session' = bootstrap via KasAuth and reuse the credential token. Overrides config and KAS_AUTHTYPE.
--config string path to the kasapi-cli config file (overrides the default location)
--dry-run preview a destructive command's KAS request (action + redacted parameters) and exit 0 without dispatching or prompting; honours --output
--login string KAS login (overrides config and KAS_LOGIN)
--otp string 2FA one-time PIN — sent to KasAuth as session_2fa during the credential-token bootstrap. Requires auth_type=session; the KAS API does not document 2FA on direct kas_auth_type=plain calls.
-o, --output string output format: json|yaml|table (default table)
--profile string profile to select from the config file (overrides default_profile)
--session-lifetime int session_lifetime in seconds passed to KasAuth (1..30000); 0 keeps the server default. Requires auth_type=session.
--session-update-lifetime string session_update_lifetime passed to KasAuth ('Y' = sliding window, 'N' = fixed). Empty omits the parameter. Requires auth_type=session.
-v, --verbose enable verbose logging on stderr
-y, --yes skip confirmation prompts on destructive operations
```

### SEE ALSO

* [kasapi-cli mail filters](kasapi-cli_mail_filters.md) - Inspect and manage mail standard filters (get/add/delete_mailstandardfilter)

2 changes: 1 addition & 1 deletion docs/cli/kasapi-cli_mail_filters_list.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,5 +32,5 @@ kasapi-cli mail filters list [flags]

### SEE ALSO

* [kasapi-cli mail filters](kasapi-cli_mail_filters.md) - Inspect mail standard filters (get_mailstandardfilter)
* [kasapi-cli mail filters](kasapi-cli_mail_filters.md) - Inspect and manage mail standard filters (get/add/delete_mailstandardfilter)

1 change: 1 addition & 0 deletions docs/usage/destructive-writes.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ the deviations.
| [`databases`](https://github.com/chmmou/kasapi-cli/issues/122) | **Louder delete prompt**: `delete_database` uses the verb `"permanently delete"` (vs the bare `"delete"` every other slice uses) because the action drops the database AND every row in it — the loudest data-loss surface of the v0.2.0 write phase. Password key splits between actions: `--password` → `database_password` on add, `database_new_password` on update. `--allowed-hosts` is **optional**: an empty value is the KAS API's documented "any host may connect" wildcard, sent verbatim on the wire. |
| [`ddnsusers`](https://github.com/chmmou/kasapi-cli/issues/121) | **No `_new_password` split**: `--password` maps to `dyndns_password` on both `add` and `update`. `update_ddnsuser` accepts `--target-ipv4` / `--target-ipv6` instead of `add`'s legacy `--target-ip`; the ipv4/ipv6 keys are undocumented in the KAS API docs but verified to work against the live system (the captured update request fixture is authoritative). |
| [`mail accounts`](https://github.com/chmmou/kasapi-cli/issues/114) | **Louder delete prompt**: `delete_mailaccount` uses `"permanently delete"` (shared with `databases`) — it drops the mailbox AND every message in it. `add` splits the address on the last `@` into `local_part` / `domain_part`; `update`/`delete` address the account by its generated `mail_login`. Password key splits between actions: `--password` → `mail_password` on add, `mail_new_password` on update. `add`'s Y/N/text toggles and XLIST folder names default to the KAS API's own defaults; `--responder` is passed through verbatim (`N`, `Y`, or a `<start>\|<end>` timestamp range). |
| [`mail filters`](https://github.com/chmmou/kasapi-cli/issues/116) | **Both `add` and `delete` are gated**, not just `delete`: there is no `update_mailstandardfilter`, so `add_mailstandardfilter` *replaces* the configured chain wholesale (any previously-set items not in the new `--filter` list are dropped). Repeatable `--filter <item>` is joined with `;` on the wire (items must not contain `;` and not be empty). `delete_mailstandardfilter` takes only `<mail-login>` and removes the whole chain in one shot; the prompt phrases this as *"remove all standard filters of mail account &lt;login&gt;"* to make the all-at-once effect explicit. **Known API quirk**: `delete` sometimes returns an envelope-level SOAP fault (an internal `sizeof()` PHP error) even when the chain was in fact removed on the server. The fault is surfaced verbatim — if you see it, verify the actual outcome via `kasapi-cli mail accounts get <login>` (the configured chain is reported in the `mail_spamfilter` field). |

## The contract

Expand Down
83 changes: 81 additions & 2 deletions internal/cli/mail.go
Original file line number Diff line number Diff line change
Expand Up @@ -190,9 +190,13 @@ func newMailListsGetCmd(opts *RootOptions) *cobra.Command {
func newMailFiltersCmd(opts *RootOptions) *cobra.Command {
cmd := &cobra.Command{
Use: "filters",
Short: "Inspect mail standard filters (get_mailstandardfilter)",
Short: "Inspect and manage mail standard filters (get/add/delete_mailstandardfilter)",
}
cmd.AddCommand(newMailFiltersListCmd(opts))
cmd.AddCommand(
newMailFiltersListCmd(opts),
newMailFiltersAddCmd(opts),
newMailFiltersDeleteCmd(opts),
)
return cmd
}

Expand All @@ -207,6 +211,81 @@ func newMailFiltersListCmd(opts *RootOptions) *cobra.Command {
}
}

func newMailFiltersAddCmd(opts *RootOptions) *cobra.Command {
var items []string
cmd := &cobra.Command{
Use: "add <mail-login> --filter <item> [--filter <item>...]",
Short: "Set the standard-filter chain on a mail account (add_mailstandardfilter)",
Long: `Set the configured standard-filter chain on a mail account via
add_mailstandardfilter. Each --filter is one item of the chain, either a
bare filter id (e.g. "pdw") or "<filter-id>:<option>=<value>" (e.g.
"spamc_move:move=Spam"). Items are joined with ';' on the wire and the
chain replaces what was configured before — there is no per-item add.
Use "mail filters list" for the available filter ids.`,
Args: cobra.ExactArgs(1),
RunE: runWriteE(opts, func(args []string) (writeSpec, error) {
if len(items) == 0 {
return writeSpec{}, fmt.Errorf("at least one --filter is required")
}
// Validate + join here, before the writeSpec is built, so the
// dry-run preview and the audit record see the exact chain
// string that will be dispatched and an invalid item cannot
// reach the audit log with a silently-empty filter field.
chain, err := mailfilter.JoinFilters(items)
if err != nil {
return writeSpec{}, err
}
login := args[0]
return writeSpec{
action: "add_mailstandardfilter",
destructive: true,
confirm: ConfirmAction{Verb: "replace the standard-filter chain of", Resource: "mail account", ID: login},
params: mailfilter.AddParams(login, chain),
dispatch: func(c *api.Client, ctx context.Context) (string, error) {
if derr := mailfilter.NewClient(c).Add(ctx, login, items); derr != nil {
return "", derr
}
return "configured mail standard filter for " + login, nil
},
}, nil
}),
}
cmd.Flags().StringArrayVar(&items, "filter", nil, "filter chain item (repeatable; replaces the full chain)")
return cmd
}

func newMailFiltersDeleteCmd(opts *RootOptions) *cobra.Command {
return &cobra.Command{
Use: "delete <mail-login>",
Short: "Remove every standard filter from a mail account (delete_mailstandardfilter)",
Long: `Remove every configured standard filter from a mail account via
delete_mailstandardfilter. The KAS action takes only the mail login and
drops the whole chain in one shot — there is no per-item delete.

Note: the API sometimes surfaces an envelope-level SOAP fault (an
internal "sizeof()" PHP error) even when the chain is in fact removed
on the server. The fault is surfaced verbatim; if you see it, verify
the actual outcome with "mail accounts get <login>" — the configured
chain is reported in the mail_spamfilter field.`,
Args: cobra.ExactArgs(1),
RunE: runWriteE(opts, func(args []string) (writeSpec, error) {
login := args[0]
return writeSpec{
action: "delete_mailstandardfilter",
destructive: true,
confirm: ConfirmAction{Verb: "remove all standard filters of", Resource: "mail account", ID: login},
params: mailfilter.DeleteParams(login),
dispatch: func(c *api.Client, ctx context.Context) (string, error) {
if derr := mailfilter.NewClient(c).Delete(ctx, login); derr != nil {
return "", derr
}
return "removed mail standard filters for " + login, nil
},
}, nil
}),
}
}

func newMailAccountsCmd(opts *RootOptions) *cobra.Command {
cmd := &cobra.Command{
Use: "accounts",
Expand Down
Loading
Loading