feat(android): Cockpit-App und Android-Agent per MDM ausrollen

Beide Apps lesen ihre Verbindungsdaten aus der App-Konfiguration des MDM
(Android Managed Configuration). Sind host und token gesetzt, verbindet sich
die App ohne QR-Code; die MDM-Werte haben Vorrang vor der Eingabe am Geraet.

- Cockpit (com.ariacockpit): host, token, port, tls; natives Modul
  MdmConfig, gelesen beim Start in rvs.loadConfig()
- Agent (de.hackersoft.ariaagent): zusaetzlich name, control, reply_mode;
  gelesen beim App-Start und nach dem Booten
- README: Abschnitt "Ausrollen per MDM" mit Screenshots unter docs/images/mdm/

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-11-15 15:59:49 +01:00
co-authored by Claude Opus 5.5
parent 5cb2f71c41
commit 5912e47e32
15 changed files with 300 additions and 2 deletions
@@ -38,6 +38,10 @@
android:allowBackup="false"
android:networkSecurityConfig="@xml/network_security_config"
android:theme="@style/AppTheme">
<!-- MDM-Ausrollen: welche Werte ein MDM mitgeben kann -->
<meta-data
android:name="android.content.APP_RESTRICTIONS"
android:resource="@xml/app_restrictions" />
<activity
android:name=".MainActivity"
android:label="@string/app_name"
@@ -27,6 +27,7 @@ class MainApplication : Application(), ReactApplication {
add(BackgroundAudioPackage())
add(ShareIntentPackage())
add(TlsTrustPackage())
add(MdmConfigPackage())
}
override fun getJSMainModuleName(): String = "index"
@@ -0,0 +1,52 @@
package com.ariacockpit
import android.content.Context
import android.content.RestrictionsManager
import com.facebook.react.ReactPackage
import com.facebook.react.bridge.Arguments
import com.facebook.react.bridge.NativeModule
import com.facebook.react.bridge.Promise
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.bridge.ReactContextBaseJavaModule
import com.facebook.react.bridge.ReactMethod
import com.facebook.react.uimanager.ViewManager
/**
* Verbindungsdaten aus dem MDM ("Managed Configuration" / App-Restrictions).
*
* Das MDM schreibt Schluessel/Werte fuer com.ariacockpit; welche es gibt,
* steht in res/xml/app_restrictions.xml. Die App liest sie beim Start —
* so muss beim Ausrollen niemand einen QR-Code scannen.
*
* Gibt nur die Schluessel zurueck, die das MDM wirklich gesetzt hat.
*/
class MdmConfigModule(ctx: ReactApplicationContext) : ReactContextBaseJavaModule(ctx) {
override fun getName() = "MdmConfig"
@ReactMethod
fun getConfig(promise: Promise) {
try {
val rm = reactApplicationContext.getSystemService(Context.RESTRICTIONS_SERVICE) as RestrictionsManager
val b = rm.applicationRestrictions
val out = Arguments.createMap()
for (key in b.keySet()) {
when (val v = b.get(key)) {
is String -> out.putString(key, v)
is Int -> out.putInt(key, v)
is Boolean -> out.putBoolean(key, v)
}
}
promise.resolve(out)
} catch (e: Exception) {
promise.resolve(Arguments.createMap())
}
}
}
class MdmConfigPackage : ReactPackage {
override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> =
listOf(MdmConfigModule(reactContext))
override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> =
emptyList()
}
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Texte fuer die MDM-Werte (res/xml/app_restrictions.xml) -->
<resources>
<string name="mdm_host_title" translatable="false">RVS-Host</string>
<string name="mdm_host_description" translatable="false">Hostname des RVS, z.B. aria-rvs.example.de</string>
<string name="mdm_port_title" translatable="false">RVS-Port</string>
<string name="mdm_tls_title" translatable="false">TLS verwenden</string>
<string name="mdm_token_title" translatable="false">Token</string>
<string name="mdm_token_description" translatable="false">Derselbe Token wie im QR-Code</string>
</resources>
@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Werte, die ein MDM beim Ausrollen mitgeben kann (Managed Configuration).
Sind host UND token gesetzt, verbindet sich die App damit — ohne QR-Code. -->
<restrictions xmlns:android="http://schemas.android.com/apk/res/android">
<restriction
android:key="host"
android:title="@string/mdm_host_title"
android:description="@string/mdm_host_description"
android:restrictionType="string" />
<restriction
android:key="port"
android:title="@string/mdm_port_title"
android:defaultValue="443"
android:restrictionType="integer" />
<restriction
android:key="tls"
android:title="@string/mdm_tls_title"
android:defaultValue="true"
android:restrictionType="bool" />
<restriction
android:key="token"
android:title="@string/mdm_token_title"
android:description="@string/mdm_token_description"
android:restrictionType="string" />
</restrictions>
+33
View File
@@ -491,7 +491,40 @@ class RVSConnection {
}
}
/**
* Verbindungsdaten aus dem MDM (Managed Configuration), falls beim Ausrollen
* gesetzt. Nur gueltig mit host UND token. Schluessel: res/xml/app_restrictions.xml
*/
async loadMdmConfig(): Promise<ConnectionConfig | null> {
try {
const Mdm: any = NativeModules.MdmConfig;
if (Platform.OS !== 'android' || !Mdm?.getConfig) return null;
const m = await Mdm.getConfig();
const host = String(m?.host ?? '').trim();
const token = String(m?.token ?? '').trim();
if (!host || !token) return null;
const port = Number(m.port);
return {
host,
token,
port: Number.isFinite(port) && port > 0 ? port : 443,
useTLS: m.tls !== false,
};
} catch {
return null;
}
}
async loadConfig(): Promise<ConnectionConfig | null> {
// Vom MDM gesetzte Werte haben Vorrang: so kommen geaenderte Zugangsdaten
// beim naechsten App-Start an, ohne dass jemand am Geraet etwas tun muss.
const mdm = await this.loadMdmConfig();
if (mdm) {
console.log('[RVS] Konfiguration aus dem MDM');
this.config = mdm;
await this.saveConfig(mdm);
return mdm;
}
try {
const data = await AsyncStorage.getItem(RVSConnection.STORAGE_KEY);
if (data) {