Java tutorial
// 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"); } } }