1 /* 2 * Copyright (C) 2023 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.server.credentials; 18 19 import static com.google.common.truth.Truth.assertThat; 20 21 import static org.mockito.ArgumentMatchers.anySet; 22 import static org.mockito.Mockito.anyString; 23 import static org.mockito.Mockito.verify; 24 import static org.mockito.Mockito.verifyZeroInteractions; 25 import static org.mockito.Mockito.when; 26 import static org.testng.Assert.assertThrows; 27 28 import android.app.Activity; 29 import android.app.slice.Slice; 30 import android.app.slice.SliceSpec; 31 import android.content.ComponentName; 32 import android.content.Context; 33 import android.content.Intent; 34 import android.content.pm.ServiceInfo; 35 import android.content.pm.Signature; 36 import android.content.pm.SigningDetails; 37 import android.content.pm.SigningInfo; 38 import android.credentials.Credential; 39 import android.credentials.CredentialOption; 40 import android.credentials.GetCredentialException; 41 import android.credentials.GetCredentialResponse; 42 import android.credentials.ui.GetCredentialProviderData; 43 import android.credentials.ui.ProviderPendingIntentResponse; 44 import android.net.Uri; 45 import android.os.Bundle; 46 import android.service.credentials.CallingAppInfo; 47 import android.service.credentials.CredentialEntry; 48 import android.service.credentials.CredentialProviderService; 49 import android.service.credentials.GetCredentialRequest; 50 51 import androidx.test.core.app.ApplicationProvider; 52 import androidx.test.filters.SmallTest; 53 import androidx.test.runner.AndroidJUnit4; 54 55 import org.junit.Before; 56 import org.junit.Test; 57 import org.junit.runner.RunWith; 58 import org.mockito.ArgumentCaptor; 59 import org.mockito.Mock; 60 import org.mockito.MockitoAnnotations; 61 62 import java.security.cert.CertificateException; 63 import java.util.ArrayList; 64 import java.util.HashSet; 65 import java.util.List; 66 import java.util.Set; 67 68 /** 69 * Tests for CredentialDescriptionRegistry. 70 * 71 * atest FrameworksServicesTests:com.android.server.credentials.ProviderRegistryGetSessionTest 72 */ 73 @RunWith(AndroidJUnit4.class) 74 @SmallTest 75 public class ProviderRegistryGetSessionTest { 76 77 private static final String CALLING_PACKAGE_NAME = "com.credman.app"; 78 private static final int USER_ID_1 = 1; 79 private static final ArrayList<String> FLATTENED_REQUEST = 80 new ArrayList<>(List.of("FLATTENED_REQ")); 81 private static final String CP_SERVICE_NAME = "CredentialProvider"; 82 private static final ComponentName CREDENTIAL_PROVIDER_COMPONENT = 83 new ComponentName(CALLING_PACKAGE_NAME, CP_SERVICE_NAME); 84 private static final String GET_CREDENTIAL_EXCEPTION_TYPE = "TYPE"; 85 private static final String CREDENTIAL_TYPE = "MDOC"; 86 private static final String GET_CREDENTIAL_EXCEPTION_MESSAGE = "MESSAGE"; 87 88 private ProviderRegistryGetSession mProviderRegistryGetSession; 89 @Mock private GetRequestSession mGetRequestSession; 90 private CredentialOption mGetCredentialOption; 91 @Mock private ServiceInfo mServiceInfo; 92 private CallingAppInfo mCallingAppInfo; 93 @Mock private CredentialDescriptionRegistry mCredentialDescriptionRegistry; 94 private Bundle mRetrievalData; 95 @Mock private CredentialEntry mEntry; 96 @Mock private CredentialEntry mEntry2; 97 private Slice mSlice; 98 private Slice mSlice2; 99 private CredentialDescriptionRegistry.FilterResult mResult; 100 private Set<CredentialDescriptionRegistry.FilterResult> mResponse; 101 102 @SuppressWarnings("GuardedBy") 103 @Before setUp()104 public void setUp() throws CertificateException { 105 MockitoAnnotations.initMocks(this); 106 final Context context = ApplicationProvider.getApplicationContext(); 107 mRetrievalData = new Bundle(); 108 mRetrievalData.putStringArrayList(CredentialOption.SUPPORTED_ELEMENT_KEYS, 109 FLATTENED_REQUEST); 110 mCallingAppInfo = createCallingAppInfo(); 111 mGetCredentialOption = new CredentialOption(CREDENTIAL_TYPE, mRetrievalData, 112 new Bundle(), false); 113 when(mServiceInfo.getComponentName()).thenReturn(CREDENTIAL_PROVIDER_COMPONENT); 114 CredentialDescriptionRegistry.setSession(USER_ID_1, mCredentialDescriptionRegistry); 115 mResponse = new HashSet<>(); 116 mSlice = createSlice(); 117 mSlice2 = createSlice(); 118 when(mEntry.getSlice()).thenReturn(mSlice); 119 when(mEntry2.getSlice()).thenReturn(mSlice2); 120 mResult = new CredentialDescriptionRegistry.FilterResult(CALLING_PACKAGE_NAME, 121 new HashSet<>(FLATTENED_REQUEST), 122 List.of(mEntry, mEntry2)); 123 mResponse.add(mResult); 124 when(mCredentialDescriptionRegistry.getFilteredResultForProvider(anyString(), anySet())) 125 .thenReturn(mResponse); 126 mProviderRegistryGetSession = ProviderRegistryGetSession 127 .createNewSession(context, USER_ID_1, mGetRequestSession, 128 mCallingAppInfo, 129 CALLING_PACKAGE_NAME, 130 mGetCredentialOption); 131 } 132 133 @Test testInvokeSession_existingProvider_setsResults()134 public void testInvokeSession_existingProvider_setsResults() { 135 final ArgumentCaptor<String> packageNameCaptor = ArgumentCaptor.forClass(String.class); 136 final ArgumentCaptor<Set<String>> flattenedRequestCaptor = 137 ArgumentCaptor.forClass(Set.class); 138 final ArgumentCaptor<ProviderSession.Status> statusCaptor = 139 ArgumentCaptor.forClass(ProviderSession.Status.class); 140 final ArgumentCaptor<ComponentName> cpComponentNameCaptor = 141 ArgumentCaptor.forClass(ComponentName.class); 142 143 mProviderRegistryGetSession.invokeSession(); 144 145 verify(mCredentialDescriptionRegistry).getFilteredResultForProvider( 146 packageNameCaptor.capture(), 147 flattenedRequestCaptor.capture()); 148 assertThat(packageNameCaptor.getValue()).isEqualTo(CALLING_PACKAGE_NAME); 149 assertThat(flattenedRequestCaptor.getValue()).containsExactly(FLATTENED_REQUEST); 150 verify(mGetRequestSession).onProviderStatusChanged(statusCaptor.capture(), 151 cpComponentNameCaptor.capture(), ProviderSession.CredentialsSource.REGISTRY); 152 assertThat(statusCaptor.getValue()).isEqualTo(ProviderSession.Status.CREDENTIALS_RECEIVED); 153 assertThat(cpComponentNameCaptor.getValue()).isEqualTo(CREDENTIAL_PROVIDER_COMPONENT); 154 assertThat(mProviderRegistryGetSession.mCredentialEntries).hasSize(2); 155 assertThat(mProviderRegistryGetSession.mCredentialEntries.get(0)).isSameInstanceAs(mEntry); 156 assertThat(mProviderRegistryGetSession.mCredentialEntries.get(1)).isSameInstanceAs(mEntry2); 157 } 158 159 @Test testPrepareUiData_statusNonUIInvoking_throwsIllegalStateException()160 public void testPrepareUiData_statusNonUIInvoking_throwsIllegalStateException() { 161 mProviderRegistryGetSession.setStatus(ProviderSession.Status.CREDENTIALS_RECEIVED); 162 163 assertThrows(IllegalStateException.class, 164 () -> mProviderRegistryGetSession.prepareUiData()); 165 } 166 167 @Test testPrepareUiData_statusUIInvokingNoResults_returnsNull()168 public void testPrepareUiData_statusUIInvokingNoResults_returnsNull() { 169 mProviderRegistryGetSession.setStatus(ProviderSession.Status.CANCELED); 170 171 assertThat(mProviderRegistryGetSession.prepareUiData()).isNull(); 172 } 173 174 @Test testPrepareUiData_invokeCalledSuccessfully_returnsCorrectData()175 public void testPrepareUiData_invokeCalledSuccessfully_returnsCorrectData() { 176 mProviderRegistryGetSession.invokeSession(); 177 GetCredentialProviderData providerData = (GetCredentialProviderData) 178 mProviderRegistryGetSession.prepareUiData(); 179 180 assertThat(providerData).isNotNull(); 181 assertThat(providerData.getCredentialEntries()).hasSize(2); 182 assertThat(providerData.getCredentialEntries().get(0).getSlice()).isSameInstanceAs(mSlice); 183 assertThat(providerData.getCredentialEntries().get(1).getSlice()).isSameInstanceAs(mSlice2); 184 Intent intent = providerData.getCredentialEntries().get(0).getFrameworkExtrasIntent(); 185 GetCredentialRequest getRequest = intent.getParcelableExtra(CredentialProviderService 186 .EXTRA_GET_CREDENTIAL_REQUEST, GetCredentialRequest.class); 187 assertThat(getRequest.getCallingAppInfo()).isSameInstanceAs(mCallingAppInfo); 188 assertThat(getRequest.getCredentialOptions().get(0)).isSameInstanceAs(mGetCredentialOption); 189 Intent intent2 = providerData.getCredentialEntries().get(0).getFrameworkExtrasIntent(); 190 GetCredentialRequest getRequest2 = intent2.getParcelableExtra(CredentialProviderService 191 .EXTRA_GET_CREDENTIAL_REQUEST, GetCredentialRequest.class); 192 assertThat(getRequest2.getCallingAppInfo()).isSameInstanceAs(mCallingAppInfo); 193 assertThat(getRequest2.getCredentialOptions().get(0)) 194 .isSameInstanceAs(mGetCredentialOption); 195 } 196 197 @Test testOnUiEntrySelected_wrongEntryKey_doesNothing()198 public void testOnUiEntrySelected_wrongEntryKey_doesNothing() { 199 final Intent intent = new Intent(); 200 final GetCredentialResponse response = 201 new GetCredentialResponse(new Credential(CREDENTIAL_TYPE, new Bundle())); 202 intent.putExtra(CredentialProviderService.EXTRA_GET_CREDENTIAL_RESPONSE, response); 203 final ProviderPendingIntentResponse providerPendingIntentResponse = new 204 ProviderPendingIntentResponse(Activity.RESULT_OK, intent); 205 206 mProviderRegistryGetSession.onUiEntrySelected( 207 ProviderRegistryGetSession.CREDENTIAL_ENTRY_KEY, 208 "unsupportedKey", providerPendingIntentResponse); 209 210 verifyZeroInteractions(mGetRequestSession); 211 } 212 213 @Test testOnUiEntrySelected_nullPendingIntentResponse_doesNothing()214 public void testOnUiEntrySelected_nullPendingIntentResponse_doesNothing() { 215 mProviderRegistryGetSession.onUiEntrySelected( 216 ProviderRegistryGetSession.CREDENTIAL_ENTRY_KEY, 217 ProviderRegistryGetSession.CREDENTIAL_ENTRY_KEY, null); 218 219 verifyZeroInteractions(mGetRequestSession); 220 } 221 222 @Test testOnUiEntrySelected_pendingIntentWithException_callbackWithGivenException()223 public void testOnUiEntrySelected_pendingIntentWithException_callbackWithGivenException() { 224 final ArgumentCaptor<String> exceptionTypeCaptor = 225 ArgumentCaptor.forClass(String.class); 226 final ArgumentCaptor<String> exceptionMessageCaptor = 227 ArgumentCaptor.forClass(String.class); 228 final ArgumentCaptor<ComponentName> cpComponentNameCaptor = 229 ArgumentCaptor.forClass(ComponentName.class); 230 final ArgumentCaptor<ProviderSession.Status> statusCaptor = 231 ArgumentCaptor.forClass(ProviderSession.Status.class); 232 final GetCredentialException exception = 233 new GetCredentialException(GET_CREDENTIAL_EXCEPTION_TYPE, 234 GET_CREDENTIAL_EXCEPTION_MESSAGE); 235 mProviderRegistryGetSession.invokeSession(); 236 GetCredentialProviderData providerData = (GetCredentialProviderData) 237 mProviderRegistryGetSession.prepareUiData(); 238 String entryKey = providerData.getCredentialEntries().get(0).getSubkey(); 239 final Intent intent = new Intent(); 240 intent.putExtra(CredentialProviderService.EXTRA_GET_CREDENTIAL_EXCEPTION, exception); 241 final ProviderPendingIntentResponse providerPendingIntentResponse = new 242 ProviderPendingIntentResponse(Activity.RESULT_OK, intent); 243 244 mProviderRegistryGetSession.onUiEntrySelected( 245 ProviderRegistryGetSession.CREDENTIAL_ENTRY_KEY, 246 entryKey, providerPendingIntentResponse); 247 248 assertThat(statusCaptor.getValue()).isEqualTo(ProviderSession.Status.CREDENTIALS_RECEIVED); 249 verify(mGetRequestSession).onFinalErrorReceived(cpComponentNameCaptor.capture(), 250 exceptionTypeCaptor.capture(), exceptionMessageCaptor.capture()); 251 assertThat(cpComponentNameCaptor.getValue()) 252 .isSameInstanceAs(CREDENTIAL_PROVIDER_COMPONENT); 253 assertThat(exceptionTypeCaptor.getValue()).isEqualTo(GET_CREDENTIAL_EXCEPTION_TYPE); 254 assertThat(exceptionMessageCaptor.getValue()).isEqualTo(GET_CREDENTIAL_EXCEPTION_MESSAGE); 255 } 256 257 @Test testOnUiEntrySelected_pendingIntentWithException_callbackWithCancelledException()258 public void testOnUiEntrySelected_pendingIntentWithException_callbackWithCancelledException() { 259 final ArgumentCaptor<String> exceptionTypeCaptor = 260 ArgumentCaptor.forClass(String.class); 261 final ArgumentCaptor<String> exceptionMessageCaptor = 262 ArgumentCaptor.forClass(String.class); 263 final ArgumentCaptor<ComponentName> cpComponentNameCaptor = 264 ArgumentCaptor.forClass(ComponentName.class); 265 final ArgumentCaptor<ProviderSession.Status> statusCaptor = 266 ArgumentCaptor.forClass(ProviderSession.Status.class); 267 268 mProviderRegistryGetSession.invokeSession(); 269 GetCredentialProviderData providerData = (GetCredentialProviderData) 270 mProviderRegistryGetSession.prepareUiData(); 271 String entryKey = providerData.getCredentialEntries().get(0).getSubkey(); 272 final Intent intent = new Intent(); 273 final ProviderPendingIntentResponse providerPendingIntentResponse = new 274 ProviderPendingIntentResponse(Activity.RESULT_CANCELED, intent); 275 276 mProviderRegistryGetSession.onUiEntrySelected( 277 ProviderRegistryGetSession.CREDENTIAL_ENTRY_KEY, 278 entryKey, providerPendingIntentResponse); 279 280 assertThat(statusCaptor.getValue()).isEqualTo(ProviderSession.Status.CREDENTIALS_RECEIVED); 281 verify(mGetRequestSession).onFinalErrorReceived(cpComponentNameCaptor.capture(), 282 exceptionTypeCaptor.capture(), exceptionMessageCaptor.capture()); 283 assertThat(cpComponentNameCaptor.getValue()) 284 .isSameInstanceAs(CREDENTIAL_PROVIDER_COMPONENT); 285 assertThat(exceptionTypeCaptor.getValue()) 286 .isEqualTo(GetCredentialException.TYPE_USER_CANCELED); 287 } 288 289 @Test testOnUiEntrySelected_correctEntryKeyPendingIntentResponseExists_succeeds()290 public void testOnUiEntrySelected_correctEntryKeyPendingIntentResponseExists_succeeds() { 291 final ArgumentCaptor<GetCredentialResponse> getCredentialResponseCaptor = 292 ArgumentCaptor.forClass(GetCredentialResponse.class); 293 final ArgumentCaptor<ComponentName> cpComponentNameCaptor = 294 ArgumentCaptor.forClass(ComponentName.class); 295 final ArgumentCaptor<ProviderSession.Status> statusCaptor = 296 ArgumentCaptor.forClass(ProviderSession.Status.class); 297 mProviderRegistryGetSession.invokeSession(); 298 GetCredentialProviderData providerData = (GetCredentialProviderData) 299 mProviderRegistryGetSession.prepareUiData(); 300 final Intent intent = new Intent(); 301 final GetCredentialResponse response = 302 new GetCredentialResponse(new Credential(CREDENTIAL_TYPE, new Bundle())); 303 intent.putExtra(CredentialProviderService.EXTRA_GET_CREDENTIAL_RESPONSE, response); 304 String entryKey = providerData.getCredentialEntries().get(0).getSubkey(); 305 final ProviderPendingIntentResponse providerPendingIntentResponse = new 306 ProviderPendingIntentResponse(Activity.RESULT_OK, intent); 307 308 mProviderRegistryGetSession.onUiEntrySelected( 309 ProviderRegistryGetSession.CREDENTIAL_ENTRY_KEY, 310 entryKey, providerPendingIntentResponse); 311 312 assertThat(statusCaptor.getValue()).isEqualTo(ProviderSession.Status.CREDENTIALS_RECEIVED); 313 verify(mGetRequestSession).onFinalResponseReceived(cpComponentNameCaptor.capture(), 314 getCredentialResponseCaptor.capture()); 315 assertThat(cpComponentNameCaptor.getValue()) 316 .isSameInstanceAs(CREDENTIAL_PROVIDER_COMPONENT); 317 assertThat(getCredentialResponseCaptor.getValue()).isSameInstanceAs(response); 318 } 319 createSlice()320 private static Slice createSlice() { 321 return new Slice.Builder(Uri.EMPTY, new SliceSpec("", 0)).build(); 322 } 323 createCallingAppInfo()324 private static CallingAppInfo createCallingAppInfo() throws CertificateException { 325 return new CallingAppInfo(CALLING_PACKAGE_NAME, 326 new SigningInfo( 327 new SigningDetails(new Signature[]{}, 0, 328 null))); 329 } 330 } 331