| // 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 |
| } |