-
-
Couldn't load subscription status.
- Fork 2.5k
Port MASTG-TEST-0036: Testing Enforced Updating (android) (by @appknox) #3462
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
2bb57d9
07109c2
d11e628
a9708b1
d79bb96
46700ae
b42de04
a9d792a
8e29eb0
0673135
486b13d
428c988
ebf4d1c
753f2ef
1173e1b
14961c2
831a08d
d42e1d9
fb39bc4
29666ed
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| --- | ||
| platform: android | ||
| title: Enforced Immediate Updates with Play Core API detected using semgrep | ||
| id: MASTG-DEMO-0x36 | ||
| code: [kotlin] | ||
| test: MASTG-TEST-0x336 | ||
| --- | ||
|
|
||
| ### Sample | ||
|
|
||
| The following code implements immediate in-app updates using the Google Play Core API by calling `startUpdateFlowForResult` with `AppUpdateOptions.newBuilder(1)`. | ||
sk3l10x1ng marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
sk3l10x1ng marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| {{ MastgTest.kt # MastgTest_reversed.java }} | ||
|
|
||
| ### Steps | ||
|
|
||
| Let's run @MASTG-TOOL-0110 rules against the sample code. | ||
|
|
||
| {{ ../../../../rules/mastg-android-enforced-updating.yml }} | ||
|
|
||
| {{ run.sh }} | ||
|
|
||
| ### Observation | ||
|
|
||
| The output file shows usages of the Google Play Core API enforcing immediate update. | ||
|
|
||
| {{ output.txt }} | ||
|
|
||
| ### Evaluation | ||
|
|
||
| The test passes because the app forces users to update the application immediately and does not provide a fallback. | ||
| Original file line number | Diff line number | Diff line change | ||
|---|---|---|---|---|
| @@ -0,0 +1,90 @@ | ||||
| package org.owasp.mastestapp | ||||
|
|
||||
| import android.os.Bundle | ||||
| import android.util.Log | ||||
| import androidx.activity.ComponentActivity | ||||
| import androidx.activity.compose.setContent | ||||
| import androidx.activity.enableEdgeToEdge | ||||
| import androidx.activity.result.ActivityResultLauncher | ||||
| import androidx.activity.result.IntentSenderRequest | ||||
| import androidx.activity.result.contract.ActivityResultContracts | ||||
| import androidx.compose.foundation.layout.padding | ||||
| import androidx.compose.material3.Text | ||||
| import androidx.compose.runtime.Composable | ||||
| import androidx.compose.ui.Modifier | ||||
| import androidx.compose.ui.graphics.Color | ||||
| import androidx.compose.ui.platform.testTag | ||||
| import androidx.compose.ui.text.font.FontFamily | ||||
| import androidx.compose.ui.tooling.preview.Preview | ||||
| import androidx.compose.ui.unit.dp | ||||
| import androidx.compose.ui.unit.sp | ||||
|
|
||||
| const val MASTG_TEXT_TAG = "mastgTestText" | ||||
|
|
||||
| class MainActivity : ComponentActivity() { | ||||
sk3l10x1ng marked this conversation as resolved.
Show resolved
Hide resolved
|
||||
|
|
||||
| private val mastgTest by lazy { MastgTest(applicationContext) } | ||||
| private lateinit var appUpdateResultLauncher: ActivityResultLauncher<IntentSenderRequest> | ||||
|
|
||||
| override fun onCreate(savedInstanceState: Bundle?) { | ||||
| super.onCreate(savedInstanceState) | ||||
| enableEdgeToEdge() | ||||
|
|
||||
| // mastgTest = MastgTest(applicationContext) | ||||
|
||||
| // mastgTest = MastgTest(applicationContext) |
Copilot
AI
Oct 27, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove obsolete comment referring to a removed parameter. The comment no longer provides relevant context.
Copilot
AI
Oct 27, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove obsolete comment referring to a removed parameter. The comment no longer provides relevant context.
Copilot
AI
Oct 27, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove obsolete comment referring to a removed parameter. The comment no longer provides relevant context.
Copilot
AI
Oct 27, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove commented-out initialization check and obsolete parameter comment. Since mastgTest is initialized by lazy delegation, the check is unnecessary.
sk3l10x1ng marked this conversation as resolved.
Show resolved
Hide resolved
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,97 @@ | ||
| package org.owasp.mastestapp; | ||
|
|
||
| import androidx.compose.foundation.layout.PaddingKt; | ||
| import androidx.compose.material3.TextKt; | ||
| import androidx.compose.runtime.Composer; | ||
| import androidx.compose.runtime.ComposerKt; | ||
| import androidx.compose.runtime.RecomposeScopeImplKt; | ||
| import androidx.compose.runtime.ScopeUpdateScope; | ||
| import androidx.compose.runtime.internal.ComposableLambdaKt; | ||
| import androidx.compose.ui.Modifier; | ||
| import androidx.compose.ui.graphics.Color; | ||
| import androidx.compose.ui.platform.TestTagKt; | ||
| import androidx.compose.ui.text.TextLayoutResult; | ||
| import androidx.compose.ui.text.TextStyle; | ||
| import androidx.compose.ui.text.font.FontFamily; | ||
| import androidx.compose.ui.text.font.FontStyle; | ||
| import androidx.compose.ui.text.font.FontWeight; | ||
| import androidx.compose.ui.text.style.TextAlign; | ||
| import androidx.compose.ui.text.style.TextDecoration; | ||
| import androidx.compose.ui.unit.Dp; | ||
| import androidx.compose.ui.unit.TextUnitKt; | ||
| import kotlin.Metadata; | ||
| import kotlin.Unit; | ||
| import kotlin.jvm.functions.Function0; | ||
| import kotlin.jvm.functions.Function1; | ||
| import kotlin.jvm.functions.Function2; | ||
|
|
||
| /* compiled from: MainActivity.kt */ | ||
| @Metadata(d1 = {"\u0000\u0018\n\u0000\n\u0002\u0010\u000e\n\u0000\n\u0002\u0010\u0002\n\u0002\b\u0002\n\u0002\u0018\u0002\n\u0002\b\u0002\u001a'\u0010\u0002\u001a\u00020\u00032\b\b\u0002\u0010\u0004\u001a\u00020\u00012\u000e\b\u0002\u0010\u0005\u001a\b\u0012\u0004\u0012\u00020\u00030\u0006H\u0007¢\u0006\u0002\u0010\u0007\"\u000e\u0010\u0000\u001a\u00020\u0001X\u0086T¢\u0006\u0002\n\u0000¨\u0006\b"}, d2 = {"MASTG_TEXT_TAG", "", "MainScreen", "", "displayString", "onStartClick", "Lkotlin/Function0;", "(Ljava/lang/String;Lkotlin/jvm/functions/Function0;Landroidx/compose/runtime/Composer;II)V", "app_debug"}, k = 2, mv = {2, 0, 0}, xi = 48) | ||
| /* loaded from: classes3.dex */ | ||
| public final class MainActivityKt { | ||
| public static final String MASTG_TEXT_TAG = "mastgTestText"; | ||
|
|
||
| /* JADX INFO: Access modifiers changed from: private */ | ||
| public static final Unit MainScreen$lambda$1(String str, Function0 function0, int i, int i2, Composer composer, int i3) { | ||
| MainScreen(str, function0, composer, RecomposeScopeImplKt.updateChangedFlags(i | 1), i2); | ||
| return Unit.INSTANCE; | ||
| } | ||
|
|
||
| public static final void MainScreen(final String displayString, final Function0<Unit> function0, Composer $composer, final int $changed, final int i) { | ||
| Composer $composer2 = $composer.startRestartGroup(-958245146); | ||
| ComposerKt.sourceInformation($composer2, "C(MainScreen)101@4458L280,101@4418L320:MainActivity.kt#vyvp3i"); | ||
| int $dirty = $changed; | ||
| int i2 = i & 1; | ||
| if (i2 != 0) { | ||
| $dirty |= 6; | ||
| } else if (($changed & 14) == 0) { | ||
| $dirty |= $composer2.changed(displayString) ? 4 : 2; | ||
| } | ||
| int i3 = i & 2; | ||
| if (i3 != 0) { | ||
| $dirty |= 48; | ||
| } else if (($changed & 112) == 0) { | ||
| $dirty |= $composer2.changedInstance(function0) ? 32 : 16; | ||
| } | ||
| if (($dirty & 91) != 18 || !$composer2.getSkipping()) { | ||
| if (i2 != 0) { | ||
| displayString = "App is running."; | ||
| } | ||
| if (i3 != 0) { | ||
| function0 = new Function0() { // from class: org.owasp.mastestapp.MainActivityKt$$ExternalSyntheticLambda0 | ||
| @Override // kotlin.jvm.functions.Function0 | ||
| public final Object invoke() { | ||
| return Unit.INSTANCE; | ||
| } | ||
| }; | ||
| } | ||
| BaseScreenKt.BaseScreen(function0, ComposableLambdaKt.rememberComposableLambda(891526107, true, new Function2<Composer, Integer, Unit>() { // from class: org.owasp.mastestapp.MainActivityKt.MainScreen.2 | ||
| @Override // kotlin.jvm.functions.Function2 | ||
| public /* bridge */ /* synthetic */ Unit invoke(Composer composer, Integer num) { | ||
| invoke(composer, num.intValue()); | ||
| return Unit.INSTANCE; | ||
| } | ||
|
|
||
| public final void invoke(Composer $composer3, int $changed2) { | ||
| ComposerKt.sourceInformation($composer3, "C102@4468L264:MainActivity.kt#vyvp3i"); | ||
| if (($changed2 & 11) != 2 || !$composer3.getSkipping()) { | ||
| TextKt.m2715Text4IGK_g(displayString, TestTagKt.testTag(PaddingKt.m681padding3ABfNKs(Modifier.INSTANCE, Dp.m6664constructorimpl(16)), MainActivityKt.MASTG_TEXT_TAG), Color.INSTANCE.m4219getWhite0d7_KjU(), TextUnitKt.getSp(16), (FontStyle) null, (FontWeight) null, (FontFamily) FontFamily.INSTANCE.getMonospace(), 0L, (TextDecoration) null, (TextAlign) null, 0L, 0, false, 0, 0, (Function1<? super TextLayoutResult, Unit>) null, (TextStyle) null, $composer3, 3504, 0, 130992); | ||
| return; | ||
| } | ||
| $composer3.skipToGroupEnd(); | ||
| } | ||
| }, $composer2, 54), $composer2, (($dirty >> 3) & 14) | 48, 0); | ||
| } else { | ||
| $composer2.skipToGroupEnd(); | ||
| } | ||
| ScopeUpdateScope scopeUpdateScopeEndRestartGroup = $composer2.endRestartGroup(); | ||
| if (scopeUpdateScopeEndRestartGroup != null) { | ||
| scopeUpdateScopeEndRestartGroup.updateScope(new Function2() { // from class: org.owasp.mastestapp.MainActivityKt$$ExternalSyntheticLambda1 | ||
| @Override // kotlin.jvm.functions.Function2 | ||
| public final Object invoke(Object obj, Object obj2) { | ||
| return MainActivityKt.MainScreen$lambda$1(displayString, function0, $changed, i, (Composer) obj, ((Integer) obj2).intValue()); | ||
| } | ||
| }); | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,62 @@ | ||
| package org.owasp.mastestapp | ||
|
|
||
| import android.content.Context | ||
| import android.util.Log | ||
| import androidx.activity.result.ActivityResultLauncher | ||
| import androidx.activity.result.IntentSenderRequest | ||
| import com.google.android.play.core.appupdate.AppUpdateManager | ||
| import com.google.android.play.core.appupdate.AppUpdateManagerFactory | ||
| import com.google.android.play.core.appupdate.AppUpdateOptions | ||
| import com.google.android.play.core.install.model.AppUpdateType | ||
| import com.google.android.play.core.install.model.UpdateAvailability | ||
|
|
||
| class MastgTest(context: Context) { | ||
|
|
||
| private val appUpdateManager: AppUpdateManager = AppUpdateManagerFactory.create(context) | ||
|
|
||
| /** | ||
| * Checks if an IMMEDIATE update is available on the Play Store. | ||
| */ | ||
| fun checkForUpdate( | ||
| appUpdateResultLauncher: ActivityResultLauncher<IntentSenderRequest> | ||
| ) { | ||
| Log.d("MastgTest", "Checking for an update...") | ||
| val appUpdateInfoTask = appUpdateManager.appUpdateInfo | ||
|
|
||
| appUpdateInfoTask.addOnSuccessListener { appUpdateInfo -> | ||
| val isUpdateAvailable = appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE | ||
| val isImmediateUpdateAllowed = appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.IMMEDIATE) | ||
|
|
||
| if (isUpdateAvailable && isImmediateUpdateAllowed) { | ||
| Log.d("MastgTest", "Immediate update available. Starting flow.") | ||
| appUpdateManager.startUpdateFlowForResult( | ||
| appUpdateInfo, | ||
| appUpdateResultLauncher, | ||
| AppUpdateOptions.newBuilder(AppUpdateType.IMMEDIATE).build() | ||
| ) | ||
| } else { | ||
| Log.d("MastgTest", "No immediate update available.") | ||
| } | ||
| }.addOnFailureListener { e -> | ||
| Log.e("MastgTest", "Failed to check for updates.", e) | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Resumes an update that is already in progress. This is critical for onResume(). | ||
| */ | ||
| fun resumeUpdateIfInProgress( | ||
| appUpdateResultLauncher: ActivityResultLauncher<IntentSenderRequest> | ||
| ) { | ||
| appUpdateManager.appUpdateInfo.addOnSuccessListener { appUpdateInfo -> | ||
| if (appUpdateInfo.updateAvailability() == UpdateAvailability.DEVELOPER_TRIGGERED_UPDATE_IN_PROGRESS) { | ||
| Log.d("MastgTest", "Resuming in-progress update.") | ||
| appUpdateManager.startUpdateFlowForResult( | ||
| appUpdateInfo, | ||
| appUpdateResultLauncher, | ||
| AppUpdateOptions.newBuilder(AppUpdateType.IMMEDIATE).build() | ||
| ) | ||
sk3l10x1ng marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
| } | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.