diff --git a/sdk-license/src/main/java/com/xuqm/sdk/license/LicenseConfig.kt b/sdk-license/src/main/java/com/xuqm/sdk/license/LicenseConfig.kt index fad10be..4937509 100644 --- a/sdk-license/src/main/java/com/xuqm/sdk/license/LicenseConfig.kt +++ b/sdk-license/src/main/java/com/xuqm/sdk/license/LicenseConfig.kt @@ -5,4 +5,6 @@ data class LicenseConfig( val baseUrl: String = "https://auth.dev.xuqinmin.com/", val deviceName: String? = null, val cacheWindowMs: Long = 10 * 60 * 1000L, // 10 minutes + /** True when initialized from a license file; package name validated locally before any network call. */ + val fromLicenseFile: Boolean = false, ) diff --git a/sdk-license/src/main/java/com/xuqm/sdk/license/LicenseSDK.kt b/sdk-license/src/main/java/com/xuqm/sdk/license/LicenseSDK.kt index e9799ca..95eb9f3 100644 --- a/sdk-license/src/main/java/com/xuqm/sdk/license/LicenseSDK.kt +++ b/sdk-license/src/main/java/com/xuqm/sdk/license/LicenseSDK.kt @@ -84,8 +84,9 @@ object LicenseSDK { /** * Check license status. This will: * 1. Return cached OK status if within cache window - * 2. Try to verify existing token - * 3. Register new device if no token exists + * 2. Validate package name (locally for license-file flow; via server for appKey flow) + * 3. Try to verify existing token + * 4. Register new device if no token exists * * @return LicenseResult.Success if license is valid, LicenseResult.Error otherwise */ @@ -100,6 +101,26 @@ object LicenseSDK { return@withContext LicenseResult.Success("Cached") } + // appKey-only flow: fetch configured package names from server and validate locally + if (!config.fromLicenseFile) { + val actualPackage = appContext.packageName + try { + val infoResp = apiService.getAppInfo(config.appKey) + val appInfo = infoResp.data + if (appInfo != null) { + val anyConfigured = !appInfo.androidPackageName.isNullOrBlank() + || !appInfo.iosBundleId.isNullOrBlank() + || !appInfo.harmonyBundleName.isNullOrBlank() + if (anyConfigured && !appInfo.matchesPackageName(actualPackage)) { + persistStatus(STATUS_DENIED) + return@withContext LicenseResult.Error("Package name not authorized: $actualPackage") + } + } + } catch (_: Exception) { + // If app-info fetch fails due to network, fall through and let register/verify handle it + } + } + val deviceId = getOrCreateDeviceId() val token = store.token @@ -251,6 +272,13 @@ object LicenseSDK { val ctx = LicenseContextHolder.appContext ?: return false val licenseFile = LicenseFileReader.read(ctx) ?: return false val appKey = licenseFile.appKey.takeIf { it.isNotBlank() } ?: return false + + // Validate package name locally before any network call + val configuredPackageName = licenseFile.packageName + if (!configuredPackageName.isNullOrBlank() && configuredPackageName != ctx.packageName) { + return false + } + appContext = ctx store = LicenseStore(ctx) if (store.appKey != null && store.appKey != appKey) { @@ -261,6 +289,7 @@ object LicenseSDK { appKey = appKey, baseUrl = normalizeBaseUrl(licenseFile.baseUrl ?: DEFAULT_BASE_URL), deviceName = DeviceInfoProvider.getDeviceName(), + fromLicenseFile = true, ) apiService = LicenseHttpClient.create(LicenseApiService::class.java, config.baseUrl) initialized = true diff --git a/sdk-license/src/main/java/com/xuqm/sdk/license/api/LicenseApiService.kt b/sdk-license/src/main/java/com/xuqm/sdk/license/api/LicenseApiService.kt index 9263ed5..1d8e2fa 100644 --- a/sdk-license/src/main/java/com/xuqm/sdk/license/api/LicenseApiService.kt +++ b/sdk-license/src/main/java/com/xuqm/sdk/license/api/LicenseApiService.kt @@ -1,12 +1,15 @@ package com.xuqm.sdk.license.api import com.xuqm.sdk.license.model.ApiResponse +import com.xuqm.sdk.license.model.AppInfoResponse import com.xuqm.sdk.license.model.RegisterRequest import com.xuqm.sdk.license.model.RegisterResponse import com.xuqm.sdk.license.model.VerifyRequest import com.xuqm.sdk.license.model.VerifyResponse import retrofit2.http.Body +import retrofit2.http.GET import retrofit2.http.POST +import retrofit2.http.Query interface LicenseApiService { @@ -15,4 +18,7 @@ interface LicenseApiService { @POST("api/license/verify") suspend fun verify(@Body request: VerifyRequest): ApiResponse + + @GET("api/license/app-info") + suspend fun getAppInfo(@Query("appKey") appKey: String): ApiResponse } diff --git a/sdk-license/src/main/java/com/xuqm/sdk/license/model/LicenseModels.kt b/sdk-license/src/main/java/com/xuqm/sdk/license/model/LicenseModels.kt index e004bfd..055d015 100644 --- a/sdk-license/src/main/java/com/xuqm/sdk/license/model/LicenseModels.kt +++ b/sdk-license/src/main/java/com/xuqm/sdk/license/model/LicenseModels.kt @@ -43,3 +43,12 @@ data class LicenseUserInfo( @SerializedName("email") val email: String? = null, @SerializedName("phone") val phone: String? = null, ) + +data class AppInfoResponse( + @SerializedName("androidPackageName") val androidPackageName: String? = null, + @SerializedName("iosBundleId") val iosBundleId: String? = null, + @SerializedName("harmonyBundleName") val harmonyBundleName: String? = null, +) { + fun matchesPackageName(packageName: String): Boolean = + packageName == androidPackageName || packageName == iosBundleId || packageName == harmonyBundleName +}