Commit 11821eaf authored by ale's avatar ale
Browse files

Move resource grouping out of the backend

It makes more sense to have this directly on the User object.
parent e15acb1e
......@@ -249,6 +249,85 @@ func TestService_GetUser(t *testing.T) {
}
}
func TestService_GetUser_ResourceGroups(t *testing.T) {
fb := createFakeBackend()
tx, _ := fb.NewTransaction()
svc, _ := newAccountServiceWithSSO(fb, testConfig(), &fakeValidator{})
fb.addUser(&User{
Name: "testuser2",
Resources: []*Resource{
{
ID: NewResourceID(ResourceTypeDAV, "testuser2", "dav1"),
Name: "dav1",
DAV: &WebDAV{
Homedir: "/home/users/investici.org/dav1",
},
},
{
ID: NewResourceID(ResourceTypeDAV, "testuser2", "dav1-domain2"),
Name: "dav1-domain2",
DAV: &WebDAV{
Homedir: "/home/users/investici.org/dav1/html-domain2.com/subdir",
},
},
{
ID: NewResourceID(ResourceTypeDomain, "testuser2", "domain1.com"),
Name: "domain1.com",
Website: &Website{
DocumentRoot: "/home/users/investici.org/dav1/html-domain1.com",
},
},
{
ID: NewResourceID(ResourceTypeDomain, "testuser2", "domain2.com"),
Name: "domain2.com",
Website: &Website{
DocumentRoot: "/home/users/investici.org/dav1/html-domain2.com",
},
},
{
ID: NewResourceID(ResourceTypeDatabase, "testuser2", "cn=domain2.com", "db2"),
ParentID: NewResourceID(ResourceTypeDomain, "testuser2", "domain2.com"),
Name: "db2",
Database: &Database{},
},
},
}, "", "")
req := &GetUserRequest{
RequestBase: RequestBase{
Username: "testuser2",
SSO: "testuser2",
},
}
user, err := svc.GetUser(context.TODO(), tx, req)
if err != nil {
t.Fatal(err)
}
var grouped []*Resource
for _, r := range user.Resources {
switch r.ID.Type() {
case ResourceTypeWebsite, ResourceTypeDomain, ResourceTypeDAV, ResourceTypeDatabase:
grouped = append(grouped, r)
}
}
var group string
for _, r := range grouped {
if r.Group == "" {
t.Errorf("group not set on %s", r.ID)
continue
}
if group == "" {
group = r.Group
} else if group != r.Group {
t.Errorf("wrong group on %s (%s, expected %s)", r.ID, r.Group, group)
}
}
}
func TestService_Auth(t *testing.T) {
svc, tx := testService("adminuser")
......
......@@ -274,8 +274,6 @@ func (tx *backendTX) GetUser(ctx context.Context, username string) (*accountserv
}
}
groupWebResourcesByHomedir(user.Resources)
return user, nil
}
......@@ -538,40 +536,3 @@ func (tx *backendTX) isUIDAvailable(ctx context.Context, uid int) (bool, error)
}
return true, nil
}
var siteRoot = "/home/users/investici.org/"
// The hosting directory for a website is the path component immediately after
// siteRoot. This works also for sites with nested documentRoots.
func getHostingDir(path string) string {
path = strings.TrimPrefix(path, siteRoot)
if i := strings.Index(path, "/"); i > 0 {
return path[:i]
}
return path
}
// This is a very specific function meant to address a peculiar characteristic
// of the A/I legacy data model, where DAV accounts and websites do not have an
// explicit relation.
func groupWebResourcesByHomedir(resources []*accountserver.Resource) {
// Set the group name to be the 'hostingDir' for sites and DAV
// accounts. Keep a reference of websites by ID so we can later fix the
// group for databases too, via their ParentID.
webs := make(map[string]*accountserver.Resource)
for _, r := range resources {
switch r.ID.Type() {
case accountserver.ResourceTypeWebsite, accountserver.ResourceTypeDomain:
r.Group = getHostingDir(r.Website.DocumentRoot)
webs[r.ID.String()] = r
case accountserver.ResourceTypeDAV:
r.Group = getHostingDir(r.DAV.Homedir)
}
}
// Fix databases in a second pass.
for _, r := range resources {
if r.ID.Type() == accountserver.ResourceTypeDatabase && !r.ParentID.Empty() {
r.Group = webs[r.ParentID.String()].Group
}
}
}
......@@ -102,7 +102,6 @@ func TestModel_GetUser(t *testing.T) {
Name: "unodb",
Shard: "host2",
OriginalShard: "host2",
Group: "uno",
Status: accountserver.ResourceStatusActive,
Database: &accountserver.Database{
CleartextPassword: "password",
......@@ -126,32 +125,6 @@ func TestModel_GetUser_Has2FA(t *testing.T) {
}
}
func TestModel_GetUser_Group(t *testing.T) {
stop, _, user := startServerAndGetUser(t)
defer stop()
var grouped []*accountserver.Resource
for _, r := range user.Resources {
switch r.ID.Type() {
case accountserver.ResourceTypeWebsite, accountserver.ResourceTypeDomain, accountserver.ResourceTypeDAV, accountserver.ResourceTypeDatabase:
grouped = append(grouped, r)
}
}
var group string
for _, r := range grouped {
if r.Group == "" {
t.Errorf("group not set on %s", r.ID)
continue
}
if group == "" {
group = r.Group
} else if group != r.Group {
t.Errorf("wrong group on %s (%s, expected %s)", r.ID, r.Group, group)
}
}
}
func TestModel_GetUser_Resources(t *testing.T) {
stop, b, user := startServerAndGetUser(t)
defer stop()
......
......@@ -144,6 +144,9 @@ func (s *authService) validateSSO(ssoToken string) (*sso.Ticket, error) {
return s.validator.Validate(ssoToken, "", s.ssoService, s.ssoGroups)
}
// Fetch a user from the backend and return an error if not found
// (instead of returning a nil User). Also, set dynamic attributes
// like resource Groups.
func getUserOrDie(ctx context.Context, tx TX, username string) (*User, error) {
user, err := tx.GetUser(ctx, username)
if err != nil {
......@@ -152,6 +155,9 @@ func getUserOrDie(ctx context.Context, tx TX, username string) (*User, error) {
if user == nil {
return nil, ErrUserNotFound
}
user.groupWebResources()
return user, nil
}
......
......@@ -352,3 +352,44 @@ type Blog struct {
Name string `json:"name"`
URL string `json:"url"`
}
const hardcodedWebRoot = "/home/users/investici.org"
// Group web-related resources into groups.
//
// This is a very specific function meant to address a peculiar characteristic
// of the A/I legacy data model, where DAV accounts and websites do not have an
// explicit relation.
//
// TODO: Ideally we should be able to do this without hard-coding the webroot.
func (u *User) groupWebResources() {
// Set the group name to be the 'hostingDir' for sites and DAV
// accounts. Keep a reference of websites by ID so we can later fix the
// group for databases too, via their ParentID.
webs := make(map[string]*Resource)
for _, r := range u.Resources {
switch r.ID.Type() {
case ResourceTypeWebsite, ResourceTypeDomain:
r.Group = getHostingDir(r.Website.DocumentRoot, hardcodedWebRoot)
webs[r.ID.String()] = r
case ResourceTypeDAV:
r.Group = getHostingDir(r.DAV.Homedir, hardcodedWebRoot)
}
}
// Fix databases in a second pass.
for _, r := range u.Resources {
if r.ID.Type() == ResourceTypeDatabase && !r.ParentID.Empty() {
r.Group = webs[r.ParentID.String()].Group
}
}
}
// The hosting directory for a website is the path component immediately after
// siteRoot. This works also for sites with nested documentRoots.
func getHostingDir(path, siteRoot string) string {
path = strings.TrimPrefix(strings.TrimPrefix(path, siteRoot), "/")
if i := strings.Index(path, "/"); i > 0 {
return path[:i]
}
return path
}
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment