Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 4fe99f19 authored by Rubin Xu's avatar Rubin Xu Committed by Android (Google) Code Review
Browse files

Merge "Call KeyChain credential management app APIs Settings"

parents a533dc04 c850920c
Loading
Loading
Loading
Loading
+117 −38
Original line number Diff line number Diff line
@@ -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,31 +74,100 @@ 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())) {
        if (!Credentials.ACTION_MANAGE_CREDENTIALS.equals(getIntent().getAction())) {
            Log.e(TAG, "Unable to start activity because intent action is not "
                    + Credentials.ACTION_MANAGE_CREDENTIALS);
            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);
            // This is not authenticated, as any app can ask to be the credential management app.
            mCredentialManagerPackage = getReferrer().getHost();
            mAuthenticationPolicy =

        mKeyChainTread = new HandlerThread("KeyChainConnection");
        mKeyChainTread.start();
        mKeyChainConnection = getKeyChainConnection(this, mKeyChainTread);

        AppUriAuthenticationPolicy policy =
                getIntent().getParcelableExtra(KeyChain.EXTRA_AUTHENTICATION_POLICY);
            enforceValidAuthenticationPolicy(mAuthenticationPolicy);
        if (!isValidAuthenticationPolicy(policy)) {
            Log.e(TAG, "Invalid authentication policy");
            finishWithResultCancelled();
            return;
        }
        mAuthenticationPolicy = policy;

        loadRecyclerView();
        loadButtons();
        loadExtendedFloatingActionButton();
        addOnScrollListener();
        } else {
            Log.e(TAG, "Unable to start activity because intent action is not "
                    + Credentials.ACTION_MANAGE_CREDENTIALS);
            finish();
    }

    @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() {
        mLayoutManager = new LinearLayoutManager(this);
        mRecyclerView = findViewById(R.id.apps_list);
@@ -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);
    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");
    private void finishWithResultCancelled() {
        setResult(RESULT_CANCELED);
        finish();
    }
}
}
+102 −25
Original line number Diff line number Diff line
@@ -18,18 +18,24 @@ package com.android.settings.security;

import static com.google.common.truth.Truth.assertThat;

import static org.robolectric.Shadows.shadowOf;

import android.app.Activity;
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.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,47 +73,111 @@ 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");

        mActivity.onCreate(null);

        assertThat(mActivity).isNotNull();
        assertThat(mActivity.isFinishing()).isTrue();
    }

    @Test
    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");

        initActivity(intent);
        mActivity.onCreate(null);

        assertThat(mActivity).isNotNull();
        assertThat(mActivity.isFinishing()).isTrue();
    }

    @Test
    public void onCreate_authenticationPolicyProvided_startActivity() {
    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()).isFalse();
        assertThat((RecyclerView) mActivity.findViewById(R.id.apps_list)).isNotNull();
        assertThat((LinearLayout) mActivity.findViewById(R.id.button_panel)).isNotNull();
        assertThat((Button) mActivity.findViewById(R.id.allow_button)).isNotNull();
        assertThat((Button) mActivity.findViewById(R.id.dont_allow_button)).isNotNull();
        assertThat(mActivity.isFinishing()).isTrue();
    }

    @Test
    public void onCreate_dontAllowButtonClicked_finishActivity() {
    public void onCreate_runOnManagedDevice_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(UserHandle.SYSTEM);
        when(mDevicePolicyManager.getProfileOwner()).thenReturn(null);
        when(mActivity.getLaunchedFromPackage()).thenReturn("com.example.credapp");

        Button dontAllowButton = mActivity.findViewById(R.id.dont_allow_button);
        assertThat(dontAllowButton.hasOnClickListeners()).isTrue();
        dontAllowButton.performClick();
        mActivity.onCreate(null);

        assertThat(mActivity).isNotNull();
        assertThat(mActivity.isFinishing()).isTrue();
        assertThat(mShadowActivity.getResultCode()).isEqualTo(Activity.RESULT_CANCELED);
    }

    private void initActivity(@NonNull Intent intent) {
        mActivity = Robolectric.buildActivity(RequestManageCredentials.class, intent).setup().get();
        mShadowActivity = shadowOf(mActivity);
    @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();
        assertThat((RecyclerView) mActivity.findViewById(R.id.apps_list)).isNotNull();
        assertThat((LinearLayout) mActivity.findViewById(R.id.button_panel)).isNotNull();
        assertThat((Button) mActivity.findViewById(R.id.allow_button)).isNotNull();
        assertThat((Button) mActivity.findViewById(R.id.dont_allow_button)).isNotNull();
    }

    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);
    }
}