| // 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 security |
| |
| import ( |
| "fmt" |
| "regexp" |
| "strings" |
| "v.io/v23/naming" |
| ) |
| |
| // Syntax is /{endpoint}/__({pattern})/{name} |
| var namePatternRegexp = regexp.MustCompile(`^__\(([^)]*)\)($|/)(.*)`) |
| |
| // MatchedBy returns true iff one of the presented blessings matches |
| // p as per the rules described in documentation for the BlessingPattern type. |
| func (p BlessingPattern) MatchedBy(blessings ...string) bool { |
| if len(p) == 0 || !p.IsValid() { |
| return false |
| } |
| if p == AllPrincipals { |
| return true |
| } |
| pstr, glob := trimNoExtension(string(p)) |
| if pstr == "" { |
| return false |
| } |
| for _, b := range blessings { |
| if b == pstr { |
| return true |
| } |
| if glob && strings.HasPrefix(b, pstr) && strings.HasPrefix(b[len(pstr):], ChainSeparator) { |
| return true |
| } |
| } |
| return false |
| } |
| |
| // trimNoExtension removes the trailing NoExtension component from pattern. |
| // Returns true if nothing was trimmed. |
| func trimNoExtension(pattern string) (string, bool) { |
| if suffix := string(NoExtension); pattern == suffix { |
| return "", false |
| } |
| if suffix := ChainSeparator + string(NoExtension); strings.HasSuffix(pattern, suffix) { |
| return pattern[0 : len(pattern)-len(suffix)], false |
| } |
| return pattern, true |
| } |
| |
| // splitBlessing splits in into the first component upto the ChainSeparator and |
| // the rest. |
| func splitBlessing(in string) (prefix, rest string) { |
| idx := strings.Index(in, ChainSeparator) |
| if idx == -1 { |
| return in, "" |
| } |
| return in[0:idx], in[idx+1:] |
| } |
| |
| // IsValid returns true iff the BlessingPattern is well formed, as per the |
| // rules described in documentation for the BlessingPattern type. |
| func (p BlessingPattern) IsValid() bool { |
| if len(p) == 0 { |
| return false |
| } |
| if p == AllPrincipals { |
| return true |
| } |
| pstr, _ := trimNoExtension(string(p)) |
| if strings.HasSuffix(pstr, ChainSeparator) { |
| return false |
| } |
| for len(pstr) > 0 { |
| prefix, rest := splitBlessing(pstr) |
| if validateExtension(prefix) != nil { |
| return false |
| } |
| pstr = rest |
| } |
| return true |
| } |
| |
| // MakeNonExtendable returns a pattern that is matched exactly |
| // by the blessing specified by the given pattern string. |
| // |
| // For example: |
| // onlyAlice := BlessingPattern("google:alice").MakeNonExtendable() |
| // onlyAlice.MatchedBy("google:alice") // Returns true |
| // onlyAlice.MatchedBy("google") // Returns false |
| // onlyAlice.MatchedBy("google:alice:bob") // Returns false |
| func (p BlessingPattern) MakeNonExtendable() BlessingPattern { |
| if len(p) == 0 || p == BlessingPattern(NoExtension) { |
| return BlessingPattern(NoExtension) |
| } |
| if strings.HasSuffix(string(p), ChainSeparator+string(NoExtension)) { |
| return p |
| } |
| return BlessingPattern(string(p) + ChainSeparator + string(NoExtension)) |
| } |
| |
| // PrefixPatterns returns a set of BlessingPatterns that are matched by |
| // blessings that either directly match the provided pattern or can be |
| // extended to match the provided pattern. |
| // |
| // For example: |
| // BlessingPattern("google:alice:friend").PrefixPatterns() returns |
| // ["google:$", "google:alice:$", "google:alice:friend"] |
| // BlessingPattern("google:alice:friend:$").PrefixPatterns() returns |
| // ["google:$", "google:alice:$", "google:alice:friend:$"] |
| // |
| // The returned set of BlessingPatterns are ordered by the number of |
| // ":"-separated components in the pattern. |
| func (p BlessingPattern) PrefixPatterns() []BlessingPattern { |
| if p == NoExtension { |
| return []BlessingPattern{p} |
| } |
| parts := strings.Split(string(p), ChainSeparator) |
| if parts[len(parts)-1] == string(NoExtension) { |
| parts = parts[:len(parts)-2] |
| } else { |
| parts = parts[:len(parts)-1] |
| } |
| var ret []BlessingPattern |
| for i := 0; i < len(parts); i++ { |
| ret = append(ret, BlessingPattern(strings.Join(parts[:i+1], ChainSeparator)).MakeNonExtendable()) |
| } |
| return append(ret, p) |
| } |
| |
| // SplitPatternName takes an object name and parses out the server blessing pattern. |
| // It returns the pattern specified, and the name with the pattern removed. |
| func SplitPatternName(origName string) (BlessingPattern, string) { |
| rooted := naming.Rooted(origName) |
| ep, name := naming.SplitAddressName(origName) |
| match := namePatternRegexp.FindStringSubmatch(name) |
| if len(match) == 0 { |
| return BlessingPattern(""), origName |
| } |
| |
| pattern := BlessingPattern(match[1]) |
| name = naming.Clean(match[3]) |
| if rooted { |
| name = naming.JoinAddressName(ep, name) |
| } |
| return pattern, name |
| } |
| |
| // JoinPatternName embeds the specified pattern into a name. |
| func JoinPatternName(pattern BlessingPattern, name string) string { |
| if len(pattern) == 0 { |
| return name |
| } |
| ep, rel := naming.SplitAddressName(name) |
| return naming.JoinAddressName(ep, fmt.Sprintf("__(%s)/%s", pattern, rel)) |
| } |