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)
+}