add a UserTag.WithDomain method to allow us to alter the domain associated with a user #55

Merged
merged 1 commit into from Oct 9, 2015
Jump to file or symbol
Failed to load files and symbols.
+120 −36
Split
View
@@ -19,8 +19,8 @@ var tagEqualityTests = []struct {
{NewRelationTag("wordpress:haproxy"), RelationTag{key: "wordpress.haproxy"}},
{NewEnvironTag("deadbeef-0123-4567-89ab-feedfacebeef"), EnvironTag{uuid: "deadbeef-0123-4567-89ab-feedfacebeef"}},
{NewUserTag("admin"), UserTag{name: "admin"}},
- {NewUserTag("admin@local"), UserTag{name: "admin", provider: "local"}},
- {NewUserTag("admin@foobar"), UserTag{name: "admin", provider: "foobar"}},
+ {NewUserTag("admin@local"), UserTag{name: "admin", domain: "local"}},
+ {NewUserTag("admin@foobar"), UserTag{name: "admin", domain: "foobar"}},
{NewNetworkTag("eth0"), NetworkTag{name: "eth0"}},
{NewActionTag("01234567-aaaa-4bbb-8ccc-012345678901"), ActionTag{ID: stringToUUID("01234567-aaaa-4bbb-8ccc-012345678901")}},
}
View
91 user.go
@@ -9,78 +9,121 @@ import (
)
const (
- UserTagKind = "user"
- LocalProvider = "local"
+ 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
validUserPart = "[a-zA-Z0-9][a-zA-Z0-9.+-]*[a-zA-Z0-9]"
- validName = regexp.MustCompile(fmt.Sprintf("^(?P<name>%s)(?:@(?P<provider>%s))?$", validUserPart, validUserPart))
+ validName = regexp.MustCompile(fmt.Sprintf("^(?P<name>%s)(?:@(?P<domain>%s))?$", validUserPart, validUserPart))
validUserName = regexp.MustCompile("^" + validUserPart + "$")
)
// IsValidUser returns whether id is a valid user id.
-func IsValidUser(name string) bool {
- return validName.MatchString(name)
+// 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 user's name is a valid.
+// 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)
}
-// UserTag represents a user that may be stored in the local database, or provided
-// through some remote identity provider.
+// 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
- provider string
+ name string
+ domain string
}
func (t UserTag) Kind() string { return UserTagKind }
func (t UserTag) String() string { return UserTagKind + "-" + t.Id() }
+// Id implements Tag.Id. It always returns the same id that it was
+// created with, so NewUserTag(x).Id() == x for all valid users x. This
+// means that local users might or might not have an @local domain in
+// their id.
func (t UserTag) Id() string {
- if t.provider == "" {
+ if t.domain == "" {
return t.name
}
- return t.name + "@" + t.provider
+ return t.name + "@" + t.domain
}
-func (t UserTag) Username() string { return t.name + "@" + t.Provider() }
-func (t UserTag) Name() string { return t.name }
+// Name returns the name part of the user name
+// without its associated domain.
+func (t UserTag) Name() string { return t.name }
+
+// Canonical returns the user name and its domain in canonical form.
+// Specifically, user tags in the local domain will always return an
+// @local prefix, regardless of the id the user was created with. This
+// is the only difference from the Id method.
+func (t UserTag) Canonical() string {
+ return t.name + "@" + t.Domain()
+}
// IsLocal returns true if the tag represents a local user.
func (t UserTag) IsLocal() bool {
- return t.Provider() == LocalProvider
+ return t.Domain() == LocalUserDomain
}
-// Provider returns the name of the user provider. Users in the local database
-// are from the LocalProvider. Other users are considered 'remote' users.
-func (t UserTag) Provider() string {
- if t.provider == "" {
- return LocalProvider
+// 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 {
+ if t.domain == "" {
+ return LocalUserDomain
+ }
+ 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,
}
- return t.provider
}
// 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))
}
- return UserTag{name: parts[1], provider: parts[2]}
+ return UserTag{name: parts[1], domain: parts[2]}
}
// 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, provider: LocalProvider}
+ return UserTag{name: name, domain: LocalUserDomain}
}
-// ParseUserTag parser a user tag string.
+// ParseUserTag parses a user tag string.
func ParseUserTag(tag string) (UserTag, error) {
t, err := ParseTag(tag)
if err != nil {
View
@@ -4,6 +4,8 @@
package names_test
import (
+ "fmt"
+
gc "gopkg.in/check.v1"
"github.com/juju/names"
@@ -18,26 +20,26 @@ func (s *userSuite) TestUserTag(c *gc.C) {
input string
string string
name string
- provider string
+ domain string
username string
}{
{
input: "bob",
string: "user-bob",
name: "bob",
- provider: names.LocalProvider,
+ domain: names.LocalUserDomain,
username: "bob@local",
}, {
input: "bob@local",
string: "user-bob@local",
name: "bob",
- provider: names.LocalProvider,
+ domain: names.LocalUserDomain,
username: "bob@local",
}, {
input: "bob@foo",
string: "user-bob@foo",
name: "bob",
- provider: "foo",
+ domain: "foo",
username: "bob@foo",
},
} {
@@ -46,9 +48,47 @@ func (s *userSuite) TestUserTag(c *gc.C) {
c.Check(userTag.String(), gc.Equals, t.string)
c.Check(userTag.Id(), gc.Equals, t.input)
c.Check(userTag.Name(), gc.Equals, t.name)
- c.Check(userTag.Provider(), gc.Equals, t.provider)
- c.Check(userTag.IsLocal(), gc.Equals, t.provider == names.LocalProvider)
- c.Check(userTag.Username(), gc.Equals, t.username)
+ c.Check(userTag.Domain(), gc.Equals, t.domain)
+ c.Check(userTag.IsLocal(), gc.Equals, t.domain == names.LocalUserDomain)
+ c.Check(userTag.Canonical(), gc.Equals, t.username)
+ }
+}
+
+var withDomainTests = []struct {
+ id string
+ domain string
+ expectId string
+}{{
+ id: "bob",
+ domain: names.LocalUserDomain,
+ expectId: "bob@local",
+}, {
+ id: "bob@local",
+ domain: "foo",
+ expectId: "bob@foo",
+}, {
+ id: "bob@local",
+ domain: "",
+}, {
+ id: "bob@foo",
+ domain: names.LocalUserDomain,
+ expectId: "bob@local",
+}, {
+ id: "bob",
+ domain: "@foo",
+}}
+
+func (s *userSuite) TestWithDomain(c *gc.C) {
+ for i, test := range withDomainTests {
+ c.Logf("test %d: id %q; domain %q", i, test.id, test.domain)
+ tag := names.NewUserTag(test.id)
+ if test.expectId == "" {
+ c.Assert(func() {
+ tag.WithDomain(test.domain)
+ }, gc.PanicMatches, fmt.Sprintf("invalid user domain %q", test.domain))
+ } else {
+ c.Assert(tag.WithDomain(test.domain).Id(), gc.Equals, test.expectId)
+ }
}
}
@@ -99,7 +139,7 @@ func (s *userSuite) TestIsValidUser(c *gc.C) {
}
}
-func (s *userSuite) TestIsValidUserName(c *gc.C) {
+func (s *userSuite) TestIsValidUserNameOrDomain(c *gc.C) {
for i, t := range []struct {
string string
expect bool
@@ -145,6 +185,7 @@ func (s *userSuite) TestIsValidUserName(c *gc.C) {
} {
c.Logf("test %d: %s", i, t.string)
c.Assert(names.IsValidUserName(t.string), gc.Equals, t.expect, gc.Commentf("%s", t.string))
+ c.Assert(names.IsValidUserDomain(t.string), gc.Equals, t.expect, gc.Commentf("%s", t.string))
}
}
@@ -188,9 +229,9 @@ func (s *userSuite) TestParseUserTag(c *gc.C) {
func (s *userSuite) TestNewLocalUserTag(c *gc.C) {
user := names.NewLocalUserTag("bob")
- c.Assert(user.Username(), gc.Equals, "bob@local")
+ c.Assert(user.Canonical(), gc.Equals, "bob@local")
c.Assert(user.Name(), gc.Equals, "bob")
- c.Assert(user.Provider(), gc.Equals, "local")
+ c.Assert(user.Domain(), gc.Equals, "local")
c.Assert(user.IsLocal(), gc.Equals, true)
c.Assert(user.String(), gc.Equals, "user-bob@local")