diff --git a/src/com/android/settings/security/RequestManageCredentials.java b/src/com/android/settings/security/RequestManageCredentials.java index b30f5b68781..56fa83d857a 100644 --- a/src/com/android/settings/security/RequestManageCredentials.java +++ b/src/com/android/settings/security/RequestManageCredentials.java @@ -19,17 +19,25 @@ package com.android.settings.security; import android.annotation.Nullable; import android.app.Activity; import android.app.admin.DevicePolicyManager; +import android.content.Context; +import android.content.pm.UserInfo; import android.os.Bundle; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Process; +import android.os.RemoteException; +import android.os.UserManager; import android.security.AppUriAuthenticationPolicy; import android.security.Credentials; import android.security.KeyChain; +import android.text.TextUtils; import android.util.Log; import android.view.View; import android.widget.Button; import android.widget.LinearLayout; -import android.widget.Toast; import androidx.annotation.NonNull; +import androidx.annotation.VisibleForTesting; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; @@ -66,29 +74,98 @@ public class RequestManageCredentials extends Activity { private LinearLayout mButtonPanel; private ExtendedFloatingActionButton mExtendedFab; + private HandlerThread mKeyChainTread; + private KeyChain.KeyChainConnection mKeyChainConnection; + private boolean mDisplayingButtonPanel = false; @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); - if (Credentials.ACTION_MANAGE_CREDENTIALS.equals(getIntent().getAction())) { - setContentView(R.layout.request_manage_credentials); - // This is not authenticated, as any app can ask to be the credential management app. - mCredentialManagerPackage = getReferrer().getHost(); - mAuthenticationPolicy = - getIntent().getParcelableExtra(KeyChain.EXTRA_AUTHENTICATION_POLICY); - enforceValidAuthenticationPolicy(mAuthenticationPolicy); - - loadRecyclerView(); - loadButtons(); - loadExtendedFloatingActionButton(); - addOnScrollListener(); - } else { + if (!Credentials.ACTION_MANAGE_CREDENTIALS.equals(getIntent().getAction())) { Log.e(TAG, "Unable to start activity because intent action is not " + Credentials.ACTION_MANAGE_CREDENTIALS); - finish(); + finishWithResultCancelled(); + return; } + if (isManagedDevice()) { + Log.e(TAG, "Credential management on managed devices should be done by the Device " + + "Policy Controller, not a credential management app"); + finishWithResultCancelled(); + return; + } + mCredentialManagerPackage = getLaunchedFromPackage(); + if (TextUtils.isEmpty(mCredentialManagerPackage)) { + Log.e(TAG, "Unknown credential manager app"); + finishWithResultCancelled(); + return; + } + setContentView(R.layout.request_manage_credentials); + + mKeyChainTread = new HandlerThread("KeyChainConnection"); + mKeyChainTread.start(); + mKeyChainConnection = getKeyChainConnection(this, mKeyChainTread); + + AppUriAuthenticationPolicy policy = + getIntent().getParcelableExtra(KeyChain.EXTRA_AUTHENTICATION_POLICY); + if (!isValidAuthenticationPolicy(policy)) { + Log.e(TAG, "Invalid authentication policy"); + finishWithResultCancelled(); + return; + } + mAuthenticationPolicy = policy; + + loadRecyclerView(); + loadButtons(); + loadExtendedFloatingActionButton(); + addOnScrollListener(); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + if (mKeyChainConnection != null) { + mKeyChainConnection.close(); + mKeyChainConnection = null; + mKeyChainTread.quitSafely(); + } + } + + private boolean isValidAuthenticationPolicy(AppUriAuthenticationPolicy policy) { + if (policy == null || policy.getAppAndUriMappings().isEmpty()) { + return false; + } + try { + // Check whether any of the aliases in the policy already exist + for (String alias : policy.getAliases()) { + if (mKeyChainConnection.getService().requestPrivateKey(alias) != null) { + return false; + } + } + } catch (RemoteException e) { + Log.e(TAG, "Invalid authentication policy", e); + return false; + } + return true; + } + + private boolean isManagedDevice() { + DevicePolicyManager dpm = getSystemService(DevicePolicyManager.class); + + return dpm.getDeviceOwnerUser() != null + || dpm.getProfileOwner() != null + || hasManagedProfile(); + } + + private boolean hasManagedProfile() { + UserManager um = getSystemService(UserManager.class); + for (final UserInfo userInfo : um.getProfiles(getUserId())) { + if (userInfo.isManagedProfile()) { + return true; + } + } + return false; } private void loadRecyclerView() { @@ -107,8 +184,10 @@ public class RequestManageCredentials extends Activity { Button dontAllowButton = findViewById(R.id.dont_allow_button); Button allowButton = findViewById(R.id.allow_button); - dontAllowButton.setOnClickListener(finishRequestManageCredentials()); - allowButton.setOnClickListener(setCredentialManagementApp()); + dontAllowButton.setOnClickListener(b -> { + finishWithResultCancelled(); + }); + allowButton.setOnClickListener(b -> setOrUpdateCredentialManagementApp()); } private void loadExtendedFloatingActionButton() { @@ -120,22 +199,26 @@ public class RequestManageCredentials extends Activity { }); } - private View.OnClickListener finishRequestManageCredentials() { - return v -> { - Toast.makeText(this, R.string.request_manage_credentials_dont_allow, - Toast.LENGTH_SHORT).show(); - setResult(RESULT_CANCELED); - finish(); - }; + private void setOrUpdateCredentialManagementApp() { + try { + mKeyChainConnection.getService().setCredentialManagementApp( + mCredentialManagerPackage, mAuthenticationPolicy); + } catch (RemoteException e) { + Log.e(TAG, "Unable to set credential manager app", e); + } + finish(); } - private View.OnClickListener setCredentialManagementApp() { - return v -> { - // TODO: Implement allow logic - Toast.makeText(this, R.string.request_manage_credentials_allow, - Toast.LENGTH_SHORT).show(); - finish(); - }; + @VisibleForTesting + KeyChain.KeyChainConnection getKeyChainConnection(Context context, HandlerThread thread) { + final Handler handler = new Handler(thread.getLooper()); + try { + KeyChain.KeyChainConnection connection = KeyChain.bindAsUser( + context, handler, Process.myUserHandle()); + return connection; + } catch (InterruptedException e) { + throw new RuntimeException("Faile to bind to KeyChain", e); + } } private void addOnScrollListener() { @@ -182,12 +265,8 @@ public class RequestManageCredentials extends Activity { < mRecyclerView.getAdapter().getItemCount() - 1; } - private void enforceValidAuthenticationPolicy(AppUriAuthenticationPolicy policy) { - // TODO: Check whether any of the aliases in the policy already exist - if (policy == null || policy.getAppAndUriMappings().isEmpty()) { - Log.e(TAG, "Invalid authentication policy"); - setResult(RESULT_CANCELED); - finish(); - } + private void finishWithResultCancelled() { + setResult(RESULT_CANCELED); + finish(); } } diff --git a/tests/robotests/src/com/android/settings/security/RequestManageCredentialsTest.java b/tests/robotests/src/com/android/settings/security/RequestManageCredentialsTest.java index ccc6a0b7ba9..aacbf0ce562 100644 --- a/tests/robotests/src/com/android/settings/security/RequestManageCredentialsTest.java +++ b/tests/robotests/src/com/android/settings/security/RequestManageCredentialsTest.java @@ -18,18 +18,24 @@ package com.android.settings.security; import static com.google.common.truth.Truth.assertThat; -import static org.robolectric.Shadows.shadowOf; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; -import android.app.Activity; +import android.app.admin.DevicePolicyManager; +import android.content.ComponentName; import android.content.Intent; import android.net.Uri; +import android.os.UserHandle; import android.security.AppUriAuthenticationPolicy; import android.security.Credentials; +import android.security.IKeyChainService; import android.security.KeyChain; import android.widget.Button; import android.widget.LinearLayout; -import androidx.annotation.NonNull; import androidx.recyclerview.widget.RecyclerView; import com.android.settings.R; @@ -37,10 +43,10 @@ import com.android.settings.R; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.robolectric.Robolectric; import org.robolectric.RobolectricTestRunner; -import org.robolectric.shadows.ShadowActivity; @RunWith(RobolectricTestRunner.class) public class RequestManageCredentialsTest { @@ -52,7 +58,14 @@ public class RequestManageCredentialsTest { private RequestManageCredentials mActivity; - private ShadowActivity mShadowActivity; + @Mock + private DevicePolicyManager mDevicePolicyManager; + + @Mock + private KeyChain.KeyChainConnection mKeyChainConnection; + + @Mock + private IKeyChainService mKeyChainService; @Before public void setUp() { @@ -60,21 +73,98 @@ public class RequestManageCredentialsTest { } @Test - public void onCreate_intentActionNotManageCredentials_finishActivity() { - final Intent intent = new Intent("android.security.ANOTHER_ACTION"); + public void onCreate_intentActionNotManageCredentials_finishActivity() + throws Exception { + final Intent intent = new Intent(Credentials.ACTION_MANAGE_CREDENTIALS + "_bad"); + intent.putExtra(KeyChain.EXTRA_AUTHENTICATION_POLICY, AUTHENTICATION_POLICY); + setupActivityWithAction(intent); + when(mDevicePolicyManager.getDeviceOwnerUser()).thenReturn(null); + when(mDevicePolicyManager.getProfileOwner()).thenReturn(null); + when(mActivity.getLaunchedFromPackage()).thenReturn("com.example.credapp"); - initActivity(intent); + mActivity.onCreate(null); assertThat(mActivity).isNotNull(); assertThat(mActivity.isFinishing()).isTrue(); } @Test - public void onCreate_authenticationPolicyProvided_startActivity() { + public void onCreate_noAuthenticationPolicy_finishActivity() + throws Exception { + final Intent intent = new Intent(Credentials.ACTION_MANAGE_CREDENTIALS); + setupActivityWithAction(intent); + when(mDevicePolicyManager.getDeviceOwnerUser()).thenReturn(null); + when(mDevicePolicyManager.getProfileOwner()).thenReturn(null); + when(mActivity.getLaunchedFromPackage()).thenReturn("com.example.credapp"); + + mActivity.onCreate(null); + + assertThat(mActivity).isNotNull(); + assertThat(mActivity.isFinishing()).isTrue(); + } + + @Test + public void onCreate_invalidAuthenticationPolicy_finishActivity() + throws Exception { + final Intent intent = new Intent(Credentials.ACTION_MANAGE_CREDENTIALS); + intent.putExtra(KeyChain.EXTRA_AUTHENTICATION_POLICY, "Invalid policy"); + + setupActivityWithAction(intent); + when(mDevicePolicyManager.getDeviceOwnerUser()).thenReturn(null); + when(mDevicePolicyManager.getProfileOwner()).thenReturn(null); + when(mActivity.getLaunchedFromPackage()).thenReturn("com.example.credapp"); + + mActivity.onCreate(null); + + assertThat(mActivity).isNotNull(); + assertThat(mActivity.isFinishing()).isTrue(); + } + + @Test + public void onCreate_runOnManagedProfile_finishActivity() + throws Exception { final Intent intent = new Intent(Credentials.ACTION_MANAGE_CREDENTIALS); intent.putExtra(KeyChain.EXTRA_AUTHENTICATION_POLICY, AUTHENTICATION_POLICY); - initActivity(intent); + setupActivityWithAction(intent); + when(mDevicePolicyManager.getDeviceOwnerUser()).thenReturn(null); + when(mDevicePolicyManager.getProfileOwner()).thenReturn(new ComponentName("pkg", "cls")); + when(mActivity.getLaunchedFromPackage()).thenReturn("com.example.credapp"); + + mActivity.onCreate(null); + + assertThat(mActivity).isNotNull(); + assertThat(mActivity.isFinishing()).isTrue(); + } + + @Test + public void onCreate_runOnManagedDevice_finishActivity() + throws Exception { + final Intent intent = new Intent(Credentials.ACTION_MANAGE_CREDENTIALS); + intent.putExtra(KeyChain.EXTRA_AUTHENTICATION_POLICY, AUTHENTICATION_POLICY); + + setupActivityWithAction(intent); + when(mDevicePolicyManager.getDeviceOwnerUser()).thenReturn(UserHandle.SYSTEM); + when(mDevicePolicyManager.getProfileOwner()).thenReturn(null); + when(mActivity.getLaunchedFromPackage()).thenReturn("com.example.credapp"); + + mActivity.onCreate(null); + + assertThat(mActivity).isNotNull(); + assertThat(mActivity.isFinishing()).isTrue(); + } + + @Test + public void onCreate_authenticationPolicyProvided_startActivity() throws Exception { + final Intent intent = new Intent(Credentials.ACTION_MANAGE_CREDENTIALS); + intent.putExtra(KeyChain.EXTRA_AUTHENTICATION_POLICY, AUTHENTICATION_POLICY); + setupActivityWithAction(intent); + + when(mDevicePolicyManager.getDeviceOwnerUser()).thenReturn(null); + when(mDevicePolicyManager.getProfileOwner()).thenReturn(null); + when(mActivity.getLaunchedFromPackage()).thenReturn("com.example.credapp"); + + mActivity.onCreate(null); assertThat(mActivity).isNotNull(); assertThat(mActivity.isFinishing()).isFalse(); @@ -84,23 +174,10 @@ public class RequestManageCredentialsTest { assertThat((Button) mActivity.findViewById(R.id.dont_allow_button)).isNotNull(); } - @Test - public void onCreate_dontAllowButtonClicked_finishActivity() { - final Intent intent = new Intent(Credentials.ACTION_MANAGE_CREDENTIALS); - intent.putExtra(KeyChain.EXTRA_AUTHENTICATION_POLICY, AUTHENTICATION_POLICY); - - initActivity(intent); - - Button dontAllowButton = mActivity.findViewById(R.id.dont_allow_button); - assertThat(dontAllowButton.hasOnClickListeners()).isTrue(); - dontAllowButton.performClick(); - assertThat(mActivity.isFinishing()).isTrue(); - assertThat(mShadowActivity.getResultCode()).isEqualTo(Activity.RESULT_CANCELED); + private void setupActivityWithAction(Intent intent) throws Exception { + mActivity = spy(Robolectric.buildActivity(RequestManageCredentials.class, intent).get()); + doReturn(mKeyChainConnection).when(mActivity).getKeyChainConnection(eq(mActivity), any()); + doReturn(mDevicePolicyManager).when(mActivity).getSystemService(DevicePolicyManager.class); + when(mKeyChainConnection.getService()).thenReturn(mKeyChainService); } - - private void initActivity(@NonNull Intent intent) { - mActivity = Robolectric.buildActivity(RequestManageCredentials.class, intent).setup().get(); - mShadowActivity = shadowOf(mActivity); - } - }