This my android code here in identity detail screen on click of service I need to browse it but it is giving unable to connect
package com.intrusion.endpoint.ui.screens
import android.content.Intent
import android.net.Uri
import android.util.Log
import android.widget.Toast
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.*ArrowBack*
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.*LocalContext*
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.intrusion.endpoint.IntrusionShieldApp
import com.intrusion.endpoint.model.TunnelModel
import com.intrusion.endpoint.net.dns.DnsLookupManager
import com.intrusion.endpoint.ui.theme.*
import com.intrusion.endpoint.ui.viewmodel.ZitiInfoViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlinx.serialization.json.*
import org.openziti.tunnel.Service
data class ServiceInterceptInfo(
val ports: List<Pair<Int, Int>>,
val protocols: List<String>
)
fun resolveBrowsableUrl(service: Service): List<String> {
val intercept = service.config["intercept.v1"]?.*jsonObject*
* *val addresses = intercept?.get("addresses")?.*jsonArray*?.*mapNotNull* **{**
** it**.*jsonPrimitive*.*contentOrNull*
* ***}** ?: return *emptyList*()
val baseHost = addresses.*firstOrNull* **{** !**it**.*startsWith*("*.") **}** ?: service.name
val ports = *extractServiceInterceptInfo*(service.config)?.ports?.*map* **{ it**.first **}** ?: *listOf*(443)
return ports.*map* **{** port **->**
** **val scheme = if (port == 443 || port == 9443) "https" else "http"
if ((port == 443 && scheme == "https") || (port == 80 && scheme == "http")) {
"$scheme://$baseHost"
} else {
"$scheme://$baseHost:$port"
}
**}**
}
/*fun resolveBrowsableUrl(service: org.openziti.tunnel.Service): String {
val intercept = service.config["intercept.v1"]?.jsonObject
val addresses = intercept?.get("addresses")?.jsonArray?.mapNotNull {
it.jsonPrimitive.contentOrNull
}
val url = addresses?.firstOrNull()?.replace(" ", "-")?.lowercase()
?: service.name.replace(" ", "-").lowercase()
// Prefer non-wildcard public-looking domain
val preferred = addresses!!.firstOrNull {
!it.startsWith("*.") && it.contains('.') && !it.endsWith(".zta")
}
return "https://${preferred ?: service.name}"
}*/
/*fun isIpInZitiSubnet(ip: String?): Boolean {
if (ip == null) return false
val parts = ip.split(".").mapNotNull { it.toIntOrNull() }
if (parts.size != 4) return false
return (parts[0] == 100 && parts[1] in 64..127) || // 100.64.0.0/10
(parts[0] == 10) // allow 10.x.x.x too
}*/
fun isIpInZitiSubnet(ip: String?): Boolean {
if (ip == null) return false
val parts = ip.*split*(".").*mapNotNull* **{ it**.*toIntOrNull*() **}**
** **if (parts.size != 4) return false
val first = parts[0]
val second = parts[1]
// Block 127.x.x.x as it's loopback/blocked
if (first == 127) return false
// Accept 100.64.0.0/10 and 10.x.x.x
return (first == 100 && second in 64..127) || (first == 10)
}
// Extracts all (low, high) port ranges per service/hostname
fun extractServiceInterceptInfo(config: Map<String, JsonElement>): ServiceInterceptInfo? {
val intercept = config["intercept.v1"]?.*jsonObject* ?: return null
val portRanges = intercept["portRanges"]?.*jsonArray*?.*mapNotNull* **{**
** **val obj = **it**.*jsonObject*
* *val low = obj["low"]?.*jsonPrimitive*?.*intOrNull*
* *val high = obj["high"]?.*jsonPrimitive*?.*intOrNull*
* *if (low != null && high != null) Pair(low, high) else null
**}** ?: *emptyList*()
val protocols = intercept["protocols"]?.*jsonArray*?.*mapNotNull* **{**
** it**.*jsonPrimitive*.*contentOrNull*
* ***}** ?: *emptyList*()
return ServiceInterceptInfo(ports = portRanges, protocols = protocols)
}
// Finds port ranges by matching service name to hostname or wildcard
fun findPortRangesForService(serviceName: String, portsMap: Map<String, List<Pair<Int, Int>>>): List<Pair<Int, Int>> {
return portsMap.entries.*find* **{** (host, _) **->**
** **host == serviceName || (host.*startsWith*("*.") && serviceName.*endsWith*(host.*removePrefix*("*.")))
**}**?.value ?: *emptyList*()
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun IdentityDetailsScreen(
tunnelModel: TunnelModel,
identityId: String,
zitiInfoViewModel: ZitiInfoViewModel,
dnsLookupManager: DnsLookupManager,
onBack: () -> Unit
) {
var identity by remember **{** *mutableStateOf*(tunnelModel.identity(identityId)) **}**
** **val context = *LocalContext*.current
val coroutineScope = rememberCoroutineScope()
var showDialog by remember **{** *mutableStateOf*(false) **}**
** **var shouldDeleteIdentity by remember **{** *mutableStateOf*(false) **}**
** **val snackbarHostState = remember **{** SnackbarHostState() **}**
** **val servicesState = remember **{** *mutableStateOf*(*emptyList*<org.openziti.tunnel.Service>()) **}**
** **val isEnabledState = remember **{** *mutableStateOf*(false) **}**
** **DisposableEffect(identity) **{**
** **val servicesLiveData = identity?.services()
val servicesObserver = androidx.lifecycle.Observer<List<org.openziti.tunnel.Service>> **{**
** **servicesState.value = **it** ?: *emptyList*()
**}**
** **servicesLiveData?.observeForever(servicesObserver)
val enabledLiveData = identity?.enabled()
val enabledObserver = androidx.lifecycle.Observer<Boolean> **{**
** **isEnabledState.value = **it** == true
**}**
** **enabledLiveData?.observeForever(enabledObserver)
onDispose **{**
** **servicesLiveData?.removeObserver(servicesObserver)
enabledLiveData?.removeObserver(enabledObserver)
**}**
** }**
** **if (identity == null) {
LaunchedEffect(Unit) **{** onBack() **}**
** **return
}
/* LaunchedEffect(shouldDeleteIdentity) {
if (shouldDeleteIdentity) {
val name = identity?.name()?.value ?: "Identity"
identity?.delete()
zitiInfoViewModel.clearIdentity(context)
identity = null
onBack()
}
}*/
LaunchedEffect(shouldDeleteIdentity) **{**
** **if (shouldDeleteIdentity) {
try {
identity?.setEnabled(false)
delay(2000) // give it time to cleanup routes/interfaces
identity?.delete()
} catch (e: Exception) {
Log.e("Ziti", "Delete failed", e)
} finally {
zitiInfoViewModel.clearIdentity(context)
onBack() // ⬅️ move before identity = null
identity = null
}
}
**}**
** **Scaffold(
topBar = **{**
** **TopAppBar(
title = **{** Text("Ziti Identity", color = *IntzWhite*) **}**,
navigationIcon = **{**
** **IconButton(onClick = onBack) **{**
** **Icon(Icons.Default.*ArrowBack*, contentDescription = "Back", tint = Color.White)
**}**
** }**,
colors = TopAppBarDefaults.topAppBarColors(containerColor = *IntzBlue300*)
)
**}**,
containerColor = *IntzBase500*,
snackbarHost = **{** SnackbarHost(snackbarHostState) **}**
** **) **{** padding **->**
** **Column(
modifier = Modifier
.*padding*(padding)
.*padding*(16.*dp*)
.*verticalScroll*(rememberScrollState())
) **{**
** **Text(identity!!.id, color = *IntzWhite*, fontSize = 20.*sp*, fontWeight = FontWeight.Bold)
Spacer(modifier = Modifier.*height*(12.*dp*))
Text("Network", color = *IntzGray300*, fontSize = 12.*sp*)
Text(identity!!.controllers()?.*value*.*toString*(), color = *IntzWhite*, fontSize = 14.*sp*)
Spacer(modifier = Modifier.*height*(12.*dp*))
Text("Status", color = *IntzGray300*, fontSize = 12.*sp*)
Text(if (isEnabledState.value) "Active" else "Inactive", color = *IntzWhite*, fontSize = 14.*sp*)
Spacer(modifier = Modifier.*height*(24.*dp*))
Row(
modifier = Modifier.*fillMaxWidth*(),
verticalAlignment = Alignment.CenterVertically
) **{**
** **Text("Services", color = *IntzWhite*, fontSize = 16.*sp*, fontWeight = FontWeight.Bold, modifier = Modifier.*weight*(1f))
Text(
"FORGET THIS IDENTITY",
color = *IntzBlue300*,
fontSize = 12.*sp*,
modifier = Modifier.*clickable* **{** showDialog = true **}**
** **)
**}**
** **Spacer(modifier = Modifier.*height*(8.*dp*))
servicesState.value.*forEach* **{** service **->**
** **val interceptInfo = *extractServiceInterceptInfo*(service.config)
val portsText = interceptInfo?.ports?.*joinToString*(", ") **{**
** **if (**it**.first == **it**.second) "${**it**.first}" else "${**it**.first}-${**it**.second}"
**}** ?: "N/A"
val protocolText = interceptInfo?.protocols?.*joinToString*(", ") ?: "N/A"
/* val url = "https://${service.name}"*/
val urls = *resolveBrowsableUrl*(service)
Column(
modifier = Modifier
.*fillMaxWidth*()
.*padding*(vertical = 6.*dp*)
/* .clickable(enabled = isEnabledState.value) {
try {
val resolved = InetAddress.getByName(url.removePrefix("https://"))
Log.d("Ziti-DNS", "Resolved IP for gitlab.intrusion.com = ${resolved.hostAddress}")
if (isEnabledState.value) {
if (resolved.hostAddress.startsWith("127.")) {
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url))
context.startActivity(intent)
} else {
Toast.makeText(context, "Ziti not intercepting $url", Toast.LENGTH_LONG).show()
}
}
*//* val intent = Intent(Intent.ACTION_VIEW).apply {
data = android.net.Uri.parse(url)
}
context.startActivity(intent)*//*
} catch (e: Exception) {
Toast.makeText(context, "Failed to open $url", Toast.LENGTH_SHORT).show()
}
}*//*.clickable(enabled = isEnabledState.value) {
coroutineScope.launch(Dispatchers.IO) {
try {
val host = url.removePrefix("https://").removeSuffix("/")
val results = dnsLookupManager.lookup(host)
val resolved = results?.firstOrNull()?.address
Log.d("Ziti-DNS", "Resolved IP for $host = ${resolved?.hostAddress ?: "null"}")
if (resolved?.hostAddress?.startsWith("100.127.") == true) {
val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(url))
browserIntent.resolveActivity(context.packageManager)?.let {
context.startActivity(browserIntent)
} ?: withContext(Dispatchers.Main) {
Toast.makeText(context, "No browser found to open $url", Toast.LENGTH_SHORT).show()
}
} else {
withContext(Dispatchers.Main) {
Toast.makeText(context, "Ziti not intercepting $host", Toast.LENGTH_SHORT).show()
}
}
} catch (e: Exception) {
withContext(Dispatchers.Main) {
Toast.makeText(context, "Failed to resolve/open: $url", Toast.LENGTH_SHORT).show()
}
Log.e("Ziti-DNS", "Error resolving $url", e)
}
}
}*/.*clickable*(enabled = isEnabledState.value) **{**
** **urls.*forEach* **{** url **->**
** **coroutineScope.*launch*(Dispatchers.IO) **{**
** **try {
val host = Uri.parse(url).*host* ?: return@launch
val results = dnsLookupManager.lookup(host)
Log.d("DNS", "gitlab.intrusion.com resolved to: ${results?.*firstOrNull*()?.address}")
val resolvedIp = results?.*firstOrNull*()?.address?.*hostAddress*
* *val interceptInfo = *extractServiceInterceptInfo*(service.config)
val port = Uri.parse(url).*port*.*takeIf* **{ it** != -1 **}**
** **?: if (url.*startsWith*("https")) 443 else 80
val allowedPorts = interceptInfo?.ports ?: *emptyList*()
val portInIntercept = allowedPorts.*any* **{** (low, high) **->** port in low..high **}**
** **if (*isIpInZitiSubnet*(resolvedIp) && portInIntercept) {
val intent = Intent(Intent.*ACTION_VIEW*, Uri.parse(url))
intent.resolveActivity(context.*packageManager*)?.*let* **{**
** **context.startActivity(intent)
**}** ?: withContext(Dispatchers.Main) **{**
** **Toast.makeText(context, "No browser found to open $url", Toast.*LENGTH_SHORT*).show()
**}**
** **} else {
withContext(Dispatchers.Main) **{**
** **Toast.makeText(context, "Ziti not intercepting $host:$port", Toast.*LENGTH_SHORT*).show()
**}**
** **}
} catch (e: Exception) {
withContext(Dispatchers.Main) **{**
** **Toast.makeText(context, "Failed to resolve/open $url", Toast.*LENGTH_SHORT*).show()
**}**
** **Log.e("Ziti-DNS", "Error resolving $url", e)
}
**}**
** }**
** **/* coroutineScope.launch(Dispatchers.IO) {
try {
// val host = url.removePrefix("https://").removeSuffix("/")
val results = dnsLookupManager.lookup(host)
val resolvedIp = results?.firstOrNull()?.address?.hostAddress
Log.d("ZitiStatus", "Identity enabled: ${isEnabledState.value}, IP: $resolvedIp")
val interceptInfo = extractServiceInterceptInfo(service.config)
val allowedPorts = interceptInfo?.ports ?: emptyList()
val targetPorts = listOf(443, 80, 8443)
val portInIntercept = targetPorts.any { port ->
allowedPorts.any { (low, high) -> port in low..high }
}
if (isIpInZitiSubnet(resolvedIp) && portInIntercept) {
val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(url))
browserIntent.resolveActivity(context.packageManager)?.let {
context.startActivity(browserIntent)
} ?: withContext(Dispatchers.Main) {
Toast.makeText(context, "No browser found to open $url", Toast.LENGTH_SHORT).show()
}
} else {
withContext(Dispatchers.Main) {
Toast.makeText(context, "Ziti not intercepting $host on required ports", Toast.LENGTH_SHORT).show()
}
}
} catch (e: Exception) {
withContext(Dispatchers.Main) {
Toast.makeText(context, "Failed to resolve/open: $url", Toast.LENGTH_SHORT).show()
}
Log.e("Ziti-DNS", "Error resolving $url", e)
}
}*/
**}**
** **) **{**
** **Text(
text = service.name + if (isEnabledState.value) " (Tap to open)" else " (Blocked)",
color = if (isEnabledState.value) Color.Yellow else *IntzWhite*,
fontSize = 14.*sp*
* *)
Text("Ziti Ports: $portsText", color = *IntzGray300*, fontSize = 12.*sp*)
Text("Protocols: $protocolText", color = *IntzGray300*, fontSize = 12.*sp*)
Text(
text = if (isEnabledState.value) "ZTNA Access: Allowed" else "ZTNA Access: Blocked",
color = if (isEnabledState.value) Color.Green else Color.Red,
fontSize = 12.*sp*,
fontWeight = FontWeight.Medium
)
Divider(color = *IntzGray300*.copy(alpha = 0.4f), thickness = 0.5.*dp*)
**}**
** }**
** **Spacer(modifier = Modifier.*height*(24.*dp*))
**}**
** }**
** **if (showDialog) {
AlertDialog(
onDismissRequest = **{** showDialog = false **}**,
title = **{** Text("Confirm") **}**,
text = **{** Text("Are you sure you want to delete this identity?") **}**,
confirmButton = **{**
** **TextButton(onClick = **{**
** **showDialog = false
shouldDeleteIdentity = true
**}**) **{**
** **Text("Yes")
**}**
** }**,
dismissButton = **{**
** **TextButton(onClick = **{** showDialog = false **}**) **{**
** **Text("Cancel")
**}**
** }**,
icon = **{** Icon(Icons.Default.*ArrowBack*, contentDescription = null) **}**,
containerColor = MaterialTheme.colorScheme.surface
)
}
}