blob: 569fe0f69827d28b118e83273257119cb291f4af [file] [log] [blame]
// Copyright 2016 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 conventions implements unenforced conventions for Vanadium.
//
// This is still work in progress, the conventions may change. Please do not
// rely on these conventions till this comment is removed!
package conventions
import (
"strings"
"v.io/v23/naming"
"v.io/v23/security"
)
// Blessing represents structured information encoded in a blessing name.
type Blessing struct {
IdentityProvider string // Name of the identity provider
User string // UserID attested to by the identity provider
Application string // ApplicationID attested to by the identity provider (may be empty)
Rest string // Remaining extensions of the blessing.
}
// String returns the blessing name represented by this structure.
func (b *Blessing) String() string {
switch b.inferFormat() {
case formatUser:
return join(b.IdentityProvider, "u", b.User, b.Rest)
case formatAppUser:
return join(b.IdentityProvider, "o", b.Application, b.User, b.Rest)
default:
return ""
}
}
// Home returns the "Home directory" in the global namespace for this Blessing.
func (b *Blessing) Home() string {
switch b.inferFormat() {
case formatUser:
return naming.Join("home", naming.EncodeAsNameElement(join(b.IdentityProvider, "u", b.User)))
case formatAppUser:
return naming.Join("home", naming.EncodeAsNameElement(join(b.IdentityProvider, "o", b.Application, b.User)))
default:
return ""
}
}
// UserPattern returns a BlessingPattern that would be matched by a blessing obtainable by the user (irrespective of application).
func (b *Blessing) UserPattern() security.BlessingPattern {
return pattern(b.IdentityProvider, "u", b.User)
}
// AppUserPattern returns a BlessingPattern that would be matched by a blessing for the user using the same application.
func (b *Blessing) AppUserPattern() security.BlessingPattern {
// TODO(ashankar): This should change to join(b.IdentityProvider, "a", b.Application, "u", b.User)}
return pattern(b.IdentityProvider, "o", b.Application, b.User)
}
// AppPattern returns a BlessingPattern that would be matched by all users of the same application.
func (b *Blessing) AppPattern() security.BlessingPattern {
return pattern(b.IdentityProvider, "o", b.Application)
}
func (b *Blessing) inferFormat() format {
if len(b.Application) == 0 {
return formatUser
}
return formatAppUser
}
func join(elems ...string) string {
if len(elems) > 0 && elems[len(elems)-1] == "" {
elems = elems[:len(elems)-1]
}
return strings.Join(elems, security.ChainSeparator)
}
func pattern(elems ...string) security.BlessingPattern {
for _, e := range elems {
if len(e) == 0 {
return security.NoExtension
}
}
return security.BlessingPattern(join(elems...))
}
// format defines the format of the convention used by a blessing name.
type format int
const (
formatInfer format = iota // unknown convention, try to infer it
formatUser // e.g., dev.v.io:u:bugs@bunny.com
formatAppUser // e.g., dev.v.io:o:appid:bugs@bunny.com
)
// ParseBlessingNames extracts structured information from the provided blessing names.
// Blessing names that do not adhere to the conventions of this package are ignored.
//
// Typically the set of names to provide would be obtained via a call to
// security.RemoteBlessingNames or security.LocalBlessingNames.
func ParseBlessingNames(blessingNames ...string) []Blessing {
var ret []Blessing
for _, n := range blessingNames {
if b, ok := parseOne(n); ok {
ret = append(ret, b)
}
}
return ret
}
func parseOne(blessingName string) (Blessing, bool) {
parts := strings.SplitN(blessingName, security.ChainSeparator, 4)
if len(parts) < 3 {
return Blessing{}, false
}
var rest string
if len(parts) > 3 {
rest = parts[3]
}
switch parts[1] {
case "u":
return Blessing{
IdentityProvider: parts[0],
User: parts[2],
Rest: rest,
}, true
case "o":
if len(rest) == 0 {
return Blessing{}, false
}
// TODO(ashankar): Change this - requiring a 'u' component after the app name?
moreparts := strings.SplitN(rest, security.ChainSeparator, 2)
if len(moreparts) > 1 {
rest = moreparts[1]
} else {
rest = ""
}
return Blessing{
IdentityProvider: parts[0],
User: moreparts[0],
Application: parts[2],
Rest: rest,
}, true
}
return Blessing{}, false
}