Skip to content

Commit ece5eda

Browse files
authored
Merge branch 'dev' into fadi/lab-migrate
2 parents 9cb872e + 88a8524 commit ece5eda

22 files changed

Lines changed: 906 additions & 14 deletions

File tree

changelog.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
vNext
22
----------
3+
- [MINOR] Add AIDL interface for device registration service.(#2926)
34
- [MINOR] Move debugIntuneCE and prodIntuneCE from BrokerData to AppRegistry as App instances (#3012)
45
- [MINOR] Remove LruCache from SharedPreferencesFileManager (#2910)
56
- [MINOR] Edge TB: Claims (#2925)
67
- [PATCH] Update Moshi to 1.15.2 to resolve okio CVE-2023-3635 vulnerability (#3005)
8+
- [MINOR] Edge TB: PoP support (#3006)
79
- [MINOR] Handle target="_blank" links in authorization WebView (#3010)
810
- [MINOR] Handle openid-vc urls in webview (#3013)
11+
- [MINOR] Add WebView file upload support (#3022)
912

1013
Version 24.0.1
1114
----------
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// All rights reserved.
3+
//
4+
// This code is licensed under the MIT License.
5+
//
6+
// Permission is hereby granted, free of charge, to any person obtaining a copy
7+
// of this software and associated documentation files(the "Software"), to deal
8+
// in the Software without restriction, including without limitation the rights
9+
// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
10+
// copies of the Software, and to permit persons to whom the Software is
11+
// furnished to do so, subject to the following conditions :
12+
//
13+
// The above copyright notice and this permission notice shall be included in
14+
// all copies or substantial portions of the Software.
15+
//
16+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22+
// THE SOFTWARE.
23+
package com.microsoft.identity.client;
24+
25+
/**
26+
* AIDL interface for the device registration bound service exposed by the broker.
27+
* Client applications (such as Authenticator or CP) call into this service to execute device registration operations
28+
* when the content provider strategy is not available. The implementation of this service resides in the broker app.
29+
*/
30+
interface IDeviceRegistrationService {
31+
/**
32+
* Executes a device registration protocol with the broker.
33+
*
34+
* @param protocolParams Bundle containing device registration protocol parameters
35+
* @return Bundle containing the protocol response from the broker
36+
*/
37+
Bundle executeDeviceRegistrationProtocol(in Bundle protocolParams);
38+
}

common/src/main/java/com/microsoft/identity/common/internal/broker/BoundServiceClient.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -67,14 +67,14 @@ public abstract class BoundServiceClient<T extends IInterface> {
6767
/**
6868
* Perform the given operation with the given .aidl {@link IInterface}
6969
*/
70-
abstract @Nullable Bundle performOperationInternal(@NonNull final BrokerOperationBundle inputBundle,
71-
@NonNull final T aidlInterface) throws RemoteException, BrokerCommunicationException;
70+
protected abstract @Nullable Bundle performOperationInternal(@NonNull final BrokerOperationBundle inputBundle,
71+
@NonNull final T aidlInterface) throws RemoteException, BrokerCommunicationException;
7272

7373
/**
7474
* Extracts {@link IInterface} from a given {@link IBinder}
7575
* i.e. T.Stub.asInterface(binder), where T is an .aidl {@link IInterface}.
7676
*/
77-
abstract @NonNull T getInterfaceFromIBinder(@NonNull final IBinder binder);
77+
protected abstract @NonNull T getInterfaceFromIBinder(@NonNull final IBinder binder);
7878

7979
/**
8080
* BoundServiceClient's constructor.

common/src/main/java/com/microsoft/identity/common/internal/broker/MicrosoftAuthClient.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,8 @@ public MicrosoftAuthClient(@NonNull final Context context,
7575
}
7676

7777
@Override
78-
@Nullable Bundle performOperationInternal(@NonNull final BrokerOperationBundle brokerOperationBundle,
78+
@Nullable
79+
protected Bundle performOperationInternal(@NonNull final BrokerOperationBundle brokerOperationBundle,
7980
@NonNull final IMicrosoftAuthService microsoftAuthService)
8081
throws RemoteException, BrokerCommunicationException {
8182

@@ -129,7 +130,7 @@ public MicrosoftAuthClient(@NonNull final Context context,
129130
}
130131

131132
@Override
132-
@NonNull IMicrosoftAuthService getInterfaceFromIBinder(@NonNull IBinder binder) {
133+
@NonNull protected IMicrosoftAuthService getInterfaceFromIBinder(@NonNull IBinder binder) {
133134
final IMicrosoftAuthService service = IMicrosoftAuthService.Stub.asInterface(binder);
134135
if (service == null) {
135136
throw new IllegalStateException("Failed to extract IMicrosoftAuthService from IBinder.", null);
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// All rights reserved.
3+
//
4+
// This code is licensed under the MIT License.
5+
//
6+
// Permission is hereby granted, free of charge, to any person obtaining a copy
7+
// of this software and associated documentation files(the "Software"), to deal
8+
// in the Software without restriction, including without limitation the rights
9+
// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
10+
// copies of the Software, and to permit persons to whom the Software is
11+
// furnished to do so, subject to the following conditions :
12+
//
13+
// The above copyright notice and this permission notice shall be included in
14+
// all copies or substantial portions of the Software.
15+
//
16+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22+
// THE SOFTWARE.
23+
package com.microsoft.identity.common.internal.broker.ipc
24+
25+
import android.content.Context
26+
import android.os.Bundle
27+
import android.os.IBinder
28+
import com.microsoft.identity.client.IDeviceRegistrationService
29+
import com.microsoft.identity.common.internal.broker.BoundServiceClient
30+
31+
/**
32+
* A client for communicating with the DeviceRegistrationService via IPC.
33+
* This client binds to the service and allows executing device registration protocol operations with the broker.
34+
*
35+
* @param context the application context used to bind to the service.
36+
*/
37+
class DeviceRegistrationServiceClient(context: Context) :
38+
BoundServiceClient<IDeviceRegistrationService>(
39+
context,
40+
SERVICE_CLASS_NAME,
41+
SERVICE_INTENT_FILTER
42+
) {
43+
companion object {
44+
/** The fully qualified class name of the DeviceRegistrationService to bind to. */
45+
private const val SERVICE_CLASS_NAME = "com.microsoft.identity.client.DeviceRegistrationService"
46+
47+
/** The intent filter used to identify the DeviceRegistrationService. */
48+
private const val SERVICE_INTENT_FILTER = "com.microsoft.identity.client.DeviceRegistration"
49+
}
50+
51+
/**
52+
* Extracts the [IDeviceRegistrationService] AIDL interface from the given [IBinder].
53+
*
54+
* @param binder the [IBinder] returned by the service connection.
55+
* @return the [IDeviceRegistrationService] interface for communicating with the service.
56+
*/
57+
protected override fun getInterfaceFromIBinder(binder: IBinder): IDeviceRegistrationService =
58+
IDeviceRegistrationService.Stub.asInterface(binder)
59+
60+
/**
61+
* Executes the device registration protocol operation by delegating to the AIDL interface.
62+
*
63+
* @param inputBundle the [BrokerOperationBundle] containing the operation parameters.
64+
* @param aidlInterface the [IDeviceRegistrationService] AIDL interface bound to the service.
65+
* @return a [Bundle] containing the result of the device registration protocol, or null if no result.
66+
*/
67+
protected override fun performOperationInternal(
68+
inputBundle: BrokerOperationBundle,
69+
aidlInterface: IDeviceRegistrationService
70+
): Bundle? = aidlInterface.executeDeviceRegistrationProtocol(inputBundle.bundle)
71+
}

common/src/main/java/com/microsoft/identity/common/internal/providers/oauth2/WebViewAuthorizationFragment.java

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
import android.view.View;
4747
import android.view.ViewGroup;
4848
import android.webkit.PermissionRequest;
49+
import android.webkit.ValueCallback;
4950
import android.webkit.WebChromeClient;
5051
import android.webkit.WebResourceRequest;
5152
import android.webkit.WebSettings;
@@ -54,6 +55,7 @@
5455
import android.widget.ProgressBar;
5556

5657
import androidx.activity.result.ActivityResultLauncher;
58+
import androidx.activity.result.contract.ActivityResultContracts;
5759
import androidx.annotation.NonNull;
5860
import androidx.annotation.Nullable;
5961
import androidx.annotation.VisibleForTesting;
@@ -139,6 +141,19 @@ public class WebViewAuthorizationFragment extends AuthorizationFragment {
139141

140142
private final CameraPermissionRequestHandler mCameraPermissionRequestHandler = new CameraPermissionRequestHandler(this);
141143

144+
/**
145+
* Callback for file chooser requests from the WebView.
146+
* This is set when {@link WebChromeClient#onShowFileChooser} is invoked and
147+
* must be called back with the selected file URI(s) or null if cancelled.
148+
*/
149+
private ValueCallback<Uri[]> mFileUploadCallback;
150+
151+
/**
152+
* Launcher for the file chooser activity, registered in {@link #onCreate}.
153+
* Handles the result of the file selection and passes it back to the WebView.
154+
*/
155+
private ActivityResultLauncher<Intent> mFileChooserLauncher;
156+
142157
// This is used by LegacyFido2ApiManager to launch a PendingIntent received by the legacy API.
143158
private ActivityResultLauncher<LegacyFido2ApiObject> mFidoLauncher;
144159
// This is used by the switch browser protocol to handle the resume of the flow.
@@ -158,6 +173,39 @@ public void onCreate(@Nullable Bundle savedInstanceState) {
158173
WebViewUtil.setDataDirectorySuffix(activity.getApplicationContext());
159174
}
160175

176+
// Register file chooser launcher for WebView file upload support.
177+
if (CommonFlightsManager.INSTANCE.getFlightsProvider().isFlightEnabled(CommonFlight.ENABLE_WEBVIEW_FILE_UPLOAD)) {
178+
mFileChooserLauncher = registerForActivityResult(
179+
new ActivityResultContracts.StartActivityForResult(),
180+
result -> {
181+
if (mFileUploadCallback == null) {
182+
Logger.warn(methodTag, "File upload callback is null, ignoring result.");
183+
return;
184+
}
185+
Uri[] resultUris = null;
186+
if (result.getResultCode() == FragmentActivity.RESULT_OK && result.getData() != null) {
187+
final Intent data = result.getData();
188+
if (data.getClipData() != null) {
189+
// Multiple files selected
190+
final int count = data.getClipData().getItemCount();
191+
resultUris = new Uri[count];
192+
for (int i = 0; i < count; i++) {
193+
resultUris[i] = data.getClipData().getItemAt(i).getUri();
194+
}
195+
} else if (data.getData() != null) {
196+
// Single file selected
197+
resultUris = new Uri[]{data.getData()};
198+
}
199+
Logger.info(methodTag, "File chooser returned "
200+
+ (resultUris != null ? resultUris.length : 0) + " file(s).");
201+
} else {
202+
Logger.info(methodTag, "File chooser cancelled or returned no data.");
203+
}
204+
mFileUploadCallback.onReceiveValue(resultUris);
205+
mFileUploadCallback = null;
206+
}
207+
);
208+
}
161209
if (CommonFlightsManager.INSTANCE.getFlightsProvider().isFlightEnabled(CommonFlight.ENABLE_LEGACY_FIDO_SECURITY_KEY_LOGIC)
162210
&& Build.VERSION.SDK_INT < Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
163211
mFidoLauncher = registerForActivityResult(
@@ -387,6 +435,17 @@ public void onPermissionRequest(final PermissionRequest request) {
387435
});
388436
}
389437

438+
@Override
439+
public boolean onShowFileChooser(
440+
final WebView webView,
441+
final ValueCallback<Uri[]> filePathCallback,
442+
final FileChooserParams fileChooserParams) {
443+
final FragmentActivity host = getActivity();
444+
final SpanContext parentSpanContext = host instanceof AuthorizationActivity
445+
? ((AuthorizationActivity) host).getSpanContext() : null;
446+
return handleFileUploadRequest(filePathCallback, fileChooserParams, parentSpanContext);
447+
}
448+
390449
@Override
391450
public Bitmap getDefaultVideoPoster() {
392451
// When not playing, video elements are represented by a 'poster' image.
@@ -515,6 +574,87 @@ boolean isTlrUrl(@Nullable final String url) {
515574
&& lowerUrl.contains(AuthenticationConstants.Broker.TLR_START_PATH);
516575
}
517576

577+
/**
578+
* Handles a file chooser request from the WebView. Creates a telemetry span,
579+
* manages the file upload callback, and launches the system file picker.
580+
*
581+
* @param filePathCallback The callback to deliver file selection results to the WebView.
582+
* @param fileChooserParams Parameters describing the file chooser request.
583+
* @param parentSpanContext The parent span context for telemetry, or null.
584+
* @return {@code true} if the file chooser was launched, {@code false} otherwise.
585+
*/
586+
@VisibleForTesting
587+
boolean handleFileUploadRequest(
588+
@NonNull final ValueCallback<Uri[]> filePathCallback,
589+
@NonNull final WebChromeClient.FileChooserParams fileChooserParams,
590+
@Nullable final SpanContext parentSpanContext) {
591+
final String methodTag = TAG + ":handleFileUploadRequest";
592+
593+
if (!CommonFlightsManager.INSTANCE.getFlightsProvider()
594+
.isFlightEnabled(CommonFlight.ENABLE_WEBVIEW_FILE_UPLOAD)) {
595+
Logger.info(methodTag, "ENABLE_WEBVIEW_FILE_UPLOAD flight is disabled.");
596+
return false;
597+
}
598+
599+
final Span span = OTelUtility.createSpanFromParent(
600+
SpanName.WebViewFileUpload.name(), parentSpanContext);
601+
602+
try (final Scope scope = SpanExtension.makeCurrentSpan(span)) {
603+
// Cancel any existing callback to avoid a dangling reference.
604+
if (mFileUploadCallback != null) {
605+
mFileUploadCallback.onReceiveValue(null);
606+
}
607+
// Clear any previous callback reference before handling the new request.
608+
mFileUploadCallback = null;
609+
610+
// Ensure the file chooser launcher is initialized before attempting to launch.
611+
if (mFileChooserLauncher == null) {
612+
Logger.error(methodTag,
613+
"File chooser launcher is not initialized. Cannot handle file upload request.",
614+
null);
615+
// Notify the caller that no file was selected/returned.
616+
filePathCallback.onReceiveValue(null);
617+
span.setStatus(StatusCode.ERROR);
618+
return false;
619+
}
620+
621+
// At this point we have a valid launcher; store the callback for the result.
622+
mFileUploadCallback = filePathCallback;
623+
624+
final Intent intent = fileChooserParams.createIntent();
625+
Logger.info(methodTag, "Launching file chooser for WebView file upload.");
626+
mFileChooserLauncher.launch(intent);
627+
span.setStatus(StatusCode.OK);
628+
return true;
629+
} catch (final Exception e) {
630+
Logger.error(methodTag, "Failed to launch file chooser.", e);
631+
span.recordException(e);
632+
span.setStatus(StatusCode.ERROR);
633+
if (mFileUploadCallback != null) {
634+
mFileUploadCallback.onReceiveValue(null);
635+
mFileUploadCallback = null;
636+
}
637+
return false;
638+
} finally {
639+
span.end();
640+
}
641+
}
642+
643+
@VisibleForTesting
644+
void setFileUploadCallback(@Nullable final ValueCallback<Uri[]> callback) {
645+
mFileUploadCallback = callback;
646+
}
647+
648+
@VisibleForTesting
649+
ValueCallback<Uri[]> getFileUploadCallback() {
650+
return mFileUploadCallback;
651+
}
652+
653+
@VisibleForTesting
654+
void setFileChooserLauncher(@Nullable final ActivityResultLauncher<Intent> launcher) {
655+
mFileChooserLauncher = launcher;
656+
}
657+
518658
/**
519659
* Loads starting authorization request url into WebView.
520660
*/
@@ -557,6 +697,14 @@ public void onDestroy() {
557697
// but we should still have a check here just to be safe.
558698
mFidoLauncher.unregister();
559699
}
700+
// Clean up file upload callback to prevent memory leaks.
701+
if (mFileUploadCallback != null) {
702+
mFileUploadCallback.onReceiveValue(null);
703+
mFileUploadCallback = null;
704+
}
705+
if (mFileChooserLauncher != null) {
706+
mFileChooserLauncher.unregister();
707+
}
560708
}
561709

562710
/**

common/src/main/java/com/microsoft/identity/common/internal/request/AuthenticationSchemeTypeAdapter.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import static com.microsoft.identity.common.java.authscheme.BearerAuthenticationSchemeInternal.SCHEME_BEARER;
2727
import static com.microsoft.identity.common.java.authscheme.PopAuthenticationSchemeInternal.SCHEME_POP;
2828
import static com.microsoft.identity.common.java.authscheme.PopAuthenticationSchemeWithClientKeyInternal.SCHEME_POP_WITH_CLIENT_KEY;
29+
import static com.microsoft.identity.common.java.authscheme.WebAppsPopAuthenticationSchemeInternal.SCHEME_POP_PREGENERATED;
2930

3031
import androidx.annotation.NonNull;
3132

@@ -42,6 +43,7 @@
4243
import com.microsoft.identity.common.java.authscheme.BearerAuthenticationSchemeInternal;
4344
import com.microsoft.identity.common.java.authscheme.PopAuthenticationSchemeInternal;
4445
import com.microsoft.identity.common.java.authscheme.PopAuthenticationSchemeWithClientKeyInternal;
46+
import com.microsoft.identity.common.java.authscheme.WebAppsPopAuthenticationSchemeInternal;
4547
import com.microsoft.identity.common.logging.Logger;
4648

4749
import java.lang.reflect.Type;
@@ -85,6 +87,9 @@ public AbstractAuthenticationScheme deserialize(@NonNull final JsonElement json,
8587
case SCHEME_POP_WITH_CLIENT_KEY:
8688
return context.deserialize(json, PopAuthenticationSchemeWithClientKeyInternal.class);
8789

90+
case SCHEME_POP_PREGENERATED:
91+
return context.deserialize(json, WebAppsPopAuthenticationSchemeInternal.class);
92+
8893
default:
8994
Logger.warn(
9095
methodTag,
@@ -111,6 +116,9 @@ public JsonElement serialize(@NonNull final AbstractAuthenticationScheme src,
111116
case SCHEME_POP_WITH_CLIENT_KEY:
112117
return context.serialize(src, PopAuthenticationSchemeWithClientKeyInternal.class);
113118

119+
case SCHEME_POP_PREGENERATED:
120+
return context.serialize(src, WebAppsPopAuthenticationSchemeInternal.class);
121+
114122
default:
115123
Logger.warn(
116124
methodTag,

0 commit comments

Comments
 (0)