| // 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 handlers_test |
| |
| import ( |
| "bytes" |
| "encoding/base64" |
| "encoding/json" |
| "io/ioutil" |
| "net/http" |
| "net/http/httptest" |
| "net/url" |
| "reflect" |
| "sort" |
| "testing" |
| "time" |
| |
| "v.io/v23" |
| "v.io/v23/security" |
| "v.io/v23/vom" |
| |
| _ "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" |
| "v.io/x/ref/test/testutil" |
| ) |
| |
| func TestBlessingRoot(t *testing.T) { |
| // TODO(ashankar,ataly): Handle multiple root names? |
| blessingNames := []string{"test-root"} |
| p := testutil.NewPrincipal(blessingNames...) |
| |
| ts := httptest.NewServer(handlers.BlessingRoot{p}) |
| defer ts.Close() |
| response, err := http.Get(ts.URL) |
| if err != nil { |
| t.Fatal(err) |
| } |
| dec := json.NewDecoder(response.Body) |
| var res identity.BlessingRootResponse |
| if err := dec.Decode(&res); err != nil { |
| t.Fatal(err) |
| } |
| |
| // Check that the names are correct. |
| sort.Strings(blessingNames) |
| sort.Strings(res.Names) |
| if !reflect.DeepEqual(res.Names, blessingNames) { |
| t.Errorf("Response has incorrect name. Got %v, want %v", res.Names, blessingNames) |
| } |
| |
| // Check that the public key is correct. |
| gotMarshalled, err := base64.URLEncoding.DecodeString(res.PublicKey) |
| if err != nil { |
| t.Fatal(err) |
| } |
| got, err := security.UnmarshalPublicKey(gotMarshalled) |
| if err != nil { |
| t.Fatal(err) |
| } |
| if want := p.PublicKey(); !reflect.DeepEqual(got, want) { |
| t.Errorf("Response has incorrect public key. Got %v, want %v", got, want) |
| } |
| } |
| |
| func TestBlessUsingAccessToken(t *testing.T) { |
| var ( |
| blesserPrin = testutil.NewPrincipal("blesser") |
| blesseePrin = testutil.NewPrincipal("blessee") |
| |
| methodCav, _ = security.NewMethodCaveat("foo") |
| expiryCav, _ = security.NewExpiryCaveat(time.Now().Add(time.Hour)) |
| |
| mkReqURL = func(baseURLStr string, caveats []security.Caveat) string { |
| baseURL, err := url.Parse(baseURLStr) |
| if err != nil { |
| t.Fatal(err) |
| } |
| caveatsVom, err := vom.Encode(caveats) |
| if err != nil { |
| t.Fatal(err) |
| } |
| 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") |
| baseURL.RawQuery = params.Encode() |
| return baseURL.String() |
| } |
| |
| decodeBlessings = func(blessingsVomB64 string) security.Blessings { |
| blessingsVom, err := base64.URLEncoding.DecodeString(blessingsVomB64) |
| if err != nil { |
| t.Fatal(err) |
| } |
| var res security.Blessings |
| if err := vom.Decode(blessingsVom, &res); err != nil { |
| t.Fatal(err) |
| } |
| return res |
| } |
| ) |
| |
| ctx, shutdown := test.V23Init() |
| defer shutdown() |
| var err error |
| if ctx, err = v23.WithPrincipal(ctx, blesserPrin); err != nil { |
| t.Fatal(err) |
| } |
| |
| // Make the blessee trust the blesser's roots |
| if err := blesseePrin.AddToRoots(blesserPrin.BlessingStore().Default()); err != nil { |
| t.Fatal(err) |
| } |
| |
| testEmail := "foo@bar.com" |
| testClientID := "test-client-id" |
| revocationManager := revocation.NewMockRevocationManager(ctx) |
| oauthProvider := oauth.NewMockOAuth(testEmail, testClientID) |
| |
| testcases := []struct { |
| params blesser.OAuthBlesserParams |
| caveats []security.Caveat |
| }{ |
| { |
| blesser.OAuthBlesserParams{ |
| OAuthProvider: oauthProvider, |
| BlessingDuration: 24 * time.Hour, |
| }, |
| nil, |
| }, |
| { |
| blesser.OAuthBlesserParams{ |
| OAuthProvider: oauthProvider, |
| RevocationManager: revocationManager, |
| }, |
| nil, |
| }, |
| { |
| blesser.OAuthBlesserParams{ |
| OAuthProvider: oauthProvider, |
| RevocationManager: revocationManager, |
| }, |
| []security.Caveat{expiryCav, methodCav}, |
| }, |
| } |
| for _, testcase := range testcases { |
| ts := httptest.NewServer(handlers.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) |
| } |
| } 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) |
| } |
| } |
| } |
| |
| type caveatsSorter struct { |
| caveats []security.Caveat |
| t *testing.T |
| } |
| |
| func (c caveatsSorter) Len() int { return len(c.caveats) } |
| func (c caveatsSorter) Swap(i, j int) { c.caveats[i], c.caveats[j] = c.caveats[j], c.caveats[i] } |
| func (c caveatsSorter) Less(i, j int) bool { |
| b_i, err := vom.Encode(c.caveats[i]) |
| if err != nil { |
| c.t.Fatal(err) |
| } |
| b_j, err := vom.Encode(c.caveats[j]) |
| if err != nil { |
| c.t.Fatal(err) |
| } |
| return bytes.Compare(b_i, b_j) == -1 |
| } |
| |
| func caveatsMatch(t *testing.T, got, want []security.Caveat) bool { |
| if len(got) != len(want) { |
| return false |
| } |
| g, w := caveatsSorter{got, t}, caveatsSorter{want, t} |
| sort.Sort(g) |
| sort.Sort(w) |
| return reflect.DeepEqual(g, w) |
| } |