Commit 11821eaf authored by ale's avatar ale

Move resource grouping out of the backend

It makes more sense to have this directly on the User object.
parent e15acb1e
Pipeline #1029 passed with stages
in 1 minute and 27 seconds
......@@ -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
}
Markdown is supported
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