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
File renamed without changes.
32 changes: 32 additions & 0 deletions .github/workflows/ci-policy-checks.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
name: CI – Policy Checks

on:
pull_request:

jobs:
policy-checks:
name: Check policies for missing version codes and signatures
runs-on: ubuntu-latest

steps:
- name: Checkout repository
uses: actions/checkout@v3

- name: Set up Java 17
uses: actions/setup-java@v3
with:
distribution: temurin
java-version: '17'

- name: Cache Gradle dependencies
uses: actions/cache@v3
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*','**/gradle-wrapper.properties') }}
restore-keys: |
${{ runner.os }}-gradle-

- name: Check policies
run: ./gradlew :generators:policies:checkPolicies
42 changes: 32 additions & 10 deletions README.MD
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
## 📑 Table of Contents
- [Introduction](#introduction)
- [Available Modes](#here-is-a-simple-tree-of-all-available-modes-)
- [Directory Structure](#️-directory-structure)
- [Supported Policy Types & Templates](#📐-supported-policy-types--templates)
- [Directory Structure](#️-directory-structure-)
- [Supported Policy Types & Templates](#-supported-policy-types--templates)
- [FixedPolicy](#1️⃣-fixedpolicy)
- [ModeBasedPolicy](#2️⃣-modebasedpolicy)
- [MultiModePolicy](#3️⃣-multimodePolicy)
Expand All @@ -15,14 +15,14 @@
- [Validating JSON Files](#validating-json-files)
- [Questions & Answers](#❓-questions--answers)
- [What is the difference between LOCAL_ONLY and OFFLINE?](#what-is-the-difference-between-local_only-and-offline)
- [Optional Flags: Content and Risk Warnings](#⚠️-optional-flags-content-and-risk-warnings)
- [Optional Flags: Content and Risk Warnings](#-optional-flags-content-and-risk-warnings)
- [Why would an app require Play Store installation?](#why-would-an-app-require-play-store-installation)
- [Example: Risky Applications](#🔄-example-risky-applications)
- [Example: Risky Applications](#-example-risky-applications)
- [Importing KDroid Database Modules](#importing-kdroid-database-modules)
- [Core Module](#core-module)
- [Localization Module](#localization-module)
- [Downloader Module](#downloader-module)
- [DAO Module](#dao-module)
- [Core Module](#core-module-)
- [Localization Module](#localization-module-)
- [Downloader Module](#downloader-module-)
- [DAO Module](#dao-module-)

## Introduction

Expand Down Expand Up @@ -236,8 +236,14 @@ Use when each user mode has multiple variants, each with its own rules and optio

1. 🔎 **Validate JSON** with a linter (e.g., [jsonlint.com](https://jsonlint.com/)) or use the project's built-in validation task (see below).
2. 📂 **Place** your file under the correct category folder.
3. 📜 **Commit only** the JSON file—no code or docs changes.
4. 📝 **PR title** should clearly state the app package.
3. 🔢 **Include a minimum version code** (`minimumVersionCode`) for each app. This is imperative for proper app validation. While our system can try to determine this automatically, it's not 100% reliable and will always use the latest version available.
4. 🔐 **Include the SHA1 signature** (`sha1`) of the app's certificate. This is crucial for security verification. Our system can attempt to retrieve this, but it's not always reliable.
5. 📜 **Commit only** the JSON file—no code or docs changes.
6. 📝 **PR title** should clearly state the app package.

> **Note**: For system apps (category `SYSTEM`), the minimum version code and SHA1 signature are not required.
>
> **Important**: If our system cannot determine the minimum version code or signature for a non-system app, the CI will fail. Make sure these values are either provided in the policy file or can be reliably retrieved from the Aptoide API.

CI will reject invalid JSON or misplaced files. Good luck! 🙌

Expand All @@ -262,6 +268,22 @@ This task checks all JSON files in the `app-policies` directory to ensure they:

The task will report any validation errors found, making it easier to identify and fix issues before submitting.

### Checking Policies

To check policies for missing version codes and signatures:

```bash
./gradlew :generators:policies:checkPolicies
```

This task verifies that all policy files have:
- A valid minimum version code (greater than 0)
- A valid SHA1 signature for the app's certificate

The system will attempt to retrieve missing values from the Aptoide API, but this is not always reliable. The task will report any policies that fail these checks, except for system apps which are exempt.

**Note**: This task is run as part of the CI pipeline. If the system cannot determine the minimum version code or signature for a non-system app, the CI will fail.

---

## ❓ Questions & Answers
Expand Down
5 changes: 3 additions & 2 deletions app-policies/music-audio/com.apple.bnd.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@
"type": "Fixed",
"packageName": "com.apple.bnd",
"category": "MUSIC_AUDIO",
"minimumVersionCode": 0,
"minimumVersionCode": 25500,
"hasUnmodestImage": true,
"networkPolicy": {
"mode": "FULL_OPEN"
}
},
"sha1" : "33e7ba4c0f1cab0831c32fab77d3d23e24505f61"
}
5 changes: 3 additions & 2 deletions app-policies/music-audio/kcm.fm.volume.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@
"type": "Fixed",
"packageName": "kcm.fm.volume",
"category": "MUSIC_AUDIO",
"minimumVersionCode": 0,
"minimumVersionCode": 30,
"networkPolicy": {
"mode": "FULL_OPEN"
}
},
"sha1" : "e99f8d4f6cf2bfbed8d82b8185931898681aa25c"
}
3 changes: 2 additions & 1 deletion app-policies/productivity/com.github.android.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,6 @@
"networkPolicy": {
"mode": "FULL_OPEN"
},
"minimumVersionCode": 0
"minimumVersionCode": 10257,
"sha1" : "75ac713fee04aa0a6d8325af6110e37c47de7698"
}
5 changes: 3 additions & 2 deletions app-policies/torah/com.mendelg.otzaria.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@
"type": "Fixed",
"packageName": "com.mendelg.otzaria",
"category": "TORAH",
"minimumVersionCode": 0,
"minimumVersionCode": 44389637,
"networkPolicy": {
"mode": "FULL_OPEN"
}
},
"sha1" : "bb4acfb8a076d09c22ddee39bf4633d956edf369"
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ sealed interface AppPolicy {
val hasUnmodestImage : Boolean
val isPotentiallyDangerous : Boolean
val isRecommendedInStore : Boolean
val sha1: String

val detectionRules: List<DetectionRule>
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,6 @@ data class FixedPolicy(
override val hasUnmodestImage: Boolean = false,
override val isPotentiallyDangerous: Boolean = false,
override val isRecommendedInStore: Boolean = false,
override val sha1: String = "",
override val detectionRules: List<DetectionRule> = emptyList()
) : AppPolicy
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,6 @@ data class ModeBasedPolicy(
override val hasUnmodestImage: Boolean = false,
override val isPotentiallyDangerous: Boolean = false,
override val isRecommendedInStore: Boolean = false,
override val sha1: String = "",
override val detectionRules: List<DetectionRule> = emptyList()
) : AppPolicy
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ data class MultiModePolicy(
override val hasUnmodestImage: Boolean = false,
override val isPotentiallyDangerous: Boolean = false,
override val isRecommendedInStore: Boolean = false,
override val sha1: String = "",
override val detectionRules: List<DetectionRule> = emptyList()
) : AppPolicy {

Expand Down
3 changes: 2 additions & 1 deletion dao/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,8 @@ kotlin {
commonMain.dependencies {
api(project(":core"))
implementation(project(":localization"))
api(libs.gplay.scrapper.core)
api(libs.storekit.gplayscrapper)

implementation(libs.kotlinx.coroutines.core)
implementation(libs.kotlinx.serialization.json)
implementation(libs.kermit)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package io.github.kdroidfilter.database.dao

import com.kdroid.gplayscrapper.core.model.GooglePlayApplicationInfo
import io.github.kdroidfilter.storekit.gplay.core.model.GooglePlayApplicationInfo

/**
* Extended class to add additional information to the GooglePlayApplicationInfo model
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
package io.github.kdroidfilter.database.dao

import com.kdroid.gplayscrapper.core.model.GooglePlayApplicationInfo
import io.github.kdroidfilter.database.store.Database
import io.github.kdroidfilter.database.core.AppCategory
import io.github.kdroidfilter.database.localization.LocalizedAppCategory
import io.github.kdroidfilter.database.store.App_categories
import io.github.kdroidfilter.database.store.Applications
import io.github.kdroidfilter.database.store.Developers
import io.github.kdroidfilter.storekit.gplay.core.model.GooglePlayApplicationInfo

/**
* Data Access Object for Applications
Expand Down
3 changes: 2 additions & 1 deletion downloader/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ kotlin {

commonMain.dependencies {
api(project(":core"))
api(libs.gplay.scrapper)
api("io.github.kdroidfilter.androidappstorekit.gplay:scrapper:0.4.0")

implementation(libs.kotlinx.coroutines.core)
implementation(libs.kermit)
implementation(libs.platform.tools.release.fetcher)
Expand Down
23 changes: 21 additions & 2 deletions generators/policies/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,27 @@ kotlin {


sourceSets {
jvmMain.dependencies {
jvmTest.dependencies {
implementation(kotlin("test"))
}

jvmMain.dependencies {
implementation(project(":core"))
implementation(libs.kotlinx.coroutines.core)
implementation(libs.kotlinx.coroutines.test)
implementation(libs.kotlinx.serialization.json)
implementation(libs.kermit)
implementation(libs.platform.tools.release.fetcher)
implementation(libs.kotlinx.coroutines.swing)
implementation(libs.sqlite.jdbc)
implementation(libs.maven.slf4j.provider)
implementation(libs.storekit.aptoide.api)

implementation(libs.ktor.client.core)
implementation(libs.ktor.client.content.negotiation)
implementation(libs.ktor.client.serialization)
implementation(libs.ktor.client.logging)
implementation(libs.ktor.client.cio)

}

}
Expand Down Expand Up @@ -64,3 +71,15 @@ tasks.register<JavaExec>("validateJson") {
)
mainClass.set("JsonValidatorKt")
}

tasks.register<JavaExec>("checkPolicies") {
group = "validation"
description = "Checks policies for missing version codes and signatures"

dependsOn(kotlin.jvm().compilations["main"].compileTaskProvider)
classpath = files(
kotlin.jvm().compilations["main"].output.allOutputs,
kotlin.jvm().compilations["main"].runtimeDependencyFiles
)
mainClass.set("PolicyCheckerKt")
}
107 changes: 107 additions & 0 deletions generators/policies/src/jvmMain/kotlin/PolicyChecker.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import co.touchlab.kermit.Logger
import io.github.kdroidfilter.database.core.AppCategory
import io.github.kdroidfilter.database.core.policies.AppPolicy
import io.github.kdroidfilter.storekit.aptoide.api.extensions.toFormattedSha1
import io.github.kdroidfilter.storekit.aptoide.api.services.AptoideService
import kotlinx.coroutines.runBlocking
import java.nio.file.Files
import java.nio.file.Path
import kotlin.system.exitProcess

/**
* Utility to check policies for missing version codes and signatures
*/
object PolicyChecker {
private val logger = Logger.withTag("PolicyChecker")

/**
* Checks all policies for missing version codes and signatures
* @param policiesDir The directory containing policy files
* @return true if all checks pass, false otherwise
*/
fun checkPolicies(policiesDir: Path): Boolean {
logger.i { "🔍 Checking policies in: $policiesDir" }

val aptoideService = AptoideService()
val policies = PolicyRepository.loadAll(policiesDir)

val versionCodeIssues = mutableListOf<String>()
val signatureIssues = mutableListOf<String>()

policies.forEach { policy ->
runBlocking {
// Check version code
if (policy.minimumVersionCode == 0) {
try {
val appMeta = aptoideService.getAppMetaByPackageName(policy.packageName)
logger.i { "✅ Successfully retrieved version code for ${policy.packageName}" }
} catch (e: Exception) {
// If it's not a system app, add to issues list
if (policy.category != AppCategory.SYSTEM) {
logger.w { "❌ Failed to retrieve version code for ${policy.packageName}: ${e.message}" }
versionCodeIssues.add(policy.packageName)
} else {
logger.i { "⚠️ System app ${policy.packageName} has version code 0 but is exempt from check" }
}
}
}

// Check signature
if (policy.sha1.isEmpty()) {
try {
val appMeta = aptoideService.getAppMetaByPackageName(policy.packageName)
val signature = appMeta.file.signature.toFormattedSha1()
logger.i { "✅ Successfully retrieved signature for ${policy.packageName}" }
} catch (e: Exception) {
// If it's not a system app, add to issues list
if (policy.category != AppCategory.SYSTEM) {
logger.w { "❌ Failed to retrieve signature for ${policy.packageName}: ${e.message}" }
signatureIssues.add(policy.packageName)
} else {
logger.i { "⚠️ System app ${policy.packageName} has empty signature but is exempt from check" }
}
}
}
}
}

// Print summary
logger.i { "📊 Check complete: ${policies.size} policies checked" }

val hasIssues = versionCodeIssues.isNotEmpty() || signatureIssues.isNotEmpty()

if (versionCodeIssues.isNotEmpty()) {
logger.e { "❌ Found ${versionCodeIssues.size} policies with version code 0 that cannot be retrieved from API:" }
versionCodeIssues.forEach { packageName ->
logger.e { " - $packageName" }
}
}

if (signatureIssues.isNotEmpty()) {
logger.e { "❌ Found ${signatureIssues.size} policies with empty signature that cannot be retrieved from API:" }
signatureIssues.forEach { packageName ->
logger.e { " - $packageName" }
}
}

return !hasIssues
}
}

/**
* Main function to run the policy checker
*/
fun main() {
val logger = Logger.withTag("PolicyChecker")
val projectDir = java.nio.file.Paths.get("").toAbsolutePath()
val root = projectDir.parent.resolve("../app-policies")

val isValid = PolicyChecker.checkPolicies(root)

if (!isValid) {
logger.e { "❌ Policy checks failed. Please fix the issues above." }
exitProcess(1)
} else {
logger.i { "✅ All policy checks passed!" }
}
}
Loading
Loading