diff --git a/integrationtest/errors_test.go b/integrationtest/errors_test.go
index 3d43f09580025e2a915758a3b4486791e73b00df..e485805ea7d21252977073cd4afb9234281a6274 100644
--- a/integrationtest/errors_test.go
+++ b/integrationtest/errors_test.go
@@ -54,6 +54,51 @@ func TestIntegration_Error_GetUser(t *testing.T) {
 
 }
 
+func TestIntegration_Error_GetResource(t *testing.T) {
+	stop, _, c := startService(t)
+	defer stop()
+
+	testdata := []struct {
+		authUser        string
+		authGroup       string
+		resourceID      string
+		expectedErrCode int
+	}{
+		{"uno@investici.org", "", "cn=example.com,uid=uno@investici.org,ou=People,dc=example,dc=com", http.StatusForbidden},
+		{testAdminUser, testAdminGroup, "cn=not-existing.com,uid=uno@investici.org,ou=People,dc=example,dc=com", http.StatusNotFound},
+		{testAdminUser, testAdminGroup, "badly,formatted,DN", http.StatusInternalServerError},
+	}
+
+	for _, td := range testdata {
+		var groups []string
+		if td.authGroup != "" {
+			groups = append(groups, td.authGroup)
+		}
+		err := c.request("/api/resource/get", &as.GetResourceRequest{
+			AdminResourceRequestBase: as.AdminResourceRequestBase{
+				ResourceRequestBase: as.ResourceRequestBase{
+					RequestBase: as.RequestBase{
+						SSO: c.ssoTicket(td.authUser, groups...),
+					},
+					ResourceID: as.ResourceID(td.resourceID),
+				},
+			},
+		}, nil)
+		if err == nil {
+			t.Errorf("oops, request as %s succeeded unexpectedly", td.authUser)
+			continue
+		}
+		var httpErr *httpError
+		if !errors.As(err, &httpErr) {
+			t.Errorf("oops, request as %s returned unexpected error: %v", td.authUser, err)
+			continue
+		}
+		if httpErr.code != td.expectedErrCode {
+			t.Errorf("request as %s returned error code %d, expected %d", td.authUser, httpErr.code, td.expectedErrCode)
+		}
+	}
+}
+
 func TestIntegration_Error_Validation(t *testing.T) {
 	stop, _, c := startService(t)
 	defer stop()
diff --git a/integrationtest/resources_test.go b/integrationtest/resources_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..d70876e809274667f5f7e9d6105f97aed2bcd6e3
--- /dev/null
+++ b/integrationtest/resources_test.go
@@ -0,0 +1,171 @@
+package integrationtest
+
+import (
+	"encoding/json"
+	"errors"
+	"testing"
+
+	as "git.autistici.org/ai3/accountserver"
+)
+
+func TestIntegration_GetResource_Auth(t *testing.T) {
+	stop, _, c := startService(t)
+	defer stop()
+
+	rid := "cn=example.com,uid=uno@investici.org,ou=People,dc=example,dc=com"
+	testdata := []struct {
+		authUser   string
+		authGroup  string
+		resourceID string
+		expectedOk bool
+	}{
+		{"uno@investici.org", "users", rid, false},
+		{testAdminUser, testAdminGroup, rid, true},
+	}
+
+	for _, td := range testdata {
+		var groups []string
+		if td.authGroup != "" {
+			groups = append(groups, td.authGroup)
+		}
+
+		err := c.request("/api/resource/get", &as.GetResourceRequest{
+			AdminResourceRequestBase: as.AdminResourceRequestBase{
+				ResourceRequestBase: as.ResourceRequestBase{
+					RequestBase: as.RequestBase{
+						SSO: c.ssoTicket(td.authUser, groups...),
+					},
+					ResourceID: as.ResourceID(td.resourceID),
+				},
+			},
+		}, nil)
+		if td.expectedOk && err != nil {
+			t.Errorf("access error for user %s: expected ok, got error: %v", td.authUser, err)
+		} else if !td.expectedOk && err == nil {
+			t.Errorf("access error for user %s: expected error, got ok", td.authUser)
+		}
+	}
+}
+
+func TestIntegration_SetResourceStatus(t *testing.T) {
+	stop, _, c := startService(t)
+	defer stop()
+
+	rid := "cn=example.com,uid=uno@investici.org,ou=People,dc=example,dc=com"
+	err := c.request("/api/resource/set_status", &as.SetResourceStatusRequest{
+		AdminResourceRequestBase: as.AdminResourceRequestBase{
+			ResourceRequestBase: as.ResourceRequestBase{
+				RequestBase: as.RequestBase{
+					SSO: c.ssoTicket(testAdminUser, testAdminGroup),
+				},
+				ResourceID: as.ResourceID(rid),
+			},
+		},
+		Status: as.ResourceStatusInactive,
+	}, nil)
+	if err != nil {
+		t.Fatalf("SetResourceStatus(): %v", err)
+	}
+}
+
+func TestIntegration_AdminUpdateResource(t *testing.T) {
+	stop, _, c := startService(t)
+	defer stop()
+
+	rid := "cn=example.com,uid=uno@investici.org,ou=People,dc=example,dc=com"
+
+	getResource := func() *as.Resource {
+		var rsrc as.GetResourceResponse
+		err := c.request("/api/resource/get", &as.GetResourceRequest{
+			AdminResourceRequestBase: as.AdminResourceRequestBase{
+				ResourceRequestBase: as.ResourceRequestBase{
+					RequestBase: as.RequestBase{
+						SSO: c.ssoTicket(testAdminUser, testAdminGroup),
+					},
+					ResourceID: as.ResourceID(rid),
+				},
+			},
+		}, &rsrc)
+		if err != nil {
+			t.Fatalf("GetResource(): %v", err)
+		}
+		return rsrc.Resource
+	}
+
+	rsrc := getResource()
+	if rsrc.Website == nil {
+		t.Fatalf("resource has empty Website object: %+v", *rsrc)
+	}
+	rsrc.Website.AcceptMail = false
+
+	err := c.request("/api/resource/update", &as.AdminUpdateResourceRequest{
+		AdminResourceRequestBase: as.AdminResourceRequestBase{
+			ResourceRequestBase: as.ResourceRequestBase{
+				RequestBase: as.RequestBase{
+					SSO: c.ssoTicket(testAdminUser, testAdminGroup),
+				},
+				ResourceID: as.ResourceID(rid),
+			},
+		},
+		Resource: rsrc,
+	}, nil)
+	if err != nil {
+		t.Fatalf("AdminUpdateResource(): %v", err)
+	}
+
+	rsrc = getResource()
+	if rsrc.Website.AcceptMail {
+		t.Fatal("AdminUpdateResource() did not update the acceptMail field")
+	}
+}
+
+func TestIntegration_AdminUpdateResource_FailValidation(t *testing.T) {
+	stop, _, c := startService(t)
+	defer stop()
+
+	rid := "cn=example.com,uid=uno@investici.org,ou=People,dc=example,dc=com"
+
+	var resp as.GetResourceResponse
+	err := c.request("/api/resource/get", &as.GetResourceRequest{
+		AdminResourceRequestBase: as.AdminResourceRequestBase{
+			ResourceRequestBase: as.ResourceRequestBase{
+				RequestBase: as.RequestBase{
+					SSO: c.ssoTicket(testAdminUser, testAdminGroup),
+				},
+				ResourceID: as.ResourceID(rid),
+			},
+		},
+	}, &resp)
+	if err != nil {
+		t.Fatalf("GetResource(): %v", err)
+	}
+
+	rsrc := resp.Resource
+	rsrc.Website.UID = 42999
+
+	err = c.request("/api/resource/update", &as.AdminUpdateResourceRequest{
+		AdminResourceRequestBase: as.AdminResourceRequestBase{
+			ResourceRequestBase: as.ResourceRequestBase{
+				RequestBase: as.RequestBase{
+					SSO: c.ssoTicket(testAdminUser, testAdminGroup),
+				},
+				ResourceID: as.ResourceID(rid),
+			},
+		},
+		Resource: rsrc,
+	}, nil)
+	if err == nil {
+		t.Fatal("AdminUpdateResource() accepted faulty update")
+	}
+	var httpErr *httpError
+	if !errors.As(err, &httpErr) {
+		t.Fatalf("AdminUpdateResource() did not return a structured error: %v", err)
+	}
+	var fields map[string][]string
+	if err := json.Unmarshal(httpErr.data, &fields); err != nil {
+		t.Fatalf("error unmarshaling JSON error: %v", err)
+	}
+	if values, ok := fields["website.uid"]; !ok || len(values) != 1 {
+		t.Fatalf("error does not contain details for the 'website.uid' field: %+v", fields)
+	}
+}