Skip to content

feat(policy): enforce server client policies on Android - #32

Merged
CallMeTechie merged 2 commits into
mainfrom
feat/client-policies
Oct 2, 2026
Merged

CallMeTechie merged 2 commits into
mainfrom
feat/client-policies

Conversation

@CallMeTechie

Copy link
Copy Markdown
Owner

Android part of Client-Richtlinien vom Server (server: CallMeTechie/gatecontrol#256).

What

  • ClientPolicy (core/common): parsing with per-field unrestricted fallbacks, allowed split modes, forced auto-connect, always-on, JSON for the cache.
  • ClientPolicyRepository (core/data): the last known policy lives in DataStore, so it applies offline. A policy that was never fetched means no restriction.
  • ApiClient.getClientPolicy(If-None-Match) → 304 handling. policyVersion on the heartbeat and permissions answers.
  • ClientPolicyManager:
    • refreshes on app start, on a changed policyVersion (heartbeat/permissions), and after a server change (reset)
    • when the server is unreachable or old (404), the last known policy stays
    • forces auto-connect and clamps a split mode that is not allowed

Enforcement

Policy Android
auto-connect required setting forced on and locked. Connects on boot, on app start and when the policy arrives (VPN consent needed)
auto-connect always_on additionally no disconnect from the app or the Quick Settings tile
autostart required/forbidden maps to connect-on-boot (forced on/off)
split-tunnel modes disallowed modes are disabled. TunnelConnector clamps the mode (full tunnel if allowed). A locked server preset keeps priority (existing adminLocked)
lockSettings auto-connect, split mode, networks and apps are locked. Theme and language stay free
lockServer server page and config import are hidden and refused

Locked controls show "Vom Administrator festgelegt" / "Set by your administrator". Settings shows a "managed" banner.

Android limits

An app cannot switch on the system Always-on VPN or "Block connections without VPN". When the policy requires a kill switch or always-on, the app shows a prominent card with a button to the system VPN settings, on the VPN screen and in Settings. Enrollment through an external setup link is still possible under lockServer, because it needs a fresh admin setup code. Documented in the README, together with the note that the policy is not a security boundary against the device owner.

Tests

./gradlew testDebugUnitTest lintDebug assembleDebug: green locally, 421 unit tests including new ones:

  • ClientPolicyTest
  • ClientPolicyManagerTest: fetch/304/offline/version hint/reset
  • API client policy + heartbeat version
  • connector clamp
  • tile and VM disconnect refusal
  • settings locks

🤖 Generated with Claude Code

https://claude.ai/code/session_016xX1efcZF1f6G9rhmaJNLD


Generated by Claude Code

Claude added 2 commits October 2, 2026 20:43
- ClientPolicy (core/common): parsing with unrestricted fallbacks, allowed
  split modes, forced auto-connect, always-on, JSON for the cache.
- ClientPolicyRepository (core/data): last known policy in DataStore
  (applies offline; never fetched = no restriction).
- ApiClient: GET /api/v1/client/policy with If-None-Match (304),
  policyVersion on heartbeat and permissions answers.
- ClientPolicyManager: refresh on app start, on a new policyVersion
  (heartbeat / permissions), reset after a server change; forces the
  auto-connect setting and clamps a disallowed split-tunnel mode.
- Enforcement: boot auto-connect follows the policy, required/always-on
  connect on app start (VPN consent needed), always-on refuses the in-app
  and tile disconnect, TunnelConnector clamps the split mode (locked
  server preset keeps priority), settings/split-tunnel/server screens
  show locked controls with "Vom Administrator festgelegt", server change
  and config import hidden under lockServer.
- Android limits: the app cannot turn on system Always-on VPN or "Block
  connections without VPN"; a prominent card with a button to the system
  VPN settings is shown when the policy requires a kill switch/always-on.
  Documented in the README (not a security boundary).
- de/en strings, unit tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016xX1efcZF1f6G9rhmaJNLD
Brings in the support bundle feature. Conflicts resolved by keeping both
sides: heartbeat answer (policyVersion + support request), API models
and imports, SettingsViewModel (support collector/uploader + client
policy manager), strings and tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016xX1efcZF1f6G9rhmaJNLD
@CallMeTechie
CallMeTechie merged commit d2b1702 into main Oct 2, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants