blob: 7a0b3d11662ef667b19158e246bf810b0c25368d [file] [log] [blame]
// 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 test
import (
"fmt"
"io"
"sync"
"time"
"v.io/v23/context"
"v.io/v23/i18n"
"v.io/v23/rpc"
"v.io/v23/security"
"v.io/v23/security/access"
"v.io/v23/uniqueid"
"v.io/v23/vtrace"
)
// This file contains a test server and dispatcher which are used by other
// tests, especially those in full_test.
type userType string
type testServer struct{}
func (*testServer) Closure(*context.T, rpc.ServerCall) error {
return nil
}
func (*testServer) Error(*context.T, rpc.ServerCall) error {
return errMethod
}
func (*testServer) Echo(_ *context.T, call rpc.ServerCall, arg string) (string, error) {
return fmt.Sprintf("method:%q,suffix:%q,arg:%q", "Echo", call.Suffix(), arg), nil
}
func (*testServer) EchoUser(_ *context.T, call rpc.ServerCall, arg string, u userType) (string, userType, error) {
return fmt.Sprintf("method:%q,suffix:%q,arg:%q", "EchoUser", call.Suffix(), arg), u, nil
}
func (*testServer) EchoLang(ctx *context.T, call rpc.ServerCall) (string, error) {
return string(i18n.GetLangID(ctx)), nil
}
func (*testServer) EchoBlessings(ctx *context.T, call rpc.ServerCall) (server, client string, _ error) {
local := security.LocalBlessingNames(ctx, call.Security())
remote, _ := security.RemoteBlessingNames(ctx, call.Security())
return fmt.Sprintf("%v", local), fmt.Sprintf("%v", remote), nil
}
func (*testServer) EchoGrantedBlessings(_ *context.T, call rpc.ServerCall, arg string) (result, blessing string, _ error) {
return arg, fmt.Sprintf("%v", call.GrantedBlessings()), nil
}
func (*testServer) EchoAndError(_ *context.T, call rpc.ServerCall, arg string) (string, error) {
result := fmt.Sprintf("method:%q,suffix:%q,arg:%q", "EchoAndError", call.Suffix(), arg)
if arg == "error" {
return result, errMethod
}
return result, nil
}
func (*testServer) Stream(_ *context.T, call rpc.StreamServerCall, arg string) (string, error) {
result := fmt.Sprintf("method:%q,suffix:%q,arg:%q", "Stream", call.Suffix(), arg)
var u userType
var err error
for err = call.Recv(&u); err == nil; err = call.Recv(&u) {
result += " " + string(u)
if err := call.Send(u); err != nil {
return "", err
}
}
if err == io.EOF {
err = nil
}
return result, err
}
func (*testServer) Unauthorized(*context.T, rpc.StreamServerCall) (string, error) {
return "UnauthorizedResult", nil
}
type testServerAuthorizer struct{}
func (testServerAuthorizer) Authorize(ctx *context.T, call security.Call) error {
// Verify that the Call object seen by the authorizer
// has the necessary fields.
lb := call.LocalBlessings()
if lb.IsZero() {
return fmt.Errorf("testServerAuthorzer: Call object %v has no LocalBlessings", call)
}
if tpcavs := lb.ThirdPartyCaveats(); len(tpcavs) > 0 && call.LocalDischarges() == nil {
return fmt.Errorf("testServerAuthorzer: Call object %v has no LocalDischarges even when LocalBlessings have third-party caveats", call)
}
if call.LocalPrincipal() == nil {
return fmt.Errorf("testServerAuthorzer: Call object %v has no LocalPrincipal", call)
}
if call.Method() == "" {
return fmt.Errorf("testServerAuthorzer: Call object %v has no Method", call)
}
if call.LocalEndpoint().IsZero() {
return fmt.Errorf("testServerAuthorzer: Call object %v has no LocalEndpoint", call)
}
if call.RemoteEndpoint().IsZero() {
return fmt.Errorf("testServerAuthorzer: Call object %v has no RemoteEndpoint", call)
}
// Do not authorize the method "Unauthorized".
if call.Method() == "Unauthorized" {
return fmt.Errorf("testServerAuthorizer denied access")
}
return nil
}
type testServerDisp struct{ server interface{} }
func (t testServerDisp) Lookup(_ *context.T, suffix string) (interface{}, security.Authorizer, error) {
// If suffix is "nilAuth" we use default authorization, if it is "aclAuth" we
// use an AccessList-based authorizer, and otherwise we use the custom testServerAuthorizer.
var authorizer security.Authorizer
switch suffix {
case "discharger":
return &dischargeServer{}, testServerAuthorizer{}, nil
case "nilAuth":
authorizer = nil
case "aclAuth":
authorizer = &access.AccessList{
In: []security.BlessingPattern{"test-blessing:client", "test-blessing:server"},
}
default:
authorizer = testServerAuthorizer{}
}
return t.server, authorizer, nil
}
type dischargeServer struct {
mu sync.Mutex
count int
}
func (ds *dischargeServer) Discharge(ctx *context.T, call rpc.StreamServerCall, cav security.Caveat, _ security.DischargeImpetus) (security.Discharge, error) {
ds.mu.Lock()
ds.count++
ds.mu.Unlock()
tp := cav.ThirdPartyDetails()
if tp == nil {
return security.Discharge{}, fmt.Errorf("discharger: %v does not represent a third-party caveat", cav)
}
if err := tp.Dischargeable(ctx, call.Security()); err != nil {
return security.Discharge{}, fmt.Errorf("third-party caveat %v cannot be discharged for this context: %v", cav, err)
}
// Add a fakeTimeCaveat to be able to control discharge expiration via 'clock'.
expiry, err := security.NewCaveat(fakeTimeCaveat, clock.Now())
if err != nil {
return security.Discharge{}, fmt.Errorf("failed to create an expiration on the discharge: %v", err)
}
return call.Security().LocalPrincipal().MintDischarge(cav, expiry)
}
// granter implements rpc.Granter.
//
// It returns the specified (security.Blessings, error) pair if either the
// blessing or the error is specified. Otherwise it returns a blessing
// derived from the local blessings of the current call.
type granter struct {
rpc.CallOpt
b security.Blessings
err error
}
func (g granter) Grant(ctx *context.T, call security.Call) (security.Blessings, error) {
if !g.b.IsZero() || g.err != nil {
return g.b, g.err
}
return call.LocalPrincipal().Bless(
call.RemoteBlessings().PublicKey(),
call.LocalBlessings(),
"blessed",
security.UnconstrainedUse())
}
// dischargeTestServer implements the discharge service. Always fails to
// issue a discharge, but records the impetus and traceid of the RPC call.
type dischargeTestServer struct {
p security.Principal
impetus []security.DischargeImpetus
traceid []uniqueid.Id
}
func (s *dischargeTestServer) Discharge(ctx *context.T, _ rpc.ServerCall, cav security.Caveat, impetus security.DischargeImpetus) (security.Discharge, error) {
s.impetus = append(s.impetus, impetus)
s.traceid = append(s.traceid, vtrace.GetSpan(ctx).Trace())
return security.Discharge{}, fmt.Errorf("discharges not issued")
}
func (s *dischargeTestServer) Release() ([]security.DischargeImpetus, []uniqueid.Id) {
impetus, traceid := s.impetus, s.traceid
s.impetus, s.traceid = nil, nil
return impetus, traceid
}
type streamRecvInGoroutineServer struct{ c chan error }
func (s *streamRecvInGoroutineServer) RecvInGoroutine(_ *context.T, call rpc.StreamServerCall) error {
// Spawn a goroutine to read streaming data from the client.
go func() {
var i interface{}
for {
err := call.Recv(&i)
if err != nil {
s.c <- err
return
}
}
}()
// Imagine the server did some processing here and now that it is done,
// it does not care to see what else the client has to say.
return nil
}
type expiryDischarger struct {
mu sync.Mutex
count int
}
func (ed *expiryDischarger) Discharge(ctx *context.T, call rpc.StreamServerCall, cav security.Caveat, _ security.DischargeImpetus) (security.Discharge, error) {
tp := cav.ThirdPartyDetails()
if tp == nil {
return security.Discharge{}, fmt.Errorf("discharger: %v does not represent a third-party caveat", cav)
}
if err := tp.Dischargeable(ctx, call.Security()); err != nil {
return security.Discharge{}, fmt.Errorf("third-party caveat %v cannot be discharged for this context: %v", cav, err)
}
expDur := 10 * time.Millisecond
expiry, err := security.NewExpiryCaveat(time.Now().Add(expDur))
if err != nil {
return security.Discharge{}, fmt.Errorf("failed to create an expiration on the discharge: %v", err)
}
d, err := call.Security().LocalPrincipal().MintDischarge(cav, expiry)
if err != nil {
return security.Discharge{}, err
}
ed.mu.Lock()
ed.count++
ed.mu.Unlock()
return d, nil
}