| // Copyright 2015 The Vanadium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package io.v.baku.toolkit.blessings; |
| |
| import android.content.Context; |
| import android.content.SharedPreferences; |
| import android.preference.PreferenceManager; |
| import android.util.Base64; |
| import android.util.JsonReader; |
| |
| import com.google.common.base.Strings; |
| import com.google.common.collect.Collections2; |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.collect.ImmutableMap; |
| import com.google.common.collect.ImmutableSet; |
| import com.google.common.collect.Maps; |
| |
| import java.io.IOException; |
| import java.io.InputStreamReader; |
| import java.net.MalformedURLException; |
| import java.net.URL; |
| import java.nio.charset.StandardCharsets; |
| import java.security.interfaces.ECPublicKey; |
| import java.util.ArrayList; |
| import java.util.List; |
| import java.util.Set; |
| import java.util.regex.Matcher; |
| import java.util.regex.Pattern; |
| |
| import io.v.android.v23.V; |
| import io.v.impl.google.naming.NamingUtil; |
| import io.v.v23.context.VContext; |
| import io.v.v23.security.BlessingPattern; |
| import io.v.v23.security.BlessingRoots; |
| import io.v.v23.security.Blessings; |
| import io.v.v23.security.CryptoUtil; |
| import io.v.v23.security.VPrincipal; |
| import io.v.v23.security.VSecurity; |
| import io.v.v23.security.access.AccessList; |
| import io.v.v23.security.access.Constants; |
| import io.v.v23.security.access.Permissions; |
| import io.v.v23.security.access.Tag; |
| import io.v.v23.verror.VException; |
| import io.v.v23.vom.VomUtil; |
| import java8.util.stream.Collectors; |
| import java8.util.stream.Stream; |
| import java8.util.stream.StreamSupport; |
| import lombok.experimental.UtilityClass; |
| import lombok.extern.slf4j.Slf4j; |
| |
| /** |
| * Common utilities for blessing and ACL management. In the future, we may want to factor these out |
| * of Baku into the V23 Java libraries. |
| */ |
| @Slf4j |
| @UtilityClass |
| public class BlessingsUtils { |
| public static final String |
| PREF_BLESSINGS = "VanadiumBlessings", |
| GLOBAL_BLESSING_ROOT_URL = "https://dev.v.io/auth/blessing-root"; |
| public static final Pattern DEV_V_IO_CLIENT_USER = |
| Pattern.compile("dev\\.v\\.io:o:([^:]+):([^:]+).*"); |
| |
| public static final AccessList OPEN_ACL = new AccessList( |
| ImmutableList.of(new BlessingPattern("...")), ImmutableList.of()); |
| |
| public static final ImmutableSet<Tag> |
| DATA_TAGS = ImmutableSet.of(Constants.READ, Constants.WRITE, Constants.ADMIN), |
| MOUNT_TAGS = ImmutableSet.of(Constants.READ, Constants.RESOLVE, Constants.ADMIN), |
| SYNCGROUP_TAGS = ImmutableSet.of(Constants.READ, Constants.WRITE, Constants.RESOLVE, |
| Constants.ADMIN, Constants.DEBUG); |
| |
| public static final Permissions |
| OPEN_DATA_PERMS = dataPermissions(OPEN_ACL), |
| OPEN_MOUNT_PERMS = mountPermissions(OPEN_ACL); |
| |
| public static void writeSharedPrefs(final Context context, final Blessings blessings) |
| throws VException { |
| writeSharedPrefs(PreferenceManager.getDefaultSharedPreferences(context), PREF_BLESSINGS, |
| blessings); |
| } |
| |
| public static void writeSharedPrefs(final SharedPreferences prefs, final String key, |
| final Blessings blessings) throws VException { |
| prefs.edit().putString(key, VomUtil.encodeToString(blessings, Blessings.class)).apply(); |
| } |
| |
| public static Blessings readSharedPrefs(final Context context) throws VException { |
| return readSharedPrefs(PreferenceManager.getDefaultSharedPreferences(context), |
| PREF_BLESSINGS); |
| } |
| |
| public static Blessings readSharedPrefs(final SharedPreferences prefs, final String key) |
| throws VException { |
| final String blessingsVom = prefs.getString(key, ""); |
| return Strings.isNullOrEmpty(blessingsVom) ? null : decodeBlessings(blessingsVom); |
| } |
| |
| public static Blessings decodeBlessings(final String blessings) throws VException { |
| return (Blessings) VomUtil.decodeFromString(blessings, Blessings.class); |
| } |
| |
| public static Blessings decodeBlessings(final byte[] blessings) throws VException { |
| return (Blessings) VomUtil.decode(blessings, Blessings.class); |
| } |
| |
| public static Set<String> getBlessingNames(final VContext ctx, final Blessings blessings) { |
| return ImmutableSet.copyOf(VSecurity.getBlessingNames(V.getPrincipal(ctx), blessings)); |
| } |
| |
| public static AccessList blessingsToAcl(final VContext ctx, final Blessings blessings) { |
| return new AccessList(ImmutableList.copyOf(Collections2.transform( |
| getBlessingNames(ctx, blessings), |
| s -> new BlessingPattern(s))), //method reference confuses Android Studio |
| ImmutableList.of()); |
| } |
| |
| /** |
| * This method adds the given {@link BlessingPattern} to the given {@link AccessList} but does |
| * not perform deduping or checking to make sure the new pattern is not in |
| * {@link AccessList#getNotIn()}. |
| */ |
| public static AccessList augmentAcl(final AccessList acl, final BlessingPattern newBlessing) { |
| return new AccessList(ImmutableList.<BlessingPattern>builder() |
| .addAll(acl.getIn()) |
| .add(newBlessing).build(), |
| ImmutableList.of()); |
| } |
| |
| public static Stream<ClientUser> blessingsToClientUserStream(final VContext ctx, |
| final Blessings blessings) { |
| return StreamSupport.stream(getBlessingNames(ctx, blessings)) |
| .map(DEV_V_IO_CLIENT_USER::matcher) |
| .filter(Matcher::matches) |
| .map(m -> new ClientUser(m.group(1), m.group(2))); |
| } |
| |
| /** |
| * This method finds and parses all blessings of the form dev.v.io/o/.... |
| */ |
| public static Set<ClientUser> blessingsToClientUsers( |
| final VContext ctx, final Blessings blessings) { |
| return blessingsToClientUserStream(ctx, blessings).collect(Collectors.toSet()); |
| } |
| |
| public static String userMount(final String username) { |
| return NamingUtil.join("users", username); |
| } |
| |
| public static String clientMount(final String clientId) { |
| return NamingUtil.join("tmp", "clients", clientId); |
| } |
| |
| public static Stream<String> blessingsToClientMountStream(final VContext ctx, |
| final Blessings blessings) { |
| return blessingsToClientUserStream(ctx, blessings) |
| .map(cu -> BlessingsUtils.clientMount(cu.getClientId())); |
| } |
| |
| public static Set<String> blessingsToClientMounts(final VContext ctx, |
| final Blessings blessings) { |
| return blessingsToClientMountStream(ctx, blessings).collect(Collectors.toSet()); |
| } |
| |
| public static Permissions homogeneousPermissions(final Set<Tag> tags, final AccessList acl) { |
| return new Permissions(Maps.toMap(Collections2.transform(tags, Tag::getValue), x -> acl)); |
| } |
| |
| public static Permissions dataPermissions(final AccessList acl) { |
| return homogeneousPermissions(DATA_TAGS, acl); |
| } |
| |
| public static Permissions mountPermissions(final AccessList acl) { |
| return homogeneousPermissions(MOUNT_TAGS, acl); |
| } |
| |
| public static Permissions syncgroupPermissions(final AccessList acl) { |
| return homogeneousPermissions(SYNCGROUP_TAGS, acl); |
| } |
| |
| /** |
| * TODO(rosswang): This probably won't be best practice in the long run, but we'll need it until |
| * we can bless the cloud Syncbase instance remotely. |
| */ |
| public static Permissions cloudSyngroupPermissions(final AccessList userAcl, |
| final BlessingPattern sgHostBlessing) { |
| final AccessList cloudAcl = augmentAcl(userAcl, sgHostBlessing); |
| return new Permissions(ImmutableMap.of( |
| Constants.ADMIN.getValue(), cloudAcl, |
| Constants.READ.getValue(), cloudAcl, |
| Constants.WRITE.getValue(), userAcl, |
| Constants.RESOLVE.getValue(), userAcl, |
| Constants.DEBUG.getValue(), userAcl |
| )); |
| } |
| |
| /** |
| * Standard blessing handling for Vanadium applications: |
| * <ul> |
| * <li>Provide the given blessings when anybody connects to us.</li> |
| * <li>Provide these blessings when we connect to other services (for example, when we talk |
| * to the mounttable).</li> |
| * <li>Trust these blessings and all the "parent" blessings.</li> |
| * </ul> |
| */ |
| public static void assumeBlessings(final VContext vContext, final Blessings blessings) |
| throws VException { |
| log.info("Assuming blessings: " + blessings); |
| final VPrincipal principal = V.getPrincipal(vContext); |
| principal.blessingStore().setDefaultBlessings(blessings); |
| principal.blessingStore().set(blessings, new BlessingPattern("...")); |
| VSecurity.addToRoots(principal, blessings); |
| } |
| |
| public static void addGlobalBlessingRoots(final VContext vContext) |
| throws IOException, VException { |
| final URL url; |
| try { |
| url = new URL(GLOBAL_BLESSING_ROOT_URL); |
| } catch (final MalformedURLException e) { |
| throw new RuntimeException(e); |
| } |
| |
| ECPublicKey publicKey = null; |
| final List<BlessingPattern> names = new ArrayList<>(); |
| |
| try (final JsonReader json = new JsonReader( |
| new InputStreamReader(url.openStream(), StandardCharsets.US_ASCII))) { |
| json.beginObject(); |
| while (json.hasNext()) { |
| final String name = json.nextName(); |
| if ("publicKey".equals(name)) { |
| final String strKey = json.nextString(); |
| final byte[] binKey = Base64.decode(strKey.getBytes(StandardCharsets.US_ASCII), |
| Base64.URL_SAFE); |
| publicKey = CryptoUtil.decodeECPublicKey(binKey); |
| } else if ("names".equals(name)) { |
| json.beginArray(); |
| while (json.hasNext()) { |
| names.add(new BlessingPattern(json.nextString())); |
| } |
| json.endArray(); |
| } else { |
| json.skipValue(); |
| } |
| } |
| } |
| |
| if (publicKey != null) { |
| final BlessingRoots roots = V.getPrincipal(vContext).roots(); |
| for (final BlessingPattern name : names) { |
| log.info("Adding global blessing root " + name); |
| roots.add(publicKey, name); |
| } |
| } else { |
| log.warn("No global blessing roots found"); |
| } |
| } |
| } |