diff --git a/actions.go b/actions.go index 484a267a9f027a935eab328fe66f6cfb7dff57a9..28042d2005cd4b8ac31e29f4f3fceef686111c92 100644 --- a/actions.go +++ b/actions.go @@ -39,11 +39,12 @@ type Backend interface { type TX interface { Commit(context.Context) error + GetResource(context.Context, ResourceID) (*Resource, error) + UpdateResource(context.Context, *Resource) error + SetResourcePassword(context.Context, *Resource, string) error + GetUser(context.Context, string) (*User, error) - GetResource(context.Context, string, string) (*Resource, error) - UpdateResource(context.Context, string, *Resource) error SetUserPassword(context.Context, *User, string) error - SetResourcePassword(context.Context, string, *Resource, string) error GetUserEncryptionKeys(context.Context, *User) ([]*UserEncryptionKey, error) SetUserEncryptionKeys(context.Context, *User, []*UserEncryptionKey) error SetUserEncryptionPublicKey(context.Context, *User, []byte) error @@ -194,8 +195,8 @@ func (s *AccountService) GetUser(ctx context.Context, tx TX, req *GetUserRequest // setResourceStatus sets the status of a single resource (shared // logic between enable / disable resource methods). -func (s *AccountService) setResourceStatus(ctx context.Context, tx TX, username, resourceID, status string) error { - r, err := tx.GetResource(ctx, username, resourceID) +func (s *AccountService) setResourceStatus(ctx context.Context, tx TX, username string, resourceID ResourceID, status string) error { + r, err := tx.GetResource(ctx, resourceID) if err != nil { return newBackendError(err) } @@ -203,7 +204,7 @@ func (s *AccountService) setResourceStatus(ctx context.Context, tx TX, username, return ErrResourceNotFound } r.Status = status - if err := tx.UpdateResource(ctx, username, r); err != nil { + if err := tx.UpdateResource(ctx, r); err != nil { return newBackendError(err) } return nil @@ -211,7 +212,7 @@ func (s *AccountService) setResourceStatus(ctx context.Context, tx TX, username, type DisableResourceRequest struct { RequestBase - ResourceID string `json:"resource_id"` + ResourceID ResourceID `json:"resource_id"` } // DisableResource disables a resource belonging to the user. @@ -224,7 +225,7 @@ func (s *AccountService) DisableResource(ctx context.Context, tx TX, req *Disabl type EnableResourceRequest struct { RequestBase - ResourceID string `json:"resource_id"` + ResourceID ResourceID `json:"resource_id"` } // EnableResource enables a resource belonging to the user. @@ -275,7 +276,7 @@ func (s *AccountService) ChangeUserPassword(ctx context.Context, tx TX, req *Cha return newBackendError(err) } for _, r := range user.GetResourcesByType(ResourceTypeEmail) { - if err := tx.SetResourcePassword(ctx, user.Name, r, encPass); err != nil { + if err := tx.SetResourcePassword(ctx, r, encPass); err != nil { return newBackendError(err) } } @@ -466,8 +467,8 @@ func (s *AccountService) DeleteApplicationSpecificPassword(ctx context.Context, type ChangeResourcePasswordRequest struct { RequestBase - ResourceID string `json:"resource_id"` - Password string `json:"password"` + ResourceID ResourceID `json:"resource_id"` + Password string `json:"password"` } func (r *ChangeResourcePasswordRequest) Validate() error { @@ -490,7 +491,7 @@ func (s *AccountService) ChangeResourcePassword(ctx context.Context, tx TX, req return newRequestError(err) } - r, err := tx.GetResource(ctx, req.Username, req.ResourceID) + r, err := tx.GetResource(ctx, req.ResourceID) if err != nil { return newBackendError(err) } @@ -499,7 +500,7 @@ func (s *AccountService) ChangeResourcePassword(ctx context.Context, tx TX, req } encPass := pwhash.Encrypt(req.Password) - if err := tx.SetResourcePassword(ctx, req.Username, r, encPass); err != nil { + if err := tx.SetResourcePassword(ctx, r, encPass); err != nil { return newBackendError(err) } return nil @@ -507,8 +508,8 @@ func (s *AccountService) ChangeResourcePassword(ctx context.Context, tx TX, req type MoveResourceRequest struct { RequestBase - ResourceID string `json:"resource_id"` - Shard string `json:"shard"` + ResourceID ResourceID `json:"resource_id"` + Shard string `json:"shard"` } type MoveResourceResponse struct { @@ -526,7 +527,7 @@ func (s *AccountService) MoveResource(ctx context.Context, tx TX, req *MoveResou } // Collect all related resources, as they should all be moved at once. - r, err := tx.GetResource(ctx, req.Username, req.ResourceID) + r, err := tx.GetResource(ctx, req.ResourceID) if err != nil { return nil, err } @@ -540,7 +541,7 @@ func (s *AccountService) MoveResource(ctx context.Context, tx TX, req *MoveResou var resp MoveResourceResponse for _, r := range resources { r.Shard = req.Shard - if err := tx.UpdateResource(ctx, req.Username, r); err != nil { + if err := tx.UpdateResource(ctx, r); err != nil { return nil, err } resp.MovedIDs = append(resp.MovedIDs, r.ID.String()) diff --git a/actions_test.go b/actions_test.go index a503a80c65ac125a3bbced063423e9cd111e5c98..0a9f7cf618ab566ae2d784acd69ce96cf2f6b14f 100644 --- a/actions_test.go +++ b/actions_test.go @@ -27,11 +27,11 @@ func (b *fakeBackend) GetUser(_ context.Context, username string) (*User, error) return b.users[username], nil } -func (b *fakeBackend) GetResource(_ context.Context, username, resourceID string) (*Resource, error) { - return b.resources[username][resourceID], nil +func (b *fakeBackend) GetResource(_ context.Context, resourceID ResourceID) (*Resource, error) { + return b.resources[resourceID.User()][resourceID.String()], nil } -func (b *fakeBackend) UpdateResource(_ context.Context, username string, r *Resource) error { +func (b *fakeBackend) UpdateResource(_ context.Context, r *Resource) error { return nil } @@ -40,7 +40,7 @@ func (b *fakeBackend) SetUserPassword(_ context.Context, user *User, password st return nil } -func (b *fakeBackend) SetResourcePassword(_ context.Context, username string, r *Resource, password string) error { +func (b *fakeBackend) SetResourcePassword(_ context.Context, r *Resource, password string) error { return nil } @@ -84,7 +84,7 @@ type fakeValidator struct { adminUser string } -func (v *fakeValidator) Validate(tkt string, nonce string, service string, _ []string) (*sso.Ticket, error) { +func (v *fakeValidator) Validate(tkt, nonce, service string, _ []string) (*sso.Ticket, error) { // The sso ticket username is just the ticket itself. var groups []string if tkt == v.adminUser { diff --git a/backend/model.go b/backend/model.go index bb1ea8b45287429e4e3be3b03070754c8d935fe8..32f4b1884c39141d4c14f6a05416402e368d4d66 100644 --- a/backend/model.go +++ b/backend/model.go @@ -337,7 +337,7 @@ func (tx *backendTX) DeleteUserTOTPSecret(ctx context.Context, user *accountserv return nil } -func (tx *backendTX) SetResourcePassword(ctx context.Context, username string, r *accountserver.Resource, encryptedPassword string) error { +func (tx *backendTX) SetResourcePassword(ctx context.Context, r *accountserver.Resource, encryptedPassword string) error { dn, _ := tx.backend.resources.GetDN(r.ID) tx.setAttr(dn, "userPassword", encryptedPassword) return nil @@ -378,12 +378,7 @@ func (tx *backendTX) HasAnyResource(ctx context.Context, resourceIDs []accountse } // GetResource returns a ResourceWrapper, as part of a read-modify-update transaction. -func (tx *backendTX) GetResource(ctx context.Context, username, resourceID string) (*accountserver.Resource, error) { - rsrcID, err := accountserver.ParseResourceID(resourceID) - if err != nil { - return nil, err - } - +func (tx *backendTX) GetResource(ctx context.Context, rsrcID accountserver.ResourceID) (*accountserver.Resource, error) { // From the resource ID we can obtain the DN, and fetch it // straight from LDAP without even doing a real search. dn, err := tx.backend.resources.GetDN(rsrcID) @@ -417,7 +412,7 @@ func (tx *backendTX) GetResource(ctx context.Context, username, resourceID strin } // UpdateResource updates a LDAP-backed resource that was obtained by a previous GetResource call. -func (tx *backendTX) UpdateResource(ctx context.Context, username string, r *accountserver.Resource) error { +func (tx *backendTX) UpdateResource(ctx context.Context, r *accountserver.Resource) error { dn, err := tx.backend.resources.GetDN(r.ID) if err != nil { return err @@ -464,7 +459,7 @@ func groupWebResourcesByHomedir(resources []*accountserver.Resource) { webs := make(map[string]*accountserver.Resource) for _, r := range resources { switch r.ID.Type() { - case accountserver.ResourceTypeWebsite: + case accountserver.ResourceTypeWebsite, accountserver.ResourceTypeDomain: r.Group = getHostingDir(r.Website.DocumentRoot) webs[r.ID.String()] = r case accountserver.ResourceTypeDAV: diff --git a/backend/model_test.go b/backend/model_test.go index cedc36e7c040430d731ff19ee311a0577e83ccdc..46f55fef45012dedbea1d3d07038b7f69d1e5051 100644 --- a/backend/model_test.go +++ b/backend/model_test.go @@ -2,7 +2,6 @@ package backend import ( "context" - "fmt" "testing" "git.autistici.org/ai3/accountserver" @@ -15,13 +14,12 @@ const ( testUser1 = "uno@investici.org" ) -func TestModel_GetUser(t *testing.T) { +func startServerAndGetUser(t testing.TB) (func(), accountserver.Backend, *accountserver.User) { stop := startTestLDAPServer(t, &testLDAPServerConfig{ Port: testLDAPPort, Base: "dc=example,dc=com", LDIFs: []string{"testdata/base.ldif", "testdata/test1.ldif"}, }) - defer stop() b, err := NewLDAPBackend(testLDAPAddr, "cn=manager,dc=example,dc=com", "password", "dc=example,dc=com") if err != nil { @@ -37,13 +35,18 @@ func TestModel_GetUser(t *testing.T) { t.Fatalf("could not find test user %s", testUser1) } - //t.Logf("%+v", user) + return stop, b, user +} + +func TestModel_GetUser(t *testing.T) { + stop, _, user := startServerAndGetUser(t) + defer stop() if user.Name != testUser1 { t.Fatalf("bad username: expected %s, got %s", testUser1, user.Name) } - if len(user.Resources) != 4 { - t.Fatalf("expected 4 resources, got %d", len(user.Resources)) + if len(user.Resources) != 5 { + t.Fatalf("expected 5 resources, got %d", len(user.Resources)) } // Test a specific resource (the database). @@ -75,6 +78,59 @@ func TestModel_GetUser(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() + + // Fetch individually all user resources, one by one, and + // check that they match what we have already. + tx2, _ := b.NewTransaction() + for _, r := range user.Resources { + fr, err := tx2.GetResource(context.Background(), r.ID) + if err != nil { + t.Errorf("could not fetch resource %s: %v", r.ID, err) + continue + } + if fr == nil { + t.Errorf("resource %s is missing", r.ID) + continue + } + // It's ok if Group is unset in the GetResource response. + rr := *r + rr.Group = "" + if err := deep.Equal(fr, &rr); err != nil { + t.Errorf("error in fetched resource %s: %v", r.ID, err) + continue + } + } +} + func TestModel_SetResourceStatus(t *testing.T) { stop := startTestLDAPServer(t, &testLDAPServerConfig{ Port: testLDAPPort, @@ -89,8 +145,8 @@ func TestModel_SetResourceStatus(t *testing.T) { } tx, _ := b.NewTransaction() - rsrcID := fmt.Sprintf("email/%s/%s", testUser1, testUser1) - r, err := tx.GetResource(context.Background(), testUser1, rsrcID) + rsrcID := accountserver.NewResourceID(accountserver.ResourceTypeEmail, testUser1, testUser1) + r, err := tx.GetResource(context.Background(), rsrcID) if err != nil { t.Fatal("GetResource", err) } @@ -98,10 +154,8 @@ func TestModel_SetResourceStatus(t *testing.T) { t.Fatalf("could not find test resource %s", rsrcID) } - //t.Logf("%+v", r) - r.Status = accountserver.ResourceStatusInactive - if err := tx.UpdateResource(context.Background(), "uno@investici.org", r); err != nil { + if err := tx.UpdateResource(context.Background(), r); err != nil { t.Fatal("UpdateResource", err) } if err := tx.Commit(context.Background()); err != nil { diff --git a/backend/resources.go b/backend/resources.go index ea48de46ef3f7b2d1d737637f0036ba4524bfbb3..3da6cea0a949de7bf4c8ba6dc78c3a3d2b92cc50 100644 --- a/backend/resources.go +++ b/backend/resources.go @@ -368,7 +368,7 @@ func (h *webdavResourceHandler) GetDN(id accountserver.ResourceID) (string, erro return "", errors.New("unqualified resource id") } - dn := replaceVars("cn=${resource},uid=${user},ou=People", map[string]string{ + dn := replaceVars("ftpname=${resource},uid=${user},ou=People", map[string]string{ "user": id.User(), "resource": id.Name(), }) @@ -425,7 +425,7 @@ type databaseResourceHandler struct { baseDN string } -func makeDatabaseResourceID(dn string) (rsrcID accountserver.ResourceID, parentID accountserver.ResourceID, err error) { +func makeDatabaseResourceID(dn string) (rsrcID, parentID accountserver.ResourceID, err error) { parsed, perr := ldap.ParseDN(dn) if perr != nil { err = perr diff --git a/backend/testdata/test1.ldif b/backend/testdata/test1.ldif index 308e52447a670a0d6770d23fa9ecfe4cb49e0b50..9bf6880683b247b165f7b48ccd62866470c24af8 100644 --- a/backend/testdata/test1.ldif +++ b/backend/testdata/test1.ldif @@ -82,6 +82,18 @@ creationDate: 01-08-2013 originalHost: host2 statsId: 2191 +dn: cn=example.com,uid=uno@investici.org,ou=People,dc=example,dc=com +status: active +acceptMail: true +objectClass: top +objectClass: virtualHost +cn: example.com +host: host2 +documentRoot: /home/users/investici.org/uno/html-example.com +creationDate: 02-08-2013 +originalHost: host2 +statsId: 2192 + dn: dbname=unodb,alias=uno,uid=uno@investici.org,ou=People,dc=example,dc=com status: active clearPassword: password