Skip to content
Open
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
31 changes: 31 additions & 0 deletions demos/android/MASVS-CODE/MASTG-DEMO-0x36/MASTG-DEMO-0x36.md
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)`.

{{ 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.
83 changes: 83 additions & 0 deletions demos/android/MASVS-CODE/MASTG-DEMO-0x36/MainActivity.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
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() {

private val mastgTest by lazy { MastgTest(applicationContext) }
private lateinit var appUpdateResultLauncher: ActivityResultLauncher<IntentSenderRequest>

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()

appUpdateResultLauncher = registerForActivityResult(
ActivityResultContracts.StartIntentSenderForResult()
) { result ->
if (result.resultCode != RESULT_OK) {
Log.e(
"MainActivity",
"Update flow was cancelled or failed! Result code: ${result.resultCode}. Re-initiating."
)

mastgTest.checkForUpdate(appUpdateResultLauncher)
} else {
Log.d("MainActivity", "Update accepted. The update is now in progress.")
}
}

setContent {
MainScreen(
displayString = "App is running. Checking for mandatory updates...",
onStartClick = {
mastgTest.checkForUpdate(appUpdateResultLauncher)
}
)
}

mastgTest.checkForUpdate(appUpdateResultLauncher)
}

override fun onResume() {
super.onResume()
mastgTest.resumeUpdateIfInProgress(appUpdateResultLauncher)
}
}

@Preview
@Composable
fun MainScreen(
displayString: String = "App is running.",
onStartClick: () -> Unit = {}
) {
BaseScreen(onStartClick = onStartClick) {
Text(
modifier = Modifier
.padding(16.dp)
.testTag(MASTG_TEXT_TAG),
text = displayString,
color = Color.White,
fontSize = 16.sp,
fontFamily = FontFamily.Monospace
)
}
}
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());
}
});
}
}
}
62 changes: 62 additions & 0 deletions demos/android/MASVS-CODE/MASTG-DEMO-0x36/MastgTest.kt
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()
)
}
}
}
}
117 changes: 117 additions & 0 deletions demos/android/MASVS-CODE/MASTG-DEMO-0x36/MastgTest_reversed.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
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.gms.tasks.OnFailureListener;
import com.google.android.gms.tasks.OnSuccessListener;
import com.google.android.gms.tasks.Task;
import com.google.android.play.core.appupdate.AppUpdateInfo;
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 kotlin.Metadata;
import kotlin.Unit;
import kotlin.jvm.functions.Function1;
import kotlin.jvm.internal.Intrinsics;

/* compiled from: MastgTest.kt */
@Metadata(d1 = {"\u0000*\n\u0002\u0018\u0002\n\u0002\u0010\u0000\n\u0000\n\u0002\u0018\u0002\n\u0002\b\u0003\n\u0002\u0018\u0002\n\u0000\n\u0002\u0010\u0002\n\u0000\n\u0002\u0018\u0002\n\u0002\u0018\u0002\n\u0002\b\u0002\b\u0007\u0018\u00002\u00020\u0001B\u000f\u0012\u0006\u0010\u0002\u001a\u00020\u0003¢\u0006\u0004\b\u0004\u0010\u0005J\u0014\u0010\b\u001a\u00020\t2\f\u0010\n\u001a\b\u0012\u0004\u0012\u00020\f0\u000bJ\u0014\u0010\r\u001a\u00020\t2\f\u0010\n\u001a\b\u0012\u0004\u0012\u00020\f0\u000bR\u000e\u0010\u0006\u001a\u00020\u0007X\u0082\u0004¢\u0006\u0002\n\u0000¨\u0006\u000e"}, d2 = {"Lorg/owasp/mastestapp/MastgTest;", "", "context", "Landroid/content/Context;", "<init>", "(Landroid/content/Context;)V", "appUpdateManager", "Lcom/google/android/play/core/appupdate/AppUpdateManager;", "checkForUpdate", "", "appUpdateResultLauncher", "Landroidx/activity/result/ActivityResultLauncher;", "Landroidx/activity/result/IntentSenderRequest;", "resumeUpdateIfInProgress", "app_debug"}, k = 1, mv = {2, 0, 0}, xi = 48)
/* loaded from: classes3.dex */
public final class MastgTest {
public static final int $stable = 8;
private final AppUpdateManager appUpdateManager;

public MastgTest(Context context) {
Intrinsics.checkNotNullParameter(context, "context");
AppUpdateManager appUpdateManagerCreate = AppUpdateManagerFactory.create(context);
Intrinsics.checkNotNullExpressionValue(appUpdateManagerCreate, "create(...)");
this.appUpdateManager = appUpdateManagerCreate;
}

public final void checkForUpdate(final ActivityResultLauncher<IntentSenderRequest> appUpdateResultLauncher) {
Intrinsics.checkNotNullParameter(appUpdateResultLauncher, "appUpdateResultLauncher");
Log.d("MastgTest", "Checking for an update...");
Task appUpdateInfoTask = this.appUpdateManager.getAppUpdateInfo();
Intrinsics.checkNotNullExpressionValue(appUpdateInfoTask, "getAppUpdateInfo(...)");
final Function1 function1 = new Function1() { // from class: org.owasp.mastestapp.MastgTest$$ExternalSyntheticLambda0
@Override // kotlin.jvm.functions.Function1
public final Object invoke(Object obj) {
return MastgTest.checkForUpdate$lambda$0(this.f$0, appUpdateResultLauncher, (AppUpdateInfo) obj);
}
};
appUpdateInfoTask.addOnSuccessListener(new OnSuccessListener() { // from class: org.owasp.mastestapp.MastgTest$$ExternalSyntheticLambda1
@Override // com.google.android.gms.tasks.OnSuccessListener
public final void onSuccess(Object obj) {
MastgTest.checkForUpdate$lambda$1(function1, obj);
}
}).addOnFailureListener(new OnFailureListener() { // from class: org.owasp.mastestapp.MastgTest$$ExternalSyntheticLambda2
@Override // com.google.android.gms.tasks.OnFailureListener
public final void onFailure(Exception exc) {
MastgTest.checkForUpdate$lambda$2(exc);
}
});
}

/* JADX INFO: Access modifiers changed from: private */
public static final void checkForUpdate$lambda$1(Function1 tmp0, Object p0) {
Intrinsics.checkNotNullParameter(tmp0, "$tmp0");
tmp0.invoke(p0);
}

/* JADX INFO: Access modifiers changed from: private */
public static final Unit checkForUpdate$lambda$0(MastgTest this$0, ActivityResultLauncher appUpdateResultLauncher, AppUpdateInfo appUpdateInfo) {
Intrinsics.checkNotNullParameter(this$0, "this$0");
Intrinsics.checkNotNullParameter(appUpdateResultLauncher, "$appUpdateResultLauncher");
boolean isUpdateAvailable = appUpdateInfo.updateAvailability() == 2;
boolean isImmediateUpdateAllowed = appUpdateInfo.isUpdateTypeAllowed(1);
if (!isUpdateAvailable || !isImmediateUpdateAllowed) {
Log.d("MastgTest", "No immediate update available.");
} else {
Log.d("MastgTest", "Immediate update available. Starting flow.");
this$0.appUpdateManager.startUpdateFlowForResult(appUpdateInfo, appUpdateResultLauncher, AppUpdateOptions.newBuilder(1).build());
}
return Unit.INSTANCE;
}

/* JADX INFO: Access modifiers changed from: private */
public static final void checkForUpdate$lambda$2(Exception e) {
Intrinsics.checkNotNullParameter(e, "e");
Log.e("MastgTest", "Failed to check for updates.", e);
}

/* JADX INFO: Access modifiers changed from: private */
public static final void resumeUpdateIfInProgress$lambda$4(Function1 tmp0, Object p0) {
Intrinsics.checkNotNullParameter(tmp0, "$tmp0");
tmp0.invoke(p0);
}

public final void resumeUpdateIfInProgress(final ActivityResultLauncher<IntentSenderRequest> appUpdateResultLauncher) {
Intrinsics.checkNotNullParameter(appUpdateResultLauncher, "appUpdateResultLauncher");
Task<AppUpdateInfo> appUpdateInfo = this.appUpdateManager.getAppUpdateInfo();
final Function1 function1 = new Function1() { // from class: org.owasp.mastestapp.MastgTest$$ExternalSyntheticLambda3
@Override // kotlin.jvm.functions.Function1
public final Object invoke(Object obj) {
return MastgTest.resumeUpdateIfInProgress$lambda$3(this.f$0, appUpdateResultLauncher, (AppUpdateInfo) obj);
}
};
appUpdateInfo.addOnSuccessListener(new OnSuccessListener() { // from class: org.owasp.mastestapp.MastgTest$$ExternalSyntheticLambda4
@Override // com.google.android.gms.tasks.OnSuccessListener
public final void onSuccess(Object obj) {
MastgTest.resumeUpdateIfInProgress$lambda$4(function1, obj);
}
});
}

/* JADX INFO: Access modifiers changed from: private */
public static final Unit resumeUpdateIfInProgress$lambda$3(MastgTest this$0, ActivityResultLauncher appUpdateResultLauncher, AppUpdateInfo appUpdateInfo) {
Intrinsics.checkNotNullParameter(this$0, "this$0");
Intrinsics.checkNotNullParameter(appUpdateResultLauncher, "$appUpdateResultLauncher");
if (appUpdateInfo.updateAvailability() == 3) {
Log.d("MastgTest", "Resuming in-progress update.");
this$0.appUpdateManager.startUpdateFlowForResult(appUpdateInfo, appUpdateResultLauncher, AppUpdateOptions.newBuilder(1).build());
}
return Unit.INSTANCE;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
implementation("com.google.android.play:app-update-ktx:2.1.0")
Loading
Loading