| // 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 internal |
| |
| import ( |
| "encoding/json" |
| "io/ioutil" |
| "os" |
| "path/filepath" |
| "strings" |
| |
| "v.io/v23/context" |
| "v.io/v23/rpc" |
| "v.io/v23/security" |
| "v.io/v23/verror" |
| |
| "v.io/x/ref/services/discharger" |
| "v.io/x/ref/services/role" |
| |
| "v.io/x/lib/vlog" |
| ) |
| |
| const requiredSuffix = security.ChainSeparator + role.RoleSuffix |
| |
| // NewDispatcher returns a dispatcher object for a role service and its |
| // associated discharger service. |
| // The configRoot is the top level directory where the role configuration files |
| // are stored. |
| // The dischargerLocation is the object name or address of the discharger |
| // service for the third-party caveats attached to the role blessings returned |
| // by the role service. |
| func NewDispatcher(configRoot, dischargerLocation string) rpc.Dispatcher { |
| return &dispatcher{&serverConfig{configRoot, dischargerLocation}} |
| } |
| |
| type serverConfig struct { |
| root string |
| dischargerLocation string |
| } |
| |
| type dispatcher struct { |
| config *serverConfig |
| } |
| |
| func (d *dispatcher) Lookup(suffix string) (interface{}, security.Authorizer, error) { |
| if len(suffix) == 0 { |
| return discharger.DischargerServer(&dischargerImpl{d.config}), security.AllowEveryone(), nil |
| } |
| fileName := filepath.Join(d.config.root, filepath.FromSlash(suffix+".conf")) |
| if !strings.HasPrefix(fileName, d.config.root) { |
| // Guard against ".." in the suffix that could be used to read |
| // files outside of the config root. |
| return nil, nil, verror.New(verror.ErrNoExistOrNoAccess, nil) |
| } |
| roleConfig, err := loadExpandedConfig(fileName, nil) |
| if err != nil && !os.IsNotExist(err) { |
| // The config file exists, but we failed to read it for some |
| // reason. This is likely a server configuration error. |
| vlog.Errorf("loadConfig(%q, %q): %v", d.config.root, suffix, err) |
| return nil, nil, verror.Convert(verror.ErrInternal, nil, err) |
| } |
| obj := &roleService{serverConfig: d.config, role: suffix, roleConfig: roleConfig} |
| return role.RoleServer(obj), &authorizer{roleConfig}, nil |
| } |
| |
| type authorizer struct { |
| config *Config |
| } |
| |
| func (a *authorizer) Authorize(ctx *context.T, call security.Call) error { |
| if call.Method() == "__Glob" { |
| // The Glob implementation only shows objects that the caller |
| // has access to. So this blanket approval is OK. |
| return nil |
| } |
| if a.config == nil { |
| return verror.New(verror.ErrNoExistOrNoAccess, ctx) |
| } |
| remoteBlessingNames, _ := security.RemoteBlessingNames(ctx, call) |
| |
| if hasAccess(a.config, remoteBlessingNames) { |
| return nil |
| } |
| return verror.New(verror.ErrNoExistOrNoAccess, ctx) |
| } |
| |
| func hasAccess(c *Config, blessingNames []string) bool { |
| for _, pattern := range c.Members { |
| if pattern.MatchedBy(blessingNames...) { |
| return true |
| } |
| } |
| return false |
| } |
| |
| func loadExpandedConfig(fileName string, seenFiles map[string]struct{}) (*Config, error) { |
| if seenFiles == nil { |
| seenFiles = make(map[string]struct{}) |
| } |
| if _, seen := seenFiles[fileName]; seen { |
| return nil, nil |
| } |
| seenFiles[fileName] = struct{}{} |
| c, err := loadConfig(fileName) |
| if err != nil { |
| return nil, err |
| } |
| parentDir := filepath.Dir(fileName) |
| for _, imp := range c.ImportMembers { |
| f := filepath.Join(parentDir, filepath.FromSlash(imp+".conf")) |
| ic, err := loadExpandedConfig(f, seenFiles) |
| if err != nil { |
| vlog.Errorf("loadExpandedConfig(%q) failed: %v", f, err) |
| continue |
| } |
| if ic == nil { |
| continue |
| } |
| c.Members = append(c.Members, ic.Members...) |
| } |
| c.ImportMembers = nil |
| dedupMembers(c) |
| return c, nil |
| } |
| |
| func loadConfig(fileName string) (*Config, error) { |
| contents, err := ioutil.ReadFile(fileName) |
| if err != nil { |
| return nil, err |
| } |
| var c Config |
| if err := json.Unmarshal(contents, &c); err != nil { |
| return nil, err |
| } |
| for i, pattern := range c.Members { |
| if p := string(pattern); !strings.HasSuffix(p, requiredSuffix) { |
| c.Members[i] = security.BlessingPattern(p + requiredSuffix) |
| } |
| } |
| return &c, nil |
| } |
| |
| func dedupMembers(c *Config) { |
| members := make(map[security.BlessingPattern]struct{}) |
| for _, m := range c.Members { |
| members[m] = struct{}{} |
| } |
| c.Members = []security.BlessingPattern{} |
| for m := range members { |
| c.Members = append(c.Members, m) |
| } |
| } |