Java tutorial
/* * Copyright (C) 2017 The Regents of the University of Michigan * * 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 edu.umich.oasis.service; import android.content.Context; import android.content.SharedPreferences; import android.os.RemoteException; import android.util.Log; import org.apache.commons.lang3.tuple.ImmutablePair; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; import edu.umich.oasis.common.TaintSet; import edu.umich.oasis.kvs.IRemoteSharedPrefs; import edu.umich.oasis.kvs.IRemoteSharedPrefsEditor; import edu.umich.oasis.kvs.IRemoteSharedPrefsListener; public final class KVSSharedPrefs extends IRemoteSharedPrefs.Stub implements AutoCloseable { private static final String TAG = "OASIS.SharedPrefs.Impl"; private static final boolean localLOGV = Log.isLoggable(TAG, Log.VERBOSE); private static final boolean localLOGD = Log.isLoggable(TAG, Log.DEBUG); private static final char SEPARATOR = ':'; private static final String DATA = "data"; private static final String TAINT = "taint"; private static final String CONFIG = "config"; private static final String TAINT_SET = "taint-set"; private static final String CONFIG_PUBLIC_READABLE = "isPublicReadable"; private static final String CONFIG_PUBLIC_WRITABLE = "isPublicWritable"; private static final String PLATFORM_NAME_PREFIX = "kvs" + SEPARATOR; private final NamespaceSharedPrefs mPrefs; private final OASISApplication mContext; private final Sandbox mSandbox; private final String mOwningPackage; private final String mStoreName; private final boolean canReadPublic; private final boolean canWritePublic; private final boolean isReadable; private final boolean isWritable; private boolean isClosed; public final class Editor extends IRemoteSharedPrefsEditor.Stub { // Anything in this set has been modified, and will have sandbox taint added on commit. // The values represent additional taint to be added, beyond the sandbox taint. // Maps to null for deleted keys. private final HashMap<String, TaintSet.Builder> mPendingChanges = new HashMap<>(); private final NamespaceSharedPrefs.Editor mEditor = mPrefs.edit(); private boolean isCommitted = false; private void throwIfCommitted() { if (isCommitted) { throw new IllegalStateException("Already committed"); } } private synchronized TaintSet.Builder markModified(String key) { TaintSet.Builder ts = mPendingChanges.get(key); if (ts == null) { // Either this key was deleted, or it's never been seen before. // Either way, it gets a new taint label. ts = new TaintSet.Builder(); mPendingChanges.put(key, ts); } return ts; } private synchronized void addTaintFromSandbox(String key) { throwIfCommitted(); markModified(key); } @Override public synchronized void putString(String key, String value) { addTaintFromSandbox(key); mEditor.putString(DATA, key, value); } @Override public synchronized void putStringSet(String key, List<String> values) { addTaintFromSandbox(key); mEditor.putStringSet(DATA, key, new HashSet<>(values)); } @Override public synchronized void putInt(String key, int value) { addTaintFromSandbox(key); mEditor.putInt(DATA, key, value); } @Override public synchronized void putLong(String key, long value) { addTaintFromSandbox(key); mEditor.putLong(DATA, key, value); } @Override public synchronized void putFloat(String key, float value) { addTaintFromSandbox(key); mEditor.putFloat(DATA, key, value); } @Override public synchronized void putBoolean(String key, boolean value) { addTaintFromSandbox(key); mEditor.putBoolean(DATA, key, value); } @Override public synchronized void addTaint(String key, TaintSet taint) { markModified(key).unionWith(taint); } @Override public synchronized void addTaintToAll(TaintSet taint) { // Mark all keys we haven't seen as modified. // Deleted keys are considered seen, so they'll be skipped. for (ImmutablePair<String, String> key : mPrefs.getAll().keySet()) { if (DATA.equals(key.getLeft())) { if (!mPendingChanges.containsKey(key.getRight())) { markModified(key.getRight()); } } } // Add taint to all modified keys. for (TaintSet.Builder ts : mPendingChanges.values()) { if (ts != null) { ts.unionWith(taint); } } } @Override public synchronized void remove(String key) { throwIfCommitted(); mPendingChanges.put(key, null); mEditor.remove(DATA, key); } @Override public synchronized void clear() { throwIfCommitted(); mPendingChanges.clear(); for (ImmutablePair<String, String> pair : mPrefs.getAll().keySet()) { if (DATA.equals(pair.getLeft())) { mPendingChanges.put(pair.getRight(), null); mEditor.remove(pair.getLeft(), pair.getRight()); } } } @SuppressWarnings("unchecked") private synchronized void prepareChanges() { for (Map.Entry<String, TaintSet.Builder> entry : mPendingChanges.entrySet()) { String key = entry.getKey(); TaintSet.Builder builder = entry.getValue(); TaintSet ts; if (builder == null) { ts = (mSandbox != null) ? mSandbox.getTaints() : TaintSet.EMPTY; } else { if (mSandbox != null) { builder.unionWith(mSandbox.getTaints()); } ts = builder.build(); } if (localLOGV) { Log.v(TAG, "Tainting " + mStoreName + '/' + key + " with " + ts); } if (ts.equals(TaintSet.EMPTY)) { mEditor.remove(TAINT, key); } else { mEditor.putTaint(TAINT, key, ts); } } mEditor.putBoolean(CONFIG, CONFIG_PUBLIC_READABLE, canReadPublic).putBoolean(CONFIG, CONFIG_PUBLIC_WRITABLE, canWritePublic); isCommitted = true; } @Override public synchronized boolean commit() { // Synchronize on external SharedPrefs, to ensure that we don't get // the taint tag from before and the data from after (or vice-versa). synchronized (KVSSharedPrefs.this) { prepareChanges(); return mEditor.commit(); } } @Override public void apply() { synchronized (KVSSharedPrefs.this) { prepareChanges(); mEditor.apply(); } } } private SharedPreferences getPlatformSharedPrefs(String store) { final String prefsName = PLATFORM_NAME_PREFIX + mOwningPackage + SEPARATOR + store; return mContext.getSharedPreferences(prefsName, Context.MODE_PRIVATE); } @SuppressWarnings("deprecation") public KVSSharedPrefs(Sandbox sandbox, String owningPackage, String callingPackage, String storeName, int mode) { mContext = OASISApplication.getInstance(); mSandbox = sandbox; mStoreName = storeName; mOwningPackage = mContext.checkPackageName(owningPackage); SharedPreferences sp = getPlatformSharedPrefs(storeName); mPrefs = NamespaceSharedPrefs.get(sp, TAINT_SET, TAINT); // Handle read and write permissions, as previously configured. boolean configReadPublic = mPrefs.getBoolean(CONFIG, CONFIG_PUBLIC_READABLE, false); boolean configWritePublic = mPrefs.getBoolean(CONFIG, CONFIG_PUBLIC_WRITABLE, false); if (localLOGD) { Log.d(TAG, String.format("Store '%s', owner '%s', caller '%s'", storeName, owningPackage, callingPackage)); } // Are we trying to load this package's prefs, or something else? boolean isSamePackage = Objects.equals(owningPackage, callingPackage); // Set the readable and writable flags for this object... isReadable = isSamePackage || configReadPublic; isWritable = isSamePackage || configWritePublic; // ...And set the flags that will be written on update. canReadPublic = (mode & Context.MODE_WORLD_READABLE) != 0; canWritePublic = (mode & Context.MODE_WORLD_WRITEABLE) != 0; isClosed = false; } private void checkClosed() { if (isClosed) { throw new IllegalStateException("SharedPreferences has been closed"); } } private void checkRead() { checkClosed(); if (!isReadable) { throw new SecurityException("Can't read this SharedPreferences"); } } private void checkReadKey(String key) { checkRead(); if (mSandbox != null) { mSandbox.addTaint(mPrefs.getTaint(TAINT, key, TaintSet.EMPTY)); } } /** * Wrap a {@link ClassCastException} to be thrown across the wire. * @param cce The ClassCastException to wrap. * @return An exception capable of being handled by {@link android.os.Parcel#writeException(Exception)}. */ private RuntimeException wrapClassCast(final ClassCastException cce) { return new IllegalArgumentException(cce.getMessage()); } @Override public synchronized boolean contains(String key) { checkReadKey(key); return mPrefs.contains(DATA, key); } @Override public synchronized Editor edit() { checkClosed(); if (!isWritable) { throw new SecurityException("This SharedPreferences cannot be modified"); } return new Editor(); } @Override public synchronized boolean getBoolean(String key, boolean defValue) { checkReadKey(key); try { return mPrefs.getBoolean(DATA, key, defValue); } catch (ClassCastException cce) { throw wrapClassCast(cce); } } @Override public synchronized float getFloat(String key, float defValue) { checkReadKey(key); try { return mPrefs.getFloat(DATA, key, defValue); } catch (ClassCastException cce) { throw wrapClassCast(cce); } } @Override public synchronized int getInt(String key, int defValue) { checkReadKey(key); try { return mPrefs.getInt(DATA, key, defValue); } catch (ClassCastException cce) { throw wrapClassCast(cce); } } @Override public synchronized long getLong(String key, long defValue) { checkReadKey(key); try { return mPrefs.getLong(DATA, key, defValue); } catch (ClassCastException cce) { throw wrapClassCast(cce); } } @Override @SuppressWarnings("unchecked") public synchronized Map<String, ?> getAll() { checkRead(); TaintSet.Builder tsb = new TaintSet.Builder(); Map<String, Object> data = new HashMap<>(); for (Map.Entry<ImmutablePair<String, String>, ?> entry : mPrefs.getAll().entrySet()) { ImmutablePair<String, String> typeAndKey = entry.getKey(); if (DATA.equals(typeAndKey.getLeft())) { // If this is a data entry, remember it. data.put(typeAndKey.getRight(), entry.getValue()); } else if (TAINT.equals(typeAndKey.getKey())) { // If this is a taint entry, add it to the overall taint label. tsb.unionWith((TaintSet) entry.getValue()); } } if (mSandbox != null) { mSandbox.addTaint(tsb.build()); } return data; } @Override public synchronized String getString(String key) { checkReadKey(key); try { return mPrefs.getString(DATA, key, null); } catch (ClassCastException cce) { throw wrapClassCast(cce); } } @Override public synchronized List<String> getStringSet(String key) { checkReadKey(key); try { Set<String> retSet = mPrefs.getStringSet(DATA, key, null); return (retSet != null) ? new ArrayList<>(retSet) : null; } catch (ClassCastException cce) { throw wrapClassCast(cce); } } @Override public synchronized void close() { isClosed = true; } @Override public String toString() { return "TaintableSharedPrefs{" + "mSandbox=" + mSandbox + ", mOwningPackage='" + mOwningPackage + '\'' + ", isClosed=" + isClosed + '}'; } @Override public void registerOnSharedPreferenceChangeListener(IRemoteSharedPrefsListener listener) throws RemoteException { throw new IllegalStateException("Not implemented"); } @Override public void unregisterOnSharedPreferenceChangeListener(IRemoteSharedPrefsListener listener) throws RemoteException { throw new IllegalStateException("Not implemented"); } }