Merge "runtimes/google/ipc: Encode expected server blessings in the name instead of options.RemoteID"
diff --git a/runtimes/google/ipc/client.go b/runtimes/google/ipc/client.go
index 34f8bb5..4e12fb9 100644
--- a/runtimes/google/ipc/client.go
+++ b/runtimes/google/ipc/client.go
@@ -5,6 +5,7 @@
 	"io"
 	"math"
 	"math/rand"
+	"regexp"
 	"strings"
 	"sync"
 	"time"
@@ -31,6 +32,11 @@
 	errNonRootedName          = verror.BadArgf("ipc: cannot connect to a non-rooted name")
 )
 
+var serverPatternRegexp = regexp.MustCompile("^\\[([^\\]]+)\\](.*)")
+
+// TODO(ribrdb): Flip this to true once everything is updated.
+const enableSecureServerAuth = false
+
 type client struct {
 	streamMgr stream.Manager
 	ns        naming.Namespace
@@ -228,6 +234,9 @@
 // tryCall makes a single attempt at a call.
 func (c *client) tryCall(ctx context.T, name, method string, args []interface{}, opts []ipc.CallOpt) (ipc.Call, verror.E) {
 	ctx, _ = vtrace.WithNewSpan(ctx, fmt.Sprintf("Client Call: %s.%s", name, method))
+
+	_, serverPattern, name := splitObjectName(name)
+
 	// Resolve name unless told not to.
 	var servers []string
 	if getNoResolveOpt(opts) {
@@ -258,7 +267,7 @@
 		// and thus wanted to skip authorization as well.
 		if flow.LocalPrincipal() != nil {
 			// Validate caveats on the server's identity for the context associated with this call.
-			if serverB, grantedB, err = c.authorizeServer(flow, name, suffix, method, opts); err != nil {
+			if serverB, grantedB, err = c.authorizeServer(flow, name, suffix, method, serverPattern, opts); err != nil {
 				lastErr = verror.NoAccessf("ipc: client unwilling to invoke %q.%q on server %v: %v", name, method, flow.RemoteBlessings(), err)
 				flow.Close()
 				continue
@@ -306,17 +315,23 @@
 // the RPC name.method for the client (local end of the flow). It returns the blessings at the
 // server that are authorized for this purpose and any blessings that are to be granted to
 // the server (via ipc.Granter implementations in opts.)
-func (c *client) authorizeServer(flow stream.Flow, name, suffix, method string, opts []ipc.CallOpt) (serverBlessings []string, grantedBlessings security.Blessings, err error) {
+func (c *client) authorizeServer(flow stream.Flow, name, suffix, method string, serverPattern security.BlessingPattern, opts []ipc.CallOpt) (serverBlessings []string, grantedBlessings security.Blessings, err error) {
 	if flow.RemoteBlessings() == nil {
 		return nil, nil, fmt.Errorf("server has not presented any blessings")
 	}
-	serverBlessings = flow.RemoteBlessings().ForContext(serverAuthContext{flow, time.Now()})
+	ctxt := serverAuthContext{flow, time.Now()}
+	serverBlessings = flow.RemoteBlessings().ForContext(ctxt)
+	if serverPattern != "" {
+		if !serverPattern.MatchedBy(serverBlessings...) {
+			return nil, nil, fmt.Errorf("server %v does not match the provided pattern %q", serverBlessings, serverPattern)
+		}
+	} else if enableSecureServerAuth {
+		if err := (defaultAuthorizer{}).Authorize(ctxt); err != nil {
+			return nil, nil, fmt.Errorf("default authorization precludes talking to server %v", serverBlessings)
+		}
+	}
 	for _, o := range opts {
 		switch v := o.(type) {
-		case options.RemoteID:
-			if !security.BlessingPattern(v).MatchedBy(serverBlessings...) {
-				return nil, nil, fmt.Errorf("server %v does not match the provided pattern %q", serverBlessings, v)
-			}
 		case ipc.Granter:
 			if b, err := v.Grant(flow.RemoteBlessings()); err != nil {
 				return nil, nil, fmt.Errorf("failed to grant blessing to server %v: %v", serverBlessings, err)
@@ -580,3 +595,28 @@
 func (serverAuthContext) Suffix() string                            { return "" }
 func (serverAuthContext) Label() (l security.Label)                 { return l }
 func (serverAuthContext) Discharges() map[string]security.Discharge { return nil }
+
+func splitObjectName(name string) (mtPattern, serverPattern security.BlessingPattern, objectName string) {
+	objectName = name
+	match := serverPatternRegexp.FindSubmatch([]byte(name))
+	if match != nil {
+		objectName = string(match[2])
+		if naming.Rooted(objectName) {
+			mtPattern = security.BlessingPattern(match[1])
+		} else {
+			serverPattern = security.BlessingPattern(match[1])
+			return
+		}
+	}
+	if !naming.Rooted(objectName) {
+		return
+	}
+
+	address, relative := naming.SplitAddressName(objectName)
+	match = serverPatternRegexp.FindSubmatch([]byte(relative))
+	if match != nil {
+		serverPattern = security.BlessingPattern(match[1])
+		objectName = naming.JoinAddressName(address, string(match[2]))
+	}
+	return
+}
diff --git a/runtimes/google/ipc/full_test.go b/runtimes/google/ipc/full_test.go
index 1e0b892..cd83055 100644
--- a/runtimes/google/ipc/full_test.go
+++ b/runtimes/google/ipc/full_test.go
@@ -358,7 +358,7 @@
 			t.Errorf("%s: failed ot create client: %v", name, err)
 			continue
 		}
-		if call, err := client.StartCall(testContext(), "mountpoint/server/suffix", "Method", nil, options.RemoteID(test.pattern)); !matchesErrorPattern(err, test.err) {
+		if call, err := client.StartCall(testContext(), fmt.Sprintf("[%s]mountpoint/server/suffix", test.pattern), "Method", nil); !matchesErrorPattern(err, test.err) {
 			t.Errorf(`%s: client.StartCall: got error "%v", want to match "%v"`, name, err, test.err)
 		} else if call != nil {
 			blessings, proof := call.RemoteBlessings()
@@ -1088,6 +1088,46 @@
 	}
 }
 
+func TestSplitObjectName(t *testing.T) {
+	cases := []struct {
+		input, mt, server, name string
+	}{
+		{"[foo/bar]", "", "foo/bar", ""},
+		{"[x/y/...]/", "x/y/...", "", "/"},
+		{"[foo/...]//", "", "foo/...", "//"},
+		{"[foo]//abc@@/foo", "", "foo", "//abc@@/foo"},
+		{"[foo]a", "", "foo", "a"},
+		{"[foo]/a", "foo", "", "/a"},
+		{"[foo]/a/[bar]", "foo", "bar", "/a"},
+		{"a/b", "", "", "a/b"},
+		{"[foo]a/b", "", "foo", "a/b"},
+		{"/a/b", "", "", "/a/b"},
+		{"[foo]/a/b", "foo", "", "/a/b"},
+		{"/a/[bar]b", "", "bar", "/a/b"},
+		{"[foo]/a/[bar]b", "foo", "bar", "/a/b"},
+		{"/a/b[foo]", "", "", "/a/b[foo]"},
+		{"/a/b/[foo]c", "", "", "/a/b/[foo]c"},
+		{"/[01:02::]:444", "", "", "/[01:02::]:444"},
+		{"[foo]/[01:02::]:444", "foo", "", "/[01:02::]:444"},
+		{"/[01:02::]:444/foo", "", "", "/[01:02::]:444/foo"},
+		{"[a]/[01:02::]:444/foo", "a", "", "/[01:02::]:444/foo"},
+		{"/[01:02::]:444/[b]foo", "", "b", "/[01:02::]:444/foo"},
+		{"[c]/[01:02::]:444/[d]foo", "c", "d", "/[01:02::]:444/foo"},
+	}
+	for _, c := range cases {
+		mt, server, name := splitObjectName(c.input)
+		if string(mt) != c.mt {
+			t.Errorf("%q: unexpected mt pattern: %q not %q", c.input, mt, c.mt)
+		}
+		if string(server) != c.server {
+			t.Errorf("%q: unexpected server pattern: %q not %q", c.input, server, c.server)
+		}
+		if name != c.name {
+			t.Errorf("%q: unexpected name: %q not %q", c.input, name, c.name)
+		}
+	}
+}
+
 func init() {
 	testutil.Init()
 	vom.Register(fakeTimeCaveat(0))