| // 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. |
| |
| // Browspr is the browser version of WSPR, intended to communicate with javascript through postMessage. |
| package browspr |
| |
| import ( |
| "fmt" |
| "reflect" |
| "sync" |
| |
| "v.io/v23" |
| "v.io/v23/context" |
| "v.io/v23/rpc" |
| "v.io/v23/vdl" |
| "v.io/v23/vtrace" |
| "v.io/x/ref/lib/security" |
| "v.io/x/ref/services/wspr/internal/account" |
| "v.io/x/ref/services/wspr/internal/app" |
| "v.io/x/ref/services/wspr/internal/principal" |
| ) |
| |
| // Browspr is an intermediary between our javascript code and the vanadium |
| // network that allows our javascript library to use vanadium. |
| type Browspr struct { |
| ctx *context.T |
| listenSpec *rpc.ListenSpec |
| namespaceRoots []string |
| accountManager *account.AccountManager |
| postMessage func(instanceId int32, ty, msg string) |
| principalManager *principal.PrincipalManager |
| |
| mu sync.Mutex |
| activeInstances map[int32]*pipe // GUARDED_BY mu |
| } |
| |
| // Create a new Browspr instance. |
| func NewBrowspr(ctx *context.T, |
| postMessage func(instanceId int32, ty, msg string), |
| listenSpec *rpc.ListenSpec, |
| identd string, |
| wsNamespaceRoots []string, |
| principalSerializer security.SerializerReaderWriter) *Browspr { |
| if listenSpec.Proxy == "" { |
| ctx.Fatalf("a vanadium proxy must be set") |
| } |
| if identd == "" { |
| ctx.Fatalf("an identd server must be set") |
| } |
| |
| browspr := &Browspr{ |
| listenSpec: listenSpec, |
| namespaceRoots: wsNamespaceRoots, |
| postMessage: postMessage, |
| ctx: ctx, |
| activeInstances: make(map[int32]*pipe), |
| } |
| |
| // TODO(nlacasse, bjornick) use a serializer that can actually persist. |
| var err error |
| p := v23.GetPrincipal(ctx) |
| |
| if principalSerializer == nil { |
| ctx.Fatalf("principalSerializer must not be nil") |
| } |
| |
| if browspr.principalManager, err = principal.NewPrincipalManager(p, principalSerializer); err != nil { |
| ctx.Fatalf("principal.NewPrincipalManager failed: %s", err) |
| } |
| |
| browspr.accountManager = account.NewAccountManager(identd, browspr.principalManager) |
| |
| return browspr |
| } |
| |
| func (b *Browspr) Shutdown() { |
| // TODO(ataly, bprosnitz): Get rid of this method if possible. |
| } |
| |
| // CreateInstance creates a new pipe and stores it in activeInstances map. |
| func (b *Browspr) createInstance(instanceId int32, origin string, namespaceRoots []string, proxy string) (*pipe, error) { |
| b.mu.Lock() |
| defer b.mu.Unlock() |
| |
| // Make sure we don't already have an instance. |
| p, ok := b.activeInstances[instanceId] |
| if ok { |
| return nil, fmt.Errorf("InstanceId %v already has an open instance.", instanceId) |
| } |
| |
| p = newPipe(b, instanceId, origin, namespaceRoots, proxy) |
| if p == nil { |
| return nil, fmt.Errorf("Could not create pipe for instanceId %v and origin %v", instanceId, origin) |
| } |
| b.activeInstances[instanceId] = p |
| return p, nil |
| } |
| |
| // HandleMessage handles most messages from javascript and forwards them to a |
| // Controller. |
| func (b *Browspr) HandleMessage(instanceId int32, origin string, msg app.Message) error { |
| b.mu.Lock() |
| p, ok := b.activeInstances[instanceId] |
| b.mu.Unlock() |
| if !ok { |
| return fmt.Errorf("No pipe found for instanceId %v. Must send CreateInstance message first.", instanceId) |
| } |
| |
| if origin != p.origin { |
| return fmt.Errorf("Invalid message origin. InstanceId %v has origin %v, but message is from %v.", instanceId, p.origin, origin) |
| } |
| |
| return p.handleMessage(msg) |
| } |
| |
| // HandleCleanupRpc cleans up the specified instance state. (For instance, |
| // when a browser tab is closed) |
| func (b *Browspr) HandleCleanupRpc(val *vdl.Value) (*vdl.Value, error) { |
| var msg CleanupMessage |
| if err := vdl.Convert(&msg, val); err != nil { |
| return nil, fmt.Errorf("HandleCleanupRpc did not receive CleanupMessage, received: %v, %v", val, err) |
| } |
| |
| b.mu.Lock() |
| |
| if pipe, ok := b.activeInstances[msg.InstanceId]; ok { |
| // We must unlock the mutex before calling cleanunp, otherwise |
| // browspr deadlocks. |
| b.mu.Unlock() |
| pipe.cleanup(b.ctx) |
| |
| b.mu.Lock() |
| delete(b.activeInstances, msg.InstanceId) |
| } |
| |
| b.mu.Unlock() |
| |
| return nil, nil |
| } |
| |
| // Handler for creating an account in the principal manager. |
| // A valid OAuth2 access token must be supplied in the request body, |
| // which is exchanged for blessings from the vanadium blessing server. |
| // An account based on the blessings is then added to WSPR's principal |
| // manager, and the set of blessing strings are returned to the client. |
| func (b *Browspr) HandleAuthCreateAccountRpc(val *vdl.Value) (*vdl.Value, error) { |
| var msg CreateAccountMessage |
| if err := vdl.Convert(&msg, val); err != nil { |
| return nil, fmt.Errorf("HandleAuthCreateAccountRpc did not receive CreateAccountMessage, received: %v, %v", val, err) |
| } |
| |
| ctx, _ := vtrace.WithNewTrace(b.ctx) |
| account, err := b.accountManager.CreateAccount(ctx, msg.Token) |
| if err != nil { |
| return nil, err |
| } |
| |
| return vdl.ValueFromReflect(reflect.ValueOf(account)) |
| } |
| |
| // HandleAssociateAccountMessage associates an account with the specified origin. |
| func (b *Browspr) HandleAuthAssociateAccountRpc(val *vdl.Value) (*vdl.Value, error) { |
| var msg AssociateAccountMessage |
| if err := vdl.Convert(&msg, val); err != nil { |
| return nil, fmt.Errorf("HandleAuthAssociateAccountRpc did not receive AssociateAccountMessage, received: %v, %v", val, err) |
| } |
| ctx, _ := vtrace.WithNewTrace(b.ctx) |
| if err := b.accountManager.AssociateAccount(ctx, msg.Origin, msg.Account, msg.Caveats); err != nil { |
| return nil, err |
| } |
| return nil, nil |
| } |
| |
| // HandleAuthGetAccountsRpc gets the root account name from the account manager. |
| func (b *Browspr) HandleAuthGetAccountsRpc(*vdl.Value) (*vdl.Value, error) { |
| accounts := b.accountManager.GetAccounts() |
| return vdl.ValueFromReflect(reflect.ValueOf(accounts)) |
| } |
| |
| // HandleAuthOriginHasAccountRpc returns true iff the origin has an associated |
| // principal. |
| func (b *Browspr) HandleAuthOriginHasAccountRpc(val *vdl.Value) (*vdl.Value, error) { |
| var msg OriginHasAccountMessage |
| if err := vdl.Convert(&msg, val); err != nil { |
| return nil, fmt.Errorf("HandleAuthOriginHasAccountRpc did not receive OriginHasAccountMessage, received: %v, %v", val, err) |
| } |
| |
| res := b.accountManager.OriginHasAccount(msg.Origin) |
| return vdl.ValueFromReflect(reflect.ValueOf(res)) |
| } |
| |
| // HandleCreateInstanceRpc sets the namespace root and proxy on the pipe, if |
| // any are provided. |
| func (b *Browspr) HandleCreateInstanceRpc(val *vdl.Value) (*vdl.Value, error) { |
| var msg CreateInstanceMessage |
| if err := vdl.Convert(&msg, val); err != nil { |
| return nil, fmt.Errorf("HandleCreateInstanceMessage did not receive CreateInstanceMessage, received: %v, %v", val, err) |
| } |
| |
| _, err := b.createInstance(msg.InstanceId, msg.Origin, msg.NamespaceRoots, msg.Proxy) |
| if err != nil { |
| return nil, err |
| } |
| |
| return nil, nil |
| } |