blob: 332ee277285dac7e6e9870250c458a1c06314fba [file] [log] [blame]
// 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
}