From dbe8e10131339e6b18d8db0a2f409fdc9017f97d Mon Sep 17 00:00:00 2001 From: Jan Mottl Date: Thu, 28 Nov 2024 16:19:42 +0100 Subject: [PATCH 1/9] =?UTF-8?q?=F0=9F=91=B7=20Add=20Github=20workflows=20a?= =?UTF-8?q?nd=20actions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/actions/basic-preflight-check.yml | 26 ++++++++++ .github/actions/setup.yml | 14 ++++++ .github/workflows/deploy.yml | 50 +++++++++++++++++++ .github/workflows/main_branch.yml | 13 +++++ .github/workflows/pull_request.yml | 14 ++++++ .../plugin/RegisterPreflightChecksPlugin.kt | 4 ++ .../ackeecz/security/verification/GetTag.kt | 2 + 7 files changed, 123 insertions(+) create mode 100644 .github/actions/basic-preflight-check.yml create mode 100644 .github/actions/setup.yml create mode 100644 .github/workflows/deploy.yml create mode 100644 .github/workflows/main_branch.yml create mode 100644 .github/workflows/pull_request.yml diff --git a/.github/actions/basic-preflight-check.yml b/.github/actions/basic-preflight-check.yml new file mode 100644 index 0000000..1f974ad --- /dev/null +++ b/.github/actions/basic-preflight-check.yml @@ -0,0 +1,26 @@ +# This action's steps must be synced with preMergeRequestCheck Gradle task's checks to ensure that +# we check required stuff on CI and at the same time developers can run the Gradle task to verify their +# changes before making a PR. This action can be used in more workflows than just PR, since these +# basic pre merge request checks (Detekt, library tests, ...) are fundamental to be checked in +# basically all situations like merging, pushing to dev, etc. +name: Basic preflight check +description: Action that contains basic checks like running Detekt or library tests that are common to multiple workflows + +runs: + using: "composite" + steps: + - name: Detekt + run: ./gradlew detekt + + - name: Assemble release variant + # Exclude sample app module from build. It requires the library artifacts to be published. + run: ./gradlew assembleRelease -x :app:assembleRelease + + - name: Library tests + run: ./gradlew testDebugUnitTest + + - name: Binary compatibility check + run: ./gradlew apiCheck + + - name: Build logic tests + run: ./gradlew build-logic:logic:test diff --git a/.github/actions/setup.yml b/.github/actions/setup.yml new file mode 100644 index 0000000..a21033f --- /dev/null +++ b/.github/actions/setup.yml @@ -0,0 +1,14 @@ +name: Setup +description: Action that performs common setup tasks like checking out code, etc. + +runs: + using: "composite" + steps: + - name: Check out code + uses: actions/checkout@v4 + + - name: Set up JDK 17 + uses: actions/setup-java@v4 + with: + java-version: '17' + distribution: 'corretto' diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 0000000..8718dc7 --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,50 @@ +name: Deploy + +on: + push: + tags: + # Gradle build logic relies on this tag format, so if you need to change it, you need to change + # it there as well. + - bom-* + +jobs: + # This job's steps must be synced with prePublishCheck Gradle task's checks to ensure that + # we check required stuff on CI and at the same time developers can run the Gradle task to verify + # their changes before publishing. + preflight_check: + runs-on: ubuntu-latest + steps: + - uses: ./github/actions/setup + - uses: ./github/actions/basic-preflight-check + + - name: Verify publishing + run: ./gradlew verifyPublishing + + - name: Verify BOM version + run: ./gradlew verifyBomVersion + + - name: Artifacts tests + # We need to publish the latest versions to Maven local first before we can run tests + # on published artifacts + run: | + ./gradlew publishToMavenLocal + ./gradlew artifactsTests + + publish: + needs: preflight_check + runs-on: ubuntu-latest + steps: + - uses: ./github/actions/setup + + - name: Publish to Maven Central + env: + GPG_KEY: ${{ secrets.ANDROID_GPG_KEY }} + GPG_PASSWORD: ${{ secrets.ANDROID_SIGNING_PASSWORD }} + MAVEN_USERNAME: ${{ secrets.ANDROID_MAVEN_CENTRAL_USERNAME }} + MAVEN_PASSWORD: ${{ secrets.ANDROID_MAVEN_CENTRAL_PASSWORD }} + run: | + ./gradlew --stacktrace publishAndReleaseToMavenCentral \ + -PsigningInMemoryKey=$GPG_KEY \ + -PsigningInMemoryKeyPassword=$GPG_PASSWORD \ + -PmavenCentralUsername=$MAVEN_USERNAME \ + -PmavenCentralPassword=$MAVEN_PASSWORD diff --git a/.github/workflows/main_branch.yml b/.github/workflows/main_branch.yml new file mode 100644 index 0000000..cfa54a9 --- /dev/null +++ b/.github/workflows/main_branch.yml @@ -0,0 +1,13 @@ +name: Main branch + +on: + push: + branches: + - main + +jobs: + main: + runs-on: ubuntu-latest + steps: + - uses: ./github/actions/setup + - uses: ./github/actions/basic-preflight-check diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml new file mode 100644 index 0000000..72ae27b --- /dev/null +++ b/.github/workflows/pull_request.yml @@ -0,0 +1,14 @@ +name: Pull request + +on: + pull_request: + types: + - opened + - synchronize + +jobs: + pull_request: + runs-on: ubuntu-latest + steps: + - uses: ./github/actions/setup + - uses: ./github/actions/basic-preflight-check diff --git a/build-logic/logic/src/main/kotlin/io/github/ackeecz/security/plugin/RegisterPreflightChecksPlugin.kt b/build-logic/logic/src/main/kotlin/io/github/ackeecz/security/plugin/RegisterPreflightChecksPlugin.kt index e109f06..babf81f 100644 --- a/build-logic/logic/src/main/kotlin/io/github/ackeecz/security/plugin/RegisterPreflightChecksPlugin.kt +++ b/build-logic/logic/src/main/kotlin/io/github/ackeecz/security/plugin/RegisterPreflightChecksPlugin.kt @@ -31,6 +31,8 @@ internal class RegisterPreflightChecksPlugin : Plugin { private inner class RegisterPreMergeRequestCheck(private val currentProject: Project) { operator fun invoke() { + // Changes to this task must be synchronized with the basic-preflight-check.yml action + // to run the same checks on the CI as well currentProject.tasks.register(PRE_MERGE_REQUEST_CHECK_TASK_NAME) { group = Constants.ACKEE_TASKS_GROUP description = "Performs basic verifications before making a MR like running Detekt, tests, etc." @@ -82,6 +84,8 @@ internal class RegisterPreflightChecksPlugin : Plugin { private inner class RegisterPrePublishCheck(private val currentProject: Project) { operator fun invoke() { + // Changes to this task must be synchronized with the deploy.yml workflow + // to run the same checks on the CI as well currentProject.tasks.register(PRE_PUBLISH_CHECK_TASK_NAME) { group = Constants.ACKEE_TASKS_GROUP description = "Performs all necessary verifications before publishing new artifacts versions" diff --git a/build-logic/logic/src/main/kotlin/io/github/ackeecz/security/verification/GetTag.kt b/build-logic/logic/src/main/kotlin/io/github/ackeecz/security/verification/GetTag.kt index 5817276..5687dd8 100644 --- a/build-logic/logic/src/main/kotlin/io/github/ackeecz/security/verification/GetTag.kt +++ b/build-logic/logic/src/main/kotlin/io/github/ackeecz/security/verification/GetTag.kt @@ -16,6 +16,8 @@ internal interface GetTag { companion object { + // Deploy Github workflow relies on this tag format, so if you need to change it, you need + // to change it there as well. const val BOM_VERSION_TAG_PREFIX = "bom-" } } From d277f0d1879fc007875ff9c46fc2b27ce662406e Mon Sep 17 00:00:00 2001 From: Jan Mottl Date: Sat, 30 Nov 2024 00:11:34 +0100 Subject: [PATCH 2/9] =?UTF-8?q?=F0=9F=8E=A8=20Improve=20public=20interface?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- jetpack/api/jetpack.api | 4 ++-- .../ackeecz/security/jetpack/EncryptedFile.kt | 4 ++-- .../security/jetpack/EncryptedSharedPreferences.kt | 14 +++++++------- .../ackeecz/security/jetpack/EncryptedFileTest.kt | 2 +- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/jetpack/api/jetpack.api b/jetpack/api/jetpack.api index 2a97852..7931a46 100644 --- a/jetpack/api/jetpack.api +++ b/jetpack/api/jetpack.api @@ -5,7 +5,7 @@ public final class io/github/ackeecz/security/jetpack/EncryptedFile { } public final class io/github/ackeecz/security/jetpack/EncryptedFile$Builder { - public fun (Landroid/content/Context;Ljava/io/File;Lio/github/ackeecz/security/jetpack/EncryptedFile$FileEncryptionScheme;Lkotlin/jvm/functions/Function1;)V + public fun (Ljava/io/File;Landroid/content/Context;Lio/github/ackeecz/security/jetpack/EncryptedFile$FileEncryptionScheme;Lkotlin/jvm/functions/Function1;)V public final fun build ()Lio/github/ackeecz/security/jetpack/EncryptedFile; public final fun setBackgroundDispatcher (Lkotlinx/coroutines/CoroutineDispatcher;)Lio/github/ackeecz/security/jetpack/EncryptedFile$Builder; public final fun setKeysetAlias (Ljava/lang/String;)Lio/github/ackeecz/security/jetpack/EncryptedFile$Builder; @@ -38,7 +38,7 @@ public abstract interface class io/github/ackeecz/security/jetpack/EncryptedShar } public final class io/github/ackeecz/security/jetpack/EncryptedSharedPreferences$Companion { - public final fun create (Landroid/content/Context;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lio/github/ackeecz/security/jetpack/EncryptedSharedPreferences$PrefKeyEncryptionScheme;Lio/github/ackeecz/security/jetpack/EncryptedSharedPreferences$PrefValueEncryptionScheme;)Lio/github/ackeecz/security/jetpack/EncryptedSharedPreferences; + public final fun create (Ljava/lang/String;Lkotlin/jvm/functions/Function1;Landroid/content/Context;Lio/github/ackeecz/security/jetpack/EncryptedSharedPreferences$PrefKeyEncryptionScheme;Lio/github/ackeecz/security/jetpack/EncryptedSharedPreferences$PrefValueEncryptionScheme;)Lio/github/ackeecz/security/jetpack/EncryptedSharedPreferences; } public final class io/github/ackeecz/security/jetpack/EncryptedSharedPreferences$DefaultImpls { diff --git a/jetpack/src/main/java/io/github/ackeecz/security/jetpack/EncryptedFile.kt b/jetpack/src/main/java/io/github/ackeecz/security/jetpack/EncryptedFile.kt index a2e14b3..3f4e088 100644 --- a/jetpack/src/main/java/io/github/ackeecz/security/jetpack/EncryptedFile.kt +++ b/jetpack/src/main/java/io/github/ackeecz/security/jetpack/EncryptedFile.kt @@ -57,8 +57,8 @@ import java.security.GeneralSecurityException * val getMasterKey = suspend { MasterKey.getOrCreate() } * val file = File(context.filesDir, "secret_data") * val encryptedFile = EncryptedFile.Builder( - * context = context, * file = file, + * context = context, * encryptionScheme = EncryptedFile.FileEncryptionScheme.AES256_GCM_HKDF_4KB, * getMasterKey = getMasterKey, * ).build() @@ -213,8 +213,8 @@ public class EncryptedFile private constructor(private val builder: Builder) { } public class Builder public constructor( - context: Context, internal val file: File, + context: Context, internal val encryptionScheme: FileEncryptionScheme, internal val getMasterKey: suspend () -> MasterKey, ) { diff --git a/jetpack/src/main/java/io/github/ackeecz/security/jetpack/EncryptedSharedPreferences.kt b/jetpack/src/main/java/io/github/ackeecz/security/jetpack/EncryptedSharedPreferences.kt index d9d21be..12d75ab 100644 --- a/jetpack/src/main/java/io/github/ackeecz/security/jetpack/EncryptedSharedPreferences.kt +++ b/jetpack/src/main/java/io/github/ackeecz/security/jetpack/EncryptedSharedPreferences.kt @@ -66,15 +66,15 @@ private const val VALUE_KEYSET_ALIAS = "__androidx_security_crypto_encrypted_pre * * Basic use of the class: *``` - * val sharedPreferences = EncryptedSharedPreferences.create( - * context = context, + * val encryptedSharedPreferences = EncryptedSharedPreferences.create( * fileName = "secret_shared_prefs", * getMasterKey = { MasterKey.getOrCreate() }, + * context = context, * prefKeyEncryptionScheme = EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV, * prefValueEncryptionScheme = EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM, * ) * // Use EncryptedSharedPreferences and Editor as you would normally use SharedPreferences - * sharedPreferences.edit { + * encryptedSharedPreferences.edit { * putString("secret_key", "secret_value") * } *``` @@ -248,16 +248,16 @@ public interface EncryptedSharedPreferences { * @throws IOException when [fileName] can not be used */ public fun create( - context: Context, fileName: String, getMasterKey: suspend () -> MasterKey, + context: Context, prefKeyEncryptionScheme: PrefKeyEncryptionScheme, prefValueEncryptionScheme: PrefValueEncryptionScheme, ): EncryptedSharedPreferences { return create( - context = context, fileName = fileName, getMasterKey = getMasterKey, + context = context, prefKeyEncryptionScheme = prefKeyEncryptionScheme, prefValueEncryptionScheme = prefValueEncryptionScheme, weakReferenceFactory = WeakReferenceFactory(), @@ -268,18 +268,18 @@ public interface EncryptedSharedPreferences { @Suppress("LongParameterList") @VisibleForTesting internal fun create( - context: Context, fileName: String, getMasterKey: suspend () -> MasterKey, + context: Context, prefKeyEncryptionScheme: PrefKeyEncryptionScheme, prefValueEncryptionScheme: PrefValueEncryptionScheme, weakReferenceFactory: WeakReferenceFactory, defaultDispatcher: CoroutineDispatcher, ): EncryptedSharedPreferences { return EncryptedSharedPreferencesImpl( - context = context, fileName = fileName, getMasterKey = getMasterKey, + context = context, prefKeyEncryptionScheme = prefKeyEncryptionScheme, prefValueEncryptionScheme = prefValueEncryptionScheme, weakReferenceFactory = weakReferenceFactory, diff --git a/jetpack/src/test/java/io/github/ackeecz/security/jetpack/EncryptedFileTest.kt b/jetpack/src/test/java/io/github/ackeecz/security/jetpack/EncryptedFileTest.kt index 8dce47a..923c7bd 100644 --- a/jetpack/src/test/java/io/github/ackeecz/security/jetpack/EncryptedFileTest.kt +++ b/jetpack/src/test/java/io/github/ackeecz/security/jetpack/EncryptedFileTest.kt @@ -42,7 +42,7 @@ internal class EncryptedFileTest : AndroidTestWithKeyStore() { encryptionScheme: EncryptedFile.FileEncryptionScheme = EncryptedFile.FileEncryptionScheme.AES256_GCM_HKDF_4KB, getMasterKey: suspend () -> MasterKey = { MasterKey.getOrCreate() }, ): EncryptedFile.Builder { - return EncryptedFile.Builder(context, file, encryptionScheme, getMasterKey) + return EncryptedFile.Builder(file, context, encryptionScheme, getMasterKey) .setBackgroundDispatcher(coroutineRule.testDispatcher) } From 7f8e61089829e402250a18d766e341eafdf16000 Mon Sep 17 00:00:00 2001 From: Jan Mottl Date: Sat, 30 Nov 2024 00:12:02 +0100 Subject: [PATCH 3/9] =?UTF-8?q?=F0=9F=93=9D=20Add=20README?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 287 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 286 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 904eaa5..d1b3bdc 100644 --- a/README.md +++ b/README.md @@ -1 +1,286 @@ -# ackee-security +# Ackee Security + +[![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](LICENSE) +[![Maven Central](https://img.shields.io/maven-central/v/io.github.ackeecz/security-bom)](https://central.sonatype.com/artifact/io.github.ackeecz/security-bom) + +## Overview + +Ackee Security is a library focusing on a security-related logic. In Ackee we mainly use it to share +some common security-related implementations across our projects, but it contains useful logic suited +for anyone's needs. More specifically, you can use it as a 100% compatible replacement for [Jetpack +Security Crypto](https://developer.android.com/reference/kotlin/androidx/security/crypto/package-summary) +library and there is more! + +## Architecture + +Library consists of several modules: +- `core` contains some basic core logic like `MasterKey` class that is being used by other modules of the library +- `datastore` provides encrypted `DataStore` implementation +- `datastore-preferences` provides encrypted `PreferenceDataStore` implementation +- `jetpack` is a rewritten and improved Jetpack Security library + +### Core + +Contains basic core security-related logic like `MasterKey` class (rewritten from Jetpack Security) +that is used by other modules to encrypt the data. You don't have to depend on this module directly, +if you use `datastore` modules or `jetpack`. + +### DataStore + +DataStore modules provide an encrypted version of `DataStore`s. They use [Tink](https://github.com/tink-crypto/tink-java) +library for cryptographic algorithms under the hood the same as original Jetpack Security and +`jetpack` module as well. + +### Jetpack + +This was the main reason why we decided to create this library. We have been using Jetpack Security +library on several projects, but it had some issues. First, it was +[silently deprecated](https://developer.android.com/privacy-and-security/cryptography#jetpack_security_crypto_library) +without providing any alternative. The latest stable version was released in 2021, which is pretty old, +especially considering that it is a library focused on security. There were several issues, some of them +fixed in alpha releases, but they never made it to stable. Since we have been already using Jetpack +Security, needed some alternative and otherwise liked the abstractions it provided, we decided to +completely rewrite it and fix the known issues it had. + +`jetpack` module contains `EncryptedSharedPreferences` and `EncryptedFile` implementations. `MasterKey` +is ported as well, but is part of `core` module, because it is reused by other modules as well. However, +if you need original Jetpack Security functionality, it is sufficient to depend on `jetpack` only and +`core` is included automatically. + +#### Compatibility with Jetpack Security + +`jetpack` is 100% compatible in terms of data compatibility with Jetpack Security. It means that if +you already use Jetpack Security on your project and want to switch to `jetpack`, you can just replace +the library, make necessary adjustments to source code and run the app. The already created encrypted data +will work fine with the `jetpack` implementation. + +Regarding source code compatibility, we had to make some big necessary breaking changes to improve the +implementation and we also did some smaller not necessary breaking changes, which are easy to adapt to, but +we believe they improve the API. We tried to keep the API as consistent with Jetpack Security as possible +and only broke it when it provided some benefits. + +The smaller changes mostly involve `MasteKey` class changes. When you use this class to get a master key, +it actually returns its instance that needs to be passed to encrypted implementations. This provides +a more type-safe API compared to a general `String` representation. + +The biggest breaking changes involve `EncryptedSharedPreferences`. The original Jetpack Security's +implementation returned the instance of `SharedPreferences`, which was beneficial, because you could +have used this on all places where you needed a regular `SharedPreferences` types. However, there were +also some problems. Those problems might not be noticeable for a few key-value pairs stored to preferences, +but becomes visible for a lot of key-value pairs or data of a bigger size. All crypto operations of +original `EncryptedSharedPreferences` (and `EncryptedFile` as well) are executed on the caller's thread, +possibly blocking it for more intensive operations. This is especially problematic for methods, where +you do not expect this even for the regular `SharedPreferences` like `apply`, which is actually one of +those most problematic methods. Since we wanted to improve this and use coroutines for that, we had +to break this completely and we introduced a new `EncryptedSharedPreferences` interface that is basically +a 1:1 copy of the `SharedPreferences` interface, but have all relevant methods `suspend` to not block +caller's thread. We understand, that this big breaking change might be problematic for apps relying +heavily on `SharedPreferences` (e.g. passing it to a third-party library), so there is also an extension +`EncryptedSharedPreferences.adaptToSharedPreferences`, which adapts `EncryptedSharedPreferences` to +`SharedPreferences`. However, you should not use this, unless really necessary, and you should migrate +to `EncryptedSharedPreferences` to get all benefits it offers, as the adapter just blocks while waiting +for the internal `EncryptedSharedPreferences` suspend functions to complete. + +#### Improvements over Jetpack Security + +During rewrite of Jetpack Security library we made following improvements: +- Rewritten from Java to 100% Kotlin. +- All logic is covered by tests. We followed a careful process of refactoring, when we first covered +all the existing functionality by tests and then started to rewrite the implementations, which gave +us a confidence to not break anything. +- Improve some APIs like `MasterKey`, which is now more type-safe and also offers `KeyGenParameterSpec.Builder` +configured with the same default values as the original implementation, but you can take this and apply +additional custom configurations before building the final spec and getting a key. +- Improve performance of the `EncryptedSharedPreferences` for various methods like getting all key-value +pairs that made unnecessary extra encryptions/decryptions under the hood. +- Remove all blocking calls and making heavy methods suspend instead. +- Fix synchronization issues during master key creation and increase Tink library version from the old one, +used in Jetpack Security, that also had some synchronization issues, that were fixed in later releases. +- Since one of the major issues of Jetpack Security was an outdated Tink library, which makes all the +crypto operations and Jetpack Security was basically just a thin abstraction over it, we wanted to +try to prevent the same issues in the future and so we decided to force clients of Ackee Security library +to depend on Tink explicitly. This allows clients to have a better control over updates, independent +of Ackee Security updates. +- Fix several bugs discovered in `EncryptedSharedPreferences` during covering the logic by tests: + - If you saved empty string `Set`, you didn't get it back by using `getStringSet`, but you got + default value passed in parameter instead. + - Storing `Set` with null threw NPE. + - `get*` methods didn't throw `ClassCastException` as specified in `SharedPreferences` contracts, + when you tried to access some key using an incorrect get method. + - Contract of `SharedPreferences.registerOnSharedPreferenceChangeListener` specifies that it does not + store strong references on the listener objects, but it actually incorrectly did. + - Contract of `OnSharedPreferenceChangeListener.onSharedPreferenceChanged` specifies, that it has to be + invoked from the main thread, but this was not ensured. + - `OnSharedPreferenceChangeListener.onSharedPreferenceChanged` was being called multiple times per + one key in one editor, if the editor did multiple changes on the same key. + - `OnSharedPreferenceChangeListener.onSharedPreferenceChanged` was being called even when the key + was added and then removed in the same editor. + +## Setup + +Add the following dependencies to your `libs.versions.toml`, depending on what you need. You should +always use BOM to be sure to get binary compatible dependencies. If you need only `jetpack` features, +just declare BOM and `io.github.ackeecz:security-jetpack`. If you need only particular DataStore, +then declare BOM and particular DataStore dependency, e.g. `io.github.ackeecz:security-datastore`. +You don't need to declare `io.github.ackeecz:security-core` dependency, unless you depend only on +`core` without any DataStore or `jetpack` modules. + +```toml +[versions] +ackee-security-bom = "SPECIFY_VERSION" +tink = "SPECIFY_VERSION" + +[libraries] +ackee-security-bom = { module = "io.github.ackeecz:security-bom", version.ref = "ackee-security-bom" } +ackee-security-core = { module = "io.github.ackeecz:security-core" } +ackee-security-datastore = { module = "io.github.ackeecz:security-datastore" } +ackee-security-datastore-preferences = { module = "io.github.ackeecz:security-datastore-preferences" } +ackee-security-jetpack = { module = "io.github.ackeecz:security-jetpack" } + +tink-android = { module = "com.google.crypto.tink:tink-android", version.ref = "tink" } +``` + +Then specify dependencies in your `build.gradle.kts`: + +```kotlin +dependencies { + + // Always use BOM + implementation(platform(libs.ackee.security.bom)) + // Optional core dependency. Needed to be specified only if you do not use any other artifact + // and want to use core in your app. + implementation(libs.ackee.security.core) + // For encrypted DataStore + implementation(libs.ackee.security.datastore) + // For encrypted preferences DataStore + implementation(libs.ackee.security.datastore.preferences) + // For Jetpack Security port + implementation(libs.ackee.security.jetpack) + + // Dependency on Tink must be included explicitly. This allows clients of Ackee Security library + // to control the version of Tink themselves, being able to keep it up-to-date as much as possible + // and not depend on Ackee Security releases. + implementation(libs.tink.android) +} +``` + +## Usage + +Basic usage of the main library functionality is described bellow. You can also take a look on tests +to get even more detailed picture. + +### DataStore + +The usages of encrypted DataStore implementations are almost the same as the classic DataStore. Both +classic and preferences encrypted DataStore implementations can be created using property delegates +or factories. The main difference is the `DataStoreCryptoParams` class that contains necessary +parameters specific to crypto operations over DataStore. Check the documentation of this class for +more details of what you can customize. Once you create an encrypted version of DataStore, you can +use it exactly the same as the classic unencrypted DataStore instance. + +Encrypted DataStore delegate: + +```kotlin +val Context.myDataStore by encryptedDataStore( + cryptoParams = DataStoreCryptoParams( + encryptionScheme = DataStoreEncryptionScheme.AES256_GCM_HKDF_4KB, + getMasterKey = { MasterKey.getOrCreate() }, + ), + fileName = "filename", + serializer = serializer, + // Other params as in dataStore delegate +) +``` + +Encrypted DataStore factory: + +```kotlin +DataStoreFactory.createEncrypted( + context = context, + cryptoParams = DataStoreCryptoParams( + encryptionScheme = DataStoreEncryptionScheme.AES256_GCM_HKDF_4KB, + getMasterKey = { MasterKey.getOrCreate() }, + ), + serializer = serializer, + produceFile = { context.dataStoreFile("encrypted_data") }, + // Other params as in DataStoreFactory.create +) +``` + +Encrypted PreferenceDataStore delegate: + +```kotlin +val Context.myDataStore by encryptedPreferencesDataStore( + cryptoParams = DataStoreCryptoParams( + encryptionScheme = DataStoreEncryptionScheme.AES256_GCM_HKDF_4KB, + getMasterKey = { MasterKey.getOrCreate() }, + ), + name = "preferences_name", + // Other params as in preferencesDataStore delegate +) +``` + +Encrypted PreferenceDataStore factory: + +```kotlin +PreferenceDataStoreFactory.createEncrypted( + context = context, + cryptoParams = DataStoreCryptoParams( + encryptionScheme = DataStoreEncryptionScheme.AES256_GCM_HKDF_4KB, + getMasterKey = { MasterKey.getOrCreate() }, + ), + produceFile = { context.preferencesDataStoreFile("encrypted_data") }, + // Other params as in PreferenceDataStoreFactory.create +) +``` + +### Jetpack + +Using classes from `jetpack` module is almost the same as using the Jetpack Security classes. + +`EncryptedFile`: + +```kotlin +val encryptedFile = EncryptedFile.Builder( + file = File(context.filesDir, "secret_data"), + context = context, + encryptionScheme = EncryptedFile.FileEncryptionScheme.AES256_GCM_HKDF_4KB, + getMasterKey = { MasterKey.getOrCreate() }, +).build() +// Write to the encrypted file +val encryptedOutputStream = encryptedFile.openFileOutput() +// Read the encrypted file +val encryptedInputStream = encryptedFile.openFileInput() +``` + +`EncryptedSharedPreferences`: + +```kotlin +val encryptedSharedPreferences: EncryptedSharedPreferences = EncryptedSharedPreferences.create( + fileName = "secret_shared_prefs", + getMasterKey = { MasterKey.getOrCreate() }, + context = context, + prefKeyEncryptionScheme = EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV, + prefValueEncryptionScheme = EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM, +) +// Use EncryptedSharedPreferences and Editor as you would normally use SharedPreferences +encryptedSharedPreferences.edit { + putString("secret_key", "secret_value") +} +``` + +As discussed above in Architecture section, `EncryptedSharedPreferences.create` no longer return +`SharedPreferences` type but a new `EncryptedSharedPreferences`. You are highly encouraged to +use this new type, but if you really **do** need `SharedPreferences`, there is an extension, that +can adapt `EncryptedSharedPreferences` to `SharedPreferences`. + +```kotlin +val sharedPreferences: SharedPreferences = encryptedSharedPreferences.adaptToSharedPreferences() +``` + +## Credits + +Developed by [Ackee](https://www.ackee.cz) team with 💙. + +`MasterKey` class from `core` and `EncryptedFile` and `EncryptedSharedPreferences` classes from +`jetpack` are based on [Jetpack Security Crypto library](https://developer.android.com/reference/kotlin/androidx/security/crypto/package-summary). From de599f2ca832dd57b0fb366f9a641a3fce71eb21 Mon Sep 17 00:00:00 2001 From: Jan Mottl Date: Sat, 30 Nov 2024 01:19:34 +0100 Subject: [PATCH 4/9] =?UTF-8?q?=F0=9F=93=9D=20Add=20CHANGELOG?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..9e929a6 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,30 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres +to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] +### core +### datastore +### datastore-preferences +### jetpack + +## BOM [1.0.0] - TBD + +### core +#### Added +- First version of the artifact 🎉 + +### datastore +#### Added +- First version of the artifact 🎉 + +### datastore-preferences +#### Added +- First version of the artifact 🎉 + +### jetpack +#### Added +- First version of the artifact 🎉 From 20b620764c18907b515fb913712d2304f04252ce Mon Sep 17 00:00:00 2001 From: Jan Mottl Date: Sat, 30 Nov 2024 21:54:59 +0100 Subject: [PATCH 5/9] =?UTF-8?q?=F0=9F=93=9D=20Add=20RELEASING?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- RELEASING.md | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 RELEASING.md diff --git a/RELEASING.md b/RELEASING.md new file mode 100644 index 0000000..fb8c0ff --- /dev/null +++ b/RELEASING.md @@ -0,0 +1,46 @@ +# Releasing + +If you release for the first time or you forgot the details, long version is recommended, which +explains the steps in a great detail. Otherwise you can use TLDR version. + +## TLDR + +1. Increase versions of the necessary library artifacts to be released, including the BOM version. + You can use `checkIfUpdateNeededSinceCurrentTag` Gradle task to help you figure out what + artifacts have changed since the last release. +2. Update `CHANGELOG` for the new release. +3. Create a tag for the new BOM version in the required format. +4. Run `prePublishCheck` Gradle task and fix all found issues if needed. +5. If you were forced to publish more artifacts, update `CHANGELOG` and fast-forward the tag to the + latest commit. +6. Push the tag. CI will perform necessary checks and publish all artifacts. + +## Long version + +Once you are ready to publish new versions of library artifacts, you can start publishing process: + +1. First you need to increase the versions of all artifacts that need to be published, including the BOM version. + You can use `checkIfUpdateNeededSinceCurrentTag` Gradle task to figure out what modules have changed since the + last release. It does not have to mean that everything needs to be released. For example if you + did changes to both `datastore` and `jetpack`, the task will report changes in both, but you might + want to release just `datastore` and keep the changes in `jetpack` for the future release. However, + there are situations, when you will be forced to publish some updates, e.g. when you update `core-internal`. + In this case you will need to publish new versions of all artifacts that depend on it to preserve + binary compatibility between modules, because `core-internal` might contain breaking changes to public + API. It might be difficult to ensure that proper artifacts are updated when necessary due to these + kind of dependencies and that's why `verifyPublishing` task exists, that fails if there might be some + potentially incompatible dependencies and forces you to publish relevant artifacts together in a single + BOM. +2. Update `CHANGELOG` for the new release. +3. Create a tag for the new BOM version in the required format. You can optionally run `verifyBomVersion` + Gradle task to verify, if the version of the BOM artifact is synced with the version in the tag. +4. Run `prePublishCheck` Gradle task. This task performs same checks as CI during a deployment, so you + can fix issues faster by running this quicker local verification before pushing to the remote. + This task performs usual checks like building modules, running tests, etc., but it also + runs all custom check tasks mentioned above, so you actually do not have to run them separately and + you can just run `prePublishCheck`, but it is good to know that they exist and what they do. You + can also find more detailed information in the documentation comments in the source code of those tasks. +5. If you were forced to publish more artifacts, update `CHANGELOG` and fast-forward the tag to the + latest commit. +6. Push the tag. CI will perform necessary checks and publish all artifacts. + From 418ea7e5bd710be6e240ea8be1580d7861f3a87a Mon Sep 17 00:00:00 2001 From: Jan Mottl Date: Sat, 30 Nov 2024 22:48:36 +0100 Subject: [PATCH 6/9] =?UTF-8?q?=F0=9F=93=84=20Comply=20with=20Jetpack=20Se?= =?UTF-8?q?curity's=20license?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- NOTICE.txt | 2 ++ README.md | 11 ++++++----- .../github/ackeecz/security/core/MasterKey.kt | 18 ++++++++++++++++++ .../ackeecz/security/jetpack/EncryptedFile.kt | 4 +++- .../jetpack/EncryptedSharedPreferences.kt | 3 +++ 5 files changed, 32 insertions(+), 6 deletions(-) create mode 100644 NOTICE.txt diff --git a/NOTICE.txt b/NOTICE.txt new file mode 100644 index 0000000..834a174 --- /dev/null +++ b/NOTICE.txt @@ -0,0 +1,2 @@ +This project includes code derived from the Jetpack Security Crypto library, +developed by Google LLC, and licensed under the Apache License 2.0. diff --git a/README.md b/README.md index d1b3bdc..e61f378 100644 --- a/README.md +++ b/README.md @@ -5,10 +5,10 @@ ## Overview -Ackee Security is a library focusing on a security-related logic. In Ackee we mainly use it to share -some common security-related implementations across our projects, but it contains useful logic suited -for anyone's needs. More specifically, you can use it as a 100% compatible replacement for [Jetpack -Security Crypto](https://developer.android.com/reference/kotlin/androidx/security/crypto/package-summary) +Ackee Security is a library focusing on a security-related logic. In [Ackee](https://www.ackee.cz/) +we mainly use it to share some common security-related implementations across our projects, but it +contains useful logic suited for anyone's needs. More specifically, you can use it as a 100% +compatible replacement for [Jetpack Security Crypto](https://developer.android.com/reference/kotlin/androidx/security/crypto/package-summary) library and there is more! ## Architecture @@ -283,4 +283,5 @@ val sharedPreferences: SharedPreferences = encryptedSharedPreferences.adaptToSha Developed by [Ackee](https://www.ackee.cz) team with 💙. `MasterKey` class from `core` and `EncryptedFile` and `EncryptedSharedPreferences` classes from -`jetpack` are based on [Jetpack Security Crypto library](https://developer.android.com/reference/kotlin/androidx/security/crypto/package-summary). +`jetpack` are based on [Jetpack Security Crypto library](https://developer.android.com/reference/kotlin/androidx/security/crypto/package-summary) +published by Google LLC, under the Apache License 2.0. diff --git a/core/src/main/kotlin/io/github/ackeecz/security/core/MasterKey.kt b/core/src/main/kotlin/io/github/ackeecz/security/core/MasterKey.kt index 002c9a5..5407658 100644 --- a/core/src/main/kotlin/io/github/ackeecz/security/core/MasterKey.kt +++ b/core/src/main/kotlin/io/github/ackeecz/security/core/MasterKey.kt @@ -1,3 +1,21 @@ +/* + * Copyright 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * This file is based on the original MasterKeys from Jetpack Security Crypto library + * https://developer.android.com/reference/kotlin/androidx/security/crypto/MasterKeys + */ package io.github.ackeecz.security.core import android.security.keystore.KeyGenParameterSpec diff --git a/jetpack/src/main/java/io/github/ackeecz/security/jetpack/EncryptedFile.kt b/jetpack/src/main/java/io/github/ackeecz/security/jetpack/EncryptedFile.kt index 3f4e088..d4ed2b9 100644 --- a/jetpack/src/main/java/io/github/ackeecz/security/jetpack/EncryptedFile.kt +++ b/jetpack/src/main/java/io/github/ackeecz/security/jetpack/EncryptedFile.kt @@ -1,4 +1,3 @@ -// TODO Should this licence be here and for other JetSec original files? /* * Copyright 2018 The Android Open Source Project * @@ -13,6 +12,9 @@ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. + * + * This file is based on the original EncryptedFile from Jetpack Security Crypto library + * https://developer.android.com/reference/kotlin/androidx/security/crypto/EncryptedFile */ package io.github.ackeecz.security.jetpack diff --git a/jetpack/src/main/java/io/github/ackeecz/security/jetpack/EncryptedSharedPreferences.kt b/jetpack/src/main/java/io/github/ackeecz/security/jetpack/EncryptedSharedPreferences.kt index 12d75ab..51381f4 100644 --- a/jetpack/src/main/java/io/github/ackeecz/security/jetpack/EncryptedSharedPreferences.kt +++ b/jetpack/src/main/java/io/github/ackeecz/security/jetpack/EncryptedSharedPreferences.kt @@ -12,6 +12,9 @@ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. + * + * This file is based on the original EncryptedSharedPreferences from Jetpack Security Crypto library + * https://developer.android.com/reference/kotlin/androidx/security/crypto/EncryptedSharedPreferences */ package io.github.ackeecz.security.jetpack From 0c0ce80a38caf92ea1ffe7818c202b70d3f41d2f Mon Sep 17 00:00:00 2001 From: Jan Mottl Date: Sun, 1 Dec 2024 04:14:52 +0100 Subject: [PATCH 7/9] =?UTF-8?q?=F0=9F=90=9B=20Fix=20CI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../action.yml} | 7 +++- .../actions/{setup.yml => setup/action.yml} | 3 -- .github/workflows/deploy.yml | 32 ++++++++++++------ .github/workflows/main_branch.yml | 7 ++-- .github/workflows/pull_request.yml | 7 ++-- .idea/compiler.xml | 8 +---- .idea/misc.xml | 2 +- .idea/runConfigurations.xml | 13 -------- app/build.gradle.kts | 33 ------------------- .../plugin/RegisterPreflightChecksPlugin.kt | 4 +-- .../github/ackeecz/security/util/Constants.kt | 1 - 11 files changed, 42 insertions(+), 75 deletions(-) rename .github/actions/{basic-preflight-check.yml => basic-preflight-check/action.yml} (87%) rename .github/actions/{setup.yml => setup/action.yml} (81%) delete mode 100644 .idea/runConfigurations.xml diff --git a/.github/actions/basic-preflight-check.yml b/.github/actions/basic-preflight-check/action.yml similarity index 87% rename from .github/actions/basic-preflight-check.yml rename to .github/actions/basic-preflight-check/action.yml index 1f974ad..847df54 100644 --- a/.github/actions/basic-preflight-check.yml +++ b/.github/actions/basic-preflight-check/action.yml @@ -10,17 +10,22 @@ runs: using: "composite" steps: - name: Detekt + shell: bash run: ./gradlew detekt - name: Assemble release variant + shell: bash # Exclude sample app module from build. It requires the library artifacts to be published. run: ./gradlew assembleRelease -x :app:assembleRelease - name: Library tests - run: ./gradlew testDebugUnitTest + shell: bash + run: ./gradlew testDebugUnitTest -x :app:testDebugUnitTest - name: Binary compatibility check + shell: bash run: ./gradlew apiCheck - name: Build logic tests + shell: bash run: ./gradlew build-logic:logic:test diff --git a/.github/actions/setup.yml b/.github/actions/setup/action.yml similarity index 81% rename from .github/actions/setup.yml rename to .github/actions/setup/action.yml index a21033f..af49b46 100644 --- a/.github/actions/setup.yml +++ b/.github/actions/setup/action.yml @@ -4,9 +4,6 @@ description: Action that performs common setup tasks like checking out code, etc runs: using: "composite" steps: - - name: Check out code - uses: actions/checkout@v4 - - name: Set up JDK 17 uses: actions/setup-java@v4 with: diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 8718dc7..99559a3 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -7,6 +7,12 @@ on: # it there as well. - bom-* +env: + GPG_KEY: ${{ secrets.ANDROID_GPG_KEY }} + GPG_PASSWORD: ${{ secrets.ANDROID_SIGNING_PASSWORD }} + MAVEN_USERNAME: ${{ secrets.ANDROID_MAVEN_CENTRAL_USERNAME }} + MAVEN_PASSWORD: ${{ secrets.ANDROID_MAVEN_CENTRAL_PASSWORD }} + jobs: # This job's steps must be synced with prePublishCheck Gradle task's checks to ensure that # we check required stuff on CI and at the same time developers can run the Gradle task to verify @@ -14,8 +20,11 @@ jobs: preflight_check: runs-on: ubuntu-latest steps: - - uses: ./github/actions/setup - - uses: ./github/actions/basic-preflight-check + - name: Check out code + uses: actions/checkout@v4 + + - uses: ./.github/actions/setup + - uses: ./.github/actions/basic-preflight-check - name: Verify publishing run: ./gradlew verifyPublishing @@ -27,21 +36,24 @@ jobs: # We need to publish the latest versions to Maven local first before we can run tests # on published artifacts run: | - ./gradlew publishToMavenLocal - ./gradlew artifactsTests + ./gradlew publishToMavenLocal \ + -PsigningInMemoryKey=$GPG_KEY \ + -PsigningInMemoryKeyPassword=$GPG_PASSWORD \ + -PmavenCentralUsername=$MAVEN_USERNAME \ + -PmavenCentralPassword=$MAVEN_PASSWORD + + ./gradlew :app:testDebugUnitTest publish: needs: preflight_check runs-on: ubuntu-latest steps: - - uses: ./github/actions/setup + - name: Check out code + uses: actions/checkout@v4 + + - uses: ./.github/actions/setup - name: Publish to Maven Central - env: - GPG_KEY: ${{ secrets.ANDROID_GPG_KEY }} - GPG_PASSWORD: ${{ secrets.ANDROID_SIGNING_PASSWORD }} - MAVEN_USERNAME: ${{ secrets.ANDROID_MAVEN_CENTRAL_USERNAME }} - MAVEN_PASSWORD: ${{ secrets.ANDROID_MAVEN_CENTRAL_PASSWORD }} run: | ./gradlew --stacktrace publishAndReleaseToMavenCentral \ -PsigningInMemoryKey=$GPG_KEY \ diff --git a/.github/workflows/main_branch.yml b/.github/workflows/main_branch.yml index cfa54a9..eb1c135 100644 --- a/.github/workflows/main_branch.yml +++ b/.github/workflows/main_branch.yml @@ -9,5 +9,8 @@ jobs: main: runs-on: ubuntu-latest steps: - - uses: ./github/actions/setup - - uses: ./github/actions/basic-preflight-check + - name: Check out code + uses: actions/checkout@v4 + + - uses: ./.github/actions/setup + - uses: ./.github/actions/basic-preflight-check diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 72ae27b..a3e2cb4 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -10,5 +10,8 @@ jobs: pull_request: runs-on: ubuntu-latest steps: - - uses: ./github/actions/setup - - uses: ./github/actions/basic-preflight-check + - name: Check out code + uses: actions/checkout@v4 + + - uses: ./.github/actions/setup + - uses: ./.github/actions/basic-preflight-check diff --git a/.idea/compiler.xml b/.idea/compiler.xml index 7edb8b7..b589d56 100644 --- a/.idea/compiler.xml +++ b/.idea/compiler.xml @@ -1,12 +1,6 @@ - - - - - - - + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml index 2cdc89a..824785d 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,5 +1,5 @@ - + \ No newline at end of file diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml deleted file mode 100644 index 931b96c..0000000 --- a/.idea/runConfigurations.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/app/build.gradle.kts b/app/build.gradle.kts index c5f133e..18fe9ca 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -1,5 +1,4 @@ import io.github.ackeecz.security.properties.LibraryProperties -import io.github.ackeecz.security.util.Constants plugins { alias(libs.plugins.ackeecz.security.android.application) @@ -8,33 +7,12 @@ plugins { alias(libs.plugins.ackeecz.security.testing.protobuf) } -private val includeArtifactsTestsProperty = "includeTests" -private val artifactsTestsPackage = "io.github.ackeecz.security.sample.*" - android { namespace = "io.github.ackeecz.security.sample" defaultConfig { applicationId = "io.github.ackeecz.security" } - - @Suppress("UnstableApiUsage") - testOptions { - unitTests.all { - it.filter { - // By default (when property is not set) we exclude artifacts tests, because they rely - // on artifacts to be published, so we do not want them to run together with all other - // tests using classic Gradle test tasks like testDebugUnitTest. We want to run them - // only in a special custom task that sets this property and run this task only under - // certain special conditions, like during pre-publish check on published artifacts to - // Maven local before real publishing. - if (!project.hasProperty(includeArtifactsTestsProperty)) { - excludeTestsMatching(artifactsTestsPackage) - isFailOnNoMatchingTests = false - } - } - } - } } @Suppress("UseTomlInstead") @@ -54,14 +32,3 @@ dependencies { testImplementation(libs.bouncyCastle.bcpkix) } - -/** - * Tests published artifacts. This verifies things like correctly published artifacts including BOM - * or binary compatibility of the dependent artifacts. - */ -tasks.register(Constants.ARTIFACTS_TESTS_TASK_NAME) { - group = Constants.ACKEE_TASKS_GROUP - description = "Tests published artifacts of the library" - ext.set(includeArtifactsTestsProperty, true) - dependsOn("testDebugUnitTest") -} diff --git a/build-logic/logic/src/main/kotlin/io/github/ackeecz/security/plugin/RegisterPreflightChecksPlugin.kt b/build-logic/logic/src/main/kotlin/io/github/ackeecz/security/plugin/RegisterPreflightChecksPlugin.kt index babf81f..bb824a1 100644 --- a/build-logic/logic/src/main/kotlin/io/github/ackeecz/security/plugin/RegisterPreflightChecksPlugin.kt +++ b/build-logic/logic/src/main/kotlin/io/github/ackeecz/security/plugin/RegisterPreflightChecksPlugin.kt @@ -116,7 +116,7 @@ internal class RegisterPreflightChecksPlugin : Plugin { // We need to publish the latest versions to Maven local first before we can run tests // on published artifacts project.executeGradleTask(taskName = "publishToMavenLocal") - project.executeGradleTask(taskName = Constants.ARTIFACTS_TESTS_TASK_NAME) + project.executeGradleTask(taskName = ":$SAMPLE_APP_NAME:testDebugUnitTest") } } @@ -128,7 +128,7 @@ internal class RegisterPreflightChecksPlugin : Plugin { project = project, ) when (result) { - is ExecuteCommand.Result.Success -> logger.info(result.commandOutput) + is ExecuteCommand.Result.Success -> println(result.commandOutput) is ExecuteCommand.Result.Error -> throw GradleException(result.commandOutput) } } diff --git a/build-logic/logic/src/main/kotlin/io/github/ackeecz/security/util/Constants.kt b/build-logic/logic/src/main/kotlin/io/github/ackeecz/security/util/Constants.kt index 24a114d..6b1ab5a 100644 --- a/build-logic/logic/src/main/kotlin/io/github/ackeecz/security/util/Constants.kt +++ b/build-logic/logic/src/main/kotlin/io/github/ackeecz/security/util/Constants.kt @@ -13,5 +13,4 @@ public object Constants { public val JVM_TARGET: JvmTarget = JvmTarget.JVM_11 public const val ACKEE_TASKS_GROUP: String = "ackee" - public const val ARTIFACTS_TESTS_TASK_NAME: String = "artifactsTests" } From 6d3e112a88dc4327d0f301be241a538efd6616c7 Mon Sep 17 00:00:00 2001 From: Jan Mottl Date: Sun, 1 Dec 2024 16:20:15 +0100 Subject: [PATCH 8/9] =?UTF-8?q?=E2=9C=85=20Improve=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../GetReleaseDependentProjectsTest.kt | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/build-logic/logic/src/test/kotlin/io/github/ackeecz/security/verification/GetReleaseDependentProjectsTest.kt b/build-logic/logic/src/test/kotlin/io/github/ackeecz/security/verification/GetReleaseDependentProjectsTest.kt index fabbace..6147aa2 100644 --- a/build-logic/logic/src/test/kotlin/io/github/ackeecz/security/verification/GetReleaseDependentProjectsTest.kt +++ b/build-logic/logic/src/test/kotlin/io/github/ackeecz/security/verification/GetReleaseDependentProjectsTest.kt @@ -5,7 +5,7 @@ import io.github.ackeecz.security.testutil.addDependencies import io.github.ackeecz.security.testutil.addImplementationDependencies import io.github.ackeecz.security.testutil.buildProject import io.kotest.core.spec.style.FunSpec -import io.kotest.inspectors.forAll +import io.kotest.datatest.withData import io.kotest.matchers.collections.shouldBeEmpty import io.kotest.matchers.collections.shouldContainExactlyInAnyOrder import org.gradle.api.Project @@ -14,7 +14,7 @@ private lateinit var underTest: GetReleaseDependentProjects internal class GetReleaseDependentProjectsTest : FunSpec({ - beforeEach { + beforeTest { underTest = GetReleaseDependentProjects() } @@ -39,8 +39,8 @@ internal class GetReleaseDependentProjectsTest : FunSpec({ actual shouldContainProjectsExactlyInAnyOrder listOf(dependentProject1, dependentProject2) } - test("get dependent projects for release configurations") { - listOf( + context("get dependent projects for release configurations") { + withData( "api", "compileOnly", "compileOnlyApi", @@ -51,7 +51,7 @@ internal class GetReleaseDependentProjectsTest : FunSpec({ "releaseImplementation", "releaseRuntimeOnly", "runtimeOnly", - ).forAll { configuration -> + ) { configuration -> val rootProject = buildProject(name = "root") val checkedProject = buildProject(name = "checked", parent = rootProject) val notDependentProject = buildProject(name = "not-dependent", parent = rootProject) @@ -64,8 +64,8 @@ internal class GetReleaseDependentProjectsTest : FunSpec({ } } - test("get no dependent projects for non-release configurations") { - listOf( + context("get no dependent projects for non-release configurations") { + withData( "androidTestApi", "androidTestImplementation", "debugApi", @@ -73,7 +73,7 @@ internal class GetReleaseDependentProjectsTest : FunSpec({ "testDebugImplementation", "testFixturesImplementation", "testImplementation", - ).forAll { configuration -> + ) { configuration -> val rootProject = buildProject(name = "root") val checkedProject = buildProject(name = "checked", parent = rootProject) buildProject(name = "dependent-with-non-release-configuration", parent = rootProject) From eb63b1f59ba34a7993e24466c3e8d047090fe8a9 Mon Sep 17 00:00:00 2001 From: Jan Mottl Date: Sun, 1 Dec 2024 18:06:43 +0100 Subject: [PATCH 9/9] =?UTF-8?q?=F0=9F=8E=A8=20Small=20improvements?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/actions/setup/action.yml | 2 +- RELEASING.md | 2 +- .../ackeecz/security/plugin/RegisterPreflightChecksPlugin.kt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/actions/setup/action.yml b/.github/actions/setup/action.yml index af49b46..76dd43f 100644 --- a/.github/actions/setup/action.yml +++ b/.github/actions/setup/action.yml @@ -1,5 +1,5 @@ name: Setup -description: Action that performs common setup tasks like checking out code, etc. +description: Action that performs common setup tasks like setting up Java runs: using: "composite" diff --git a/RELEASING.md b/RELEASING.md index fb8c0ff..5e45b2b 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -3,7 +3,7 @@ If you release for the first time or you forgot the details, long version is recommended, which explains the steps in a great detail. Otherwise you can use TLDR version. -## TLDR +## TLDR version 1. Increase versions of the necessary library artifacts to be released, including the BOM version. You can use `checkIfUpdateNeededSinceCurrentTag` Gradle task to help you figure out what diff --git a/build-logic/logic/src/main/kotlin/io/github/ackeecz/security/plugin/RegisterPreflightChecksPlugin.kt b/build-logic/logic/src/main/kotlin/io/github/ackeecz/security/plugin/RegisterPreflightChecksPlugin.kt index bb824a1..c1bb5cd 100644 --- a/build-logic/logic/src/main/kotlin/io/github/ackeecz/security/plugin/RegisterPreflightChecksPlugin.kt +++ b/build-logic/logic/src/main/kotlin/io/github/ackeecz/security/plugin/RegisterPreflightChecksPlugin.kt @@ -31,7 +31,7 @@ internal class RegisterPreflightChecksPlugin : Plugin { private inner class RegisterPreMergeRequestCheck(private val currentProject: Project) { operator fun invoke() { - // Changes to this task must be synchronized with the basic-preflight-check.yml action + // Changes to this task must be synchronized with the basic-preflight-check/action.yml action // to run the same checks on the CI as well currentProject.tasks.register(PRE_MERGE_REQUEST_CHECK_TASK_NAME) { group = Constants.ACKEE_TASKS_GROUP