blob: 3303d1aa29d5aeed1e06a7b30a8a000a6c4b72b8 [file] [log] [blame]
// 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))
}