| // Binary pbankd is a simple implementation of the bank service. |
| // Binary bank clients can connect to this bank service to manage virtual bank |
| // accounts. Unlike bankd, pbankd uses the Veyron Store. It can recover user |
| // data after a crash, but it must always connect to the stored service. |
| // Unlike bankd, pbankd prevents race conditions with transactions. |
| package main |
| |
| // --------------------------------------------------------- |
| // NOTE(kash): This file is commented out because we are making large changes |
| // to the Store API and we don't want to repeatedly update this example. |
| // Additionally, the server uses a common transaction across all requests, |
| // so there is a pretty bad race condition that we don't want people to |
| // copy. The only reason we are keeping this file is because it demonstrates |
| // how to write a custom dispatcher. |
| // --------------------------------------------------------- |
| |
| // import ( |
| // "errors" |
| // "flag" |
| // "fmt" |
| // "math/rand" |
| // "os" |
| // "os/user" |
| // "path/filepath" |
| // "reflect" |
| // "regexp" |
| // "strings" |
| // "time" |
| |
| // "veyron/examples/bank" |
| // "veyron/examples/bank/schema" |
| // "veyron/lib/signals" |
| // vsecurity "veyron/security" |
| // "veyron/security/caveat" |
| // idutil "veyron/services/identity/util" |
| |
| // "veyron2" |
| // "veyron2/ipc" |
| // "veyron2/naming" |
| // "veyron2/rt" |
| // "veyron2/security" |
| // "veyron2/storage" |
| // "veyron2/storage/vstore" |
| // "veyron2/vlog" |
| // ) |
| |
| // // Duration of a bank account blessing, intended to be very long. |
| // const BLESS_DURATION = 24 * 10000 * time.Hour |
| |
| // // Ensure that account numbers are all 6 digits long. |
| // const MIN_ACCOUNT_NUMBER = 100000 |
| // const MAX_ACCOUNT_NUMBER = 999999 |
| // const SUFFIX_REGEXP = "/[0-9]{6}" |
| |
| // // Where we will store the bank in the store database. |
| // const BANK_ROOT string = "/Bank" |
| |
| // var ( |
| // storeName string |
| // ACCOUNTS string |
| // runtime veyron2.Runtime |
| // ) |
| |
| // func init() { |
| // username := "unknown" |
| // if u, err := user.Current(); err == nil { |
| // username = u.Username |
| // } |
| // hostname := "unknown" |
| // if h, err := os.Hostname(); err == nil { |
| // hostname = h |
| // } |
| // dir := "global/vstore/" + hostname + "/" + username |
| |
| // // Parse the flags (variableName, flagName, defaultValue, description) |
| // flag.StringVar(&storeName, "store", dir, "Name of the Veyron store") |
| |
| // // Set the random seed to the current time to increase psuedorandomness. |
| // rand.Seed(time.Now().Unix()) |
| |
| // // Set the name for the ACCOUNTS 'constant'. TODO(alexfandrianto): Is there a better way to know the field is named "Accounts"? |
| // ACCOUNTS = reflect.TypeOf(schema.Bank{}).Field(0).Name |
| // } |
| |
| // // The following struct and functions handle construction of the persistent bank. |
| // type pbankd struct { |
| // // Pointer to the store |
| // store storage.Store |
| |
| // // Current Transaction name; empty if there's no transaction |
| // tname string |
| |
| // // The bank's private ID (used for blessing) |
| // ID security.PrivateID |
| // } |
| |
| // // newPbankd creates a new persistent bank structure. |
| // func newPbankd(store storage.Store, identity security.PrivateID) *pbankd { |
| // b := &pbankd{ |
| // store: store, |
| // ID: identity, |
| // } |
| // return b |
| // } |
| |
| // // InitializeBank bank details in the store; currently only initializes the root. |
| // func (b *pbankd) initializeBank() { |
| // if err := b.newTransaction(); err != nil { |
| // vlog.Fatal(err) |
| // } |
| // // NOTE(sadovsky): initializeBankRoot ought to return an error. Currently, |
| // // some errors (e.g. failed puts) could slip through unnoticed. |
| // b.initializeBankRoot() |
| // if err := b.commit(); err != nil { |
| // vlog.Fatal(err) |
| // } |
| // } |
| |
| // // initializeBankRoot prepares the bank root as BANK_ROOT if it isn't yet in the Veyron store. |
| // func (b *pbankd) initializeBankRoot() { |
| // // Create parent directories for the bank root, if necessary |
| // l := strings.Split(BANK_ROOT, "/") |
| // fmt.Println(l) |
| // for i, _ := range l { |
| // fmt.Println(i) |
| // prefix := filepath.Join(l[:i]...) |
| // o := b.store.BindObject(naming.Join(b.tname, prefix)) |
| // if exist, err := o.Exists(runtime.TODOContext()); err != nil { |
| // vlog.Infof("Error checking existence at %q: %s", prefix, err) |
| // } else if !exist { |
| // if _, err := o.Put(runtime.TODOContext(), &schema.Dir{}); err != nil { |
| // vlog.Infof("Error creating parent %q: %s", prefix, err) |
| // } |
| // fmt.Printf("%q was created!\n", prefix) |
| // } else { |
| // fmt.Printf("%q was already present in the store.\n", prefix) |
| // } |
| // } |
| |
| // // Add the bank schema to the store at BANK_ROOT, if necessary |
| // o := b.store.BindObject(naming.Join(b.tname, BANK_ROOT)) |
| // if exist, err := o.Exists(runtime.TODOContext()); err != nil { |
| // vlog.Infof("Error checking existence at %q: %s", BANK_ROOT, err) |
| // } else if !exist { |
| // _, err := o.Put(runtime.TODOContext(), &schema.Bank{}) |
| // if err != nil { |
| // vlog.Infof("Error creating bank at %q: %s", BANK_ROOT, err) |
| // } |
| // } |
| // } |
| |
| // // Register creates an account for a new user with the given bankName. |
| // func (b *pbankd) Connect(context ipc.ServerContext) (string, int64, error) { |
| // // Check if the RemoteID() has been blessed by the bank |
| // if num := getBankAccountNumber(context); num != 0 { |
| // // Look up the user and return their bank account number |
| // fmt.Println("This client is blessed!") |
| // fmt.Printf("ID: %d\n", num) |
| // return "", num, nil |
| // } else { |
| // fmt.Println("This client isn't blessed. Let's bless them!") |
| // // Use the store |
| // if err := b.newTransaction(); err != nil { |
| // vlog.Fatal(err) |
| // } |
| |
| // // Keep rolling until we get an unseen number |
| // randID := rand.Int63n(MAX_ACCOUNT_NUMBER-MIN_ACCOUNT_NUMBER) + MIN_ACCOUNT_NUMBER |
| // for b.isUser(randID) { |
| // randID = rand.Int63n(MAX_ACCOUNT_NUMBER-MIN_ACCOUNT_NUMBER) + MIN_ACCOUNT_NUMBER |
| // } |
| // fmt.Printf("ID: %d\n", randID) |
| |
| // // Bless the user |
| // pp := security.PrincipalPattern(context.LocalID().Names()[0]) |
| // pID, err := b.ID.Bless( |
| // context.RemoteID(), |
| // fmt.Sprintf("%d", randID), |
| // BLESS_DURATION, |
| // []security.ServiceCaveat{security.UniversalCaveat(caveat.PeerIdentity{pp})}, |
| // ) |
| // if err != nil { |
| // vlog.Fatal(err) |
| // } |
| |
| // // Encode the public ID |
| // enc, err := idutil.Base64VomEncode(pID) |
| // if err != nil { |
| // vlog.Fatal(err) |
| // } |
| |
| // // Store the user into the database |
| // b.registerNewUser(randID) |
| // err = b.commit() |
| // if err != nil { |
| // vlog.Fatal(err) |
| // } |
| |
| // // Ensure the new user is a user before returning the blessing and ID |
| // if b.isUser(randID) { |
| // return enc, randID, nil |
| // } |
| // return "", 0, fmt.Errorf("failed to register user") |
| // } |
| // } |
| |
| // // Deposit adds the amount given to this account. |
| // func (b *pbankd) Deposit(context ipc.ServerContext, amount int64) error { |
| // user := getBankAccountNumber(context) |
| // if user == 0 { |
| // return fmt.Errorf("couldn't retrieve account number") |
| // } |
| // if err := b.newTransaction(); err != nil { |
| // return err |
| // } |
| // if !b.isUser(user) { |
| // return fmt.Errorf("user isn't registered") |
| // } else if amount < 0 { |
| // return fmt.Errorf("deposit amount %d is negative", amount) |
| // } |
| // b.changeBalance(user, amount) |
| // return b.commit() |
| // } |
| |
| // // Withdraw reduces the amount given from this account. |
| // func (b *pbankd) Withdraw(context ipc.ServerContext, amount int64) error { |
| // user := getBankAccountNumber(context) |
| // if user == 0 { |
| // return fmt.Errorf("couldn't retrieve account number") |
| // } |
| // if err := b.newTransaction(); err != nil { |
| // return err |
| // } |
| // if !b.isUser(user) { |
| // return fmt.Errorf("user isn't registered") |
| // } else if amount < 0 { |
| // return fmt.Errorf("withdraw amount %d is negative", amount) |
| // } else if balance := b.checkBalance(user); amount > balance { |
| // return fmt.Errorf("withdraw amount %d exceeds balance %d", amount, balance) |
| // } |
| // b.changeBalance(user, -amount) |
| // return b.commit() |
| // } |
| |
| // // Transfer moves the amount given to the receiver. |
| // func (b *pbankd) Transfer(context ipc.ServerContext, accountNumber int64, amount int64) error { |
| // user := getBankAccountNumber(context) |
| // if user == 0 { |
| // return fmt.Errorf("couldn't retrieve account number") |
| // } |
| // if err := b.newTransaction(); err != nil { |
| // return err |
| // } |
| // if !b.isUser(user) { |
| // return fmt.Errorf("user isn't registered") |
| // } else if !b.isUser(accountNumber) { |
| // return fmt.Errorf("%d isn't registered", accountNumber) |
| // } else if amount < 0 { |
| // return fmt.Errorf("transfer amount %d is negative", amount) |
| // } else if balance := b.checkBalance(user); amount > balance { |
| // return fmt.Errorf("transfer amount %d exceeds balance %d", amount, balance) |
| // } |
| // b.changeBalance(user, -amount) |
| // b.changeBalance(accountNumber, amount) |
| // return b.commit() |
| // } |
| |
| // // Balance returns the amount stored by the given user. |
| // // Throws an error if the user is invalid. |
| // func (b *pbankd) Balance(context ipc.ServerContext) (int64, error) { |
| // user := getBankAccountNumber(context) |
| // if user == 0 { |
| // return 0, fmt.Errorf("couldn't retrieve account number") |
| // } |
| // if !b.isUser(user) { |
| // return 0, fmt.Errorf("user isn't registered") |
| // } |
| // return b.checkBalance(user), nil |
| // } |
| |
| // /* |
| // Helper functions for the persistent bank service that deal with store access. |
| // */ |
| |
| // // newTransaction starts a new transaction. |
| // func (b *pbankd) newTransaction() error { |
| // tid, err := b.store.BindTransactionRoot("").CreateTransaction(runtime.TODOContext()) |
| // if err != nil { |
| // b.tname = "" |
| // return err |
| // } |
| // b.tname = tid // Transaction is rooted at "", so tname == tid. |
| // return nil |
| // } |
| |
| // // commit commits the current transaction. |
| // func (b *pbankd) commit() error { |
| // if b.tname == "" { |
| // return errors.New("No transaction to commit") |
| // } |
| // err := b.store.BindTransaction(b.tname).Commit(runtime.TODOContext()) |
| // b.tname = "" |
| // if err != nil { |
| // return fmt.Errorf("Failed to commit transaction: %s", err) |
| // } |
| // return nil |
| // } |
| |
| // // isUser helps determine if the given user is part of the system or not. |
| // func (b *pbankd) isUser(accountNumber int64) bool { |
| // // If this is a user, their location in the store should exist. |
| // prefix := filepath.Join(BANK_ROOT, ACCOUNTS, fmt.Sprintf("%d", accountNumber)) |
| // o := b.store.BindObject(naming.Join(b.tname, prefix)) |
| // exist, err := o.Exists(runtime.TODOContext()) |
| // if err != nil { |
| // vlog.Infof("Error checking existence at %s: %s", prefix, err) |
| // return false |
| // } |
| // return exist |
| // } |
| |
| // // Obtains the bank account number of the user. Returns 0 if it could not be found. |
| // func getBankAccountNumber(context ipc.ServerContext) int64 { |
| // bankName := context.LocalID().Names()[0] |
| |
| // // Untrusted clients have no names. |
| // if len(context.RemoteID().Names()) == 0 { |
| // return 0 |
| // } |
| |
| // // Otherwise, extract the account number from the name. |
| // name := context.RemoteID().Names()[0] |
| // if match, err := regexp.MatchString(bankName+SUFFIX_REGEXP, name); err != nil { |
| // vlog.Infof("MatchString error: %s", err) |
| // return 0 |
| // } else if !match { |
| // vlog.Infof("No matching name found") |
| // return 0 |
| // } |
| // var v int64 |
| // _, err := fmt.Sscanf(name, bankName+"/%d", &v) |
| // if err != nil { |
| // vlog.Infof("Failure to parse ID from %s: %s", name, err) |
| // return 0 |
| // } |
| // return v |
| // } |
| |
| // // registerNewUser adds the user to the system under the specified name and returns success. |
| // func (b *pbankd) registerNewUser(user int64) { |
| // // Create the user's account |
| // prefix := filepath.Join(BANK_ROOT, ACCOUNTS, fmt.Sprintf("%d", user)) |
| // o := b.store.BindObject(naming.Join(b.tname, prefix)) |
| // if _, err := o.Put(runtime.TODOContext(), int64(0)); err != nil { |
| // vlog.Infof("Error creating %s: %s", prefix, err) |
| // } |
| // } |
| |
| // // checkBalance gets the user's balance from the store |
| // func (b *pbankd) checkBalance(user int64) int64 { |
| // prefix := filepath.Join(BANK_ROOT, ACCOUNTS, fmt.Sprintf("%d", user)) |
| // o := b.store.BindObject(naming.Join(b.tname, prefix)) |
| // e, err := o.Get(runtime.TODOContext()) |
| // if err != nil { |
| // vlog.Infof("Error getting %s: %s", prefix, err) |
| // } |
| // value, _ := e.Value.(int64) |
| // return value |
| // } |
| |
| // // changeBalance modifies the user's balance in the store |
| // func (b *pbankd) changeBalance(user int64, amount int64) { |
| // prefix := filepath.Join(BANK_ROOT, ACCOUNTS, fmt.Sprintf("%d", user)) |
| // o := b.store.BindObject(naming.Join(b.tname, prefix)) |
| // e, err := o.Get(runtime.TODOContext()) |
| // if err != nil { |
| // vlog.Infof("Error getting %s: %s", prefix, err) |
| // } |
| // if _, err := o.Put(runtime.TODOContext(), e.Value.(int64)+amount); err != nil { |
| // vlog.Infof("Error changing %s: %s", prefix, err) |
| // } |
| // } |
| |
| // // This custom bank dispatcher has two interfaces with distinct authorizers. |
| // func newBankDispatcher(bankServer interface{}, bankAccountServer interface{}, authBank security.Authorizer, authBankAccount security.Authorizer) ipc.Dispatcher { |
| // return BankDispatcher{ipc.ReflectInvoker(bankServer), ipc.ReflectInvoker(bankAccountServer), authBank, authBankAccount} |
| // } |
| |
| // type BankDispatcher struct { |
| // invokerBank ipc.Invoker |
| // invokerBankAccount ipc.Invoker |
| // authBank security.Authorizer |
| // authBankAccount security.Authorizer |
| // } |
| |
| // func (d BankDispatcher) Lookup(suffix string) (ipc.Invoker, security.Authorizer, error) { |
| // fmt.Println("Dispatcher Lookup Suffix:", suffix) |
| // if suffix != "" { |
| // return d.invokerBankAccount, d.authBankAccount, nil |
| // } |
| // return d.invokerBank, d.authBank, nil |
| // } |
| |
| // // The custom account authorizer checks if the RemoteID matches a regexp pattern and allows only Reads and Writes. |
| // type AccountAuthorizer string |
| |
| // func (aa AccountAuthorizer) Authorize(ctx security.Context) error { |
| // name := ctx.RemoteID().Names()[0] |
| // match, err := regexp.MatchString(string(aa), name) |
| // fmt.Printf("Authorizing for Account %s %t\n", name, match) |
| // if err != nil { |
| // return err |
| // } |
| // if match { |
| // if ctx.Label() != security.ReadLabel && ctx.Label() != security.WriteLabel { |
| // return errors.New("unauthorized; may only read or write") |
| // } |
| // return nil |
| // } |
| // return errors.New("unauthorized to access account") |
| // } |
| |
| func main() { |
| // // Create a new server instance. |
| // runtime = rt.Init() |
| // s, err := runtime.NewServer() |
| // if err != nil { |
| // vlog.Fatal("failure creating server: ", err) |
| // } |
| |
| // // Connect to the Veyron Store |
| // vlog.Infof("Binding to store on %s", storeName) |
| // st, err := vstore.New(storeName) |
| // if err != nil { |
| // vlog.Fatalf("Can't connect to store: %s: %s", storeName, err) |
| // } |
| |
| // // Create the bank server and bank account server stubs, using the store connection |
| // pbankd := newPbankd(st, runtime.Identity()) |
| // pbankd.initializeBank() |
| // bankServer := bank.NewServerBank(pbankd) |
| // bankAccountServer := bank.NewServerBankAccount(pbankd) |
| |
| // // Setup bank and account authorizers. |
| // bankAuth := vsecurity.NewACLAuthorizer(security.NewWhitelistACL( |
| // map[security.PrincipalPattern]security.LabelSet{ |
| // security.AllPrincipals: security.LabelSet(security.ReadLabel | security.WriteLabel), |
| // })) |
| // bankAccountAuth := AccountAuthorizer(runtime.Identity().PublicID().Names()[0] + SUFFIX_REGEXP) |
| |
| // dispatcher := newBankDispatcher(bankServer, bankAccountServer, bankAuth, bankAccountAuth) |
| |
| // // Create an endpoint and begin listening. |
| // endpoint, err := s.Listen("tcp", "127.0.0.1:0") |
| // if err == nil { |
| // fmt.Printf("Listening at: %v\n", endpoint) |
| // } else { |
| // vlog.Fatal("error listening to service: ", err) |
| // } |
| |
| // // Publish the service in the mount table. |
| // mountName := "veyron/bank" |
| // fmt.Printf("Mounting bank on %s, endpoint /%s\n", mountName, endpoint) |
| // if err := s.Serve(mountName, dispatcher); err != nil { |
| // vlog.Fatal("s.Serve() failed: ", err) |
| // } |
| |
| // // Wait forever. |
| // <-signals.ShutdownOnSignals() |
| } |