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