"x/ref/services/identity": JSON responses for HTTP Blessing service
This CL updates the HTTP service for exchanging an access token
for a blessing by taking an output_format in the request paramaters
and encoding the blessing response in the specified output format.
This CL adds support for the Base64-VOM and Base64-JSON output
formats.
Change-Id: I1ebea7e93a6bd8ec0850af9c8ec270c0b45230ff
diff --git a/runtime/internal/discovery/advertise.go b/runtime/internal/discovery/advertise.go
deleted file mode 100644
index 1422760..0000000
--- a/runtime/internal/discovery/advertise.go
+++ /dev/null
@@ -1,16 +0,0 @@
-// 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 discovery
-
-import (
- "v.io/v23/context"
- "v.io/v23/discovery"
- "v.io/v23/security/access"
-)
-
-// Advertise implements discovery.Advertiser.
-func (ds *ds) Advertise(ctx *context.T, service discovery.Service, perms access.Permissions) error {
- return nil
-}
diff --git a/runtime/internal/discovery/discovery.go b/runtime/internal/discovery/discovery.go
deleted file mode 100644
index 53501d7..0000000
--- a/runtime/internal/discovery/discovery.go
+++ /dev/null
@@ -1,9 +0,0 @@
-// 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 discovery
-
-// ds is an implementation of discovery.T.
-type ds struct {
-}
diff --git a/runtime/internal/discovery/plugin.go b/runtime/internal/discovery/plugin.go
deleted file mode 100644
index c275d89..0000000
--- a/runtime/internal/discovery/plugin.go
+++ /dev/null
@@ -1,9 +0,0 @@
-// 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 discovery
-
-// Plugin is the basic interface for a plugin to discovery service.
-type Plugin interface {
-}
diff --git a/runtime/internal/discovery/scan.go b/runtime/internal/discovery/scan.go
deleted file mode 100644
index f955326..0000000
--- a/runtime/internal/discovery/scan.go
+++ /dev/null
@@ -1,15 +0,0 @@
-// 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 discovery
-
-import (
- "v.io/v23/context"
- "v.io/v23/discovery"
-)
-
-// Scan implements discovery.Scanner.
-func (ds *ds) Scan(ctx *context.T, query string) (<-chan discovery.Update, error) {
- return nil, nil
-}
diff --git a/services/device/mgmt_v23_test.go b/services/device/mgmt_v23_test.go
index 70593ad..23345a4 100644
--- a/services/device/mgmt_v23_test.go
+++ b/services/device/mgmt_v23_test.go
@@ -39,7 +39,6 @@
"errors"
"flag"
"fmt"
- "io"
"io/ioutil"
"math/rand"
"os"
@@ -47,7 +46,6 @@
"path/filepath"
"regexp"
"strings"
- "sync"
"time"
"v.io/x/ref"
@@ -104,6 +102,7 @@
func testCore(i *v23tests.T, appUser, deviceUser string, withSuid bool) {
defer fmt.Fprintf(os.Stderr, "--------------- SHUTDOWN ---------------\n")
+ userFlag := "--single_user"
tempDir := ""
if withSuid {
@@ -215,7 +214,7 @@
if withSuid {
deviceScriptArguments = append(deviceScriptArguments, "--devuser="+deviceUser)
} else {
- deviceScriptArguments = append(deviceScriptArguments, "--single_user")
+ deviceScriptArguments = append(deviceScriptArguments, userFlag)
}
deviceScriptArguments = append(deviceScriptArguments, []string{
@@ -227,24 +226,9 @@
deviceScript.Start(deviceScriptArguments...).WaitOrDie(os.Stdout, os.Stderr)
deviceScript.Start("start").WaitOrDie(os.Stdout, os.Stderr)
- dmLog := filepath.Join(dmInstallDir, "dmroot/device-manager/logs/deviced.INFO")
- stopDevMgr := func() {
- deviceScript.Run("stop")
- if dmLogF, err := os.Open(dmLog); err != nil {
- i.Errorf("Failed to read dm log: %v", err)
- } else {
- fmt.Fprintf(os.Stderr, "--------------- START DM LOG ---------------\n")
- defer dmLogF.Close()
- if _, err := io.Copy(os.Stderr, dmLogF); err != nil {
- i.Errorf("Error dumping dm log: %v", err)
- }
- fmt.Fprintf(os.Stderr, "--------------- END DM LOG ---------------\n")
- }
- }
- var stopDevMgrOnce sync.Once
- defer stopDevMgrOnce.Do(stopDevMgr)
// Grab the endpoint for the claimable service from the device manager's
// log.
+ dmLog := filepath.Join(dmInstallDir, "dmroot/device-manager/logs/deviced.INFO")
var claimableEP string
expiry := time.Now().Add(30 * time.Second)
for {
@@ -512,7 +496,7 @@
mtEP = resolveChange(mtName, mtEP)
// Shut down the device manager.
- stopDevMgrOnce.Do(stopDevMgr)
+ deviceScript.Run("stop")
// Wait for the mounttable entry to go away.
resolveGone := func(name string) string {
diff --git a/services/identity/internal/handlers/bless.go b/services/identity/internal/handlers/bless.go
index e7607c5..45142ac 100644
--- a/services/identity/internal/handlers/bless.go
+++ b/services/identity/internal/handlers/bless.go
@@ -6,6 +6,7 @@
import (
"encoding/base64"
+ "encoding/json"
"fmt"
"net/http"
"strings"
@@ -20,9 +21,13 @@
)
const (
- PublicKeyFormKey = "public_key"
- AccessTokenFormKey = "token"
- CaveatsFormKey = "caveats"
+ publicKeyFormKey = "public_key"
+ tokenFormKey = "token"
+ caveatsFormKey = "caveats"
+ outputFormatFormKey = "output_format"
+
+ jsonFormat = "json"
+ base64VomFormat = "base64vom"
)
type accessTokenBlesser struct {
@@ -30,7 +35,7 @@
params blesser.OAuthBlesserParams
}
-// NewOAuthBlessingHandler returns an http.Handler that uses OAuth2 access tokens
+// NewOAuthBlessingHandler returns an http.Handler that uses Google OAuth2 Access tokens
// to obtain the username of the requestor and reponds with blessings for that username.
//
// The blessings are namespaced under the ClientID for the access token. In particular,
@@ -41,11 +46,16 @@
// RevocationManager is specified by the params or they carry an ExpiryCaveat that
// expires after the duration specified by the params.
//
-// The handler expects the following parameters in the form data sent during a
-// request
+// The handler expects the following request parameters:
// - "public_key": Base64 DER encoded PKIX representation of the client's public key
-// - "caveats": Base64 VOM encoded list of caveats
-// - "token": OAuth2 access token
+// - "caveats": Base64 VOM encoded list of caveats [OPTIONAL]
+// - "token": Google OAuth2 Access token
+// - "output_format": The encoding format for the returned blessings. The following
+// formats are supported:
+// - "json": JSON-encoding of the wire format of Blessings.
+// - "base64vom": Base64 encoding of VOM-encoded Blessings [DEFAULT]
+//
+// The response consists of blessings encoded in the requested output format.
//
// WARNINGS:
// - There is no binding between the channel over which the access token
@@ -59,16 +69,17 @@
}
func (a *accessTokenBlesser) blessingCaveats(r *http.Request, p security.Principal) ([]security.Caveat, error) {
- caveatsVom, err := base64.URLEncoding.DecodeString(r.FormValue(CaveatsFormKey))
- if err != nil {
- return nil, fmt.Errorf("failed to base64 decode caveats: %v", err)
- }
-
var caveats []security.Caveat
- if err := vom.Decode(caveatsVom, &caveats); err != nil {
- return nil, fmt.Errorf("failed to VOM decode caveats: %v", err)
- }
+ if base64VomCaveats := r.FormValue(caveatsFormKey); len(base64VomCaveats) != 0 {
+ vomCaveats, err := base64.URLEncoding.DecodeString(base64VomCaveats)
+ if err != nil {
+ return nil, fmt.Errorf("base64.URLEncoding.DecodeString failed: %v", err)
+ }
+ if err := vom.Decode(vomCaveats, &caveats); err != nil {
+ return nil, fmt.Errorf("vom.Decode failed: %v", err)
+ }
+ }
// TODO(suharshs, ataly): Should we ensure that we have at least a
// revocation or expiry caveat?
if len(caveats) == 0 {
@@ -91,15 +102,15 @@
}
func (a *accessTokenBlesser) remotePublicKey(r *http.Request) (security.PublicKey, error) {
- publicKeyVom, err := base64.URLEncoding.DecodeString(r.FormValue(PublicKeyFormKey))
+ publicKeyVom, err := base64.URLEncoding.DecodeString(r.FormValue(publicKeyFormKey))
if err != nil {
- return nil, fmt.Errorf("failed to base64 decode public key: %v", err)
+ return nil, fmt.Errorf("base64.URLEncoding.DecodeString failed: %v", err)
}
return security.UnmarshalPublicKey(publicKeyVom)
}
func (a *accessTokenBlesser) blessingExtension(r *http.Request) (string, error) {
- email, clientID, err := a.params.OAuthProvider.GetEmailAndClientID(r.FormValue(AccessTokenFormKey))
+ email, clientID, err := a.params.OAuthProvider.GetEmailAndClientID(r.FormValue(tokenFormKey))
if err != nil {
return "", err
}
@@ -116,11 +127,23 @@
return strings.Join([]string{clientID, email}, security.ChainSeparator), nil
}
+func (a *accessTokenBlesser) encodeBlessingsJson(b security.Blessings) ([]byte, error) {
+ return json.Marshal(security.MarshalBlessings(b))
+}
+
+func (a *accessTokenBlesser) encodeBlessingsVom(b security.Blessings) (string, error) {
+ bVom, err := vom.Encode(b)
+ if err != nil {
+ return "", fmt.Errorf("vom.Encode(%v) failed: %v", b, err)
+ }
+ return base64.URLEncoding.EncodeToString(bVom), nil
+}
+
func (a *accessTokenBlesser) ServeHTTP(w http.ResponseWriter, r *http.Request) {
remoteKey, err := a.remotePublicKey(r)
if err != nil {
a.ctx.Info("Failed to decode public key [%v] for request %#v", err, r)
- util.HTTPServerError(w, err)
+ util.HTTPServerError(w, fmt.Errorf("failed to decode public key: %v", err))
return
}
@@ -130,31 +153,50 @@
caveats, err := a.blessingCaveats(r, p)
if err != nil {
a.ctx.Info("Failed to constuct caveats for blessing [%v] for request %#v", err, r)
- util.HTTPServerError(w, err)
+ util.HTTPServerError(w, fmt.Errorf("failed to construct caveats for blessing: %v", err))
return
}
extension, err := a.blessingExtension(r)
if err != nil {
a.ctx.Info("Failed to process access token [%v] for request %#v", err, r)
- util.HTTPServerError(w, err)
+ util.HTTPServerError(w, fmt.Errorf("failed to process access token: %v", err))
return
}
blessings, err := p.Bless(remoteKey, with, extension, caveats[0], caveats[1:]...)
if err != nil {
a.ctx.Info("Failed to Bless [%v] for request %#v", err, r)
- util.HTTPServerError(w, fmt.Errorf("Bless failed: %v", err))
+ util.HTTPServerError(w, fmt.Errorf("failed to Bless: %v", err))
return
}
- blessingsVom, err := vom.Encode(blessings)
- if err != nil {
- a.ctx.Info("Failed to VOM encode blessings [%v] for request %#v", err, r)
- util.HTTPServerError(w, fmt.Errorf("failed to VOM encode blessings: %v", err))
+ outputFormat := r.FormValue(outputFormatFormKey)
+ if len(outputFormat) == 0 {
+ outputFormat = base64VomFormat
+ }
+ switch outputFormat {
+ case jsonFormat:
+ encodedBlessings, err := a.encodeBlessingsJson(blessings)
+ if err != nil {
+ a.ctx.Info("Failed to encode blessings [%v] for request %#v", err, r)
+ util.HTTPServerError(w, fmt.Errorf("failed to encode blessings in format %v: %v", outputFormat, err))
+ return
+ }
+ w.Header().Set("Content-Type", "application/json")
+ w.Write(encodedBlessings)
+ case base64VomFormat:
+ encodedBlessings, err := a.encodeBlessingsVom(blessings)
+ if err != nil {
+ a.ctx.Info("Failed to encode blessings [%v] for request %#v", err, r)
+ util.HTTPServerError(w, fmt.Errorf("failed to encode blessings in format %v: %v", outputFormat, err))
+ return
+ }
+ w.Header().Set("Content-Type", "application/text")
+ w.Write([]byte(encodedBlessings))
+ default:
+ a.ctx.Info("Unrecognized output format [%v] in request %#v", outputFormat, r)
+ util.HTTPServerError(w, fmt.Errorf("unrecognized output format [%v] in request. Allowed formats are [%v, %v]", outputFormat, base64VomFormat, jsonFormat))
return
}
- blessingsVomB64 := base64.URLEncoding.EncodeToString(blessingsVom)
- w.Header().Set("Content-Type", "application/text")
- w.Write([]byte(blessingsVomB64))
}
diff --git a/services/identity/internal/handlers/handlers_test.go b/services/identity/internal/handlers/handlers_test.go
index 3740451..5046b7f 100644
--- a/services/identity/internal/handlers/handlers_test.go
+++ b/services/identity/internal/handlers/handlers_test.go
@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-package handlers_test
+package handlers
import (
"bytes"
@@ -24,7 +24,6 @@
_ "v.io/x/ref/runtime/factories/generic"
"v.io/x/ref/services/identity"
"v.io/x/ref/services/identity/internal/blesser"
- "v.io/x/ref/services/identity/internal/handlers"
"v.io/x/ref/services/identity/internal/oauth"
"v.io/x/ref/services/identity/internal/revocation"
"v.io/x/ref/test"
@@ -36,7 +35,7 @@
blessingNames := []string{"test-root"}
p := testutil.NewPrincipal(blessingNames...)
- ts := httptest.NewServer(handlers.BlessingRoot{p})
+ ts := httptest.NewServer(BlessingRoot{p})
defer ts.Close()
response, err := http.Get(ts.URL)
if err != nil {
@@ -69,7 +68,7 @@
}
}
-func TestBlessUsingAccessToken(t *testing.T) {
+func TestBless(t *testing.T) {
var (
blesserPrin = testutil.NewPrincipal("blesser")
blesseePrin = testutil.NewPrincipal("blessee")
@@ -77,35 +76,59 @@
methodCav, _ = security.NewMethodCaveat("foo")
expiryCav, _ = security.NewExpiryCaveat(time.Now().Add(time.Hour))
- mkReqURL = func(baseURLStr string, caveats []security.Caveat) string {
+ mkReqURL = func(baseURLStr string, caveats []security.Caveat, outputFormat string) string {
baseURL, err := url.Parse(baseURLStr)
if err != nil {
t.Fatal(err)
}
- caveatsVom, err := vom.Encode(caveats)
- if err != nil {
- t.Fatal(err)
+ params := url.Values{}
+
+ if len(caveats) != 0 {
+ caveatsVom, err := vom.Encode(caveats)
+ if err != nil {
+ t.Fatal(err)
+ }
+ params.Add(caveatsFormKey, base64.URLEncoding.EncodeToString(caveatsVom))
}
keyBytes, err := blesseePrin.PublicKey().MarshalBinary()
if err != nil {
t.Fatal(err)
}
- params := url.Values{}
- params.Add(handlers.CaveatsFormKey, base64.URLEncoding.EncodeToString(caveatsVom))
- params.Add(handlers.PublicKeyFormKey, base64.URLEncoding.EncodeToString(keyBytes))
- params.Add(handlers.AccessTokenFormKey, "mocktoken")
+ params.Add(publicKeyFormKey, base64.URLEncoding.EncodeToString(keyBytes))
+ params.Add(tokenFormKey, "mocktoken")
+ params.Add(outputFormatFormKey, outputFormat)
+
baseURL.RawQuery = params.Encode()
return baseURL.String()
}
- decodeBlessings = func(blessingsVomB64 string) security.Blessings {
- blessingsVom, err := base64.URLEncoding.DecodeString(blessingsVomB64)
+ vomRoundTrip = func(in, out interface{}) error {
+ data, err := vom.Encode(in)
if err != nil {
- t.Fatal(err)
+ return err
+ }
+ return vom.Decode(data, out)
+ }
+
+ decodeBlessings = func(b []byte, outputFormat string) security.Blessings {
+ if len(outputFormat) == 0 {
+ outputFormat = base64VomFormat
}
var res security.Blessings
- if err := vom.Decode(blessingsVom, &res); err != nil {
- t.Fatal(err)
+ switch outputFormat {
+ case base64VomFormat:
+ if raw, err := base64.URLEncoding.DecodeString(string(b)); err != nil {
+ t.Fatal(err)
+ } else if err = vom.Decode(raw, &res); err != nil {
+ t.Fatal(err)
+ }
+ case jsonFormat:
+ var wb security.WireBlessings
+ if err := json.Unmarshal(b, &wb); err != nil {
+ t.Fatal(err)
+ } else if err = vomRoundTrip(wb, &res); err != nil {
+ t.Fatal(err)
+ }
}
return res
}
@@ -155,49 +178,50 @@
},
}
for _, testcase := range testcases {
- ts := httptest.NewServer(handlers.NewOAuthBlessingHandler(ctx, testcase.params))
- defer ts.Close()
+ for _, outputFormat := range []string{jsonFormat, base64VomFormat, ""} {
+ ts := httptest.NewServer(NewOAuthBlessingHandler(ctx, testcase.params))
+ defer ts.Close()
- response, err := http.Get(mkReqURL(ts.URL, testcase.caveats))
- if err != nil {
- t.Fatal(err)
- }
- blessingsVomB64, err := ioutil.ReadAll(response.Body)
- if err != nil {
- t.Fatal(err)
- }
-
- blessings := decodeBlessings(string(blessingsVomB64))
-
- // Blessing should be bound to the blessee.
- if got, want := blessings.PublicKey(), blesseePrin.PublicKey(); !reflect.DeepEqual(got, want) {
- t.Errorf("got blessings for public key %v, want blessings for public key %v", got, want)
- }
-
- // Verify the name and caveats on the blessings.
- binfo := blesseePrin.BlessingsInfo(blessings)
- if len(binfo) != 1 {
- t.Errorf("got blessings with %d names, want blessings with 1 name", len(binfo))
- }
- wantName := "blesser" + security.ChainSeparator + testClientID + security.ChainSeparator + testEmail
- caveats, ok := binfo[wantName]
- if !ok {
- t.Errorf("expected blessing with name %v, got none", wantName)
- }
-
- if len(testcase.caveats) > 0 {
- // The blessing must have exactly those caveats that were provided in the request.
- if !caveatsMatch(t, caveats, testcase.caveats) {
- t.Errorf("got blessings with caveats %v, want blessings with caveats %v", caveats, testcase.caveats)
+ response, err := http.Get(mkReqURL(ts.URL, testcase.caveats, outputFormat))
+ if err != nil {
+ t.Fatal(err)
}
- } else if len(caveats) != 1 {
- t.Errorf("got blessings with %d caveats, want blessings with 1 caveats", len(caveats))
- } else if testcase.params.RevocationManager != nil && caveats[0].Id != security.PublicKeyThirdPartyCaveat.Id {
- // The blessing must have a third-party revocation caveat.
- t.Errorf("got blessings with caveat (%v), want blessings with a PublicKeyThirdPartyCaveat", caveats[0].Id)
- } else if testcase.params.RevocationManager == nil && caveats[0].Id != security.ExpiryCaveat.Id {
- // The blessing must have an expiry caveat.
- t.Errorf("got blessings with caveat (%v), want blessings with an ExpiryCaveat", caveats[0].Id)
+ b, err := ioutil.ReadAll(response.Body)
+ if err != nil {
+ t.Fatal(err)
+ }
+ blessings := decodeBlessings(b, outputFormat)
+
+ // Blessing should be bound to the blessee.
+ if got, want := blessings.PublicKey(), blesseePrin.PublicKey(); !reflect.DeepEqual(got, want) {
+ t.Errorf("got blessings for public key %v, want blessings for public key %v", got, want)
+ }
+
+ // Verify the name and caveats on the blessings.
+ binfo := blesseePrin.BlessingsInfo(blessings)
+ if len(binfo) != 1 {
+ t.Errorf("got blessings with %d names, want blessings with 1 name", len(binfo))
+ }
+ wantName := "blesser" + security.ChainSeparator + testClientID + security.ChainSeparator + testEmail
+ caveats, ok := binfo[wantName]
+ if !ok {
+ t.Errorf("expected blessing with name %v, got none", wantName)
+ }
+
+ if len(testcase.caveats) > 0 {
+ // The blessing must have exactly those caveats that were provided in the request.
+ if !caveatsMatch(t, caveats, testcase.caveats) {
+ t.Errorf("got blessings with caveats %v, want blessings with caveats %v", caveats, testcase.caveats)
+ }
+ } else if len(caveats) != 1 {
+ t.Errorf("got blessings with %d caveats, want blessings with 1 caveats", len(caveats))
+ } else if testcase.params.RevocationManager != nil && caveats[0].Id != security.PublicKeyThirdPartyCaveat.Id {
+ // The blessing must have a third-party revocation caveat.
+ t.Errorf("got blessings with caveat (%v), want blessings with a PublicKeyThirdPartyCaveat", caveats[0].Id)
+ } else if testcase.params.RevocationManager == nil && caveats[0].Id != security.ExpiryCaveat.Id {
+ // The blessing must have an expiry caveat.
+ t.Errorf("got blessings with caveat (%v), want blessings with an ExpiryCaveat", caveats[0].Id)
+ }
}
}
}