services/identity/internal/blesser: BlessUsingAccessTokenWithCaveats

Add new bless method that allows addition of caveats from android app.

MultiPart: 1/2

Change-Id: Ia08cc2678fa06f1b93da7fc37be6e165315bda0e
diff --git a/.gerrit_commit_message b/.gerrit_commit_message
new file mode 100644
index 0000000..2375477
--- /dev/null
+++ b/.gerrit_commit_message
@@ -0,0 +1,4 @@
+ref: 
+
+
+MultiPart: 1/2
diff --git a/services/identity/identity.vdl b/services/identity/identity.vdl
index 5b265b5..9ecba7b 100644
--- a/services/identity/identity.vdl
+++ b/services/identity/identity.vdl
@@ -24,6 +24,7 @@
   // BlessUsingAccessToken uses the provided access token to obtain the email
   // address and returns a blessing along with the email address.
   BlessUsingAccessToken(token string) (blessing security.WireBlessings, email string | error)
+  BlessUsingAccessTokenWithCaveats(token string, caveats []security.Caveat) (blessing security.WireBlessings, email string | error)
 }
 
 // MacaroonBlesser returns a blessing given the provided macaroon string.
diff --git a/services/identity/identity.vdl.go b/services/identity/identity.vdl.go
index fc7b6d2..dd261eb 100644
--- a/services/identity/identity.vdl.go
+++ b/services/identity/identity.vdl.go
@@ -57,6 +57,7 @@
 	// BlessUsingAccessToken uses the provided access token to obtain the email
 	// address and returns a blessing along with the email address.
 	BlessUsingAccessToken(ctx *context.T, token string, opts ...rpc.CallOpt) (blessing security.Blessings, email string, err error)
+	BlessUsingAccessTokenWithCaveats(ctx *context.T, token string, caveats []security.Caveat, opts ...rpc.CallOpt) (blessing security.Blessings, email string, err error)
 }
 
 // OAuthBlesserClientStub adds universal methods to OAuthBlesserClientMethods.
@@ -79,6 +80,11 @@
 	return
 }
 
+func (c implOAuthBlesserClientStub) BlessUsingAccessTokenWithCaveats(ctx *context.T, i0 string, i1 []security.Caveat, opts ...rpc.CallOpt) (o0 security.Blessings, o1 string, err error) {
+	err = v23.GetClient(ctx).Call(ctx, c.name, "BlessUsingAccessTokenWithCaveats", []interface{}{i0, i1}, []interface{}{&o0, &o1}, opts...)
+	return
+}
+
 // OAuthBlesserServerMethods is the interface a server writer
 // implements for OAuthBlesser.
 //
@@ -99,6 +105,7 @@
 	// BlessUsingAccessToken uses the provided access token to obtain the email
 	// address and returns a blessing along with the email address.
 	BlessUsingAccessToken(ctx *context.T, call rpc.ServerCall, token string) (blessing security.Blessings, email string, err error)
+	BlessUsingAccessTokenWithCaveats(ctx *context.T, call rpc.ServerCall, token string, caveats []security.Caveat) (blessing security.Blessings, email string, err error)
 }
 
 // OAuthBlesserServerStubMethods is the server interface containing
@@ -140,6 +147,10 @@
 	return s.impl.BlessUsingAccessToken(ctx, call, i0)
 }
 
+func (s implOAuthBlesserServerStub) BlessUsingAccessTokenWithCaveats(ctx *context.T, call rpc.ServerCall, i0 string, i1 []security.Caveat) (security.Blessings, string, error) {
+	return s.impl.BlessUsingAccessTokenWithCaveats(ctx, call, i0, i1)
+}
+
 func (s implOAuthBlesserServerStub) Globber() *rpc.GlobState {
 	return s.gs
 }
@@ -168,6 +179,17 @@
 				{"email", ``},    // string
 			},
 		},
+		{
+			Name: "BlessUsingAccessTokenWithCaveats",
+			InArgs: []rpc.ArgDesc{
+				{"token", ``},   // string
+				{"caveats", ``}, // []security.Caveat
+			},
+			OutArgs: []rpc.ArgDesc{
+				{"blessing", ``}, // security.Blessings
+				{"email", ``},    // string
+			},
+		},
 	},
 }
 
diff --git a/services/identity/internal/blesser/oauth.go b/services/identity/internal/blesser/oauth.go
index 4403a95..40138e6 100644
--- a/services/identity/internal/blesser/oauth.go
+++ b/services/identity/internal/blesser/oauth.go
@@ -64,24 +64,37 @@
 	if err != nil {
 		return noblessings, "", err
 	}
-	return b.bless(ctx, call.Security(), email, clientName)
+	return b.bless(ctx, call.Security(), email, clientName, nil)
 }
 
-func (b *oauthBlesser) bless(ctx *context.T, call security.Call, email, clientName string) (security.Blessings, string, error) {
+func (b *oauthBlesser) BlessUsingAccessTokenWithCaveats(ctx *context.T, call rpc.ServerCall, accessToken string, caveats []security.Caveat) (security.Blessings, string, error) {
+	var noblessings security.Blessings
+	email, clientName, err := b.oauthProvider.GetEmailAndClientName(accessToken, b.accessTokenClients)
+	if err != nil {
+		return noblessings, "", err
+	}
+	return b.bless(ctx, call.Security(), email, clientName, caveats)
+}
+
+func (b *oauthBlesser) bless(ctx *context.T, call security.Call, email, clientName string, caveats []security.Caveat) (security.Blessings, string, error) {
 	var noblessings security.Blessings
 	self := call.LocalPrincipal()
 	if self == nil {
 		return noblessings, "", fmt.Errorf("server error: no authentication happened")
 	}
-	var caveat security.Caveat
-	var err error
-	if b.revocationManager != nil {
-		caveat, err = b.revocationManager.NewCaveat(self.PublicKey(), b.dischargerLocation)
-	} else {
-		caveat, err = security.NewExpiryCaveat(time.Now().Add(b.duration))
-	}
-	if err != nil {
-		return noblessings, "", err
+	// TODO(suharshs, ataly): Should we ensure that we have at least a revocation or expiry caveat?
+	if len(caveats) == 0 {
+		var caveat security.Caveat
+		var err error
+		if b.revocationManager != nil {
+			caveat, err = b.revocationManager.NewCaveat(self.PublicKey(), b.dischargerLocation)
+		} else {
+			caveat, err = security.NewExpiryCaveat(time.Now().Add(b.duration))
+		}
+		if err != nil {
+			return noblessings, "", err
+		}
+		caveats = append(caveats, caveat)
 	}
 	// Append clientName (e.g., "android", "chrome") to the email and then bless under that.
 	// Since blessings issued by this process do not have many caveats on them and typically
@@ -89,7 +102,7 @@
 	// servers can explicitly distinguish these clients while specifying authorization policies
 	// (say, via AccessLists).
 	extension := strings.Join([]string{email, clientName}, security.ChainSeparator)
-	blessing, err := self.Bless(call.RemoteBlessings().PublicKey(), call.LocalBlessings(), extension, caveat)
+	blessing, err := self.Bless(call.RemoteBlessings().PublicKey(), call.LocalBlessings(), extension, caveats[0], caveats[1:]...)
 	if err != nil {
 		return noblessings, "", err
 	}
diff --git a/services/identity/internal/blesser/oauth_test.go b/services/identity/internal/blesser/oauth_test.go
index fbb1f5a..bb8941f 100644
--- a/services/identity/internal/blesser/oauth_test.go
+++ b/services/identity/internal/blesser/oauth_test.go
@@ -6,6 +6,7 @@
 
 import (
 	"reflect"
+	"sort"
 	"strings"
 	"testing"
 	"time"
@@ -61,3 +62,74 @@
 		t.Errorf("BlessingsInfo %v does not have name %s", binfo, wantExtension)
 	}
 }
+
+func TestOAuthBlesserWithCaveats(t *testing.T) {
+	var (
+		provider, user = testutil.NewPrincipal(), testutil.NewPrincipal()
+		ctx, call      = fakeContextAndCall(provider, user)
+	)
+	mockEmail := "testemail@example.com"
+	blesser := NewOAuthBlesserServer(OAuthBlesserParams{
+		OAuthProvider:    oauth.NewMockOAuth(mockEmail),
+		BlessingDuration: time.Hour,
+	})
+
+	expiryCav, err := security.NewExpiryCaveat(time.Now().Add(time.Minute))
+	if err != nil {
+		t.Fatal(err)
+	}
+	methodCav, err := security.NewMethodCaveat("foo", "bar")
+	if err != nil {
+		t.Fatal(err)
+	}
+	caveats := []security.Caveat{expiryCav, methodCav}
+
+	b, extension, err := blesser.BlessUsingAccessTokenWithCaveats(ctx, call, "test-access-token", caveats)
+	if err != nil {
+		t.Errorf("BlessUsingAccessToken failed: %v", err)
+	}
+
+	wantExtension := join(mockEmail, oauth.MockClient)
+	if extension != wantExtension {
+		t.Errorf("got extension: %s, want: %s", extension, wantExtension)
+	}
+
+	if !reflect.DeepEqual(b.PublicKey(), user.PublicKey()) {
+		t.Errorf("Received blessing for public key %v. Client:%v, Blesser:%v", b.PublicKey(), user.PublicKey(), provider.PublicKey())
+	}
+
+	// When the user does not recognize the provider, it should not see any strings for
+	// the client's blessings.
+	if got := user.BlessingsInfo(b); got != nil {
+		t.Errorf("Got blessing with info %v, want nil", got)
+	}
+	// But once it recognizes the provider, it should see exactly the name
+	// "provider/testemail@example.com/test-client".
+	user.AddToRoots(b)
+	binfo := user.BlessingsInfo(b)
+	if num := len(binfo); num != 1 {
+		t.Errorf("Got blessings with %d names, want exactly one name", num)
+	}
+	cavs, ok := binfo[join("provider", wantExtension)]
+	if !ok {
+		t.Errorf("BlessingsInfo %v does not have name %s", binfo, wantExtension)
+	}
+	if !caveatsMatch(cavs, caveats) {
+		t.Errorf("got %v, want %v", cavs, caveats)
+	}
+}
+
+func caveatsMatch(got, want []security.Caveat) bool {
+	if len(got) != len(want) {
+		return false
+	}
+	gotStrings := make([]string, len(got))
+	wantStrings := make([]string, len(want))
+	for i := 0; i < len(got); i++ {
+		gotStrings[i] = got[i].String()
+		wantStrings[i] = want[i].String()
+	}
+	sort.Strings(gotStrings)
+	sort.Strings(wantStrings)
+	return reflect.DeepEqual(gotStrings, wantStrings)
+}