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

Commit 9ff79aea authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Hook up the full slice access checkbox"

parents 87ea3c3d ddb8a96f
Loading
Loading
Loading
Loading
+131 −0
Original line number Original line Diff line number Diff line
/*
 * Copyright (C) 2018 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the
 * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied. See the License for the specific language governing
 * permissions and limitations under the License.
 */

package com.android.server.slice;

import android.content.Context;
import android.content.pm.UserInfo;
import android.os.UserManager;
import android.util.ArraySet;
import android.util.Log;
import android.util.SparseArray;

import com.android.internal.util.XmlUtils;

import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;

import java.io.IOException;
import java.util.List;

public class SliceFullAccessList {

    static final int DB_VERSION = 1;
    private static final String TAG = "SliceFullAccessList";

    private static final String TAG_LIST = "slice-access-list";
    private static final String TAG_PKG = "pkg";
    private static final String TAG_USER = "user";

    private final String ATT_USER_ID = "user";
    private final String ATT_VERSION = "version";

    private final SparseArray<ArraySet<String>> mFullAccessPkgs = new SparseArray<>();
    private final Context mContext;

    public SliceFullAccessList(Context context) {
        mContext = context;
    }

    public boolean hasFullAccess(String pkg, int userId) {
        ArraySet<String> pkgs = mFullAccessPkgs.get(userId, null);
        return pkgs != null && pkgs.contains(pkg);
    }

    public void grantFullAccess(String pkg, int userId) {
        ArraySet<String> pkgs = mFullAccessPkgs.get(userId, null);
        if (pkgs == null) {
            pkgs = new ArraySet<>();
            mFullAccessPkgs.put(userId, pkgs);
        }
        pkgs.add(pkg);
    }

    public void writeXml(XmlSerializer out) throws IOException {
        out.startTag(null, TAG_LIST);
        out.attribute(null, ATT_VERSION, String.valueOf(DB_VERSION));

        final int N = mFullAccessPkgs.size();
        for (int i = 0 ; i < N; i++) {
            final int userId = mFullAccessPkgs.keyAt(i);
            final ArraySet<String> pkgs = mFullAccessPkgs.valueAt(i);
            out.startTag(null, TAG_USER);
            out.attribute(null, ATT_USER_ID, Integer.toString(userId));
            if (pkgs != null) {
                final int M = pkgs.size();
                for (int j = 0; j < M; j++) {
                        out.startTag(null, TAG_PKG);
                        out.text(pkgs.valueAt(j));
                        out.endTag(null, TAG_PKG);

                }
            }
            out.endTag(null, TAG_USER);
        }
        out.endTag(null, TAG_LIST);
    }

    public void readXml(XmlPullParser parser) throws XmlPullParserException, IOException {
        // upgrade xml
        int xmlVersion = XmlUtils.readIntAttribute(parser, ATT_VERSION, 0);
        final List<UserInfo> activeUsers = UserManager.get(mContext).getUsers(true);
        for (UserInfo userInfo : activeUsers) {
            upgradeXml(xmlVersion, userInfo.getUserHandle().getIdentifier());
        }

        mFullAccessPkgs.clear();
        // read grants
        int type;
        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
            String tag = parser.getName();
            if (type == XmlPullParser.END_TAG
                    && TAG_LIST.equals(tag)) {
                break;
            }
            if (type == XmlPullParser.START_TAG) {
                if (TAG_USER.equals(tag)) {
                    final int userId = XmlUtils.readIntAttribute(parser, ATT_USER_ID, 0);
                    ArraySet<String> pkgs = new ArraySet<>();
                    while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
                        String userTag = parser.getName();
                        if (type == XmlPullParser.END_TAG
                                && TAG_USER.equals(userTag)) {
                            break;
                        }
                        if (type == XmlPullParser.START_TAG) {
                            if (TAG_PKG.equals(userTag)) {
                                final String pkg = parser.nextText();
                                pkgs.add(pkg);
                            }
                        }
                    }
                    mFullAccessPkgs.put(userId, pkgs);
                }
            }
        }
    }

    protected void upgradeXml(final int xmlVersion, final int userId) {}
}
+76 −15
Original line number Original line Diff line number Diff line
@@ -39,6 +39,7 @@ import android.content.pm.ResolveInfo;
import android.database.ContentObserver;
import android.database.ContentObserver;
import android.net.Uri;
import android.net.Uri;
import android.os.Binder;
import android.os.Binder;
import android.os.Environment;
import android.os.Handler;
import android.os.Handler;
import android.os.IBinder;
import android.os.IBinder;
import android.os.Looper;
import android.os.Looper;
@@ -47,7 +48,10 @@ import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserHandle;
import android.util.ArrayMap;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.ArraySet;
import android.util.AtomicFile;
import android.util.Log;
import android.util.Log;
import android.util.Slog;
import android.util.Xml.Encoding;


import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.annotations.VisibleForTesting;
@@ -57,6 +61,16 @@ import com.android.server.LocalServices;
import com.android.server.ServiceThread;
import com.android.server.ServiceThread;
import com.android.server.SystemService;
import com.android.server.SystemService;


import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlPullParserFactory;
import org.xmlpull.v1.XmlSerializer;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.ArrayList;
import java.util.List;
import java.util.List;
import java.util.Objects;
import java.util.Objects;
@@ -77,6 +91,8 @@ public class SliceManagerService extends ISliceManager.Stub {
    private final ArraySet<SliceGrant> mUserGrants = new ArraySet<>();
    private final ArraySet<SliceGrant> mUserGrants = new ArraySet<>();
    private final Handler mHandler;
    private final Handler mHandler;
    private final ContentObserver mObserver;
    private final ContentObserver mObserver;
    private final AtomicFile mSliceAccessFile;
    private final SliceFullAccessList mAccessList;


    public SliceManagerService(Context context) {
    public SliceManagerService(Context context) {
        this(context, createHandler().getLooper());
        this(context, createHandler().getLooper());
@@ -101,6 +117,21 @@ public class SliceManagerService extends ISliceManager.Stub {
                }
                }
            }
            }
        };
        };
        final File systemDir = new File(Environment.getDataDirectory(), "system");
        mSliceAccessFile = new AtomicFile(new File(systemDir, "slice_access.xml"));
        mAccessList = new SliceFullAccessList(mContext);

        synchronized (mSliceAccessFile) {
            if (!mSliceAccessFile.exists()) return;
            try {
                InputStream input = mSliceAccessFile.openRead();
                XmlPullParser parser = XmlPullParserFactory.newInstance().newPullParser();
                parser.setInput(input, Encoding.UTF_8.name());
                mAccessList.readXml(parser);
            } catch (IOException | XmlPullParserException e) {
                Slog.d(TAG, "Can't read slice access file", e);
            }
        }
    }
    }


    ///  ----- Lifecycle stuff -----
    ///  ----- Lifecycle stuff -----
@@ -175,11 +206,11 @@ public class SliceManagerService extends ISliceManager.Stub {
                == PERMISSION_GRANTED) {
                == PERMISSION_GRANTED) {
            return SliceManager.PERMISSION_GRANTED;
            return SliceManager.PERMISSION_GRANTED;
        }
        }
        if (hasFullSliceAccess(pkg, uid)) {
        if (hasFullSliceAccess(pkg, UserHandle.getUserId(uid))) {
            return SliceManager.PERMISSION_GRANTED;
            return SliceManager.PERMISSION_GRANTED;
        }
        }
        synchronized (mLock) {
        synchronized (mLock) {
            if (mUserGrants.contains(new SliceGrant(uri, pkg))) {
            if (mUserGrants.contains(new SliceGrant(uri, pkg, UserHandle.getUserId(uid)))) {
                return SliceManager.PERMISSION_USER_GRANTED;
                return SliceManager.PERMISSION_USER_GRANTED;
            }
            }
        }
        }
@@ -192,10 +223,13 @@ public class SliceManagerService extends ISliceManager.Stub {
        getContext().enforceCallingOrSelfPermission(permission.MANAGE_SLICE_PERMISSIONS,
        getContext().enforceCallingOrSelfPermission(permission.MANAGE_SLICE_PERMISSIONS,
                "Slice granting requires MANAGE_SLICE_PERMISSIONS");
                "Slice granting requires MANAGE_SLICE_PERMISSIONS");
        if (allSlices) {
        if (allSlices) {
            // TODO: Manage full access grants.
            mAccessList.grantFullAccess(pkg, Binder.getCallingUserHandle().getIdentifier());
            mHandler.post(mSaveAccessList);
        } else {
        } else {
            synchronized (mLock) {
            synchronized (mLock) {
                mUserGrants.add(new SliceGrant(uri, pkg));
                mUserGrants.add(new SliceGrant(uri, pkg,
                        Binder.getCallingUserHandle().getIdentifier()));
            }
        }
        }
        long ident = Binder.clearCallingIdentity();
        long ident = Binder.clearCallingIdentity();
        try {
        try {
@@ -203,7 +237,6 @@ public class SliceManagerService extends ISliceManager.Stub {
        } finally {
        } finally {
            Binder.restoreCallingIdentity(ident);
            Binder.restoreCallingIdentity(ident);
        }
        }
        }
        synchronized (mLock) {
        synchronized (mLock) {
            for (PinnedSliceState p : mPinnedSlicesByUri.values()) {
            for (PinnedSliceState p : mPinnedSlicesByUri.values()) {
                p.recheckPackage(pkg);
                p.recheckPackage(pkg);
@@ -260,7 +293,7 @@ public class SliceManagerService extends ISliceManager.Stub {
    protected int checkAccess(String pkg, Uri uri, int uid, int pid) {
    protected int checkAccess(String pkg, Uri uri, int uid, int pid) {
        int user = UserHandle.getUserId(uid);
        int user = UserHandle.getUserId(uid);
        // Check for default launcher/assistant.
        // Check for default launcher/assistant.
        if (!hasFullSliceAccess(pkg, uid)) {
        if (!hasFullSliceAccess(pkg, user)) {
            // Also allow things with uri access.
            // Also allow things with uri access.
            if (getContext().checkUriPermission(uri, pid, uid,
            if (getContext().checkUriPermission(uri, pid, uid,
                    Intent.FLAG_GRANT_WRITE_URI_PERMISSION) != PERMISSION_GRANTED) {
                    Intent.FLAG_GRANT_WRITE_URI_PERMISSION) != PERMISSION_GRANTED) {
@@ -411,8 +444,7 @@ public class SliceManagerService extends ISliceManager.Stub {
    }
    }


    private boolean isGrantedFullAccess(String pkg, int userId) {
    private boolean isGrantedFullAccess(String pkg, int userId) {
        // TODO: This will be user granted access, if we allow this through a prompt.
        return mAccessList.hasFullAccess(pkg, userId);
        return false;
    }
    }


    private static ServiceThread createHandler() {
    private static ServiceThread createHandler() {
@@ -422,6 +454,32 @@ public class SliceManagerService extends ISliceManager.Stub {
        return handlerThread;
        return handlerThread;
    }
    }


    private final Runnable mSaveAccessList = new Runnable() {
        @Override
        public void run() {
            synchronized (mSliceAccessFile) {
                final FileOutputStream stream;
                try {
                    stream = mSliceAccessFile.startWrite();
                } catch (IOException e) {
                    Slog.w(TAG, "Failed to save access file", e);
                    return;
                }

                try {
                    XmlSerializer out = XmlPullParserFactory.newInstance().newSerializer();
                    out.setOutput(stream, Encoding.UTF_8.name());
                    mAccessList.writeXml(out);
                    out.flush();
                    mSliceAccessFile.finishWrite(stream);
                } catch (IOException | XmlPullParserException e) {
                    Slog.w(TAG, "Failed to save access file, restoring backup", e);
                    mSliceAccessFile.failWrite(stream);
                }
            }
        }
    };

    public static class Lifecycle extends SystemService {
    public static class Lifecycle extends SystemService {
        private SliceManagerService mService;
        private SliceManagerService mService;


@@ -456,10 +514,12 @@ public class SliceManagerService extends ISliceManager.Stub {
    private class SliceGrant {
    private class SliceGrant {
        private final Uri mUri;
        private final Uri mUri;
        private final String mPkg;
        private final String mPkg;
        private final int mUserId;


        public SliceGrant(Uri uri, String pkg) {
        public SliceGrant(Uri uri, String pkg, int userId) {
            mUri = uri;
            mUri = uri;
            mPkg = pkg;
            mPkg = pkg;
            mUserId = userId;
        }
        }


        @Override
        @Override
@@ -471,7 +531,8 @@ public class SliceManagerService extends ISliceManager.Stub {
        public boolean equals(Object obj) {
        public boolean equals(Object obj) {
            if (!(obj instanceof SliceGrant)) return false;
            if (!(obj instanceof SliceGrant)) return false;
            SliceGrant other = (SliceGrant) obj;
            SliceGrant other = (SliceGrant) obj;
            return Objects.equals(other.mUri, mUri) && Objects.equals(other.mPkg, mPkg);
            return Objects.equals(other.mUri, mUri) && Objects.equals(other.mPkg, mPkg)
                    && (other.mUserId == mUserId);
        }
        }
    }
    }
}
}
+103 −0
Original line number Original line Diff line number Diff line
/*
 * Copyright (C) 2018 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the
 * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied. See the License for the specific language governing
 * permissions and limitations under the License.
 */

package com.android.server.slice;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

import android.support.test.filters.SmallTest;
import android.util.Xml.Encoding;

import com.android.server.UiServiceTestCase;

import org.junit.Before;
import org.junit.Test;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlPullParserFactory;
import org.xmlpull.v1.XmlSerializer;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;

@SmallTest
public class SliceFullAccessListTest extends UiServiceTestCase {

    private static final String TEST_XML = "<slice-access-list version=\"1\"><user "
            + "user=\"0\"><pkg>pkg</pkg><pkg>pkg1</pkg></user><user "
            + "user=\"1\"><pkg>pkg</pkg></user><user "
            + "user=\"3\"><pkg>pkg2</pkg></user></slice-access-list>";

    private SliceFullAccessList mAccessList;

    @Before
    public void setup() {
        mAccessList = new SliceFullAccessList(mContext);
    }

    @Test
    public void testNoDefaultAccess() {
        assertFalse(mAccessList.hasFullAccess("pkg", 0));
    }

    @Test
    public void testGrantAccess() {
        mAccessList.grantFullAccess("pkg", 0);
        assertTrue(mAccessList.hasFullAccess("pkg", 0));
    }

    @Test
    public void testUserSeparation() {
        mAccessList.grantFullAccess("pkg", 1);
        assertFalse(mAccessList.hasFullAccess("pkg", 0));
    }

    @Test
    public void testSerialization() throws XmlPullParserException, IOException {
        mAccessList.grantFullAccess("pkg", 0);
        mAccessList.grantFullAccess("pkg1", 0);
        mAccessList.grantFullAccess("pkg", 1);
        mAccessList.grantFullAccess("pkg2", 3);

        ByteArrayOutputStream output = new ByteArrayOutputStream();
        XmlSerializer out = XmlPullParserFactory.newInstance().newSerializer();
        out.setOutput(output, Encoding.UTF_8.name());
        mAccessList.writeXml(out);
        out.flush();

        assertEquals(TEST_XML, output.toString(Encoding.UTF_8.name()));
    }

    @Test
    public void testDeSerialization() throws XmlPullParserException, IOException {
        ByteArrayInputStream input = new ByteArrayInputStream(TEST_XML.getBytes());
        XmlPullParser parser = XmlPullParserFactory.newInstance().newPullParser();
        parser.setInput(input, Encoding.UTF_8.name());

        mAccessList.readXml(parser);

        assertTrue(mAccessList.hasFullAccess("pkg", 0));
        assertTrue(mAccessList.hasFullAccess("pkg1", 0));
        assertTrue(mAccessList.hasFullAccess("pkg", 1));
        assertTrue(mAccessList.hasFullAccess("pkg2", 3));

        assertFalse(mAccessList.hasFullAccess("pkg3", 0));
        assertFalse(mAccessList.hasFullAccess("pkg1", 1));
        assertFalse(mAccessList.hasFullAccess("pkg", 3));
        assertFalse(mAccessList.hasFullAccess("pkg", 2));
    }
}
 No newline at end of file