1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130
|
// Copyright 2013 Canonical Ltd.
// Licensed under the LGPLv3, see LICENCE file for details.
package names
import (
"fmt"
"regexp"
)
const (
UserTagKind = "user"
LocalUserDomain = "local"
)
var (
// TODO this does not allow single character usernames or
// domains. Is that deliberate?
// https://github.com/juju/names/issues/54
validUserNameSnippet = "[a-zA-Z0-9][a-zA-Z0-9.+-]*[a-zA-Z0-9]"
validUserSnippet = fmt.Sprintf("(?:%s(?:@%s)?)", validUserNameSnippet, validUserNameSnippet)
validName = regexp.MustCompile(fmt.Sprintf("^(?P<name>%s)(?:@(?P<domain>%s))?$", validUserNameSnippet, validUserNameSnippet))
validUserName = regexp.MustCompile("^" + validUserNameSnippet + "$")
)
// IsValidUser returns whether id is a valid user id.
// Valid users may or may not be qualified with an
// @domain suffix. Examples of valid users include
// bob, bob@local, bob@somewhere-else, 0-a-f@123.
func IsValidUser(id string) bool {
return validName.MatchString(id)
}
// IsValidUserName returns whether the given
// name is a valid name part of a user. That is,
// usernames with a domain suffix will return
// false.
func IsValidUserName(name string) bool {
return validUserName.MatchString(name)
}
// IsValidUserDomain returns whether the given user
// domain is valid.
func IsValidUserDomain(domain string) bool {
return validUserName.MatchString(domain)
}
// UserTag represents a user that may be stored locally
// or associated with some external domain.
type UserTag struct {
name string
domain string
}
func (t UserTag) Kind() string { return UserTagKind }
func (t UserTag) String() string { return UserTagKind + "-" + t.Id() }
// Id implements Tag.Id. Local users will always have
// an Id value without any domain.
func (t UserTag) Id() string {
if t.domain == "" || t.domain == LocalUserDomain {
return t.name
}
return t.name + "@" + t.domain
}
// Name returns the name part of the user name
// without its associated domain.
func (t UserTag) Name() string { return t.name }
// IsLocal returns true if the tag represents a local user.
// Users without an explicit domain are considered local.
func (t UserTag) IsLocal() bool {
return t.Domain() == LocalUserDomain || t.Domain() == ""
}
// Domain returns the user domain. Users in the local database
// are from the LocalDomain. Other users are considered 'remote' users.
func (t UserTag) Domain() string {
return t.domain
}
// WithDomain returns a copy of the user tag with the
// domain changed to the given argument.
// The domain must satisfy IsValidUserDomain
// or this function will panic.
func (t UserTag) WithDomain(domain string) UserTag {
if !IsValidUserDomain(domain) {
panic(fmt.Sprintf("invalid user domain %q", domain))
}
return UserTag{
name: t.name,
domain: domain,
}
}
// NewUserTag returns the tag for the user with the given name.
// It panics if the user name does not satisfy IsValidUser.
func NewUserTag(userName string) UserTag {
parts := validName.FindStringSubmatch(userName)
if len(parts) != 3 {
panic(fmt.Sprintf("invalid user tag %q", userName))
}
domain := parts[2]
if domain == LocalUserDomain {
domain = ""
}
return UserTag{name: parts[1], domain: domain}
}
// NewLocalUserTag returns the tag for a local user with the given name.
func NewLocalUserTag(name string) UserTag {
if !IsValidUserName(name) {
panic(fmt.Sprintf("invalid user name %q", name))
}
return UserTag{name: name}
}
// ParseUserTag parses a user tag string.
func ParseUserTag(tag string) (UserTag, error) {
t, err := ParseTag(tag)
if err != nil {
return UserTag{}, err
}
ut, ok := t.(UserTag)
if !ok {
return UserTag{}, invalidTagError(tag, UserTagKind)
}
return ut, nil
}
|