snapshot of https://github.com/cortezaproject/corteza.git (2024.9.x, 2026-09-18; blobs > 131072B omitted)
Some checks failed
SonarQube / SonarQube (push) Failing after 5m28s
E2e test on main branch push / server and client test (map[name:admin port:8080]) (push) Failing after 3m33s
Server and lib tests / Library test (js) (push) Failing after 1m36s
Server and lib tests / Library test (vue) (push) Successful in 2m24s
Server and lib tests / Web client test (admin) (push) Successful in 2m52s
Server and lib tests / Web client test (compose) (push) Successful in 3m23s
Server and lib tests / Web client test (discovery) (push) Successful in 2m49s
Server and lib tests / Web client test (one) (push) Successful in 2m52s
Server and lib tests / Web client test (privacy) (push) Successful in 2m44s
Server and lib tests / Web client test (reporter) (push) Successful in 2m56s
Server and lib tests / Web client test (workflow) (push) Successful in 3m0s
Server and lib tests / Server tests (push) Failing after 4m55s
E2e test on main branch push / notify-failure (push) Failing after 9s
Server and lib tests / Send matrix testing notification (push) Successful in 2s
Mark stale issues and pull requests / stale (push) Successful in 40s

This commit is contained in:
upstream-mirror
2026-09-18 01:22:23 +05:00
commit 7e385fc126
8391 changed files with 1766236 additions and 0 deletions

View File

@@ -0,0 +1,67 @@
package system
import (
"context"
"net/http"
"testing"
"time"
"github.com/cortezaproject/corteza/server/pkg/actionlog"
"github.com/cortezaproject/corteza/server/pkg/id"
"github.com/cortezaproject/corteza/server/store"
"github.com/cortezaproject/corteza/server/system/service"
"github.com/cortezaproject/corteza/server/system/types"
"github.com/cortezaproject/corteza/server/tests/helpers"
jsonpath "github.com/steinfletcher/apitest-jsonpath"
)
func (h helper) clearActionLog() {
h.noError(store.TruncateActionlogs(context.Background(), service.DefaultStore))
}
func (h helper) repoMakeActionLog() *actionlog.Action {
var res = &actionlog.Action{
ID: id.Next(),
Timestamp: time.Now(),
ActorID: id.Next(),
Resource: types.ComponentRbacResource(),
Action: "lookup",
}
h.a.NoError(store.CreateActionlog(context.Background(), service.DefaultStore, res))
return res
}
func TestActionLogList(t *testing.T) {
h := newHelper(t)
h.clearActionLog()
helpers.AllowMe(h, types.ComponentRbacResource(), "action-log.read")
h.repoMakeActionLog()
h.apiInit().
Get("/actionlog/").
Header("Accept", "application/json").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
Assert(jsonpath.Len(`$.response.set`, 1)).
End()
}
func TestActionLogListForbidden(t *testing.T) {
h := newHelper(t)
h.clearActionLog()
h.repoMakeActionLog()
h.apiInit().
Get("/actionlog/").
Header("Accept", "application/json").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertError("access_denied")).
End()
}

View File

@@ -0,0 +1,678 @@
package system
import (
"context"
"fmt"
"net/http"
"strconv"
"testing"
"time"
"github.com/cortezaproject/corteza/server/pkg/id"
"github.com/cortezaproject/corteza/server/store"
"github.com/cortezaproject/corteza/server/system/service"
"github.com/cortezaproject/corteza/server/system/types"
"github.com/cortezaproject/corteza/server/tests/helpers"
jsonpath "github.com/steinfletcher/apitest-jsonpath"
)
func (h helper) createRouteWithFilter(s string, fkind string) (*types.ApigwRoute, *types.ApigwFilter) {
return h.createRouteAndFilterWithEnabled(s, fkind, true, true)
}
func (h helper) createRouteWithFilterEnabled(s string, fkind string, enable bool) (*types.ApigwRoute, *types.ApigwFilter) {
return h.createRouteAndFilterWithEnabled(s, fkind, true, enable)
}
func (h helper) createRouteAndFilterWithEnabled(s string, fkind string, rEnable, fEnable bool) (*types.ApigwRoute, *types.ApigwFilter) {
r := h.createRoute(&types.ApigwRoute{Endpoint: "/" + s, Method: "GET", Enabled: rEnable})
f := h.createFilters(&types.ApigwFilter{Kind: fkind, Enabled: fEnable}, r.ID)
return r, f
}
func (h helper) createRoute(route *types.ApigwRoute) *types.ApigwRoute {
if route.ID == 0 {
route.ID = id.Next()
}
if route.CreatedAt.IsZero() {
route.CreatedAt = time.Now()
}
h.a.NoError(service.DefaultStore.CreateApigwRoute(context.Background(), route))
return route
}
func (h helper) createFilters(f *types.ApigwFilter, routeID uint64) *types.ApigwFilter {
f.Route = routeID
if f.ID == 0 {
f.ID = id.Next()
}
if f.CreatedAt.IsZero() {
f.CreatedAt = time.Now()
}
h.a.NoError(service.DefaultStore.CreateApigwFilter(context.Background(), f))
return f
}
func (h helper) clearRoutes() {
h.noError(store.TruncateApigwFilters(context.Background(), service.DefaultStore))
h.noError(store.TruncateApigwRoutes(context.Background(), service.DefaultStore))
}
func TestApigwRouteRead(t *testing.T) {
h := newHelper(t)
h.clearRoutes()
r, _ := h.createRouteWithFilter("test", "")
helpers.AllowMe(h, types.ApigwRouteRbacResource(r.ID), "read")
h.apiInit().
Get(fmt.Sprintf("/apigw/route/%d", r.ID)).
Header("Accept", "application/json").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
Assert(jsonpath.Equal(`$.response.routeID`, strconv.FormatUint(r.ID, 10))).
Assert(jsonpath.Equal(`$.response.endpoint`, "/test")).
End()
}
func TestApigwRouteRead_forbidden(t *testing.T) {
h := newHelper(t)
h.clearRoutes()
r, _ := h.createRouteWithFilter("test", "")
h.apiInit().
Get(fmt.Sprintf("/apigw/route/%d", r.ID)).
Header("Accept", "application/json").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertError("apigw-route.errors.notAllowedToRead")).
End()
}
func TestApigwRouteSearch(t *testing.T) {
h := newHelper(t)
h.clearRoutes()
h.createRouteWithFilter("test1", "")
h.createRouteWithFilter("test2", "")
helpers.AllowMe(h, types.ComponentRbacResource(), "apigw-routes.search")
helpers.AllowMe(h, types.ApigwRouteRbacResource(0), "read")
h.apiInit().
Get(fmt.Sprintf("/apigw/route/")).
Header("Accept", "application/json").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
Assert(jsonpath.Len(`$.response.set`, 2)).
Assert(jsonpath.Equal(`$.response.set[0].endpoint`, "/test1")).
Assert(jsonpath.Equal(`$.response.set[1].endpoint`, "/test2")).
End()
}
func TestApigwRouteSearch_includeDisabled(t *testing.T) {
h := newHelper(t)
h.clearRoutes()
h.createRouteWithFilter("test1", "")
h.createRouteWithFilter("test2", "")
h.createRouteAndFilterWithEnabled("test3", "", false, true)
h.createRouteAndFilterWithEnabled("test4", "", false, false)
helpers.AllowMe(h, types.ComponentRbacResource(), "apigw-routes.search")
helpers.AllowMe(h, types.ApigwRouteRbacResource(0), "read")
h.apiInit().
Get(fmt.Sprintf("/apigw/route/")).
Query("disabled", "1").
Header("Accept", "application/json").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
Assert(jsonpath.Len(`$.response.set`, 4)).
Assert(jsonpath.Equal(`$.response.set[0].endpoint`, "/test1")).
Assert(jsonpath.Equal(`$.response.set[1].endpoint`, "/test2")).
End()
}
func TestApigwRouteSearch_forbidden(t *testing.T) {
h := newHelper(t)
h.clearRoutes()
h.createRouteWithFilter("test1", "")
h.createRouteWithFilter("test2", "")
h.apiInit().
Get(fmt.Sprintf("/apigw/route/")).
Header("Accept", "application/json").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertError("apigw-route.errors.notAllowedToSearch")).
End()
}
func TestApigwRouteSearch_forbiddenSpecific(t *testing.T) {
h := newHelper(t)
h.clearRoutes()
r, _ := h.createRouteWithFilter("test1", "")
h.createRouteWithFilter("test2", "")
helpers.AllowMe(h, types.ComponentRbacResource(), "apigw-routes.search")
helpers.AllowMe(h, types.ApigwRouteRbacResource(0), "read")
helpers.DenyMe(h, types.ApigwRouteRbacResource(r.ID), "read")
h.apiInit().
Get(fmt.Sprintf("/apigw/route/")).
Header("Accept", "application/json").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
Assert(jsonpath.Len(`$.response.set`, 1)).
Assert(jsonpath.Equal(`$.response.set[0].endpoint`, "/test2")).
End()
}
func TestApigwRouteCreate(t *testing.T) {
h := newHelper(t)
h.clearRoutes()
helpers.AllowMe(h, types.ComponentRbacResource(), "apigw-route.create")
h.apiInit().
Post(fmt.Sprintf("/apigw/route")).
Header("Accept", "application/json").
FormData("endpoint", "/test").
FormData("method", "GET").
FormData("enabled", "false").
FormData("group", "g1").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
Assert(jsonpath.Present(`$.response.routeID`)).
Assert(jsonpath.Equal(`$.response.endpoint`, "/test")).
Assert(jsonpath.Equal(`$.response.enabled`, false)).
End()
}
func TestApigwRouteCreate_forbidden(t *testing.T) {
h := newHelper(t)
h.clearRoutes()
h.apiInit().
Post(fmt.Sprintf("/apigw/route")).
Header("Accept", "application/json").
FormData("endpoint", "/test").
FormData("method", "GET").
FormData("enabled", "false").
FormData("group", "g1").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertError("apigw-route.errors.notAllowedToCreate")).
End()
}
func TestApigwRouteUpdate(t *testing.T) {
h := newHelper(t)
h.clearRoutes()
r, _ := h.createRouteWithFilter("test", "")
helpers.AllowMe(h, types.ApigwRouteRbacResource(r.ID), "update")
h.apiInit().
Put(fmt.Sprintf("/apigw/route/%d", r.ID)).
Header("Accept", "application/json").
FormData("endpoint", "/test-edited").
FormData("enabled", "false").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
Assert(jsonpath.Present(`$.response.routeID`)).
Assert(jsonpath.Equal(`$.response.endpoint`, "/test-edited")).
Assert(jsonpath.Equal(`$.response.enabled`, false)).
Assert(jsonpath.NotPresent(`$.response.deletedBy`)).
Assert(jsonpath.NotPresent(`$.response.deletedAt`)).
End()
}
func TestApigwRouteUpdateDeleted(t *testing.T) {
h := newHelper(t)
h.clearRoutes()
now := time.Now()
r := &types.ApigwRoute{Endpoint: "/deleted", Enabled: true, DeletedAt: &now, DeletedBy: 1}
h.createRoute(r)
helpers.AllowMe(h, types.ApigwRouteRbacResource(r.ID), "update")
h.apiInit().
Put(fmt.Sprintf("/apigw/route/%d", r.ID)).
Header("Accept", "application/json").
FormData("endpoint", "/deleted-and-updated").
FormData("enabled", "false").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
Assert(jsonpath.Present(`$.response.routeID`)).
Assert(jsonpath.Equal(`$.response.endpoint`, "/deleted-and-updated")).
Assert(jsonpath.Present(`$.response.deletedBy`)).
Assert(jsonpath.Present(`$.response.deletedAt`)).
End()
}
func TestApigwRouteUpdate_forbidden(t *testing.T) {
h := newHelper(t)
h.clearRoutes()
r, _ := h.createRouteWithFilter("test", "")
h.apiInit().
Put(fmt.Sprintf("/apigw/route/%d", r.ID)).
Header("Accept", "application/json").
FormData("endpoint", "/test-edited").
FormData("enabled", "false").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertError("apigw-route.errors.notAllowedToUpdate")).
End()
}
func TestApigwRouteDelete(t *testing.T) {
h := newHelper(t)
h.clearRoutes()
r, _ := h.createRouteWithFilter("test", "")
helpers.AllowMe(h, types.ApigwRouteRbacResource(r.ID), "delete")
h.apiInit().
Delete(fmt.Sprintf("/apigw/route/%d", r.ID)).
Header("Accept", "application/json").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
End()
}
func TestApigwRouteDelete_forbidden(t *testing.T) {
h := newHelper(t)
h.clearRoutes()
r, _ := h.createRouteWithFilter("test", "")
h.apiInit().
Delete(fmt.Sprintf("/apigw/route/%d", r.ID)).
Header("Accept", "application/json").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertError("apigw-route.errors.notAllowedToDelete")).
End()
}
func TestApigwRouteUnDelete(t *testing.T) {
h := newHelper(t)
h.clearRoutes()
r, _ := h.createRouteWithFilter("test", "")
helpers.AllowMe(h, types.ApigwRouteRbacResource(r.ID), "delete")
h.apiInit().
Post(fmt.Sprintf("/apigw/route/%d/undelete", r.ID)).
Header("Accept", "application/json").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
End()
}
func TestApigwRouteUnDelete_forbidden(t *testing.T) {
h := newHelper(t)
h.clearRoutes()
r, _ := h.createRouteWithFilter("test", "")
h.apiInit().
Post(fmt.Sprintf("/apigw/route/%d/undelete", r.ID)).
Header("Accept", "application/json").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertError("apigw-route.errors.notAllowedToUndelete")).
End()
}
// Filters
func TestApigwFilterRead(t *testing.T) {
h := newHelper(t)
h.clearRoutes()
r, f := h.createRouteWithFilter("test", "test")
helpers.AllowMe(h, types.ApigwRouteRbacResource(r.ID), "read")
h.apiInit().
Get(fmt.Sprintf("/apigw/filter/%d", f.ID)).
Header("Accept", "application/json").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
Assert(jsonpath.Equal(`$.response.filterID`, strconv.FormatUint(f.ID, 10))).
Assert(jsonpath.Equal(`$.response.routeID`, strconv.FormatUint(r.ID, 10))).
End()
}
func TestApigwFilterRead_forbidden(t *testing.T) {
h := newHelper(t)
h.clearRoutes()
_, f := h.createRouteWithFilter("test", "test")
h.apiInit().
Get(fmt.Sprintf("/apigw/filter/%d", f.ID)).
Header("Accept", "application/json").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertError("apigw-route.errors.notAllowedToRead")).
End()
}
func TestApigwFilterSearch(t *testing.T) {
h := newHelper(t)
h.clearRoutes()
r, f := h.createRouteWithFilter("test1", "")
helpers.AllowMe(h, types.ComponentRbacResource(), "apigw-routes.search")
helpers.AllowMe(h, types.ApigwRouteRbacResource(0), "read")
h.apiInit().
Get(fmt.Sprintf("/apigw/filter/")).
Query("routeID", strconv.FormatUint(r.ID, 10)).
Header("Accept", "application/json").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
Assert(jsonpath.Len(`$.response.set`, 1)).
Assert(jsonpath.Equal(`$.response.set[0].filterID`, strconv.FormatUint(f.ID, 10))).
Assert(jsonpath.Equal(`$.response.set[0].routeID`, strconv.FormatUint(r.ID, 10))).
End()
}
func TestApigwFilterSearch_includeDisabled(t *testing.T) {
h := newHelper(t)
h.clearRoutes()
r, f := h.createRouteWithFilterEnabled("test1", "", false)
helpers.AllowMe(h, types.ComponentRbacResource(), "apigw-routes.search")
helpers.AllowMe(h, types.ApigwRouteRbacResource(0), "read")
h.apiInit().
Get(fmt.Sprintf("/apigw/filter/")).
Query("routeID", strconv.FormatUint(r.ID, 10)).
Query("disabled", "1").
Header("Accept", "application/json").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
Assert(jsonpath.Len(`$.response.set`, 1)).
Assert(jsonpath.Equal(`$.response.set[0].filterID`, strconv.FormatUint(f.ID, 10))).
Assert(jsonpath.Equal(`$.response.set[0].routeID`, strconv.FormatUint(r.ID, 10))).
End()
}
func TestApigwFilterSearch_forbidden(t *testing.T) {
h := newHelper(t)
h.clearRoutes()
r, _ := h.createRouteWithFilter("test1", "")
h.apiInit().
Get(fmt.Sprintf("/apigw/filter/")).
Query("routeID", strconv.FormatUint(r.ID, 10)).
Header("Accept", "application/json").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertError("apigw-route.errors.notAllowedToRead")).
End()
}
func TestApigwFilterCreate(t *testing.T) {
h := newHelper(t)
h.clearRoutes()
r, _ := h.createRouteWithFilter("test1", "")
helpers.AllowMe(h, types.ApigwRouteRbacResource(r.ID), "read")
helpers.AllowMe(h, types.ApigwRouteRbacResource(r.ID), "update")
h.apiInit().
Put(fmt.Sprintf("/apigw/filter")).
Header("Accept", "application/json").
FormData("routeID", strconv.FormatUint(r.ID, 10)).
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
Assert(jsonpath.Present(`$.response.filterID`)).
Assert(jsonpath.Equal(`$.response.routeID`, strconv.FormatUint(r.ID, 10))).
End()
}
func TestApigwFilterCreate_enabled(t *testing.T) {
h := newHelper(t)
h.clearRoutes()
r, _ := h.createRouteWithFilter("test1", "")
helpers.AllowMe(h, types.ApigwRouteRbacResource(r.ID), "read")
helpers.AllowMe(h, types.ApigwRouteRbacResource(r.ID), "update")
h.apiInit().
Put(fmt.Sprintf("/apigw/filter")).
Header("Accept", "application/json").
FormData("routeID", strconv.FormatUint(r.ID, 10)).
FormData("enabled", strconv.FormatBool(true)).
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
Assert(jsonpath.Present(`$.response.filterID`)).
Assert(jsonpath.Equal(`$.response.routeID`, strconv.FormatUint(r.ID, 10))).
Assert(jsonpath.Equal(`$.response.enabled`, true)).
End()
}
func TestApigwFilterCreate_disabled(t *testing.T) {
h := newHelper(t)
h.clearRoutes()
r, _ := h.createRouteWithFilter("test1", "")
helpers.AllowMe(h, types.ApigwRouteRbacResource(r.ID), "read")
helpers.AllowMe(h, types.ApigwRouteRbacResource(r.ID), "update")
h.apiInit().
Put(fmt.Sprintf("/apigw/filter")).
Header("Accept", "application/json").
FormData("routeID", strconv.FormatUint(r.ID, 10)).
FormData("enabled", strconv.FormatBool(false)).
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
Assert(jsonpath.Present(`$.response.filterID`)).
Assert(jsonpath.Equal(`$.response.routeID`, strconv.FormatUint(r.ID, 10))).
Assert(jsonpath.NotPresent(`$.response.enabled`)).
End()
}
func TestApigwFilterCreate_forbidden(t *testing.T) {
h := newHelper(t)
h.clearRoutes()
r, _ := h.createRouteWithFilter("test1", "")
helpers.AllowMe(h, types.ApigwRouteRbacResource(r.ID), "read")
h.apiInit().
Put(fmt.Sprintf("/apigw/filter")).
Header("Accept", "application/json").
FormData("routeID", strconv.FormatUint(r.ID, 10)).
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertError("apigw-route.errors.notAllowedToUpdate")).
End()
}
func TestApigwFilterUpdate(t *testing.T) {
h := newHelper(t)
h.clearRoutes()
r, f := h.createRouteWithFilter("test1", "")
helpers.AllowMe(h, types.ApigwRouteRbacResource(r.ID), "read")
helpers.AllowMe(h, types.ApigwRouteRbacResource(r.ID), "update")
h.apiInit().
Post(fmt.Sprintf("/apigw/filter/%d", f.ID)).
Header("Accept", "application/json").
FormData("routeID", strconv.FormatUint(r.ID, 10)).
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
Assert(jsonpath.Equal(`$.response.filterID`, strconv.FormatUint(f.ID, 10))).
Assert(jsonpath.Equal(`$.response.routeID`, strconv.FormatUint(r.ID, 10))).
End()
}
func TestApigwFilterUpdate_enabled(t *testing.T) {
h := newHelper(t)
h.clearRoutes()
r, f := h.createRouteWithFilterEnabled("test1", "", false)
helpers.AllowMe(h, types.ApigwRouteRbacResource(r.ID), "read")
helpers.AllowMe(h, types.ApigwRouteRbacResource(r.ID), "update")
h.apiInit().
Post(fmt.Sprintf("/apigw/filter/%d", f.ID)).
Header("Accept", "application/json").
FormData("routeID", strconv.FormatUint(r.ID, 10)).
FormData("enabled", strconv.FormatBool(true)).
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
Assert(jsonpath.Equal(`$.response.filterID`, strconv.FormatUint(f.ID, 10))).
Assert(jsonpath.Equal(`$.response.routeID`, strconv.FormatUint(r.ID, 10))).
Assert(jsonpath.Equal(`$.response.enabled`, true)).
End()
}
func TestApigwFilterUpdate_disabled(t *testing.T) {
h := newHelper(t)
h.clearRoutes()
r, f := h.createRouteWithFilterEnabled("test1", "", true)
helpers.AllowMe(h, types.ApigwRouteRbacResource(r.ID), "read")
helpers.AllowMe(h, types.ApigwRouteRbacResource(r.ID), "update")
h.apiInit().
Post(fmt.Sprintf("/apigw/filter/%d", f.ID)).
Header("Accept", "application/json").
FormData("routeID", strconv.FormatUint(r.ID, 10)).
FormData("enabled", strconv.FormatBool(false)).
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
Assert(jsonpath.Equal(`$.response.filterID`, strconv.FormatUint(f.ID, 10))).
Assert(jsonpath.Equal(`$.response.routeID`, strconv.FormatUint(r.ID, 10))).
Assert(jsonpath.NotPresent(`$.response.enabled`)).
End()
}
func TestApigwFilterUpdate_forbidden(t *testing.T) {
h := newHelper(t)
h.clearRoutes()
r, f := h.createRouteWithFilter("test1", "")
helpers.AllowMe(h, types.ApigwRouteRbacResource(r.ID), "read")
h.apiInit().
Post(fmt.Sprintf("/apigw/filter/%d", f.ID)).
Header("Accept", "application/json").
FormData("routeID", strconv.FormatUint(r.ID, 10)).
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertError("apigw-route.errors.notAllowedToUpdate")).
End()
}
func TestApigwFilterDelete(t *testing.T) {
h := newHelper(t)
h.clearRoutes()
r, f := h.createRouteWithFilter("test1", "")
helpers.AllowMe(h, types.ApigwRouteRbacResource(r.ID), "read")
helpers.AllowMe(h, types.ApigwRouteRbacResource(r.ID), "delete")
h.apiInit().
Delete(fmt.Sprintf("/apigw/filter/%d", f.ID)).
Header("Accept", "application/json").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
End()
}
func TestApigwFilterDelete_forbidden(t *testing.T) {
h := newHelper(t)
h.clearRoutes()
_, f := h.createRouteWithFilter("test1", "")
h.apiInit().
Delete(fmt.Sprintf("/apigw/filter/%d", f.ID)).
Header("Accept", "application/json").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertError("apigw-route.errors.notAllowedToDelete")).
End()
}
func TestApigwFilterUnDelete(t *testing.T) {
h := newHelper(t)
h.clearRoutes()
r, f := h.createRouteWithFilter("test1", "")
helpers.AllowMe(h, types.ApigwRouteRbacResource(r.ID), "read")
helpers.AllowMe(h, types.ApigwRouteRbacResource(r.ID), "delete")
h.apiInit().
Post(fmt.Sprintf("/apigw/filter/%d/undelete", f.ID)).
Header("Accept", "application/json").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
End()
}
func TestApigwFilterUnDelete_forbidden(t *testing.T) {
h := newHelper(t)
h.clearRoutes()
r, f := h.createRouteWithFilter("test1", "")
helpers.AllowMe(h, types.ApigwRouteRbacResource(r.ID), "read")
h.apiInit().
Post(fmt.Sprintf("/apigw/filter/%d/undelete", f.ID)).
Header("Accept", "application/json").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertError("apigw-route.errors.notAllowedToUndelete")).
End()
}

View File

@@ -0,0 +1,669 @@
package system
import (
"context"
"fmt"
"net/http"
"net/url"
"testing"
"time"
ft "github.com/cortezaproject/corteza/server/pkg/flag/types"
"github.com/cortezaproject/corteza/server/pkg/id"
labelTypes "github.com/cortezaproject/corteza/server/pkg/label/types"
"github.com/cortezaproject/corteza/server/store"
"github.com/cortezaproject/corteza/server/system/service"
"github.com/cortezaproject/corteza/server/system/types"
"github.com/cortezaproject/corteza/server/tests/helpers"
jsonpath "github.com/steinfletcher/apitest-jsonpath"
"github.com/stretchr/testify/require"
)
func (h helper) clearApplications() {
h.noError(store.TruncateApplications(context.Background(), service.DefaultStore))
}
func (h helper) repoMakeApplication(ss ...string) *types.Application {
var res = &types.Application{
ID: id.Next(),
CreatedAt: time.Now(),
Unify: &types.ApplicationUnify{},
}
if len(ss) > 0 {
res.Name = ss[0]
} else {
res.Name = "n_" + rs()
}
h.a.NoError(store.CreateApplication(context.Background(), service.DefaultStore, res))
return res
}
func (h helper) repoFlagApplication(appID, owner uint64, flag string, active bool) *ft.Flag {
res := &ft.Flag{
Kind: "system:application",
ResourceID: appID,
OwnedBy: owner,
Name: flag,
Active: active,
}
h.a.NoError(store.CreateFlag(context.Background(), service.DefaultStore, res))
return res
}
func (h helper) lookupApplicationByID(ID uint64) *types.Application {
res, err := store.LookupApplicationByID(context.Background(), service.DefaultStore, ID)
h.noError(err)
return res
}
func (h helper) searchApplicationFlags(ID, owner uint64, flag string) ft.FlagSet {
res, _, err := store.SearchFlags(context.Background(), service.DefaultStore, ft.FlagFilter{
Kind: "system:application",
ResourceID: []uint64{ID},
OwnedBy: []uint64{owner},
Name: []string{flag},
})
h.noError(err)
return res
}
func (h helper) lookupApplicationByName(name string) *types.Application {
res, _, err := store.SearchApplications(context.Background(), service.DefaultStore, types.ApplicationFilter{
Name: name,
})
h.noError(err)
if len(res) == 0 {
return nil
}
return res[0]
}
func TestApplicationRead(t *testing.T) {
h := newHelper(t)
app := h.repoMakeApplication()
helpers.AllowMe(h, types.ComponentRbacResource(), "applications.search")
helpers.AllowMe(h, types.ApplicationRbacResource(0), "read")
h.apiInit().
Get(fmt.Sprintf("/application/%d", app.ID)).
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
Assert(jsonpath.Equal(`$.response.name`, app.Name)).
Assert(jsonpath.Equal(`$.response.applicationID`, fmt.Sprintf("%d", app.ID))).
End()
}
func TestApplicationList(t *testing.T) {
h := newHelper(t)
helpers.AllowMe(h, types.ComponentRbacResource(), "applications.search")
h.repoMakeApplication(h.randEmail())
h.repoMakeApplication(h.randEmail())
h.apiInit().
Get("/application/").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
End()
}
func TestApplicationList_filterForbidden(t *testing.T) {
h := newHelper(t)
// @todo this can be a problematic test because it leaves
// behind applications that are not denied this context
// db purge might be needed
h.repoMakeApplication("application")
f := h.repoMakeApplication()
helpers.AllowMe(h, types.ComponentRbacResource(), "applications.search")
helpers.DenyMe(h, f.RbacResource(), "read")
h.apiInit().
Get("/application/").
Query("name", f.Name).
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
Assert(jsonpath.NotPresent(fmt.Sprintf(`$.response.set[? @.name=="%s"]`, f.Name))).
End()
}
func TestApplicationCreateForbidden(t *testing.T) {
h := newHelper(t)
h.apiInit().
Post("/application/").
Header("Accept", "application/json").
FormData("name", rs()).
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertError("application.errors.notAllowedToCreate")).
End()
}
func TestApplicationCreate(t *testing.T) {
h := newHelper(t)
helpers.AllowMe(h, types.ComponentRbacResource(), "application.create")
h.apiInit().
Post("/application/").
FormData("name", rs()).
FormData("handle", "handle_"+rs()).
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
End()
}
func TestApplicationCreate_weight(t *testing.T) {
h := newHelper(t)
helpers.AllowMe(h, types.ComponentRbacResource(), "application.create")
name := "name_weight_create_" + rs()
h.apiInit().
Post("/application/").
FormData("name", name).
FormData("weight", "10").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
End()
res := h.lookupApplicationByName(name)
h.a.NotNil(res)
h.a.Equal(10, res.Weight)
}
func TestApplicationUpdateForbidden(t *testing.T) {
h := newHelper(t)
u := h.repoMakeApplication()
h.apiInit().
Put(fmt.Sprintf("/application/%d", u.ID)).
Header("Accept", "application/json").
FormData("email", h.randEmail()).
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertError("application.errors.notAllowedToUpdate")).
End()
}
func TestApplicationUpdate(t *testing.T) {
h := newHelper(t)
res := h.repoMakeApplication()
helpers.AllowMe(h, types.ApplicationRbacResource(0), "update")
newName := "updated-" + rs()
newHandle := "updated-" + rs()
h.apiInit().
Put(fmt.Sprintf("/application/%d", res.ID)).
Header("Accept", "application/json").
FormData("name", newName).
FormData("handle", newHandle).
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
End()
res = h.lookupApplicationByID(res.ID)
h.a.NotNil(res)
h.a.Equal(newName, res.Name)
}
func TestApplicationUpdate_weight(t *testing.T) {
h := newHelper(t)
res := h.repoMakeApplication()
helpers.AllowMe(h, types.ApplicationRbacResource(0), "update")
newName := "updated-" + rs()
newHandle := "updated-" + rs()
h.apiInit().
Put(fmt.Sprintf("/application/%d", res.ID)).
Header("Accept", "application/json").
FormData("name", newName).
FormData("handle", newHandle).
FormData("weight", "20").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
End()
res = h.lookupApplicationByID(res.ID)
h.a.NotNil(res)
h.a.Equal(20, res.Weight)
}
func TestApplicationReorder_forbidden(t *testing.T) {
h := newHelper(t)
helpers.AllowMe(h, types.ApplicationRbacResource(0), "update")
a := h.repoMakeApplication()
b := h.repoMakeApplication()
c := h.repoMakeApplication()
helpers.DenyMe(h, b.RbacResource(), "update")
h.apiInit().
Post("/application/reorder").
Header("Accept", "application/json").
JSON(fmt.Sprintf(`{ "applicationIDs": ["%d", "%d", "%d"] }`, b.ID, a.ID, c.ID)).
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertError("application.errors.notAllowedToUpdate")).
End()
}
func TestApplicationReorder(t *testing.T) {
h := newHelper(t)
helpers.AllowMe(h, types.ApplicationRbacResource(0), "update")
a := h.repoMakeApplication()
b := h.repoMakeApplication()
c := h.repoMakeApplication()
h.apiInit().
Post("/application/reorder").
Header("Accept", "application/json").
JSON(fmt.Sprintf(`{ "applicationIDs": ["%d", "%d", "%d"] }`, b.ID, a.ID, c.ID)).
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
End()
check := func(app uint64, w int) {
res := h.lookupApplicationByID(app)
h.a.NotNil(res)
h.a.Equal(w, res.Weight)
}
check(b.ID, 1)
check(a.ID, 2)
check(c.ID, 3)
}
func TestApplicationDeleteForbidden(t *testing.T) {
h := newHelper(t)
u := h.repoMakeApplication()
h.apiInit().
Delete(fmt.Sprintf("/application/%d", u.ID)).
Header("Accept", "application/json").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertError("application.errors.notAllowedToDelete")).
End()
}
func TestApplicationDelete(t *testing.T) {
h := newHelper(t)
helpers.AllowMe(h, types.ApplicationRbacResource(0), "delete")
res := h.repoMakeApplication()
h.apiInit().
Delete(fmt.Sprintf("/application/%d", res.ID)).
Header("Accept", "application/json").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
End()
res = h.lookupApplicationByID(res.ID)
h.a.NotNil(res)
h.a.NotNil(res.DeletedAt)
}
func TestApplicationUndelete(t *testing.T) {
h := newHelper(t)
helpers.AllowMe(h, types.ApplicationRbacResource(0), "delete")
res := h.repoMakeApplication()
h.apiInit().
Post(fmt.Sprintf("/application/%d/undelete", res.ID)).
Header("Accept", "application/json").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
End()
res = h.lookupApplicationByID(res.ID)
h.a.NotNil(res)
h.a.Nil(res.DeletedAt)
}
func TestApplicationLabels(t *testing.T) {
h := newHelper(t)
h.clearApplications()
helpers.AllowMe(h, types.ComponentRbacResource(), "applications.search")
helpers.AllowMe(h, types.ComponentRbacResource(), "application.create")
helpers.AllowMe(h, types.ApplicationRbacResource(0), "read", "update", "delete")
var (
ID uint64
)
t.Run("create", func(t *testing.T) {
var (
req = require.New(t)
payload = &types.Application{}
)
helpers.SetLabelsViaAPI(h.apiInit(), t,
"/application/",
types.Application{Labels: map[string]labelTypes.LabelValue{"foo": {Val: "bar"}, "bar": {Val: "42"}}},
payload,
)
req.NotZero(payload.ID)
h.a.Equal(payload.Labels["foo"].Val, "bar",
"labels must contain foo with value bar")
h.a.Equal(payload.Labels["bar"].Val, "42",
"labels must contain bar with value 42")
req.Equal(helpers.LabelsToStrings(payload.Labels), helpers.LabelsToStrings(helpers.LoadLabelsFromStore(t, service.DefaultStore, payload.LabelResourceKind(), payload.ID)),
"response must match stored labels")
ID = payload.ID
})
t.Run("update", func(t *testing.T) {
if ID == 0 {
t.Skip("label/create test not ran")
}
var (
req = require.New(t)
payload = &types.Application{}
)
helpers.SetLabelsViaAPI(h.apiInit(), t,
fmt.Sprintf("PUT /application/%d", ID),
&types.Application{Labels: map[string]labelTypes.LabelValue{"foo": {Val: "baz"}, "baz": {Val: "123"}}},
payload,
)
req.NotZero(payload.ID)
//req.Nil(payload.UpdatedAt, "updatedAt must not change after changing labels")
req.Equal(payload.Labels["foo"].Val, "baz",
"labels must contain foo with value baz")
req.NotContains(payload.Labels, "bar",
"labels must not contain bar")
req.Equal(payload.Labels["baz"].Val, "123",
"labels must contain baz with value 123")
req.Equal(helpers.LabelsToStrings(payload.Labels), helpers.LabelsToStrings(helpers.LoadLabelsFromStore(t, service.DefaultStore, payload.LabelResourceKind(), payload.ID)),
"response must match stored labels")
})
t.Run("search", func(t *testing.T) {
if ID == 0 {
t.Skip("label/create test not ran")
}
var (
req = require.New(t)
set = types.ApplicationSet{}
)
helpers.SearchWithLabelsViaAPI(h.apiInit(), t, "/application/", &set, url.Values{"labels": []string{"baz=123"}})
req.NotEmpty(set)
req.NotNil(set.FindByID(ID))
req.NotNil(set.FindByID(ID).Labels)
})
}
func TestApplicationFlags(t *testing.T) {
h := newHelper(t)
h.clearApplications()
helpers.AllowMe(h, types.ComponentRbacResource(), "applications.search")
helpers.AllowMe(h, types.ComponentRbacResource(), "application.create")
helpers.AllowMe(h, types.ApplicationRbacResource(0), "read")
t.Run("create", func(t *testing.T) {
helpers.AllowMe(h, types.ComponentRbacResource(), "application.flag.global")
res := h.repoMakeApplication()
h.apiInit().
Post(fmt.Sprintf("/application/%d/flag/%d/testFlag", res.ID, 0)).
Header("Accept", "application/json").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
End()
ff := h.searchApplicationFlags(res.ID, 0, "testFlag")
h.a.NotNil(ff)
h.a.Len(ff, 1)
})
t.Run("create; not allowed", func(t *testing.T) {
helpers.DenyMe(h, types.ComponentRbacResource(), "application.flag.global")
res := h.repoMakeApplication()
h.apiInit().
Post(fmt.Sprintf("/application/%d/flag/%d/testFlag", res.ID, 0)).
Header("Accept", "application/json").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertError("application.errors.notAllowedToManageFlagGlobal")).
End()
})
t.Run("create own", func(t *testing.T) {
helpers.AllowMe(h, types.ComponentRbacResource(), "application.flag.self")
res := h.repoMakeApplication()
h.repoFlagApplication(res.ID, 0, "testFlag", true)
h.apiInit().
Post(fmt.Sprintf("/application/%d/flag/%d/testFlag", res.ID, h.cUser.ID)).
Header("Accept", "application/json").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
End()
ff := h.searchApplicationFlags(res.ID, h.cUser.ID, "testFlag")
h.a.NotNil(ff)
h.a.Len(ff, 1)
})
t.Run("create own; not allowed", func(t *testing.T) {
helpers.DenyMe(h, types.ComponentRbacResource(), "application.flag.self")
res := h.repoMakeApplication()
h.repoFlagApplication(res.ID, 0, "testFlag", true)
h.apiInit().
Post(fmt.Sprintf("/application/%d/flag/%d/testFlag", res.ID, h.cUser.ID)).
Header("Accept", "application/json").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertError("application.errors.notAllowedToManageFlag")).
End()
})
t.Run("read application", func(t *testing.T) {
res := h.repoMakeApplication()
h.repoFlagApplication(res.ID, 0, "testFlag", true)
h.apiInit().
Get(fmt.Sprintf("/application/%d", res.ID)).
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
Assert(jsonpath.Present(`$.response.flags`)).
Assert(jsonpath.Len(`$.response.flags`, 1)).
Assert(jsonpath.Equal(`$.response.flags[0]`, "testFlag")).
End()
})
t.Run("list applications", func(t *testing.T) {
h.clearApplications()
res := h.repoMakeApplication()
h.repoFlagApplication(res.ID, 0, "testFlag", true)
h.apiInit().
Get("/application/").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
Assert(jsonpath.Len(`$.response.set`, 1)).
Assert(jsonpath.Len(`$.response.set[0].flags`, 1)).
Assert(jsonpath.Equal(`$.response.set[0].flags[0]`, "testFlag")).
End()
})
t.Run("read application; with own flag", func(t *testing.T) {
res := h.repoMakeApplication()
h.repoFlagApplication(res.ID, 0, "testFlag", true)
h.repoFlagApplication(res.ID, h.cUser.ID, "testFlagOwn", true)
h.apiInit().
Get(fmt.Sprintf("/application/%d", res.ID)).
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
Assert(jsonpath.Present(`$.response.flags`)).
Assert(jsonpath.Len(`$.response.flags`, 2)).
End()
})
t.Run("read application; overwrite global", func(t *testing.T) {
res := h.repoMakeApplication()
h.repoFlagApplication(res.ID, 0, "testFlag", true)
h.repoFlagApplication(res.ID, h.cUser.ID, "testFlag", false)
h.apiInit().
Get(fmt.Sprintf("/application/%d", res.ID)).
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
Assert(jsonpath.NotPresent(`$.response.flags`)).
End()
})
t.Run("filter by flags", func(t *testing.T) {
flag := rs()
h.repoMakeApplication()
h.repoMakeApplication()
res := h.repoMakeApplication()
h.repoFlagApplication(res.ID, 0, flag, true)
h.apiInit().
Get(fmt.Sprintf("/application/")).
QueryCollection(url.Values{"flags": []string{flag}}).
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
Assert(jsonpath.Len("$.response.set", 1)).
End()
})
t.Run("filter by flags; self inactive", func(t *testing.T) {
flag := rs()
h.repoMakeApplication()
h.repoMakeApplication()
res := h.repoMakeApplication()
h.repoFlagApplication(res.ID, 0, flag, true)
h.repoFlagApplication(res.ID, h.cUser.ID, flag, false)
h.apiInit().
Get(fmt.Sprintf("/application/")).
QueryCollection(url.Values{"flags": []string{flag}}).
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
Assert(jsonpath.Len("$.response.set", 0)).
End()
})
}
func TestApplicationFlags_Flow1(t *testing.T) {
h := newHelper(t)
h.clearApplications()
helpers.AllowMe(h, types.ComponentRbacResource(), "application.create")
helpers.AllowMe(h, types.ApplicationRbacResource(0), "read")
t.Run("create", func(t *testing.T) {
helpers.AllowMe(h, types.ComponentRbacResource(), "application.flag.global", "application.flag.self")
res := h.repoMakeApplication()
a := h.apiInit()
a.Post(fmt.Sprintf("/application/%d/flag/%d/testFlag", res.ID, h.cUser.ID)).
Header("Accept", "application/json").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
End()
a.Post(fmt.Sprintf("/application/%d/flag/%d/testFlag", res.ID, 0)).
Header("Accept", "application/json").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
End()
a.Delete(fmt.Sprintf("/application/%d/flag/%d/testFlag", res.ID, h.cUser.ID)).
Header("Accept", "application/json").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
End()
ff := h.searchApplicationFlags(res.ID, 0, "testFlag")
h.a.NotNil(ff)
h.a.Len(ff, 1)
h.a.True(ff[0].Active)
ff = h.searchApplicationFlags(res.ID, h.cUser.ID, "testFlag")
h.a.NotNil(ff)
h.a.Len(ff, 1)
h.a.False(ff[0].Active)
a.Delete(fmt.Sprintf("/application/%d/flag/%d/testFlag", res.ID, h.cUser.ID)).
Header("Accept", "application/json").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
End()
ff = h.searchApplicationFlags(res.ID, 0, "testFlag")
h.a.NotNil(ff)
h.a.Len(ff, 1)
h.a.True(ff[0].Active)
ff = h.searchApplicationFlags(res.ID, h.cUser.ID, "testFlag")
h.a.NotNil(ff)
h.a.Len(ff, 1)
h.a.False(ff[0].Active)
a.Post(fmt.Sprintf("/application/%d/flag/%d/testFlag", res.ID, h.cUser.ID)).
Header("Accept", "application/json").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
End()
ff = h.searchApplicationFlags(res.ID, 0, "testFlag")
h.a.NotNil(ff)
h.a.Len(ff, 1)
h.a.True(ff[0].Active)
ff = h.searchApplicationFlags(res.ID, h.cUser.ID, "testFlag")
h.a.NotNil(ff)
h.a.Len(ff, 1)
h.a.True(ff[0].Active)
})
}

View File

@@ -0,0 +1,80 @@
package system
import (
"context"
"fmt"
jsonpath "github.com/steinfletcher/apitest-jsonpath"
"net/http"
"testing"
"time"
"github.com/cortezaproject/corteza/server/pkg/id"
"github.com/cortezaproject/corteza/server/store"
"github.com/cortezaproject/corteza/server/system/service"
"github.com/cortezaproject/corteza/server/system/types"
"github.com/cortezaproject/corteza/server/tests/helpers"
)
func (h helper) clearAttachments() {
h.noError(store.TruncateAttachments(context.Background(), service.DefaultStore))
}
func (h helper) repoMakeAttachment(ss ...string) *types.Attachment {
var res = &types.Attachment{
ID: id.Next(),
CreatedAt: time.Now(),
Kind: "json",
}
if len(ss) > 0 {
res.Name = ss[0]
} else {
res.Name = "n_" + rs()
}
h.a.NoError(store.CreateAttachment(context.Background(), service.DefaultStore, res))
return res
}
func (h helper) lookupAttachmentByID(ID uint64) *types.Attachment {
res, err := store.LookupAttachmentByID(context.Background(), service.DefaultStore, ID)
h.noError(err)
return res
}
func TestAttachmentRead(t *testing.T) {
h := newHelper(t)
h.clearAttachments()
a := h.repoMakeAttachment()
h.apiInit().
Get(fmt.Sprintf("/attachment/%s/%d", a.Kind, a.ID)).
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
Assert(jsonpath.Equal(`$.response.name`, a.Name)).
Assert(jsonpath.Equal(`$.response.attachmentID`, fmt.Sprintf("%d", a.ID))).
End()
}
func TestAttachmentDelete(t *testing.T) {
h := newHelper(t)
h.clearAttachments()
a := h.repoMakeAttachment()
helpers.AllowMe(h, types.ApplicationRbacResource(0), "delete")
h.apiInit().
Delete(fmt.Sprintf("/attachment/%s/%d", a.Kind, a.ID)).
Header("Accept", "application/json").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
End()
a = h.lookupAttachmentByID(a.ID)
h.a.NotNil(a)
h.a.NotNil(a.DeletedAt)
}

View File

@@ -0,0 +1,174 @@
package system
import (
"context"
"fmt"
"net/http"
"testing"
"time"
"github.com/cortezaproject/corteza/server/pkg/id"
"github.com/cortezaproject/corteza/server/store"
"github.com/cortezaproject/corteza/server/system/service"
"github.com/cortezaproject/corteza/server/system/types"
"github.com/cortezaproject/corteza/server/tests/helpers"
jsonpath "github.com/steinfletcher/apitest-jsonpath"
)
func (h helper) clearAuthClients() {
h.noError(store.TruncateAuthClients(context.Background(), service.DefaultStore))
}
func (h helper) repoMakeAuthClient(ss ...string) *types.AuthClient {
res := &types.AuthClient{
ID: id.Next(),
CreatedAt: time.Now(),
}
if len(ss) > 0 {
res.Handle = ss[0]
} else {
res.Handle = "n_" + rs()
}
h.a.NoError(store.CreateAuthClient(context.Background(), service.DefaultStore, res))
return res
}
func (h helper) lookupAuthClientByID(id uint64) *types.AuthClient {
res, err := store.LookupAuthClientByID(context.Background(), service.DefaultStore, id)
h.noError(err)
return res
}
func (h helper) lookupAuthClientByHandle(handle string) *types.AuthClient {
res, err := store.LookupAuthClientByHandle(context.Background(), service.DefaultStore, handle)
h.noError(err)
return res
}
func TestAuthClientList(t *testing.T) {
h := newHelper(t)
h.clearAuthClients()
h.repoMakeAuthClient()
h.repoMakeAuthClient()
helpers.AllowMe(h, types.ComponentRbacResource(), "auth-clients.search")
helpers.AllowMe(h, types.AuthClientRbacResource(0), "read")
h.apiInit().
Get("/auth/clients/").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
Assert(jsonpath.Len(`$.response.set`, 2)).
End()
}
func TestAuthClientRead(t *testing.T) {
h := newHelper(t)
h.clearAuthClients()
client := h.repoMakeAuthClient()
helpers.AllowMe(h, types.AuthClientRbacResource(0), "read")
h.apiInit().
Get(fmt.Sprintf("/auth/clients/%d", client.ID)).
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
End()
}
func TestAuthClientCreate(t *testing.T) {
h := newHelper(t)
h.clearAuthClients()
handle := rs()
helpers.AllowMe(h, types.ComponentRbacResource(), "auth-clients.search")
helpers.AllowMe(h, types.ComponentRbacResource(), "auth-client.create")
h.apiInit().
Post("/auth/clients/").
Header("Accept", "application/json").
FormData("meta", fmt.Sprintf(`{"name": "%s"}`, rs())).
FormData("handle", handle).
FormData("scope", "profile api").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
End()
res := h.lookupAuthClientByHandle(handle)
h.a.NotNil(res)
h.a.Equal(handle, res.Handle)
}
func TestAuthClientUpdate(t *testing.T) {
h := newHelper(t)
h.clearAuthClients()
client := h.repoMakeAuthClient()
client.Meta = &types.AuthClientMeta{Name: rs()}
client.Handle = rs()
helpers.AllowMe(h, types.AuthClientRbacResource(0), "update")
h.apiInit().
Put(fmt.Sprintf("/auth/clients/%d", client.ID)).
Header("Accept", "application/json").
JSON(helpers.JSON(client)).
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
End()
res := h.lookupAuthClientByHandle(client.Handle)
h.a.NotNil(res)
}
func TestAuthClientDelete(t *testing.T) {
h := newHelper(t)
h.clearAuthClients()
client := h.repoMakeAuthClient()
helpers.AllowMe(h, types.AuthClientRbacResource(0), "delete")
h.apiInit().
Delete(fmt.Sprintf("/auth/clients/%d", client.ID)).
Header("Accept", "application/json").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
End()
res := h.lookupAuthClientByID(client.ID)
h.a.NotNil(res)
h.a.NotNil(res.DeletedAt)
}
func TestAuthClientUnDelete(t *testing.T) {
h := newHelper(t)
h.clearAuthClients()
client := h.repoMakeAuthClient()
helpers.AllowMe(h, types.AuthClientRbacResource(0), "delete")
h.apiInit().
Post(fmt.Sprintf("/auth/clients/%d/undelete", client.ID)).
Header("Accept", "application/json").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
End()
res := h.lookupAuthClientByID(client.ID)
h.a.NotNil(res)
h.a.Nil(res.DeletedAt)
}

View File

@@ -0,0 +1,183 @@
package system
import (
"context"
"crypto/rsa"
"crypto/tls"
"crypto/x509"
"net/http"
"net/url"
"strings"
"testing"
"time"
"github.com/cortezaproject/corteza/server/auth/handlers"
"github.com/cortezaproject/corteza/server/auth/saml"
"github.com/cortezaproject/corteza/server/system/service"
"github.com/cortezaproject/corteza/server/system/types"
s "github.com/crewjam/saml"
"github.com/crewjam/saml/samlsp"
"github.com/golang-jwt/jwt/v4"
"github.com/steinfletcher/apitest"
"go.uber.org/zap"
)
func loadSAMLService() (srvc *saml.SamlSPService, err error) {
var (
links = handlers.GetLinks()
keyPair tls.Certificate
)
if keyPair, err = tls.X509KeyPair(readStaticFile("static/spCert.cert"), readStaticFile("static/spCert.key")); err != nil {
return nil, err
}
if keyPair.Leaf, err = x509.ParseCertificate(keyPair.Certificate[0]); err != nil {
return nil, err
}
idpUrl, err := url.Parse("")
if err != nil {
return
}
// idp metadata needs to be loaded before
// the internal samlsp package
md, err := samlsp.ParseMetadata(readStaticFile("static/idp_metadata.xml"))
ru, err := url.Parse("http://localhost:8084")
rootURL := &url.URL{
Scheme: ru.Scheme,
User: ru.User,
Host: ru.Host,
}
if err != nil {
return
}
srvc, err = saml.NewSamlSPService(zap.NewNop(), saml.SamlSPArgs{
Enabled: true,
AcsURL: links.SamlCallback,
MetaURL: links.SamlMetadata,
SloURL: links.SamlLogout,
IdpURL: *idpUrl,
Host: *rootURL,
Certificate: keyPair.Leaf,
PrivateKey: keyPair.PrivateKey.(*rsa.PrivateKey),
IdpMeta: md,
})
srvc.Handler().ServiceProvider.AllowIDPInitiated = true
return
}
func TestAuthExternalSAMLSuccess(t *testing.T) {
var (
h = newHelper(t)
cookieSessionIDPtoSP = apitest.
NewCookie("saml_tCu5PV6EgxcvUAa9e57uJ2g-bTkqnNkyyHHaOu15yEfZjgWKt02AtXGe").
Value(strings.TrimSpace(string(readStaticFile("static/idp_to_sp.cookie"))))
cookieTokenIDPtoSPAfterLogin = apitest.
NewCookie("token").
Value(strings.TrimSpace(string(readStaticFile("static/idp_to_sp_token.cookie"))))
may21 = func() time.Time {
tm, _ := time.Parse("2006-01-2 15:04:05", "2021-05-17 09:17:10")
return tm
}
)
s.MaxClockSkew = time.Hour
s.MaxIssueDelay = time.Hour
jwt.TimeFunc = may21
s.TimeNow = may21
// first step, there is no session cookie, redirect to idp
// in this case, host from parsed metadata
t.Log("start login process")
h.apiInit().
Get(handlers.GetLinks().SamlInit).
Expect(t).
Assert(func(res *http.Response, req *http.Request) error {
loc, _ := res.Location()
h.assertBody("SSO Error: saml: session not present", res.Body)
h.a.NotEmpty(loc.Query().Get("RelayState"))
h.a.NotEmpty(loc.Query().Get("SAMLRequest"))
h.a.Equal(http.StatusFound, res.StatusCode)
return nil
}).
End()
cookies := []*apitest.Cookie{}
// coming back from idp, posting to sp-related endpoint
// mocking session cookie and saml response
// if everything is ok, redirect back to SAML init
t.Log("post from idp to sp")
h.apiInit().
Post(handlers.GetLinks().SamlCallback).
Header("Content-Type", "application/x-www-form-urlencoded").
FormData("RelayState", "tCu5PV6EgxcvUAa9e57uJ2g-bTkqnNkyyHHaOu15yEfZjgWKt02AtXGe").
Cookies(cookieSessionIDPtoSP).
FormData("SAMLResponse", string(readStaticFile("static/idp_to_sp.post"))).
Expect(t).
Assert(func(res *http.Response, req *http.Request) error {
loc, _ := res.Location()
h.a.NotNil(getSessionCookie("token", res.Cookies()...))
h.a.Equal(http.StatusFound, res.StatusCode)
h.a.Equal(handlers.GetLinks().SamlInit, loc.String())
cookies = append(cookies, cookieSessionIDPtoSP)
return nil
}).
End()
// idp sends a token session cookie also
cookies = append(cookies, cookieTokenIDPtoSPAfterLogin)
// once everything is set and the external authentication via
// internal Corteza services is done, redirect to default path (profile)
t.Log("redirect to profile after session is created")
h.apiInit().
Get(handlers.GetLinks().SamlInit).
Cookies(cookies...).
Expect(t).
Assert(func(res *http.Response, req *http.Request) error {
loc, _ := res.Location()
ss, _, err := service.DefaultStore.SearchAuthSessions(context.Background(), types.AuthSessionFilter{})
h.a.NoError(err)
h.a.Len(ss, 1)
h.a.NotNil(getSessionCookie("session", res.Cookies()...))
h.a.Equal(http.StatusSeeOther, res.StatusCode)
h.a.Equal(handlers.GetLinks().Profile, loc.String())
return nil
}).
End()
}
func getSessionCookie(name string, cc ...*http.Cookie) (found *http.Cookie) {
for _, c := range cc {
if c.Name == name {
found = c
return
}
}
return
}

View File

@@ -0,0 +1,56 @@
package system
import (
"context"
"net/http"
"testing"
"github.com/cortezaproject/corteza/server/system/service"
"github.com/cortezaproject/corteza/server/system/types"
"github.com/lestrrat-go/jwx/jwt"
"github.com/cortezaproject/corteza/server/tests/helpers"
)
func TestAuthImpersonate(t *testing.T) {
var (
h = newHelper(t)
ctx = context.Background()
user = h.createUserWithEmail(h.randEmail())
input = &struct {
UserID uint64 `json:",string"`
}{
UserID: user.ID,
}
)
helpers.AllowMe(h, types.UserRbacResource(user.ID), "impersonate")
var res struct {
Response struct {
JWT string `json:"jwt"`
} `json:"response"`
}
h.apiInit().
Post("/auth/impersonate").
Header("Accept", "application/json").
JSON(helpers.JSON(input)).
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
End().
JSON(&res)
// make sure response has JWT token
signedToken := res.Response.JWT
h.a.NotEmpty(signedToken)
token, err := jwt.Parse([]byte(signedToken))
h.a.Nil(err)
at, err := service.DefaultStore.LookupAuthOa2tokenByAccess(ctx, token.JwtID())
h.a.Nil(err)
h.a.NotNil(at)
h.a.Equal(at.Access, token.JwtID())
}

View File

@@ -0,0 +1,361 @@
package system
import (
"context"
"fmt"
"net/http"
"testing"
"time"
"github.com/cortezaproject/corteza/server/pkg/id"
"github.com/cortezaproject/corteza/server/store"
"github.com/cortezaproject/corteza/server/system/service"
"github.com/cortezaproject/corteza/server/system/types"
"github.com/cortezaproject/corteza/server/tests/helpers"
jsonpath "github.com/steinfletcher/apitest-jsonpath"
)
func (h helper) clearDalConnections() {
cc, _, err := store.SearchDalConnections(context.Background(), service.DefaultStore, types.DalConnectionFilter{})
h.noError(err)
for _, c := range cc {
if c.Type == types.DalPrimaryConnectionResourceType {
continue
}
h.noError(store.DeleteDalConnection(context.Background(), service.DefaultStore, c))
}
}
func (h helper) getPrimaryConnection() *types.DalConnection {
cc, _, err := store.SearchDalConnections(context.Background(), service.DefaultStore, types.DalConnectionFilter{Type: types.DalPrimaryConnectionResourceType})
h.a.NoError(err)
if len(cc) != 1 {
h.a.FailNow("invalid state: no or too many primary connections")
}
return cc[0]
}
func (h helper) createDalConnection(res *types.DalConnection) *types.DalConnection {
if res.ID == 0 {
res.ID = id.Next()
}
if res.Meta.Name == "" {
res.Meta.Name = "Test Connection"
}
if res.Handle == "" {
res.Handle = "test_connection"
}
if res.Type == "" {
res.Type = types.DalConnectionResourceType
}
if res.Meta.Ownership == "" {
res.Meta.Ownership = "tester"
}
if res.Config.DAL == nil {
res.Config.DAL = &types.ConnectionConfigDAL{
Type: "corteza::dal:connection:dsn",
Params: map[string]any{
"dsn": "sqlite3://file::memory:?cache=shared&mode=memory",
},
}
}
if res.Config.DAL.ModelIdent == "" {
res.Config.DAL.ModelIdent = "compose_records_{{namespace}}_{{module}}"
}
if res.CreatedAt.IsZero() {
res.CreatedAt = time.Now()
}
if res.CreatedBy == 0 {
res.CreatedBy = h.cUser.ID
}
h.a.NoError(service.DefaultStore.CreateDalConnection(context.Background(), res))
h.a.NoError(service.DefaultDalConnection.ReloadConnections(context.Background()))
return res
}
func Test_dal_connection_list(t *testing.T) {
h := newHelper(t)
defer h.clearDalConnections()
helpers.AllowMe(h, types.ComponentRbacResource(), "dal-connections.search")
helpers.AllowMe(h, types.DalConnectionRbacResource(0), "read")
h.apiInit().
Get("/dal/connections/").
Header("Accept", "application/json").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
Assert(jsonpath.Len("$.response.set", 1)).
End()
}
func Test_dal_connection_list_forbidden(t *testing.T) {
h := newHelper(t)
defer h.clearDalConnections()
h.apiInit().
Get("/dal/connections/").
Header("Accept", "application/json").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertError("dal-connection.errors.notAllowedToSearch")).
End()
}
func Test_dal_connection_list_forbidden_read(t *testing.T) {
h := newHelper(t)
defer h.clearDalConnections()
helpers.AllowMe(h, types.ComponentRbacResource(), "dal-connections.search")
h.apiInit().
Get("/dal/connections/").
Header("Accept", "application/json").
Expect(t).
Status(http.StatusOK).
Assert(jsonpath.Len("$.response.set", 0)).
End()
}
func Test_dal_connection_create(t *testing.T) {
h := newHelper(t)
defer h.clearDalConnections()
helpers.AllowMe(h, types.ComponentRbacResource(), "dal-connection.create")
h.apiInit().
Post("/dal/connections/").
Body(loadScenarioRequest(t, "generic.json")).
Header("Accept", "application/json").
Header("Content-Type", "application/json").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
Assert(jsonpath.Present("$.response.connectionID")).
End()
}
func Test_dal_connection_create_invalid_type(t *testing.T) {
h := newHelper(t)
defer h.clearDalConnections()
helpers.AllowMe(h, types.ComponentRbacResource(), "dal-connection.create")
h.apiInit().
Post("/dal/connections/").
Body(loadScenarioRequest(t, "generic.json")).
Header("Accept", "application/json").
Header("Content-Type", "application/json").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertErrorP("corteza::system:primary-dal-connection")).
End()
}
func Test_dal_connection_create_forbidden(t *testing.T) {
h := newHelper(t)
defer h.clearDalConnections()
h.apiInit().
Post("/dal/connections/").
Body(loadScenarioRequest(t, "generic.json")).
Header("Accept", "application/json").
Header("Content-Type", "application/json").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertError("dal-connection.errors.notAllowedToCreate")).
End()
}
func Test_dal_connection_update(t *testing.T) {
h := newHelper(t)
defer h.clearDalConnections()
sl := h.createDalConnection(&types.DalConnection{
Handle: "test_connection",
})
helpers.AllowMe(h, types.DalConnectionRbacResource(0), "update", "dal-config.manage")
h.apiInit().
Put(fmt.Sprintf("/dal/connections/%d", sl.ID)).
Header("Accept", "application/json").
Header("Content-Type", "application/json").
Body(loadScenarioRequest(t, "generic.json")).
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
Assert(jsonpath.Equal("$.response.handle", "test_connection_edited")).
End()
}
func Test_dal_connection_update_primary(t *testing.T) {
h := newHelper(t)
defer h.clearDalConnections()
sl := h.getPrimaryConnection()
helpers.AllowMe(h, types.DalConnectionRbacResource(0), "update")
// a bit of a problem with testing primary connection update
//
// when using (for running tests) anything else than connection params specified
// in the generic.json scenario, the update will fail
// with "can not update connection parameters for primary ..."
//
// see Update on dalConnection service.
h.apiInit().
Put(fmt.Sprintf("/dal/connections/%d", sl.ID)).
Header("Accept", "application/json").
Header("Content-Type", "application/json").
Body(loadScenarioRequest(t, "generic.json")).
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
Assert(jsonpath.Equal("$.response.meta.name", "Primary Connection EDITED")).
End()
}
func Test_dal_connection_update_forbidden(t *testing.T) {
h := newHelper(t)
defer h.clearDalConnections()
sl := h.createDalConnection(&types.DalConnection{
Handle: "test_connection",
})
h.apiInit().
Put(fmt.Sprintf("/dal/connections/%d", sl.ID)).
Header("Accept", "application/json").
Header("Content-Type", "application/json").
Body(loadScenarioRequest(t, "generic.json")).
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertError("dal-connection.errors.notAllowedToUpdate")).
End()
}
func Test_dal_connection_read(t *testing.T) {
h := newHelper(t)
defer h.clearDalConnections()
sl := h.createDalConnection(&types.DalConnection{
Handle: "test_connection",
})
helpers.AllowMe(h, types.DalConnectionRbacResource(0), "read")
h.apiInit().
Get(fmt.Sprintf("/dal/connections/%d", sl.ID)).
Header("Accept", "application/json").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
Assert(jsonpath.Present("$.response.connectionID")).
End()
}
func Test_dal_connection_read_forbidden(t *testing.T) {
h := newHelper(t)
defer h.clearDalConnections()
sl := h.createDalConnection(&types.DalConnection{
Handle: "test_connection",
})
h.apiInit().
Get(fmt.Sprintf("/dal/connections/%d", sl.ID)).
Header("Accept", "application/json").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertError("dal-connection.errors.notAllowedToRead")).
End()
}
func Test_dal_connection_delete(t *testing.T) {
h := newHelper(t)
defer h.clearDalConnections()
sl := h.createDalConnection(&types.DalConnection{
Handle: "test_connection",
})
helpers.AllowMe(h, types.DalConnectionRbacResource(0), "delete")
h.apiInit().
Delete(fmt.Sprintf("/dal/connections/%d", sl.ID)).
Header("Accept", "application/json").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
Assert(jsonpath.Equal("$.success.message", "OK")).
End()
}
func Test_dal_connection_delete_forbidden(t *testing.T) {
h := newHelper(t)
defer h.clearDalConnections()
sl := h.createDalConnection(&types.DalConnection{
Handle: "test_connection",
})
h.apiInit().
Delete(fmt.Sprintf("/dal/connections/%d", sl.ID)).
Header("Accept", "application/json").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertError("dal-connection.errors.notAllowedToDelete")).
End()
}
func Test_dal_connection_undelete(t *testing.T) {
h := newHelper(t)
defer h.clearDalConnections()
sl := h.createDalConnection(&types.DalConnection{
Handle: "test_connection",
DeletedAt: &h.cUser.CreatedAt,
DeletedBy: h.cUser.ID,
})
helpers.AllowMe(h, types.DalConnectionRbacResource(0), "delete")
h.apiInit().
Post(fmt.Sprintf("/dal/connections/%d/undelete", sl.ID)).
Header("Accept", "application/json").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
Assert(jsonpath.Equal("$.success.message", "OK")).
End()
}
func Test_dal_connection_undelete_forbidden(t *testing.T) {
h := newHelper(t)
defer h.clearDalConnections()
sl := h.createDalConnection(&types.DalConnection{
Handle: "test_connection",
DeletedAt: &h.cUser.CreatedAt,
DeletedBy: h.cUser.ID,
})
h.apiInit().
Post(fmt.Sprintf("/dal/connections/%d/undelete", sl.ID)).
Header("Accept", "application/json").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertError("dal-connection.errors.notAllowedToUndelete")).
End()
}

View File

@@ -0,0 +1,22 @@
package system
import (
"net/http"
"testing"
"github.com/cortezaproject/corteza/server/tests/helpers"
jsonpath "github.com/steinfletcher/apitest-jsonpath"
)
func Test_dal_crud_driver_list(t *testing.T) {
h := newHelper(t)
h.apiInit().
Get("/dal/drivers/").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
Assert(jsonpath.GreaterThan("$.response.set", 0)).
Assert(jsonpath.Present("$.response.set[0].operations")).
End()
}

View File

@@ -0,0 +1,22 @@
package system
import (
"net/http"
"testing"
"github.com/cortezaproject/corteza/server/tests/helpers"
jsonpath "github.com/steinfletcher/apitest-jsonpath"
)
func Test_dal_driver_list(t *testing.T) {
h := newHelper(t)
h.apiInit().
Get("/dal/drivers/").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
Assert(jsonpath.Len("$.response.set", 1)).
Assert(jsonpath.Present("$.response.set[0].operations")).
End()
}

View File

@@ -0,0 +1,251 @@
package system
import (
"context"
"fmt"
"net/http"
"testing"
"time"
"github.com/cortezaproject/corteza/server/pkg/id"
"github.com/cortezaproject/corteza/server/store"
"github.com/cortezaproject/corteza/server/system/service"
"github.com/cortezaproject/corteza/server/system/types"
"github.com/cortezaproject/corteza/server/tests/helpers"
jsonpath "github.com/steinfletcher/apitest-jsonpath"
)
func (h helper) clearSensitivityLevels() {
h.noError(store.TruncateDalSensitivityLevels(context.Background(), service.DefaultStore))
}
func (h helper) createSensitivityLevel(res *types.DalSensitivityLevel) *types.DalSensitivityLevel {
if res.ID == 0 {
res.ID = id.Next()
}
if res.CreatedAt.IsZero() {
res.CreatedAt = time.Now()
}
h.a.NoError(service.DefaultStore.CreateDalSensitivityLevel(context.Background(), res))
return res
}
func Test_dal_sensitivity_level_list(t *testing.T) {
h := newHelper(t)
defer h.clearSensitivityLevels()
helpers.AllowMe(h, types.ComponentRbacResource(), "dal-sensitivity-level.manage")
h.apiInit().
Get("/dal/sensitivity-levels/").
Header("Accept", "application/json").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
End()
}
func Test_dal_sensitivity_level_list_forbidden(t *testing.T) {
h := newHelper(t)
defer h.clearSensitivityLevels()
h.apiInit().
Get("/dal/sensitivity-levels/").
Header("Accept", "application/json").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertError("dal-sensitivity-level.errors.notAllowedToManage")).
End()
}
func Test_dal_sensitivity_level_create(t *testing.T) {
h := newHelper(t)
defer h.clearSensitivityLevels()
helpers.AllowMe(h, types.ComponentRbacResource(), "dal-sensitivity-level.manage")
h.apiInit().
Post("/dal/sensitivity-levels/").
Body(loadScenarioRequest(t, "generic.json")).
Header("Accept", "application/json").
Header("Content-Type", "application/json").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
Assert(jsonpath.Present("$.response.sensitivityLevelID")).
End()
}
func Test_dal_sensitivity_level_create_forbidden(t *testing.T) {
h := newHelper(t)
defer h.clearSensitivityLevels()
h.apiInit().
Post("/dal/sensitivity-levels/").
Body(loadScenarioRequest(t, "generic.json")).
Header("Accept", "application/json").
Header("Content-Type", "application/json").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertError("dal-sensitivity-level.errors.notAllowedToManage")).
End()
}
func Test_dal_sensitivity_level_update(t *testing.T) {
h := newHelper(t)
defer h.clearSensitivityLevels()
sl := h.createSensitivityLevel(&types.DalSensitivityLevel{
Handle: "test_sl",
})
helpers.AllowMe(h, types.ComponentRbacResource(), "dal-sensitivity-level.manage")
h.apiInit().
Put(fmt.Sprintf("/dal/sensitivity-levels/%d", sl.ID)).
Header("Accept", "application/json").
Header("Content-Type", "application/json").
Body(loadScenarioRequest(t, "generic.json")).
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
Assert(jsonpath.Present("$.response.sensitivityLevelID")).
Assert(jsonpath.Equal("$.response.handle", "private_edited")).
End()
}
func Test_dal_sensitivity_level_update_forbidden(t *testing.T) {
h := newHelper(t)
defer h.clearSensitivityLevels()
sl := h.createSensitivityLevel(&types.DalSensitivityLevel{
Handle: "test_sl",
})
h.apiInit().
Put(fmt.Sprintf("/dal/sensitivity-levels/%d", sl.ID)).
Header("Accept", "application/json").
Header("Content-Type", "application/json").
Body(loadScenarioRequest(t, "generic.json")).
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertError("dal-sensitivity-level.errors.notAllowedToManage")).
End()
}
func Test_dal_sensitivity_level_read(t *testing.T) {
h := newHelper(t)
defer h.clearSensitivityLevels()
sl := h.createSensitivityLevel(&types.DalSensitivityLevel{
Handle: "test_sl",
})
helpers.AllowMe(h, types.ComponentRbacResource(), "dal-sensitivity-level.manage")
h.apiInit().
Get(fmt.Sprintf("/dal/sensitivity-levels/%d", sl.ID)).
Header("Accept", "application/json").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
Assert(jsonpath.Present("$.response.sensitivityLevelID")).
End()
}
func Test_dal_sensitivity_level_read_forbidden(t *testing.T) {
h := newHelper(t)
defer h.clearSensitivityLevels()
sl := h.createSensitivityLevel(&types.DalSensitivityLevel{
Handle: "test_sl",
})
h.apiInit().
Get(fmt.Sprintf("/dal/sensitivity-levels/%d", sl.ID)).
Header("Accept", "application/json").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertError("dal-sensitivity-level.errors.notAllowedToManage")).
End()
}
func Test_dal_sensitivity_level_delete(t *testing.T) {
h := newHelper(t)
defer h.clearSensitivityLevels()
sl := h.createSensitivityLevel(&types.DalSensitivityLevel{
Handle: "test_sl",
})
helpers.AllowMe(h, types.ComponentRbacResource(), "dal-sensitivity-level.manage")
h.apiInit().
Delete(fmt.Sprintf("/dal/sensitivity-levels/%d", sl.ID)).
Header("Accept", "application/json").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
Assert(jsonpath.Equal("$.success.message", "OK")).
End()
}
func Test_dal_sensitivity_level_delete_forbidden(t *testing.T) {
h := newHelper(t)
defer h.clearSensitivityLevels()
sl := h.createSensitivityLevel(&types.DalSensitivityLevel{
Handle: "test_sl",
})
h.apiInit().
Delete(fmt.Sprintf("/dal/sensitivity-levels/%d", sl.ID)).
Header("Accept", "application/json").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertError("dal-sensitivity-level.errors.notAllowedToManage")).
End()
}
func Test_dal_sensitivity_level_undelete(t *testing.T) {
h := newHelper(t)
defer h.clearSensitivityLevels()
sl := h.createSensitivityLevel(&types.DalSensitivityLevel{
Handle: "test_sl",
DeletedAt: &h.cUser.CreatedAt,
DeletedBy: h.cUser.ID,
})
helpers.AllowMe(h, types.ComponentRbacResource(), "dal-sensitivity-level.manage")
h.apiInit().
Post(fmt.Sprintf("/dal/sensitivity-levels/%d/undelete", sl.ID)).
Header("Accept", "application/json").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
Assert(jsonpath.Equal("$.success.message", "OK")).
End()
}
func Test_dal_sensitivity_level_undelete_forbidden(t *testing.T) {
h := newHelper(t)
defer h.clearSensitivityLevels()
sl := h.createSensitivityLevel(&types.DalSensitivityLevel{
Handle: "test_sl",
DeletedAt: &h.cUser.CreatedAt,
DeletedBy: h.cUser.ID,
})
h.apiInit().
Post(fmt.Sprintf("/dal/sensitivity-levels/%d/undelete", sl.ID)).
Header("Accept", "application/json").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertError("dal-sensitivity-level.errors.notAllowedToManage")).
End()
}

View File

@@ -0,0 +1,102 @@
package system
import (
"context"
"fmt"
"github.com/cortezaproject/corteza/server/pkg/id"
"github.com/cortezaproject/corteza/server/store"
"github.com/cortezaproject/corteza/server/system/service"
"github.com/cortezaproject/corteza/server/system/types"
"github.com/cortezaproject/corteza/server/tests/helpers"
"github.com/spf13/cast"
jsonpath "github.com/steinfletcher/apitest-jsonpath"
"net/http"
"testing"
"time"
)
func (h helper) clearDataPrivacyRequestComments() {
h.noError(store.TruncateDataPrivacyRequestComments(context.Background(), service.DefaultStore))
}
func (h helper) createDataPrivacyRequestComment(comment string, requestID uint64) *types.DataPrivacyRequestComment {
res := &types.DataPrivacyRequestComment{
ID: id.Next(),
RequestID: requestID,
Comment: comment,
CreatedAt: time.Now(),
}
h.a.NoError(store.CreateDataPrivacyRequestComment(context.Background(), service.DefaultStore, res))
return res
}
func (h helper) createSampleDataPrivacyRequestComment(requestID ...uint64) *types.DataPrivacyRequestComment {
rID := id.Next()
if len(requestID) == 1 {
rID = requestID[0]
}
return h.createDataPrivacyRequestComment(rs(20), rID)
}
func TestDataPrivacyRequestCommentList(t *testing.T) {
h := newHelper(t)
h.clearDataPrivacyRequests()
h.clearDataPrivacyRequestComments()
request := h.createSampleDataPrivacyRequest()
reqID := request.ID
h.createSampleDataPrivacyRequestComment(reqID)
h.createSampleDataPrivacyRequestComment(reqID)
h.createSampleDataPrivacyRequestComment()
h.createSampleDataPrivacyRequestComment()
h.apiInit().
Get(fmt.Sprintf("/data-privacy/requests/%d/comments/", reqID)).
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
Assert(jsonpath.Len(`$.response.set`, 2)).
End()
}
func TestDataPrivacyRequestCreateComment(t *testing.T) {
h := newHelper(t)
h.clearDataPrivacyRequestComments()
request := h.createSampleDataPrivacyRequest()
reqID := request.ID
comment := rs(20)
helpers.AllowMe(h, types.DataPrivacyRequestRbacResource(0), "read")
h.apiInit().
Post(fmt.Sprintf("/data-privacy/requests/%d/comments/", reqID)).
Header("Accept", "application/json").
FormData("comment", comment).
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
Assert(jsonpath.Present(`$.response.requestID`)).
Assert(jsonpath.Present(`$.response.createdAt`)).
Assert(jsonpath.Present(`$.response.createdBy`)).
Assert(jsonpath.Equal(`$.response.requestID`, cast.ToString(reqID))).
Assert(jsonpath.Equal(`$.response.comment`, comment)).
End()
}
func TestDataPrivacyRequestCreateCommentForbidden(t *testing.T) {
h := newHelper(t)
h.clearDataPrivacyRequestComments()
comment := rs(20)
h.apiInit().
Post(fmt.Sprintf("/data-privacy/requests/%d/comments/", id.Next())).
Header("Accept", "application/json").
FormData("comment", comment).
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertError("data-privacy.errors.notFound")).
End()
}

View File

@@ -0,0 +1,241 @@
package system
import (
"context"
"net/http"
"testing"
"time"
"github.com/cortezaproject/corteza/server/pkg/id"
"github.com/cortezaproject/corteza/server/store"
"github.com/cortezaproject/corteza/server/system/service"
"github.com/cortezaproject/corteza/server/system/types"
"github.com/cortezaproject/corteza/server/tests/helpers"
"github.com/spf13/cast"
jsonpath "github.com/steinfletcher/apitest-jsonpath"
)
func (h helper) clearDataPrivacyRequests() {
h.noError(store.TruncateDataPrivacyRequests(context.Background(), service.DefaultStore))
}
func (h helper) createDataPrivacyRequest(kind types.RequestKind, status types.RequestStatus, requestedBy uint64) *types.DataPrivacyRequest {
res := &types.DataPrivacyRequest{
ID: id.Next(),
Kind: types.RequestKindCorrect,
Status: types.RequestStatusPending,
RequestedAt: time.Now(),
RequestedBy: requestedBy,
CreatedAt: time.Now(),
}
if len(kind) > 0 {
res.Kind = kind
}
if len(status) > 0 {
res.Status = status
}
h.a.NoError(store.CreateDataPrivacyRequest(context.Background(), service.DefaultStore, res))
return res
}
func (h helper) createSampleDataPrivacyRequest() *types.DataPrivacyRequest {
return h.createDataPrivacyRequest("", "", 0)
}
func TestDataPrivacyRequestList(t *testing.T) {
h := newHelper(t)
h.clearDataPrivacyRequests()
h.createSampleDataPrivacyRequest()
h.createSampleDataPrivacyRequest()
helpers.AllowMe(h, types.ComponentRbacResource(), "data-privacy-requests.search")
helpers.AllowMe(h, types.DataPrivacyRequestRbacResource(0), "read")
h.apiInit().
Get("/data-privacy/requests/").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
Assert(jsonpath.Len(`$.response.set`, 2)).
End()
}
func TestDataPrivacyRequestListWithPaging(t *testing.T) {
h := newHelper(t)
h.clearDataPrivacyRequests()
seedCount := 40
for i := 0; i < seedCount; i++ {
h.createSampleDataPrivacyRequest()
}
helpers.AllowMe(h, types.ComponentRbacResource(), "data-privacy-requests.search")
helpers.AllowMe(h, types.DataPrivacyRequestRbacResource(0), "read")
var aux = struct {
Response struct {
Filter struct {
NextPage *string
PrevPage *string
}
}
}{}
h.apiInit().
Get("/data-privacy/requests/").
Query("limit", "10").
// Query("sort", "kind,createdAt+DESC").
Query("sort", "kind").
Header("Accept", "application/json").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
Assert(jsonpath.Present(`$.response.filter`)).
Assert(jsonpath.Present(`$.response.set`)).
Assert(jsonpath.Len(`$.response.set`, 10)).
Assert(jsonpath.Present(`$.response.filter.nextPage`)).
End().
JSON(&aux)
h.a.NotNil(aux.Response.Filter.NextPage)
h.apiInit().
Get("/data-privacy/requests/").
Header("Accept", "application/json").
Query("limit", "10").
Query("sort", "kind").
// Query("sort", "kind,createdAt+DESC").
Query("pageCursor", *aux.Response.Filter.NextPage).
Expect(t).
Status(http.StatusOK).
Assert(jsonpath.Len(`$.response.set`, 10)).
Assert(jsonpath.Present(`$.response.filter.prevPage`)).
Assert(jsonpath.Present(`$.response.filter.nextPage`)).
End().
JSON(&aux)
h.a.NotNil(aux.Response.Filter.PrevPage)
h.a.NotNil(aux.Response.Filter.NextPage)
}
func TestDataPrivacyRequestListFilters(t *testing.T) {
h := newHelper(t)
h.clearDataPrivacyRequests()
h.createSampleDataPrivacyRequest()
h.createSampleDataPrivacyRequest()
h.createDataPrivacyRequest("", types.RequestStatusApproved, 0)
h.createDataPrivacyRequest(types.RequestKindExport, types.RequestStatusApproved, 0)
helpers.AllowMe(h, types.ComponentRbacResource(), "data-privacy-requests.search")
helpers.AllowMe(h, types.DataPrivacyRequestRbacResource(0), "read")
h.apiInit().
Get("/data-privacy/requests/").
Query("query", types.RequestStatusApproved.String()).
Query("kind", types.RequestKindExport.String()).
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
Assert(jsonpath.Len(`$.response.set`, 1)).
End()
}
func TestDataPrivacyRequestListWithRequestedByFilter(t *testing.T) {
h := newHelper(t)
h.clearDataPrivacyRequests()
user := h.createUserWithEmail(h.randEmail())
h.createSampleDataPrivacyRequest()
h.createSampleDataPrivacyRequest()
h.createDataPrivacyRequest("", types.RequestStatusApproved, user.ID)
h.createDataPrivacyRequest(types.RequestKindExport, types.RequestStatusApproved, user.ID)
h.createSampleDataPrivacyRequest()
helpers.AllowMe(h, types.ComponentRbacResource(), "data-privacy-requests.search")
helpers.AllowMe(h, types.DataPrivacyRequestRbacResource(0), "read")
h.apiInit().
Get("/data-privacy/requests/").
Query("requestedBy", cast.ToString(user.ID)).
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
Assert(jsonpath.Len(`$.response.set`, 2)).
End()
}
func TestDataPrivacyRequestRead(t *testing.T) {
h := newHelper(t)
h.clearDataPrivacyRequests()
client := h.createSampleDataPrivacyRequest()
helpers.AllowMe(h, types.DataPrivacyRequestRbacResource(0), "read")
h.apiInit().
Getf("/data-privacy/requests/%d", client.ID).
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
End()
}
func TestDataPrivacyRequestCreate(t *testing.T) {
h := newHelper(t)
h.clearDataPrivacyRequests()
helpers.AllowMe(h, types.ComponentRbacResource(), "data-privacy-request.create")
h.apiInit().
Post("/data-privacy/requests/").
Header("Accept", "application/json").
FormData("kind", "correct").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
Assert(jsonpath.Present(`$.response.requestID`)).
Assert(jsonpath.Equal(`$.response.kind`, types.RequestKindCorrect.String())).
Assert(jsonpath.Equal(`$.response.status`, types.RequestStatusPending.String())).
End()
}
func TestDataPrivacyRequestUpdateStatus(t *testing.T) {
h := newHelper(t)
h.clearDataPrivacyRequests()
req := h.createSampleDataPrivacyRequest()
helpers.AllowMe(h, types.DataPrivacyRequestRbacResource(0), "approve")
h.apiInit().
Patchf("/data-privacy/requests/%d/status/approved", req.ID).
Header("Accept", "application/json").
JSON(helpers.JSON(req)).
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
Assert(jsonpath.Equal(`$.response.status`, types.RequestStatusApproved.String())).
End()
}
func TestDataPrivacyRequestUpdateStatusForbidden(t *testing.T) {
h := newHelper(t)
h.clearDataPrivacyRequests()
req := h.createSampleDataPrivacyRequest()
h.apiInit().
Patchf("/data-privacy/requests/%d/status/approved", req.ID).
Header("Accept", "application/json").
JSON(helpers.JSON(req)).
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertError("data-privacy.errors.notAllowedToApprove")).
End()
}

View File

@@ -0,0 +1,230 @@
package system
import (
"context"
"embed"
"errors"
"io"
"io/ioutil"
"os"
"path"
"testing"
"github.com/cortezaproject/corteza/server/app"
"github.com/cortezaproject/corteza/server/auth/handlers"
"github.com/cortezaproject/corteza/server/auth/request"
"github.com/cortezaproject/corteza/server/auth/saml"
"github.com/cortezaproject/corteza/server/pkg/api/server"
"github.com/cortezaproject/corteza/server/pkg/auth"
"github.com/cortezaproject/corteza/server/pkg/cli"
"github.com/cortezaproject/corteza/server/pkg/id"
"github.com/cortezaproject/corteza/server/pkg/label"
ltype "github.com/cortezaproject/corteza/server/pkg/label/types"
"github.com/cortezaproject/corteza/server/pkg/locale"
"github.com/cortezaproject/corteza/server/pkg/logger"
"github.com/cortezaproject/corteza/server/pkg/objstore/plain"
"github.com/cortezaproject/corteza/server/pkg/rand"
"github.com/cortezaproject/corteza/server/pkg/rbac"
"github.com/cortezaproject/corteza/server/store"
"github.com/cortezaproject/corteza/server/store/adapters/rdbms/drivers/sqlite"
"github.com/cortezaproject/corteza/server/system/rest"
"github.com/cortezaproject/corteza/server/system/service"
"github.com/cortezaproject/corteza/server/system/types"
"github.com/cortezaproject/corteza/server/tests/helpers"
"github.com/go-chi/chi/v5"
_ "github.com/joho/godotenv/autoload"
"github.com/spf13/afero"
"github.com/steinfletcher/apitest"
"github.com/stretchr/testify/require"
"go.uber.org/zap"
"golang.org/x/text/language"
)
//go:embed static
var mockData embed.FS
type (
helper struct {
t *testing.T
a *require.Assertions
sp *saml.SamlSPService
cUser *types.User
roleID uint64
token []byte
data embed.FS
}
)
var (
testApp *app.CortezaApp
r chi.Router
hh *handlers.AuthHandlers
)
func init() {
helpers.RecursiveDotEnvLoad()
}
// random string, 10 chars long by default
func rs(a ...int) string {
var l = 10
if len(a) > 0 {
l = a[0]
}
return string(rand.Bytes(l))
}
func InitTestApp() {
var sm *request.SessionManager
if testApp == nil {
ctx := cli.Context()
testApp = helpers.NewIntegrationTestApp(ctx, func(app *app.CortezaApp) (err error) {
service.CurrentSettings.Auth.External.Enabled = true
service.DefaultObjectStore, err = plain.NewWithAfero(afero.NewMemMapFs(), "test")
if err != nil {
return err
}
service.DefaultStore, err = sqlite.ConnectInMemory(ctx)
if err != nil {
return err
}
// Tests should be executed w/o any locales
locale.SetGlobal(locale.Static(&locale.Language{Tag: language.Und}))
sm = request.NewSessionManager(service.DefaultStore, app.Opt.Auth, service.DefaultLogger)
return nil
})
}
sp, _ := loadSAMLService()
hh = &handlers.AuthHandlers{
SamlSPService: sp,
Log: zap.NewNop(),
AuthService: service.DefaultAuth,
SessionManager: sm,
}
if r == nil {
r = chi.NewRouter()
r.Use(server.BaseMiddleware(false, logger.Default())...)
helpers.BindAuthMiddleware(r)
r.Group(rest.MountRoutes())
hh.MountHttpRoutes(r)
}
}
func TestMain(m *testing.M) {
InitTestApp()
os.Exit(m.Run())
}
func newHelper(t *testing.T) helper {
h := helper{
t: t,
a: require.New(t),
roleID: id.Next(),
cUser: &types.User{
ID: id.Next(),
},
data: mockData,
}
h.cUser.SetRoles(h.roleID)
helpers.UpdateRBAC(h.roleID)
h.mockPermissionsWithAccess()
var err error
ctx := context.Background()
h.token, err = auth.TokenIssuer.Issue(ctx, auth.WithIdentity(h.cUser))
if err != nil {
panic(err)
}
return h
}
func (h helper) MyRole() uint64 {
return h.roleID
}
// Returns context w/ security details
func (h helper) secCtx() context.Context {
return auth.SetIdentityToContext(context.Background(), h.cUser)
}
// apitest basics, initialize, set handler, add auth
func (h helper) apiInit() *apitest.APITest {
InitTestApp()
return apitest.
New().
Handler(r).
Intercept(helpers.ReqHeaderRawAuthBearer(h.token))
}
func (h helper) mockPermissions(rules ...*rbac.Rule) {
h.a.NoError(rbac.Global().Grant(
// TestService we use does not have any backend storage,
context.Background(),
rules...,
))
}
// Prepends allow access rule for system service for everyone
func (h helper) mockPermissionsWithAccess(rules ...*rbac.Rule) {
h.mockPermissions(rules...)
}
// Unwraps error before it passes it to the tester
func (h helper) noError(err error) {
for errors.Unwrap(err) != nil {
err = errors.Unwrap(err)
}
h.a.NoError(err)
}
func (h helper) setLabel(res label.LabeledResource, name, value string) {
h.a.NoError(store.UpsertLabel(h.secCtx(), service.DefaultStore, &ltype.Label{
Kind: res.LabelResourceKind(),
ResourceID: res.LabelResourceID(),
Name: name,
Value: ltype.LabelValue{Val: value},
}))
}
func (h helper) assertBody(e string, s io.ReadCloser) {
b, err := io.ReadAll(s)
h.a.NoError(err)
h.a.Equal(e, string(b))
}
func (h helper) clearTemplates() {
h.noError(store.TruncateTemplates(context.Background(), service.DefaultStore))
}
func readStaticFile(f string) []byte {
c, _ := mockData.ReadFile(f)
return c
}
func loadScenarioRequest(t *testing.T, req string) string {
f, err := os.Open(path.Join("testdata", t.Name()[5:], req))
require.NoError(t, err)
defer f.Close()
bb, err := ioutil.ReadAll(f)
require.NoError(t, err)
return string(bb)
}

View File

@@ -0,0 +1,602 @@
package system
import (
"context"
"fmt"
"net/http"
"strconv"
"testing"
"time"
"github.com/cortezaproject/corteza/server/pkg/id"
"github.com/cortezaproject/corteza/server/store"
"github.com/cortezaproject/corteza/server/system/service"
"github.com/cortezaproject/corteza/server/system/types"
"github.com/cortezaproject/corteza/server/tests/helpers"
jsonpath "github.com/steinfletcher/apitest-jsonpath"
)
func (h helper) clearNotifications() {
h.noError(store.TruncateNotifications(context.Background(), service.DefaultStore))
}
func (h helper) makeNotification() *types.Notification {
return h.notification(h.cUser.ID, types.NotificationKindSimple, false)
}
func (h helper) makeNotificationByUserID(userID uint64) *types.Notification {
return h.notification(userID, types.NotificationKindSimple, false)
}
func (h helper) makeReadNotification() *types.Notification {
ntf := h.makeNotification()
now := time.Now()
ntf.ReadAt = &now
h.noError(store.UpdateNotification(context.Background(), service.DefaultStore, ntf))
return ntf
}
func (h helper) makeDeletedNotification() *types.Notification {
return h.notification(h.cUser.ID, types.NotificationKindSimple, true)
}
func (h helper) notification(userID uint64, notificationKind types.NotificationKind, deleted bool) *types.Notification {
ntf := &types.Notification{
Kind: notificationKind,
Config: types.NotificationConfig{
Simple: types.SimpleNotificationConfig{
Title: "Test notification",
Description: "This is a test notification",
},
},
Recipient: userID,
CreatedBy: h.cUser.ID,
}
ntf.ID = id.Next()
ntf.CreatedAt = time.Now()
if deleted {
now := time.Now()
ntf.DeletedAt = &now
}
h.noError(store.CreateNotification(context.Background(), service.DefaultStore, ntf))
return ntf
}
func (h helper) lookupNotificationByID(ID uint64) *types.Notification {
ntf, err := store.LookupNotificationByID(context.Background(), service.DefaultStore, ID)
h.noError(err)
return ntf
}
func TestNotificationCreate(t *testing.T) {
h := newHelper(t)
h.clearNotifications()
ntfTemplate := h.makeNotification()
h.apiInit().
Post("/notification/").
Header("Accept", "application/json").
Header("Content-Type", "application/json").
JSON(map[string]interface{}{
"kind": string(ntfTemplate.Kind),
"config": ntfTemplate.Config,
"recipient": strconv.FormatUint(ntfTemplate.Recipient, 10),
}).
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
Assert(jsonpath.Equal(`$.response.kind`, string(ntfTemplate.Kind))).
Assert(jsonpath.Equal(`$.response.config.simple.title`, ntfTemplate.Config.Simple.Title)).
Assert(jsonpath.Equal(`$.response.config.simple.description`, ntfTemplate.Config.Simple.Description)).
End()
}
func TestNotificationRead(t *testing.T) {
h := newHelper(t)
h.clearNotifications()
ntf := h.makeNotification()
h.apiInit().
Get(fmt.Sprintf("/notification/%d", ntf.ID)).
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
Assert(jsonpath.Equal(`$.response.kind`, string(ntf.Kind))).
Assert(jsonpath.Equal(`$.response.config.simple.title`, ntf.Config.Simple.Title)).
End()
}
func TestNotificationReadForbidden(t *testing.T) {
h := newHelper(t)
h.clearNotifications()
ntf := h.makeNotificationByUserID(id.Next())
h.apiInit().
Get(fmt.Sprintf("/notification/%d", ntf.ID)).
Header("Accept", "application/json").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertError("notification.errors.notFound")).
End()
}
func TestNotificationList(t *testing.T) {
h := newHelper(t)
h.clearNotifications()
// Create 3 notifications: 2 for current user (1 read, 1 unread) and 1 for another user
h.makeNotification()
h.makeReadNotification()
h.makeNotificationByUserID(id.Next())
h.apiInit().
Get("/notification/").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
Assert(jsonpath.Len("$.response.set", 1)).
End()
}
func TestNotificationListUnreadOnly(t *testing.T) {
h := newHelper(t)
h.clearNotifications()
// Create one unread notification
h.makeNotification()
// Create one read notification
h.makeReadNotification()
// Test with read=0 which should return only unread notifications
h.apiInit().
Get("/notification/").
Query("read", "0").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
Assert(jsonpath.Len("$.response.set", 1)).
End()
}
func TestNotificationListWithRead(t *testing.T) {
h := newHelper(t)
h.clearNotifications()
// Create one unread notification
h.makeNotification()
// Create one read notification
h.makeReadNotification()
// Test with read=1 which should return both read and unread notifications
h.apiInit().
Get("/notification/").
Query("read", "1").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
Assert(jsonpath.Len("$.response.set", 2)).
End()
}
func TestNotificationListReadOnly(t *testing.T) {
h := newHelper(t)
h.clearNotifications()
// Create one unread notification
h.makeNotification()
// Create one read notification
h.makeReadNotification()
// Test with read=2 which should return only read notifications
h.apiInit().
Get("/notification/").
Query("read", "2").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
Assert(jsonpath.Len("$.response.set", 1)).
End()
}
func TestNotificationListNoDeleted(t *testing.T) {
h := newHelper(t)
h.clearNotifications()
h.makeNotification()
h.makeDeletedNotification()
h.apiInit().
Get("/notification/").
Query("deleted", "0").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
Assert(jsonpath.Len("$.response.set", 1)).
End()
}
func TestNotificationListWithDeleted(t *testing.T) {
h := newHelper(t)
h.clearNotifications()
h.makeNotification()
h.makeDeletedNotification()
h.apiInit().
Get("/notification/").
Query("deleted", "1").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
Assert(jsonpath.Len("$.response.set", 2)).
End()
}
func TestNotificationListDeletedOnly(t *testing.T) {
h := newHelper(t)
h.clearNotifications()
h.makeNotification()
h.makeDeletedNotification()
h.apiInit().
Get("/notification/").
Query("deleted", "2").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
Assert(jsonpath.Len("$.response.set", 1)).
End()
}
func TestNotificationUpdate(t *testing.T) {
h := newHelper(t)
h.clearNotifications()
ntf := h.makeNotification()
// Create update data
updatedConfig := types.NotificationConfig{
Simple: types.SimpleNotificationConfig{
Title: "Updated Title",
Description: "Updated Description",
},
}
h.apiInit().
Put(fmt.Sprintf("/notification/%d", ntf.ID)).
Header("Accept", "application/json").
Header("Content-Type", "application/json").
JSON(map[string]interface{}{
"kind": string(ntf.Kind),
"config": updatedConfig,
}).
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
Assert(jsonpath.Equal(`$.response.config.simple.title`, updatedConfig.Simple.Title)).
Assert(jsonpath.Equal(`$.response.config.simple.description`, updatedConfig.Simple.Description)).
End()
}
func TestNotificationUpdateForbidden(t *testing.T) {
h := newHelper(t)
h.clearNotifications()
ntf := h.makeNotificationByUserID(id.Next())
// Create update data
updatedConfig := types.NotificationConfig{
Simple: types.SimpleNotificationConfig{
Title: "Updated Title",
Description: "Updated Description",
},
}
h.apiInit().
Put(fmt.Sprintf("/notification/%d", ntf.ID)).
Header("Accept", "application/json").
Header("Content-Type", "application/json").
JSON(map[string]interface{}{
"kind": string(ntf.Kind),
"config": updatedConfig,
}).
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertError("notification.errors.notFound")).
End()
}
func TestNotificationDelete(t *testing.T) {
h := newHelper(t)
h.clearNotifications()
ntf := h.makeNotification()
h.apiInit().
Delete(fmt.Sprintf("/notification/%d", ntf.ID)).
Header("Accept", "application/json").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
End()
// Verify notification is marked as deleted
deletedNtf := h.lookupNotificationByID(ntf.ID)
if deletedNtf.DeletedAt == nil {
t.Error("notification should be marked as deleted")
}
}
func TestNotificationDeleteForbidden(t *testing.T) {
h := newHelper(t)
h.clearNotifications()
ntf := h.makeNotificationByUserID(id.Next())
h.apiInit().
Delete(fmt.Sprintf("/notification/%d", ntf.ID)).
Header("Accept", "application/json").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertError("notification.errors.notFound")).
End()
}
func TestNotificationMarkAsRead(t *testing.T) {
h := newHelper(t)
h.clearNotifications()
ntf := h.makeNotification()
h.apiInit().
Patch(fmt.Sprintf("/notification/%d/read", ntf.ID)).
Header("Accept", "application/json").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
End()
// Verify notification is marked as read
readNtf := h.lookupNotificationByID(ntf.ID)
if readNtf.ReadAt == nil {
t.Error("notification should be marked as read")
}
}
func TestNotificationMarkAsReadForbidden(t *testing.T) {
h := newHelper(t)
h.clearNotifications()
ntf := h.makeNotificationByUserID(id.Next())
h.apiInit().
Patch(fmt.Sprintf("/notification/%d/read", ntf.ID)).
Header("Accept", "application/json").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertError("notification.errors.notAllowedToRead")).
End()
}
func TestNotificationMarkAsUnread(t *testing.T) {
h := newHelper(t)
h.clearNotifications()
ntf := h.makeNotification()
// Mark as read first so we can verify unread transition
h.apiInit().
Patch(fmt.Sprintf("/notification/%d/read", ntf.ID)).
Header("Accept", "application/json").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
End()
h.apiInit().
Patch(fmt.Sprintf("/notification/%d/unread", ntf.ID)).
Header("Accept", "application/json").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
End()
// Verify notification is marked as unread
unreadNtf := h.lookupNotificationByID(ntf.ID)
if unreadNtf.ReadAt != nil {
t.Error("notification should be marked as unread")
}
}
func TestNotificationMarkAsUnreadForbidden(t *testing.T) {
h := newHelper(t)
h.clearNotifications()
ntf := h.makeNotificationByUserID(id.Next())
h.apiInit().
Patch(fmt.Sprintf("/notification/%d/unread", ntf.ID)).
Header("Accept", "application/json").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertError("notification.errors.notAllowedToRead")).
End()
}
func TestNotificationMarkAllAsRead(t *testing.T) {
h := newHelper(t)
h.clearNotifications()
// Create multiple unread notifications
h.makeNotification()
h.makeNotification()
h.makeNotification()
h.apiInit().
Patch("/notification/all/read").
Header("Accept", "application/json").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
End()
// List read notifications and verify all are marked as read
h.apiInit().
Get("/notification/").
Query("read", "2").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
Assert(jsonpath.Len("$.response.set", 3)).
End()
}
func TestNotificationMarkAllAsUnread(t *testing.T) {
h := newHelper(t)
h.clearNotifications()
// Create multiple notifications and mark them as read
n1 := h.makeNotification()
n2 := h.makeNotification()
n3 := h.makeNotification()
h.apiInit().
Patch("/notification/all/read").
Header("Accept", "application/json").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
End()
// Now mark all as unread
h.apiInit().
Patch("/notification/all/unread").
Header("Accept", "application/json").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
End()
// Verify notifications are marked as unread
for _, n := range []uint64{n1.ID, n2.ID, n3.ID} {
ntf := h.lookupNotificationByID(n)
if ntf.ReadAt != nil {
t.Errorf("notification %d should be marked as unread", n)
}
}
}
func TestNotificationAssign_forbidden(t *testing.T) {
h := newHelper(t)
h.clearNotifications()
// Try to create a notification for another user without the proper permission
h.apiInit().
Post("/notification/").
Header("Accept", "application/json").
Header("Content-Type", "application/json").
JSON(map[string]interface{}{
"kind": string(types.NotificationKindSimple),
"config": types.NotificationConfig{
Simple: types.SimpleNotificationConfig{
Title: "Test notification",
Description: "This is a test notification",
},
},
"recipient": strconv.FormatUint(id.Next(), 10),
}).
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertError("notification.errors.notAllowedToAssign")).
End()
}
func TestNotificationAssign(t *testing.T) {
h := newHelper(t)
h.clearNotifications()
// Allow current user to assign notifications
helpers.AllowMe(h, types.ComponentRbacResource(), "notification.assign")
otherUserID := id.Next()
// Create a notification for another user with the permission
h.apiInit().
Post("/notification/").
Header("Accept", "application/json").
Header("Content-Type", "application/json").
JSON(map[string]interface{}{
"kind": string(types.NotificationKindSimple),
"config": types.NotificationConfig{
Simple: types.SimpleNotificationConfig{
Title: "Test notification",
Description: "This is a test notification",
},
},
"recipient": strconv.FormatUint(otherUserID, 10),
}).
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
Assert(jsonpath.Equal(`$.response.kind`, string(types.NotificationKindSimple))).
Assert(jsonpath.Equal(`$.response.recipient`, strconv.FormatUint(otherUserID, 10))).
End()
}
func TestNotificationUpdateRecipient_forbidden(t *testing.T) {
h := newHelper(t)
h.clearNotifications()
ntf := h.makeNotification()
otherUserID := id.Next()
// Try to update a notification with a different recipient without permission
h.apiInit().
Put(fmt.Sprintf("/notification/%d", ntf.ID)).
Header("Accept", "application/json").
Header("Content-Type", "application/json").
JSON(map[string]interface{}{
"kind": string(ntf.Kind),
"config": ntf.Config,
"recipient": strconv.FormatUint(otherUserID, 10),
}).
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertError("notification.errors.notAllowedToAssign")).
End()
}
func TestNotificationUpdateRecipient(t *testing.T) {
h := newHelper(t)
h.clearNotifications()
// Allow current user to assign notifications
helpers.AllowMe(h, types.ComponentRbacResource(), "notification.assign")
ntf := h.makeNotification()
otherUserID := id.Next()
// Update a notification with a different recipient with permission
h.apiInit().
Put(fmt.Sprintf("/notification/%d", ntf.ID)).
Header("Accept", "application/json").
Header("Content-Type", "application/json").
JSON(map[string]interface{}{
"kind": string(ntf.Kind),
"config": ntf.Config,
"recipient": strconv.FormatUint(otherUserID, 10),
}).
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
Assert(jsonpath.Equal(`$.response.recipient`, strconv.FormatUint(otherUserID, 10))).
End()
}

View File

@@ -0,0 +1,280 @@
package system
import (
"fmt"
"net/http"
"strconv"
"testing"
"github.com/cortezaproject/corteza/server/pkg/id"
jsonpath "github.com/steinfletcher/apitest-jsonpath"
"github.com/cortezaproject/corteza/server/pkg/rbac"
"github.com/cortezaproject/corteza/server/system/types"
"github.com/cortezaproject/corteza/server/tests/helpers"
)
func TestPermissionsEffective(t *testing.T) {
h := newHelper(t)
helpers.DenyMe(h, types.ComponentRbacResource(), "user.create")
h.apiInit().
Get("/permissions/effective").
Header("Accept", "application/json").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
End()
}
func TestPermissionsList(t *testing.T) {
h := newHelper(t)
helpers.AllowMe(h, types.ComponentRbacResource(), "grant")
json := h.apiInit().
Get("/permissions/").
Header("Accept", "application/json").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
Assert(jsonpath.Present(fmt.Sprintf(`$.response[? @.type=="%s"]`, types.ComponentResourceType))).
End()
fmt.Println("json: ", json.Response.Body)
}
func TestPermissionsRead(t *testing.T) {
h := newHelper(t)
helpers.AllowMe(h, types.ComponentRbacResource(), "grant")
helpers.DenyMe(h, types.ComponentRbacResource(), "user.create")
h.apiInit().
Get(fmt.Sprintf("/permissions/%d/rules", h.roleID)).
Header("Accept", "application/json").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
End()
}
func TestPermissionsReadWithFilter(t *testing.T) {
h := newHelper(t)
helpers.AllowMe(h, types.ComponentRbacResource(), "grant")
helpers.DenyMe(h, types.ComponentRbacResource(), "user.create")
// Specific resource related rules
testID := id.Next()
helpers.AllowMe(h, types.UserRbacResource(testID), "read")
helpers.AllowMe(h, types.UserRbacResource(id.Next()), "update")
t.Log("all component-level and wildcard rules")
h.apiInit().
Getf("/permissions/%d/rules", h.roleID).
Header("Accept", "application/json").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
Assert(jsonpath.Len(`$.response`, 2)).
End()
t.Log("no rules for all-users")
h.apiInit().
Getf("/permissions/%d/rules", h.roleID).
Query("resource", "corteza::system:user/*").
Header("Accept", "application/json").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
Assert(jsonpath.Len(`$.response`, 0)).
End()
t.Log("1 rule for specific user")
h.apiInit().
Getf("/permissions/%d/rules", h.roleID).
Query("resource", fmt.Sprintf("corteza::system:user/%d", testID)).
Header("Accept", "application/json").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
Assert(jsonpath.Len(`$.response`, 1)).
End()
}
func TestPermissionsUpdate(t *testing.T) {
h := newHelper(t)
helpers.AllowMe(h, types.ComponentRbacResource(), "grant")
h.apiInit().
Patch(fmt.Sprintf("/permissions/%d/rules", h.roleID)).
Header("Accept", "application/json").
JSON(fmt.Sprintf(`{"rules":[{"resource":"%s","operation":"user.create","access":"allow"}]}`, types.ComponentRbacResource())).
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
End()
}
func TestPermissionsDelete(t *testing.T) {
h := newHelper(t)
p := rbac.Global()
// Make sure our user can grant
helpers.AllowMe(h, types.ComponentRbacResource(), "grant")
// New role.
permDelRole := h.roleID + 1
h.a.Len(rbac.Global().FindRulesByRoleID(permDelRole), 0)
// Setup a few fake rules for new role
helpers.Grant(rbac.AllowRule(permDelRole, types.ComponentRbacResource(), "user.create"))
h.a.Len(p.FindRulesByRoleID(permDelRole), 1)
h.apiInit().
Delete(fmt.Sprintf("/permissions/%d/rules", permDelRole)).
Header("Accept", "application/json").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
End()
// Make sure all rules for this role are deleted
for _, r := range p.FindRulesByRoleID(permDelRole) {
h.a.True(r.Access == rbac.Inherit)
}
}
func TestPermissionsTrace(t *testing.T) {
h := newHelper(t)
helpers.AllowMe(h, types.ComponentRbacResource(), "grant")
h.apiInit().
Get("/permissions/trace").
Query("roleID[]", "1").
Header("Accept", "application/json").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
Assert(jsonpath.Present(`$.response`)).
End()
}
func TestPermissionsCloneToSingleRole(t *testing.T) {
h := newHelper(t)
p := rbac.Global()
// Make sure our user can grant
helpers.AllowMe(h, types.ComponentRbacResource(), "grant")
// New role.
roleS := h.roleID + 1
roleT := h.roleID + 2
h.a.Len(rbac.Global().FindRulesByRoleID(roleS), 0)
h.a.Len(rbac.Global().FindRulesByRoleID(roleT), 0)
// Set up a few fake rules for new role
helpers.Grant(rbac.AllowRule(roleS, types.ComponentRbacResource(), "user.create"))
helpers.Grant(rbac.AllowRule(roleT, types.ComponentRbacResource(), "user.update"))
helpers.Grant(rbac.AllowRule(roleT, types.ComponentRbacResource(), "user.delete"))
h.a.Len(p.FindRulesByRoleID(roleS), 1)
h.a.Len(p.FindRulesByRoleID(roleT), 2)
h.apiInit().
Post(fmt.Sprintf("/roles/%d/rules/clone", roleS)).
Query("cloneToRoleID", strconv.FormatUint(roleT, 10)).
Header("Accept", "application/json").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
End()
// Make sure all rules for role S are intact
h.a.Len(p.FindRulesByRoleID(roleS), 1)
// Make sure all rules for role T are cloned from role S
h.a.Len(p.FindRulesByRoleID(roleT), 1)
}
func TestPermissionsCloneToMultipleRole(t *testing.T) {
h := newHelper(t)
p := rbac.Global()
// Make sure our user can grant
helpers.AllowMe(h, types.ComponentRbacResource(), "grant")
// New role.
roleS := h.roleID + 1
roleT := h.roleID + 2
roleY := h.roleID + 3
h.a.Len(rbac.Global().FindRulesByRoleID(roleS), 0)
h.a.Len(rbac.Global().FindRulesByRoleID(roleT), 0)
h.a.Len(rbac.Global().FindRulesByRoleID(roleY), 0)
// Set up a few fake rules for new role
helpers.Grant(rbac.AllowRule(roleS, types.ComponentRbacResource(), "user.create"))
helpers.Grant(rbac.AllowRule(roleT, types.ComponentRbacResource(), "user.update"))
helpers.Grant(rbac.AllowRule(roleT, types.ComponentRbacResource(), "user.delete"))
helpers.Grant(rbac.AllowRule(roleY, types.ComponentRbacResource(), "user.create"))
helpers.Grant(rbac.AllowRule(roleY, types.ComponentRbacResource(), "user.update"))
helpers.Grant(rbac.AllowRule(roleY, types.ComponentRbacResource(), "user.delete"))
h.a.Len(p.FindRulesByRoleID(roleS), 1)
h.a.Len(p.FindRulesByRoleID(roleT), 2)
h.a.Len(p.FindRulesByRoleID(roleY), 3)
h.apiInit().
Post(fmt.Sprintf("/roles/%d/rules/clone", roleS)).
Query("cloneToRoleID", strconv.FormatUint(roleT, 10)).
Query("cloneToRoleID", strconv.FormatUint(roleY, 10)).
Header("Accept", "application/json").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
End()
// Make sure all rules for role S are intact
h.a.Len(p.FindRulesByRoleID(roleS), 1)
// Make sure all rules for role T are cloned from role S
h.a.Len(p.FindRulesByRoleID(roleT), 1)
// Make sure all rules for role Y are cloned from role S
h.a.Len(p.FindRulesByRoleID(roleY), 1)
}
func TestPermissionsCloneNotAllowed(t *testing.T) {
h := newHelper(t)
p := rbac.Global()
// New role.
roleS := h.roleID + 1
roleT := h.roleID + 2
h.a.Len(rbac.Global().FindRulesByRoleID(roleS), 0)
h.a.Len(rbac.Global().FindRulesByRoleID(roleT), 0)
// Set up a few fake rules for new role
helpers.Grant(rbac.AllowRule(roleS, types.ComponentRbacResource(), "user.create"))
helpers.Grant(rbac.AllowRule(roleT, types.ComponentRbacResource(), "user.update"))
helpers.Grant(rbac.AllowRule(roleT, types.ComponentRbacResource(), "user.delete"))
h.a.Len(p.FindRulesByRoleID(roleS), 1)
h.a.Len(p.FindRulesByRoleID(roleT), 2)
h.apiInit().
Post(fmt.Sprintf("/roles/%d/rules/clone", roleS)).
Header("Accept", "application/json").
FormData("cloneToRoleID", strconv.FormatUint(roleT, 10)).
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertError("role.errors.notAllowedToCloneRules")).
End()
}

View File

@@ -0,0 +1,176 @@
package system
import (
"context"
"fmt"
"net/http"
"testing"
"time"
"github.com/cortezaproject/corteza/server/pkg/id"
mtypes "github.com/cortezaproject/corteza/server/pkg/messagebus/types"
"github.com/cortezaproject/corteza/server/store"
"github.com/cortezaproject/corteza/server/system/service"
"github.com/cortezaproject/corteza/server/system/types"
"github.com/cortezaproject/corteza/server/tests/helpers"
jsonpath "github.com/steinfletcher/apitest-jsonpath"
)
func (h helper) clearMessagebusQueues() {
h.noError(store.TruncateQueues(context.Background(), service.DefaultStore))
}
func (h helper) repoMakeMessagebusQueue(consumer ...string) *types.Queue {
res := &types.Queue{
ID: id.Next(),
Queue: rs(),
CreatedAt: time.Now(),
}
if len(consumer) == 0 {
res.Consumer = string(mtypes.ConsumerCorteza)
} else {
res.Consumer = consumer[0]
}
h.a.NoError(store.CreateQueue(context.Background(), service.DefaultStore, res))
return res
}
func (h helper) lookupByID(id uint64) *types.Queue {
res, err := store.LookupQueueByID(context.Background(), service.DefaultStore, id)
h.noError(err)
return res
}
func (h helper) lookupByQueue(queue string) *types.Queue {
res, err := store.LookupQueueByQueue(context.Background(), service.DefaultStore, queue)
h.noError(err)
return res
}
func TestQueueList(t *testing.T) {
h := newHelper(t)
h.clearMessagebusQueues()
h.repoMakeMessagebusQueue()
h.repoMakeMessagebusQueue()
helpers.AllowMe(h, types.ComponentRbacResource(), "queues.search")
helpers.AllowMe(h, types.QueueRbacResource(0), "read")
h.apiInit().
Get("/queues/").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
Assert(jsonpath.Len(`$.response.set`, 2)).
End()
}
func TestQueueRead(t *testing.T) {
h := newHelper(t)
h.clearMessagebusQueues()
res := h.repoMakeMessagebusQueue()
helpers.AllowMe(h, types.ComponentRbacResource(), "queues.search")
helpers.AllowMe(h, types.QueueRbacResource(0), "read")
h.apiInit().
Get(fmt.Sprintf("/queues/%d", res.ID)).
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
End()
}
func TestQueueCreate(t *testing.T) {
h := newHelper(t)
h.clearMessagebusQueues()
helpers.AllowMe(h, types.ComponentRbacResource(), "queue.create")
consumer := string(mtypes.ConsumerStore)
queue := rs()
h.apiInit().
Post("/queues").
FormData("consumer", consumer).
FormData("queue", queue).
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
End()
res := h.lookupByQueue(queue)
h.a.NotNil(res)
h.a.Equal(consumer, res.Consumer)
}
func TestQueueUpdate(t *testing.T) {
h := newHelper(t)
h.clearMessagebusQueues()
consumer := string(mtypes.ConsumerRedis)
res := h.repoMakeMessagebusQueue()
res.Consumer = consumer
helpers.AllowMe(h, types.QueueRbacResource(0), "update")
h.apiInit().
Put(fmt.Sprintf("/queues/%d", res.ID)).
Header("Accept", "application/json").
JSON(helpers.JSON(res)).
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
End()
res = h.lookupByID(res.ID)
h.a.NotNil(res)
h.a.Equal(consumer, res.Consumer)
}
func TestQueueDelete(t *testing.T) {
h := newHelper(t)
h.clearMessagebusQueues()
res := h.repoMakeMessagebusQueue()
helpers.AllowMe(h, types.QueueRbacResource(0), "delete")
h.apiInit().
Delete(fmt.Sprintf("/queues/%d", res.ID)).
Header("Accept", "application/json").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
End()
res = h.lookupByID(res.ID)
h.a.NotNil(res)
h.a.NotNil(res.DeletedAt)
}
func TestQueueUnDelete(t *testing.T) {
h := newHelper(t)
h.clearMessagebusQueues()
res := h.repoMakeMessagebusQueue()
helpers.AllowMe(h, types.QueueRbacResource(0), "delete")
h.apiInit().
Post(fmt.Sprintf("/queues/%d/undelete", res.ID)).
Header("Accept", "application/json").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
End()
res = h.lookupByID(res.ID)
h.a.NotNil(res)
h.a.Nil(res.DeletedAt)
}

View File

@@ -0,0 +1,356 @@
package system
import (
"context"
"fmt"
"net/http"
"strconv"
"testing"
"time"
"github.com/cortezaproject/corteza/server/pkg/id"
"github.com/cortezaproject/corteza/server/store"
"github.com/cortezaproject/corteza/server/system/service"
"github.com/cortezaproject/corteza/server/system/types"
"github.com/cortezaproject/corteza/server/tests/helpers"
jsonpath "github.com/steinfletcher/apitest-jsonpath"
)
func (h helper) clearReminders() {
h.noError(store.TruncateReminders(context.Background(), service.DefaultStore))
}
func (h helper) makeReminder() *types.Reminder {
return h.reminder(h.cUser.ID, false)
}
func (h helper) makeReminderByUserID(userID uint64) *types.Reminder {
return h.reminder(userID, false)
}
func (h helper) makeDeletedReminder() *types.Reminder {
return h.reminder(h.cUser.ID, true)
}
func (h helper) reminder(userID uint64, deleted bool) *types.Reminder {
rm := &types.Reminder{Resource: "test:resource", AssignedTo: userID}
rm.ID = id.Next()
rm.CreatedAt = time.Now()
if deleted {
now := time.Now()
rm.DeletedAt = &now
}
h.noError(store.CreateReminder(context.Background(), service.DefaultStore, rm))
return rm
}
func (h helper) lookupReminderByID(ID uint64) *types.Reminder {
rm, err := store.LookupReminderByID(context.Background(), service.DefaultStore, ID)
h.noError(err)
return rm
}
func TestReminderCreate(t *testing.T) {
h := newHelper(t)
h.clearReminders()
h.apiInit().
Post("/reminder/").
Header("Accept", "application/json").
FormData("resource", "some:resource").
FormData("assignedTo", strconv.FormatUint(h.cUser.ID, 10)).
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
Assert(jsonpath.Equal(`$.response.resource`, "some:resource")).
End()
}
func TestReminderAssign_forbidden(t *testing.T) {
h := newHelper(t)
h.clearReminders()
h.apiInit().
Post("/reminder/").
Header("Accept", "application/json").
FormData("resource", "some:resource").
FormData("assignedTo", "404").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertError("reminder.errors.notAllowedToAssign")).
End()
}
func TestReminderAssign(t *testing.T) {
h := newHelper(t)
h.clearReminders()
helpers.AllowMe(h, types.ComponentRbacResource(), "reminder.assign")
h.apiInit().
Post("/reminder/").
Header("Accept", "application/json").
FormData("resource", "some:resource").
FormData("assignedTo", "404").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
Assert(jsonpath.Equal(`$.response.resource`, "some:resource")).
End()
}
func TestReminderRead(t *testing.T) {
h := newHelper(t)
h.clearReminders()
rm := h.makeReminder()
h.apiInit().
Get(fmt.Sprintf("/reminder/%d", rm.ID)).
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
Assert(jsonpath.Equal(`$.response.resource`, rm.Resource)).
End()
}
// TestReminderReadForbidden checks only user themself can read reminder assigned to them
func TestReminderReadForbidden(t *testing.T) {
h := newHelper(t)
h.clearReminders()
rm := h.makeReminderByUserID(id.Next())
h.apiInit().
Get(fmt.Sprintf("/reminder/%d", rm.ID)).
Header("Accept", "application/json").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertError("reminder.errors.notAllowedToRead")).
End()
}
func TestReminderList(t *testing.T) {
h := newHelper(t)
h.clearReminders()
h.makeReminder()
h.makeDeletedReminder()
h.makeDeletedReminder()
h.apiInit().
Get("/reminder/").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
Assert(jsonpath.Len("$.response.set", 1)).
End()
}
func TestReminderListIncludeDeleted(t *testing.T) {
h := newHelper(t)
h.clearReminders()
h.makeReminder()
h.makeDeletedReminder()
h.makeDeletedReminder()
h.apiInit().
Get("/reminder/").
Query("includeDeleted", "true").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
Assert(jsonpath.Len("$.response.set", 3)).
End()
}
// TestReminderListForbidden checks reminders of other users are not listed
func TestReminderListForbidden(t *testing.T) {
h := newHelper(t)
h.clearReminders()
h.makeReminder()
h.makeReminderByUserID(id.Next())
h.apiInit().
Get("/reminder/").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
Assert(jsonpath.Len("$.response.set", 1)).
End()
}
// TestReminderListAssignable checks users that can assign reminders can also
// list reminders of other users
func TestReminderListAssignable(t *testing.T) {
h := newHelper(t)
h.clearReminders()
helpers.AllowMe(h, types.ComponentRbacResource(), "reminder.assign")
h.makeReminder()
h.makeReminderByUserID(id.Next())
h.apiInit().
Get("/reminder/").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
Assert(jsonpath.Len("$.response.set", 2)).
End()
}
// TestReminderReadAssignable checks users that can assign reminders can also
// read reminders of other users
func TestReminderReadAssignable(t *testing.T) {
h := newHelper(t)
h.clearReminders()
helpers.AllowMe(h, types.ComponentRbacResource(), "reminder.assign")
rm := h.makeReminderByUserID(id.Next())
h.apiInit().
Get(fmt.Sprintf("/reminder/%d", rm.ID)).
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
Assert(jsonpath.Equal(`$.response.resource`, rm.Resource)).
End()
}
// TestReminderDismissAssignable checks users that can assign reminders can also
// dismiss reminders of other users
func TestReminderDismissAssignable(t *testing.T) {
h := newHelper(t)
h.clearReminders()
helpers.AllowMe(h, types.ComponentRbacResource(), "reminder.assign")
rm := h.makeReminderByUserID(id.Next())
h.apiInit().
Patch(fmt.Sprintf("/reminder/%d/dismiss", rm.ID)).
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
End()
}
// TestReminderSnoozeForbidden checks only user themself can snooze reminder
// assigned to them
func TestReminderSnoozeForbidden(t *testing.T) {
h := newHelper(t)
h.clearReminders()
rm := h.makeReminderByUserID(id.Next())
h.apiInit().
Patch(fmt.Sprintf("/reminder/%d/snooze", rm.ID)).
Header("Accept", "application/json").
FormData("remindAt", time.Now().Add(time.Hour).Format(time.RFC3339)).
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertError("reminder.errors.notAllowedToSnooze")).
End()
}
func TestReminderUpdateForbidden(t *testing.T) {
h := newHelper(t)
h.clearReminders()
rm := h.makeReminder()
h.apiInit().
Put(fmt.Sprintf("/reminder/%d", rm.ID)).
Header("Accept", "application/json").
FormData("resource", "changed:resource").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertError("reminder.errors.notAllowedToAssign")).
End()
}
func TestReminderUpdate(t *testing.T) {
h := newHelper(t)
h.clearReminders()
helpers.AllowMe(h, types.ComponentRbacResource(), "reminder.assign")
rm := h.makeReminder()
h.apiInit().
Put(fmt.Sprintf("/reminder/%d", rm.ID)).
Header("Accept", "application/json").
FormData("resource", "changed:resource").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
Assert(jsonpath.Equal(`$.response.resource`, "changed:resource")).
End()
}
func TestReminderDelete(t *testing.T) {
h := newHelper(t)
h.clearReminders()
rm := h.makeReminder()
h.apiInit().
Delete(fmt.Sprintf("/reminder/%d", rm.ID)).
Header("Accept", "application/json").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
End()
}
func TestReminderDismiss(t *testing.T) {
h := newHelper(t)
h.clearReminders()
rm := h.makeReminder()
h.apiInit().
Patch(fmt.Sprintf("/reminder/%d/dismiss", rm.ID)).
Header("Accept", "application/json").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
End()
}
// TestReminderDismissForbidden checks only user themself can dismiss reminder assigned to them
func TestReminderDismissForbidden(t *testing.T) {
h := newHelper(t)
h.clearReminders()
rm := h.makeReminderByUserID(id.Next())
h.apiInit().
Patch(fmt.Sprintf("/reminder/%d/dismiss", rm.ID)).
Header("Accept", "application/json").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertError("reminder.errors.notAllowedToDismiss")).
End()
}
func TestReminderSnooze(t *testing.T) {
h := newHelper(t)
h.clearReminders()
rm := h.makeReminder()
h.apiInit().
Patch(fmt.Sprintf("/reminder/%d/snooze", rm.ID)).
Header("Accept", "application/json").
FormData("remindAt", time.Now().Format(time.RFC3339)).
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
End()
}

View File

@@ -0,0 +1,210 @@
package system
import (
"context"
"fmt"
"net/http"
"testing"
"time"
"github.com/cortezaproject/corteza/server/pkg/id"
"github.com/cortezaproject/corteza/server/store"
"github.com/cortezaproject/corteza/server/system/service"
"github.com/cortezaproject/corteza/server/system/types"
"github.com/cortezaproject/corteza/server/tests/helpers"
jsonpath "github.com/steinfletcher/apitest-jsonpath"
)
func (h helper) createReport(report *types.Report) *types.Report {
if report.ID == 0 {
report.ID = id.Next()
}
if report.CreatedAt.IsZero() {
report.CreatedAt = time.Now()
}
h.a.NoError(service.DefaultStore.CreateReport(context.Background(), report))
return report
}
func (h helper) clearReports() {
h.noError(store.TruncateReports(context.Background(), service.DefaultStore))
}
func (h helper) lookupReportByHandle(handle string) *types.Report {
res, err := store.LookupReportByHandle(context.Background(), service.DefaultStore, handle)
h.noError(err)
return res
}
func TestReportFilterCasting(t *testing.T) {
h := newHelper(t)
h.clearReports()
helpers.AllowMe(h, types.ComponentRbacResource(), "report.create")
baseAssertPath := "$.response.sources[0].step.load.filter"
h.apiInit().
Post("/reports/").
Header("Accept", "application/json").
JSON(`{
"handle": "test_report",
"meta": { "name": "Test Report", "description": "" },
"sources": [
{
"meta": {},
"step": {
"load": {
"name": "Load",
"source": "composeRecords",
"definition": {
"module": "test",
"namespace": "test"
},
"columns": null,
"filter": {
"ref": "or",
"args": [
{
"ref": "eq",
"args": [
{ "symbol": "id_str" },
{
"value": { "@type": "ID", "@value": "123123123" }
}
]
},
{
"ref": "eq",
"args": [
{ "symbol": "id_uint" },
{
"value": { "@type": "ID", "@value": 123123123 }
}
]
}
]
}
}
}
}
],
"blocks": []
}`).
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
// Strings handled
Assert(jsonpath.Equal(baseAssertPath+`.args[0].args[1].value["@value"]`, "123123123")).
Assert(jsonpath.Equal(baseAssertPath+`.args[0].args[0].symbol`, "id_str")).
// uint handled
Assert(jsonpath.Equal(baseAssertPath+`.args[1].args[1].value["@value"]`, "123123123")).
Assert(jsonpath.Equal(baseAssertPath+`.args[1].args[0].symbol`, "id_uint")).
End()
}
func TestReportCreate_invalidFilter(t *testing.T) {
h := newHelper(t)
h.clearReports()
helpers.AllowMe(h, types.ComponentRbacResource(), "report.create")
h.apiInit().
Post("/reports/").
Header("Accept", "application/json").
JSON(`{
"handle": "test_report",
"meta": { "name": "Test Report", "description": "" },
"sources": [
{
"meta": {},
"step": {
"load": {
"name": "Load",
"source": "composeRecords",
"definition": {
"module": "test",
"namespace": "test"
},
"columns": null,
"filter": {
"ref": "or",
"args": [
{
"ref": "eq",
"args": [
{ "symbol": "id_str" },
{
"value": { "@type": "", "@value": "" }
}
]
}
]
}
}
}
}
],
"blocks": []
}`).
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
End()
}
func TestReportScenarios_create(t *testing.T) {
h := newHelper(t)
h.clearReports()
helpers.AllowMe(h, types.ComponentRbacResource(), "report.create")
h.apiInit().
Post("/reports/").
Header("Accept", "application/json").
JSON(`{
"handle": "test_report",
"scenarios": [{ "label": "scenario 1", "filters": { "ds_1": { "raw": "field == 'value'" } } }]
}`).
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
End()
r := h.lookupReportByHandle("test_report")
h.a.NotNil(r)
h.a.Len(r.Scenarios, 1)
sc := r.Scenarios[0]
h.a.NotNil(sc.Filters)
h.a.Contains(sc.Filters, "ds_1")
}
func TestReportScenarios_update(t *testing.T) {
h := newHelper(t)
h.clearReports()
helpers.AllowMe(h, types.ReportRbacResource(0), "update")
r := h.createReport(&types.Report{
Handle: "test_report",
})
h.apiInit().
Put(fmt.Sprintf("/reports/%d", r.ID)).
Header("Accept", "application/json").
JSON(`{
"handle": "test_report",
"scenarios": [{ "label": "scenario 1", "filters": { "ds_1": { "raw": "field == 'value'" } } }]
}`).
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
End()
r = h.lookupReportByHandle("test_report")
h.a.NotNil(r)
h.a.Len(r.Scenarios, 1)
sc := r.Scenarios[0]
h.a.NotNil(sc.Filters)
h.a.Contains(sc.Filters, "ds_1")
}

View File

@@ -0,0 +1,34 @@
package system
import (
"context"
discoveryType "github.com/cortezaproject/corteza/server/discovery/types"
"github.com/cortezaproject/corteza/server/pkg/id"
"github.com/cortezaproject/corteza/server/store"
"github.com/cortezaproject/corteza/server/system/service"
"testing"
)
func (h helper) clearActivityLog() {
h.noError(store.TruncateResourceActivitys(context.Background(), service.DefaultStore))
}
func (h helper) repoMakeActivityLog() *discoveryType.ResourceActivity {
var res = &discoveryType.ResourceActivity{
ID: id.Next(),
ResourceID: id.Next(),
ResourceType: "compose:record",
ResourceAction: "create",
}
h.a.NoError(store.CreateResourceActivity(context.Background(), service.DefaultStore, res))
return res
}
func TestCreateActivityLog(t *testing.T) {
h := newHelper(t)
h.clearActionLog()
h.repoMakeActivityLog()
}

View File

@@ -0,0 +1,560 @@
package system
import (
"context"
"fmt"
"net/http"
"net/url"
"testing"
"time"
"github.com/cortezaproject/corteza/server/pkg/id"
labelTypes "github.com/cortezaproject/corteza/server/pkg/label/types"
"github.com/cortezaproject/corteza/server/store"
"github.com/cortezaproject/corteza/server/system/service"
"github.com/cortezaproject/corteza/server/system/types"
"github.com/cortezaproject/corteza/server/tests/helpers"
jsonpath "github.com/steinfletcher/apitest-jsonpath"
"github.com/stretchr/testify/require"
)
func (h helper) clearRoles() {
h.noError(store.TruncateRoles(context.Background(), service.DefaultStore))
}
func (h helper) clearRoleMembers() {
h.noError(store.TruncateRoleMembers(context.Background(), service.DefaultStore))
}
func (h helper) createRole(res *types.Role) *types.Role {
if res.ID == 0 {
res.ID = id.Next()
}
if res.CreatedAt.IsZero() {
res.CreatedAt = time.Now()
}
h.a.NoError(service.DefaultStore.CreateRole(context.Background(), res))
return res
}
func (h helper) repoMakeRole(ss ...string) *types.Role {
var r = &types.Role{
ID: id.Next(),
CreatedAt: time.Now(),
}
if len(ss) > 1 {
r.Handle = ss[1]
} else {
r.Handle = "h_" + rs()
}
if len(ss) > 0 {
r.Name = ss[0]
} else {
r.Name = "n_" + rs()
}
h.a.NoError(store.CreateRole(context.Background(), service.DefaultStore, r))
return r
}
func (h helper) lookupRoleByID(ID uint64) *types.Role {
res, err := store.LookupRoleByID(context.Background(), service.DefaultStore, ID)
h.noError(err)
return res
}
func (h helper) createRoleMember(userID, roleID uint64) *types.RoleMember {
var r = &types.RoleMember{
RoleID: roleID,
Resource: fmt.Sprintf("corteza::system:user/%d", userID),
}
h.a.NoError(store.CreateRoleMember(context.Background(), service.DefaultStore, r))
return r
}
func TestRoleRead(t *testing.T) {
h := newHelper(t)
u := h.repoMakeRole()
h.apiInit().
Get(fmt.Sprintf("/roles/%d", u.ID)).
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
Assert(jsonpath.Equal(`$.response.name`, u.Name)).
Assert(jsonpath.Equal(`$.response.roleID`, fmt.Sprintf("%d", u.ID))).
End()
}
func TestRoleList(t *testing.T) {
h := newHelper(t)
h.repoMakeRole(h.randEmail())
h.repoMakeRole(h.randEmail())
helpers.AllowMe(h, types.ComponentRbacResource(), "roles.search")
h.apiInit().
Get("/roles/").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
End()
}
func TestRoleList_filterForbidden(t *testing.T) {
h := newHelper(t)
// @todo this can be a problematic test because it leaves
// behind roles that are not denied this context
// db purge might be needed
h.repoMakeRole("role")
f := h.repoMakeRole()
helpers.AllowMe(h, types.ComponentRbacResource(), "roles.search")
helpers.DenyMe(h, f.RbacResource(), "read")
h.apiInit().
Get("/roles/").
Query("handle", f.Handle).
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
Assert(jsonpath.NotPresent(fmt.Sprintf(`$.response.set[? @.handle=="%s"]`, f.Handle))).
End()
}
func TestRoleCreateForbidden(t *testing.T) {
h := newHelper(t)
h.apiInit().
Post("/roles/").
Header("Accept", "application/json").
FormData("name", rs()).
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertError("role.errors.notAllowedToCreate")).
End()
}
func TestRoleCreateNotUnique(t *testing.T) {
h := newHelper(t)
helpers.AllowMe(h, types.ComponentRbacResource(), "role.create")
role := h.repoMakeRole()
h.apiInit().
Post("/roles/").
Header("Accept", "application/json").
FormData("name", rs()).
FormData("handle", role.Handle).
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertError("role.errors.handleNotUnique")).
End()
h.apiInit().
Post("/roles/").
Header("Accept", "application/json").
FormData("name", role.Name).
FormData("handle", "handle_"+rs()).
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertError("role.errors.nameNotUnique")).
End()
}
func TestRoleCreate(t *testing.T) {
h := newHelper(t)
helpers.AllowMe(h, types.ComponentRbacResource(), "role.create")
h.apiInit().
Post("/roles/").
FormData("name", rs()).
FormData("handle", "handle_"+rs()).
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
End()
}
func TestRoleCreateWithJSON(t *testing.T) {
h := newHelper(t)
helpers.AllowMe(h, types.ComponentRbacResource(), "role.create")
h.clearRoles()
h.apiInit().
Post("/roles/").
Header("Accept", "application/json").
JSON(fmt.Sprintf(`{
"name": "fake role",
"handle": "fakeRoleFromJSON",
"meta": {
"description": "this is my description",
"context": {
"resourceTypes": ["corteza::compose:record"],
"expr": "userID == resource.ownedBy"
}
}
}`)).
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
End()
r, err := store.LookupRoleByHandle(context.Background(), service.DefaultStore, "fakeRoleFromJSON")
h.a.NoError(err)
h.a.NotNil(r.Meta)
h.a.NotNil(r.Meta.Context)
h.a.NotEmpty(r.Meta.Context.Resource)
h.a.Equal("corteza::compose:record", r.Meta.Context.Resource[0])
h.a.Equal("userID == resource.ownedBy", r.Meta.Context.Expr)
}
func TestRoleUpdateWithJSON(t *testing.T) {
h := newHelper(t)
helpers.AllowMe(h, types.ComponentRbacResource(), "role.create")
h.clearRoles()
h.noError(store.CreateRole(context.Background(), service.DefaultStore, &types.Role{
ID: 42,
Name: "fix",
Handle: "fix",
Meta: &types.RoleMeta{
Description: "fix",
Context: &types.RoleContext{
Resource: []string{"corteza::compose:module"},
Expr: "1!=2",
},
},
}))
h.apiInit().
Post("/roles/").
Header("Accept", "application/json").
JSON(fmt.Sprintf(`{
"name": "fake role",
"handle": "fakeRoleFromJSON",
"meta": {
"description": "this is my description",
"context": {
"resourceTypes": ["corteza::compose:record"],
"expr": "userID == resource.ownedBy"
}
}
}`)).
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
End()
r, err := store.LookupRoleByHandle(context.Background(), service.DefaultStore, "fakeRoleFromJSON")
h.a.NoError(err)
h.a.NotNil(r.Meta)
h.a.NotNil(r.Meta.Context)
h.a.NotEmpty(r.Meta.Context.Resource)
h.a.Equal("corteza::compose:record", r.Meta.Context.Resource[0])
h.a.Equal("userID == resource.ownedBy", r.Meta.Context.Expr)
}
func TestRoleUpdateForbidden(t *testing.T) {
h := newHelper(t)
u := h.repoMakeRole()
h.apiInit().
Put(fmt.Sprintf("/roles/%d", u.ID)).
Header("Accept", "application/json").
FormData("email", h.randEmail()).
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertError("role.errors.notAllowedToUpdate")).
End()
}
func TestRoleUpdate(t *testing.T) {
h := newHelper(t)
res := h.repoMakeRole()
helpers.AllowMe(h, types.RoleRbacResource(0), "update")
newName := "updated-" + rs()
newHandle := "updated-" + rs()
h.apiInit().
Put(fmt.Sprintf("/roles/%d", res.ID)).
FormData("name", newName).
FormData("handle", newHandle).
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
End()
res = h.lookupRoleByID(res.ID)
h.a.NotNil(res)
h.a.Equal(newName, res.Name)
h.a.Equal(newHandle, res.Handle)
}
func TestRoleDeleteForbidden(t *testing.T) {
h := newHelper(t)
u := h.repoMakeRole()
h.apiInit().
Delete(fmt.Sprintf("/roles/%d", u.ID)).
Header("Accept", "application/json").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertError("role.errors.notAllowedToDelete")).
End()
}
func TestRoleDelete(t *testing.T) {
h := newHelper(t)
helpers.AllowMe(h, types.RoleRbacResource(0), "delete")
res := h.repoMakeRole()
h.apiInit().
Delete(fmt.Sprintf("/roles/%d", res.ID)).
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
End()
res = h.lookupRoleByID(res.ID)
h.a.NotNil(res)
h.a.NotNil(res.DeletedAt)
}
func TestRoleUndelete(t *testing.T) {
h := newHelper(t)
helpers.AllowMe(h, types.RoleRbacResource(0), "delete")
res := h.repoMakeRole()
h.apiInit().
Post(fmt.Sprintf("/roles/%d/undelete", res.ID)).
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
End()
res = h.lookupRoleByID(res.ID)
h.a.NotNil(res)
h.a.Nil(res.DeletedAt)
}
func TestRoleArchive(t *testing.T) {
h := newHelper(t)
helpers.AllowMe(h, types.RoleRbacResource(0), "update")
res := h.repoMakeRole()
h.apiInit().
Post(fmt.Sprintf("/roles/%d/archive", res.ID)).
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
End()
res = h.lookupRoleByID(res.ID)
h.a.NotNil(res)
h.a.NotNil(res.ArchivedAt)
}
func TestRoleUnarchive(t *testing.T) {
h := newHelper(t)
helpers.AllowMe(h, types.RoleRbacResource(0), "delete")
n := time.Now()
res := h.createRole(&types.Role{
Name: "test_role",
Handle: "test_role",
ArchivedAt: &n,
})
h.apiInit().
Post(fmt.Sprintf("/roles/%d/unarchive", res.ID)).
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
End()
res = h.lookupRoleByID(res.ID)
h.a.NotNil(res)
h.a.Nil(res.ArchivedAt)
}
func TestRoleLabels(t *testing.T) {
h := newHelper(t)
h.clearRoles()
helpers.AllowMe(h, types.ComponentRbacResource(), "roles.search")
helpers.AllowMe(h, types.ComponentRbacResource(), "role.create")
helpers.AllowMe(h, types.RoleRbacResource(0), "read", "update", "delete")
var (
ID uint64
)
t.Run("create", func(t *testing.T) {
var (
req = require.New(t)
payload = &types.Role{}
)
helpers.SetLabelsViaAPI(h.apiInit(), t,
"/roles/",
types.Role{Labels: map[string]labelTypes.LabelValue{"foo": {Val: "bar"}, "bar": {Val: "42"}}},
payload,
)
req.NotZero(payload.ID)
h.a.Equal(payload.Labels["foo"].Val, "bar",
"labels must contain foo with value bar")
h.a.Equal(payload.Labels["bar"].Val, "42",
"labels must contain bar with value 42")
req.Equal(helpers.LabelsToStrings(payload.Labels), helpers.LabelsToStrings(helpers.LoadLabelsFromStore(t, service.DefaultStore, payload.LabelResourceKind(), payload.ID)),
"response must match stored labels")
ID = payload.ID
})
t.Run("update", func(t *testing.T) {
if ID == 0 {
t.Skip("label/create test not ran")
}
var (
req = require.New(t)
payload = &types.Role{}
)
helpers.SetLabelsViaAPI(h.apiInit(), t,
fmt.Sprintf("PUT /roles/%d", ID),
&types.Role{Labels: map[string]labelTypes.LabelValue{"foo": {Val: "baz"}, "baz": {Val: "123"}}},
payload,
)
req.NotZero(payload.ID)
//req.Nil(payload.UpdatedAt, "updatedAt must not change after changing labels")
req.Equal(payload.Labels["foo"].Val, "baz",
"labels must contain foo with value baz")
req.NotContains(payload.Labels, "bar",
"labels must not contain bar")
req.Equal(payload.Labels["baz"].Val, "123",
"labels must contain baz with value 123")
req.Equal(helpers.LabelsToStrings(payload.Labels), helpers.LabelsToStrings(helpers.LoadLabelsFromStore(t, service.DefaultStore, payload.LabelResourceKind(), payload.ID)),
"response must match stored labels")
})
t.Run("search", func(t *testing.T) {
if ID == 0 {
t.Skip("label/create test not ran")
}
var (
req = require.New(t)
set = types.RoleSet{}
)
helpers.SearchWithLabelsViaAPI(h.apiInit(), t, "/roles/", &set, url.Values{"labels": []string{"baz=123"}})
req.NotEmpty(set)
req.NotNil(set.FindByID(ID))
req.NotNil(set.FindByID(ID).Labels)
})
}
func TestMemberList(t *testing.T) {
h := newHelper(t)
helpers.AllowMe(h, types.RoleRbacResource(0), "read")
r := h.repoMakeRole(h.randEmail())
h.createRoleMember(id.Next(), r.ID)
h.createRoleMember(id.Next(), r.ID)
h.apiInit().
Get(fmt.Sprintf("/roles/%d/members", r.ID)).
Header("Accept", "application/json").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
Assert(jsonpath.Len(`$.response`, 2)).
End()
}
func TestMemberAdd(t *testing.T) {
h := newHelper(t)
helpers.AllowMe(h, types.UserRbacResource(0), "read")
helpers.AllowMe(h, types.RoleRbacResource(0), "members.manage")
r := h.repoMakeRole(h.randEmail())
u := h.createUserWithEmail(h.randEmail())
h.apiInit().
Post(fmt.Sprintf("/roles/%d/member/%d", r.ID, u.ID)).
Header("Accept", "application/json").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
End()
}
func TestMemberAdd_denied(t *testing.T) {
h := newHelper(t)
helpers.AllowMe(h, types.UserRbacResource(0), "read")
r := h.repoMakeRole(h.randEmail())
u := h.createUserWithEmail(h.randEmail())
h.apiInit().
Post(fmt.Sprintf("/roles/%d/member/%d", r.ID, u.ID)).
Header("Accept", "application/json").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertError("role.errors.notAllowedToManageMembers")).
End()
}
func TestMemberRemove(t *testing.T) {
h := newHelper(t)
helpers.AllowMe(h, types.UserRbacResource(0), "read")
helpers.AllowMe(h, types.RoleRbacResource(0), "members.manage")
r := h.repoMakeRole(h.randEmail())
u := h.createUserWithEmail(h.randEmail())
h.apiInit().
Post(fmt.Sprintf("/roles/%d/member/%d", r.ID, u.ID)).
Header("Accept", "application/json").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
End()
}
func TestMemberRemove_denied(t *testing.T) {
h := newHelper(t)
helpers.AllowMe(h, types.UserRbacResource(0), "read")
r := h.repoMakeRole(h.randEmail())
u := h.createUserWithEmail(h.randEmail())
h.apiInit().
Post(fmt.Sprintf("/roles/%d/member/%d", r.ID, u.ID)).
Header("Accept", "application/json").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertError("role.errors.notAllowedToManageMembers")).
End()
}

View File

@@ -0,0 +1,399 @@
package system
import (
"context"
"fmt"
"net/http"
"regexp"
"testing"
"github.com/cortezaproject/corteza/server/pkg/api/server"
"github.com/cortezaproject/corteza/server/pkg/logger"
"github.com/cortezaproject/corteza/server/store"
"github.com/cortezaproject/corteza/server/system/scim"
"github.com/cortezaproject/corteza/server/system/service"
"github.com/cortezaproject/corteza/server/system/types"
"github.com/go-chi/chi/v5"
"github.com/steinfletcher/apitest"
jsonpath "github.com/steinfletcher/apitest-jsonpath"
)
// apitest basics, initialize, set handler, add auth
func (h helper) scimApiInit(ffn ...func(*scim.Config)) *apitest.APITest {
InitTestApp()
var (
scimConfig scim.Config
scimRoutes = chi.NewRouter()
)
for _, fn := range ffn {
fn(&scimConfig)
}
scimRoutes.Use(server.BaseMiddleware(false, logger.Default())...)
scim.Routes(scimRoutes, scimConfig)
return apitest.
New().
Handler(scimRoutes)
}
func TestScimUserGet(t *testing.T) {
h := newHelper(t)
h.clearUsers()
u := h.createUserWithEmail(h.randEmail())
h.scimApiInit().
Get(fmt.Sprintf("/Users/%d", u.ID)).
Expect(t).
Status(http.StatusOK).
Assert(jsonpath.Contains(`$.schemas`, "urn:ietf:params:scim:schemas:core:2.0:User")).
Assert(jsonpath.Equal(`$.id`, fmt.Sprintf("%d", u.ID))).
End()
}
func TestScimUserCreate(t *testing.T) {
h := newHelper(t)
h.clearUsers()
h.scimApiInit().
Post("/Users").
JSON(`{
"schemas": [
"urn:ietf:params:scim:schemas:core:2.0:User"
],
"userName": "foo",
"nickName": "baz",
"emails": [
{
"value": "foo@bar.com",
"primary": true
},
{
"value": "bar@foo.com"
}
]
}`).
Expect(t).
Status(http.StatusCreated).
End()
u, err := store.LookupUserByEmail(context.Background(), service.DefaultStore, "foo@bar.com")
h.a.NoError(err)
h.a.Equal("foo", u.Username)
h.a.Equal("baz", u.Handle)
}
func TestScimUserCreateNoEmail(t *testing.T) {
h := newHelper(t)
h.clearUsers()
h.scimApiInit().
Post("/Users").
JSON(`{"schemas":["urn:ietf:params:scim:schemas:core:2.0:User"]}`).
Expect(t).
Status(http.StatusInternalServerError).
End()
}
func TestScimUserCreateOverwrite(t *testing.T) {
h := newHelper(t)
h.clearUsers()
u := h.createUserWithEmail("foo@bar.com")
h.scimApiInit().
Post("/Users").
JSON(`{"userName":"UPDATED","emails":[{"value":"foo@bar.com"}],"schemas":["urn:ietf:params:scim:schemas:core:2.0:User"]}`).
Expect(t).
Status(http.StatusOK).
End()
u, err := store.LookupUserByEmail(context.Background(), service.DefaultStore, "foo@bar.com")
h.a.NoError(err)
h.a.Equal("UPDATED", u.Username)
}
func TestScimUserExternalID(t *testing.T) {
h := newHelper(t)
h.clearUsers()
h.scimApiInit().
Post("/Users").
JSON(`{"userName":"foo","emails":[{"value":"foo@bar.com"}],"externalId":"foo42","schemas":["urn:ietf:params:scim:schemas:core:2.0:User"]}`).
Expect(t).
Status(http.StatusCreated).
End()
u, err := store.LookupUserByEmail(context.Background(), service.DefaultStore, "foo@bar.com")
h.a.NoError(err)
h.a.Equal("foo", u.Username)
h.scimApiInit().
Post("/Users").
JSON(`{"userName":"baz","emails":[{"value":"baz@bar.com"}],"externalId":"foo42","schemas":["urn:ietf:params:scim:schemas:core:2.0:User"]}`).
Expect(t).
Status(http.StatusOK).
End()
u, err = store.LookupUserByEmail(context.Background(), service.DefaultStore, "baz@bar.com")
h.a.NoError(err)
h.a.Equal("baz", u.Username)
}
func TestScimUserReplace(t *testing.T) {
h := newHelper(t)
h.clearUsers()
u := h.createUserWithEmail(h.randEmail())
h.scimApiInit().
Put(fmt.Sprintf("/Users/%d", u.ID)).
JSON(`{
"schemas": [
"urn:ietf:params:scim:schemas:core:2.0:User"
],
"userName": "bar",
"emails": [
{
"value": "foo@bar.com"
}
]
}`).
Expect(t).
Status(http.StatusOK).
End()
u, err := store.LookupUserByID(context.Background(), service.DefaultStore, u.ID)
h.a.NoError(err)
h.a.NotNil(u)
h.a.Equal("foo@bar.com", u.Email)
}
func TestScimUserPassword(t *testing.T) {
h := newHelper(t)
h.clearUsers()
service.CurrentSettings.Auth.Internal.Enabled = true
auth := service.Auth(service.AuthOptions{})
h.scimApiInit().
Post("/Users").
JSON(`{"password":"foo$bar$baz 42","emails":[{"value":"baz@bar.com"}],"externalId":"foo42","schemas":["urn:ietf:params:scim:schemas:core:2.0:User"]}`).
Expect(t).
Status(http.StatusCreated).
End()
u, err := auth.InternalLogin(context.Background(), "baz@bar.com", "foo$bar$baz 42")
h.a.NoError(err)
h.a.NotNil(u)
}
func TestScimUserDelete(t *testing.T) {
h := newHelper(t)
h.clearUsers()
u := h.createUserWithEmail(h.randEmail())
h.scimApiInit().
Delete(fmt.Sprintf("/Users/%d", u.ID)).
Expect(t).
Status(http.StatusNoContent).
End()
}
func TestScimGroupGet(t *testing.T) {
h := newHelper(t)
h.clearRoles()
u := h.repoMakeRole()
h.scimApiInit().
Get(fmt.Sprintf("/Groups/%d", u.ID)).
Expect(t).
Status(http.StatusOK).
Assert(jsonpath.Contains(`$.schemas`, "urn:ietf:params:scim:schemas:core:2.0:Group")).
Assert(jsonpath.Equal(`$.id`, fmt.Sprintf("%d", u.ID))).
End()
}
func TestScimGroupCreate(t *testing.T) {
h := newHelper(t)
h.clearRoles()
h.scimApiInit().
Post("/Groups").
JSON(`{"schemas":["urn:ietf:params:scim:schemas:core:2.0:Group"],"displayName":"foo"}`).
Expect(t).
Status(http.StatusCreated).
End()
u, err := store.LookupRoleByName(context.Background(), service.DefaultStore, "foo")
h.a.NoError(err)
h.a.Equal("foo", u.Name)
}
func TestScimGroupExternalId(t *testing.T) {
h := newHelper(t)
h.clearRoles()
h.scimApiInit().
Post("/Groups").
JSON(`{"schemas":["urn:ietf:params:scim:schemas:core:2.0:Group"],"displayName":"foo","externalId":"grp42"}`).
Expect(t).
Status(http.StatusCreated).
End()
u, err := store.LookupRoleByName(context.Background(), service.DefaultStore, "foo")
h.a.NoError(err)
h.a.Equal("foo", u.Name)
h.scimApiInit().
Post("/Groups").
JSON(`{"schemas":["urn:ietf:params:scim:schemas:core:2.0:Group"],"displayName":"bar","externalId":"grp42"}`).
Expect(t).
Status(http.StatusOK).
End()
u, err = store.LookupRoleByName(context.Background(), service.DefaultStore, "bar")
h.a.NoError(err)
h.a.Equal("bar", u.Name)
}
func TestScimGroupReplace(t *testing.T) {
h := newHelper(t)
h.clearRoles()
u := h.repoMakeRole()
h.scimApiInit().
Put(fmt.Sprintf("/Groups/%d", u.ID)).
JSON(`{"schemas":["urn:ietf:params:scim:schemas:core:2.0:Group"],"displayName":"bar"}`).
Expect(t).
End()
u, err := store.LookupRoleByID(context.Background(), service.DefaultStore, u.ID)
h.a.NoError(err)
h.a.NotNil(u)
h.a.Equal("bar", u.Name)
}
func TestScimGroupDelete(t *testing.T) {
h := newHelper(t)
h.clearRoles()
u := h.repoMakeRole(h.randEmail())
h.scimApiInit().
Delete(fmt.Sprintf("/Groups/%d", u.ID)).
Expect(t).
Status(http.StatusNoContent).
End()
}
func TestScimUserReplaceOnExternalId(t *testing.T) {
h := newHelper(t)
h.clearUsers()
// creating a new user and assigning an external ID label to it
u := h.createUserWithEmail(h.randEmail())
const externalId = `2819c223-7f76-453a-919d-413861904646`
h.setLabel(u, "SCIM_externalId", externalId)
h.scimApiInit(scimSetWithExternalId, scimSetWithUUIDValidator).
Put(fmt.Sprintf("/Users/%s", externalId)).
JSON(`{"emails":[{"value":"baz@bar.com"}],"externalId":"` + externalId + `","schemas":["urn:ietf:params:scim:schemas:core:2.0:User"]}`).
Expect(t).
Status(http.StatusOK).
End()
u, err := store.LookupUserByID(context.Background(), service.DefaultStore, u.ID)
h.a.NoError(err)
h.a.NotNil(u)
h.a.Equal("baz@bar.com", u.Email)
}
func TestScimPatchingGroupMembership(t *testing.T) {
h := newHelper(t)
h.clearUsers()
h.clearRoles()
h.clearRoleMembers()
isMember := func(r *types.Role, u *types.User) bool {
mm, _, err := store.SearchRoleMembers(h.secCtx(), service.DefaultStore, types.RoleMemberFilter{RoleID: r.ID, Resource: fmt.Sprintf("corteza::system:user/%d", u.ID)})
h.a.NoError(err)
return len(mm) > 0
}
const (
user1Id = `00000000-0000-0000-0000-000000000001`
user2Id = `00000000-0000-0000-0000-000000000002`
groupId = `00000000-0000-0000-0001-000000000001`
)
// creating a new user and assigning an external ID label to it
u1 := h.createUserWithEmail(h.randEmail())
h.setLabel(u1, "SCIM_externalId", user1Id)
u2 := h.createUserWithEmail(h.randEmail())
h.setLabel(u2, "SCIM_externalId", user2Id)
r := h.createRole(&types.Role{})
h.setLabel(r, "SCIM_externalId", groupId)
h.a.False(isMember(r, u1))
h.a.False(isMember(r, u2))
// add only user #1
h.scimApiInit(scimSetWithExternalId, scimSetWithUUIDValidator).
Patch(fmt.Sprintf("/Groups/%s", groupId)).
JSON(fmt.Sprintf(
`{"Operations":[{"op":"add","path":"members[value eq \"%s\"]"}],"schemas":["urn:ietf:params:scim:schemas:core:2.0:PatchOp"]}`,
user1Id,
)).
Expect(t).
Status(http.StatusNoContent).
End()
h.a.True(isMember(r, u1))
h.a.False(isMember(r, u2))
// remove user #1, add user #2
h.scimApiInit(scimSetWithExternalId, scimSetWithUUIDValidator).
Patch(fmt.Sprintf("/Groups/%s", groupId)).
JSON(fmt.Sprintf(
`{"Operations":[{"op":"add","path":"members[value eq \"%[1]s\"]"},{"op":"remove","path":"members[value eq \"%[2]s\"]"}],"schemas":["urn:ietf:params:scim:schemas:core:2.0:PatchOp"]}`,
user2Id,
user1Id,
)).
Expect(t).
Status(http.StatusNoContent).
End()
h.a.False(isMember(r, u1))
h.a.True(isMember(r, u2))
}
func scimSetWithExternalId(c *scim.Config) {
c.ExternalIdAsPrimary = true
}
func scimSetWithUUIDValidator(c *scim.Config) {
c.ExternalIdValidator = regexp.MustCompile(`^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}$`)
}

View File

@@ -0,0 +1,242 @@
package system
import (
"net/http"
"testing"
"github.com/cortezaproject/corteza/server/system/service"
"github.com/cortezaproject/corteza/server/system/types"
"github.com/cortezaproject/corteza/server/tests/helpers"
sqlTypes "github.com/jmoiron/sqlx/types"
jsonpath "github.com/steinfletcher/apitest-jsonpath"
)
func TestSettingsList(t *testing.T) {
h := newHelper(t)
helpers.AllowMe(h, types.ComponentRbacResource(), "settings.read")
helpers.AllowMe(h, types.ComponentRbacResource(), "settings.manage")
err := service.DefaultSettings.BulkSet(h.secCtx(), types.SettingValueSet{
&types.SettingValue{Name: "t_sys_k1.s1", Value: sqlTypes.JSONText(`"t_sys_v1"`)},
&types.SettingValue{Name: "t_sys_k1.s2", Value: sqlTypes.JSONText(`"t_sys_v2"`)},
})
h.a.NoError(err)
h.apiInit().
Get("/settings/").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
Assert(jsonpath.Present(`$.response[? @.name=="t_sys_k1.s1"]`)).
Assert(jsonpath.Present(`$.response[? @.value=="t_sys_v1"]`)).
Assert(jsonpath.Present(`$.response[? @.name=="t_sys_k1.s2"]`)).
Assert(jsonpath.Present(`$.response[? @.value=="t_sys_v2"]`)).
End()
}
func TestSettingsList_noPermissions(t *testing.T) {
h := newHelper(t)
helpers.DenyMe(h, types.ComponentRbacResource(), "settings.read")
h.apiInit().
Get("/settings/").
Header("Accept", "application/json").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertError("settings.errors.notAllowedToRead")).
End()
}
func TestSettingsUpdate(t *testing.T) {
h := newHelper(t)
helpers.AllowMe(h, types.ComponentRbacResource(), "settings.manage")
helpers.AllowMe(h, types.ComponentRbacResource(), "settings.read")
err := service.DefaultSettings.BulkSet(h.secCtx(), types.SettingValueSet{
&types.SettingValue{Name: "t_sys_k1.s1", Value: sqlTypes.JSONText(`"t_sys_v1"`)},
})
h.a.NoError(err)
h.apiInit().
Patch("/settings/").
JSON(`{"values":[{"name":"t_sys_k1.s1","value":"t_sys_v1_edited"},{"name":"t_sys_k2.s1","value":"t_sys_v2_new"}]}`).
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
End()
s, err := service.DefaultSettings.Get(h.secCtx(), "t_sys_k1.s1", 0)
h.a.NoError(err)
h.a.Equal(`"t_sys_v1_edited"`, s.Value.String(), "existing key should be updated")
s, err = service.DefaultSettings.Get(h.secCtx(), "t_sys_k2.s1", 0)
h.a.NoError(err)
h.a.Equal(`"t_sys_v2_new"`, s.Value.String(), "new key should be added")
}
func TestSettingsUpdate_noPermissions(t *testing.T) {
h := newHelper(t)
helpers.DenyMe(h, types.ComponentRbacResource(), "settings.manage")
h.apiInit().
Patch("/settings/").
Header("Accept", "application/json").
JSON(`{"values":[]}`).
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertError("settings.errors.notAllowedToManage")).
End()
}
func TestSettingsUpdate_validation(t *testing.T) {
h := newHelper(t)
helpers.AllowMe(h, types.ComponentRbacResource(), "settings.manage")
helpers.AllowMe(h, types.ComponentRbacResource(), "settings.read")
// Password constraints: The min password length should be 8 or more
h.apiInit().
Patch("/settings/").
Header("Accept", "application/json").
JSON(`{"values":[{"name":"auth.internal.password-constraints.min-length","value": 8}]}`).
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
End()
// Password constraints: The min password length should be not be less than 8
h.apiInit().
Patch("/settings/").
Header("Accept", "application/json").
JSON(`{"values":[{"name":"auth.internal.password-constraints.min-length","value":"7"}]}`).
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertError("settings.errors.invalidPasswordMinLength")).
End()
// Password constraints: The min upper case count
h.apiInit().
Patch("/settings/").
Header("Accept", "application/json").
JSON(`{"values":[{"name":"auth.internal.password-constraints.min-upper-case","value": 2}]}`).
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
End()
// Password constraints: The min upper case count should not be a negative number
h.apiInit().
Patch("/settings/").
Header("Accept", "application/json").
JSON(`{"values":[{"name":"auth.internal.password-constraints.min-upper-case","value":-1}]}`).
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertError("settings.errors.invalidPasswordMinUpperCase")).
End()
// Password constraints: The min upper case count should not be a negative number string
h.apiInit().
Patch("/settings/").
Header("Accept", "application/json").
JSON(`{"values":[{"name":"auth.internal.password-constraints.min-upper-case","value":"-1"}]}`).
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertError("settings.errors.invalidPasswordMinUpperCase")).
End()
// Password constraints: The min lower case count should not be a negative number
h.apiInit().
Patch("/settings/").
Header("Accept", "application/json").
JSON(`{"values":[{"name":"auth.internal.password-constraints.min-lower-case","value":"-1"}]}`).
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertError("settings.errors.invalidPasswordMinLowerCase")).
End()
// Password constraints: The min number of numeric characters should not be a negative number
h.apiInit().
Patch("/settings/").
Header("Accept", "application/json").
JSON(`{"values":[{"name":"auth.internal.password-constraints.min-num-count","value":"-1"}]}`).
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertError("settings.errors.invalidPasswordMinNumCount")).
End()
// Password constraints: The min number of special characters should not be a negative number
h.apiInit().
Patch("/settings/").
Header("Accept", "application/json").
JSON(`{"values":[{"name":"auth.internal.password-constraints.min-special-count","value":"-1"}]}`).
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertError("settings.errors.invalidPasswordMinSpecialCharCount")).
End()
}
func TestSettingsGet(t *testing.T) {
h := newHelper(t)
helpers.AllowMe(h, types.ComponentRbacResource(), "settings.read")
helpers.AllowMe(h, types.ComponentRbacResource(), "settings.manage")
err := service.DefaultSettings.BulkSet(h.secCtx(), types.SettingValueSet{
&types.SettingValue{Name: "t_sys_k1.s1", Value: sqlTypes.JSONText(`"t_sys_v1"`)},
})
h.a.NoError(err)
h.apiInit().
Get("/settings/t_sys_k1.s1").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
Assert(jsonpath.Present(`$.response.name=="t_sys_k1.s1"`)).
Assert(jsonpath.Present(`$.response.value=="t_sys_v1"`)).
End()
h.apiInit().
Get("/settings/t_sys_k1.missing").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
Assert(jsonpath.Equal(`$.response`, nil)).
End()
}
func TestSettingsGet_noPermissions(t *testing.T) {
h := newHelper(t)
helpers.DenyMe(h, types.ComponentRbacResource(), "settings.read")
h.apiInit().
Get("/settings/t_sys_k1.s1").
Header("Accept", "application/json").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertError("settings.errors.notAllowedToRead")).
End()
}
func TestSettingsSet_noPermissions(t *testing.T) {
h := newHelper(t)
helpers.DenyMe(h, types.ComponentRbacResource(), "settings.read")
h.apiInit().
Get("/settings/t_sys_k1.s1").
Header("Accept", "application/json").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertError("settings.errors.notAllowedToRead")).
End()
}
func TestSettingsCurrent(t *testing.T) {
h := newHelper(t)
helpers.AllowMe(h, types.ComponentRbacResource(), "settings.read")
h.apiInit().
Get("/settings/current").
Expect(t).
Status(http.StatusOK).
Assert(jsonpath.Present(`$.response`)).
End()
}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJodHRwOi8vbG9jYWxob3N0OjgwODQiLCJpYXQiOjE2MjEyNDI5OTIsImlzcyI6Imh0dHA6Ly9sb2NhbGhvc3Q6ODA4NCIsIm5iZiI6MTYyMTI0Mjk5Miwic3ViIjoidEN1NVBWNkVneGN2VUFhOWU1N3VKMmctYlRrcW5Oa3l5SEhhT3UxNXlFZlpqZ1dLdDAyQXRYR2UiLCJpZCI6ImlkLTdiZmZiMjZkNmJlZTRiNWJkZjg2ZjhkYzRkYTJjZmMxMTdkODlkMGYiLCJ1cmkiOiIvYXV0aC9leHRlcm5hbC9zYW1sL2luaXQiLCJzYW1sLWF1dGhuLXJlcXVlc3QiOnRydWV9.fBI_TorEbXYMtoNRGApQs5_89Q9IZjV-1dwkOeF5ZC9xQ6p3Mbo3r0x4CgKzYS2n8i4mMIEUDI_C4bY5jqyVEfmrwtv4qGbhYCJjnvSu1vAncJGQNfbcWCmSW0RMiiJZzfj3whHTzmK_mLgOch07iwxKGBOyNscdZfxfJp_sDMuHePwiggGssWglCC_KWXNkGh3TPad-_mo4kc_9qUf4onyISms6uyZpbJW-BqGP-iYiTnbEGdbF-24bmbTpVBU8Arv3jQjaHq8teT9XI4vFgWHfEp497LD7snYNOn3-9S05JGWKA74wrgZwFcRBaIhfVMtOqy7YMHBJZ8NNbVH8Tg

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJodHRwOi8vbG9jYWxob3N0OjgwODQiLCJleHAiOjE2MjEyNDY2MzAsImlhdCI6MTYyMTI0MzAzMCwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo4MDg0IiwibmJmIjoxNjIxMjQzMDMwLCJzdWIiOiJBQWR6WldOeVpYUXhFMmR5SFBqRStoTVhQQW5uSUNpbUt2c21XeC9YMW9MaEFOV1dGbU0wWElMS01xa1dBWjFrVldiR0J0ZjhFS0xsb2wvakM2ZlJMa25Cc3dZRDlWNUxHQUErcDFxZHBON2w2MGI3ZHYvMFcwa25VRm1SSm8vWFlTYmRkTVFweUtBMWlCNVFrejZERDVsS0Ixa2VTMzBiYWpuT29OTT0iLCJhdHRyIjp7IlNlc3Npb25JbmRleCI6WyJfYzA5YWFiNDRhNTUyOGVhNzM2NTdkNTNjYTRmODA4NDMiXSwiZGlzcGxheU5hbWUiOlsiUmljayBTYW5jaGV6Il0sImVkdVBlcnNvbkVudGl0bGVtZW50IjpbInVybjptYWNlOmRpcjplbnRpdGxlbWVudDpjb21tb24tbGliLXRlcm1zIl0sImdpdmVuTmFtZSI6WyJSaWNrIl0sIm1haWwiOlsicnNhbmNoZXpAc2FtbHRlc3QuaWQiXSwicm9sZSI6WyJtYW5hZ2VyQHNhbWx0ZXN0LmlkIl0sInNuIjpbIlNhbmNoZXoiXSwidGVsZXBob25lTnVtYmVyIjpbIisxLTU1NS01NTUtNTUxNSJdLCJ1aWQiOlsicmljayJdLCJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDphdHRyaWJ1dGU6c3ViamVjdC1pZCI6WyJyc2FuY2hlekBzYW1sdGVzdC5pZCJdfSwic2FtbC1zZXNzaW9uIjp0cnVlfQ.bNYxkq2dnISDr4x0Wg9pBXcOsSpKEvf5Bb_1vfZDLax6yH2Jjn0Dwjn7LMI6rRcz31YdMkhxrc9sISDD4Peotc1q_QNmJ92xCCzDq9jZM7ptRpKHZJIWkgwUDmmsLjCWj_YiuOwsPP6Zt70AJXNUtOcBg04Dw5ncpcR1cRqf6rTP7rAdNjQ_Euitln1gHK4Zz-fH0GgtS_pYHIzKTRarxiEktMvQGiaNce5e-1DmMOS3L9_6-lSlTa4sYG9BVG4icFf3QnUXqc3DCgGQr7waMuZNA35jqSJ1MSwhLVSHV5gb_l3HxoHKwZhue5d874apTk8cX00wlDO0Q9Z1z2R9Pg

View File

@@ -0,0 +1,19 @@
-----BEGIN CERTIFICATE-----
MIIDITCCAgmgAwIBAgIUB34XNoIgt3k4tLbC40GDnLuvYAkwDQYJKoZIhvcNAQEL
BQAwIDEeMBwGA1UEAwwVbXlzZXJ2aWNlLmV4YW1wbGUuY29tMB4XDTIxMDQyMjE3
MzIzOVoXDTIyMDQyMjE3MzIzOVowIDEeMBwGA1UEAwwVbXlzZXJ2aWNlLmV4YW1w
bGUuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAp9jElUsjM3We
UHA9CjGQLoQJlhNOqkv2oKZBvTpr8bAQF40vP9qGiwrZqRe0se1xCTmlVM/HGrct
C0I6aL7+WTamNEvwPimoWsk1V7zYY7cFW+JEmL/E379R+9giQLBxIvvg7hSd7icl
ucwG5V6WPenQiyneEegTsEEgKqagIAgSV9zYjNQrQes73WHBj8VCZhTsVEnVO+/V
mCJaEwwovZWP0loLebR29/I4gr/LmfQfrIA+j116o5zp44vl1yW98xq5snkgb4YB
fm6pTHDl2rn4pM3avL+sK1usz7HQwmKrW6cpLtKVc+Sbjf3MyLUuHxRmkPoY2J56
uJ1OWChoYwIDAQABo1MwUTAdBgNVHQ4EFgQUTjg/FEKaUjpqzm3KzCg+WMUsm2ow
HwYDVR0jBBgwFoAUTjg/FEKaUjpqzm3KzCg+WMUsm2owDwYDVR0TAQH/BAUwAwEB
/zANBgkqhkiG9w0BAQsFAAOCAQEALXtIWymIgZDMKHlVzMWq/Id/jP2aI0hpvZgS
2Lv7UDdyF1bj1MDTOtDkERoTVh/Xbhao5ls+GVhxvudjnZ8irdtlQRJNWTgj58Zi
zagO3StQ2duwZPyvt9jHgVJ8Jm9w8JVgsNP4Notl5Gj7dMGhxnm4aA2oHScJoC5g
u2ukGAAxgUpjNA7j9C/7iVLzijFzhztq7XTmbnJmuFPR50rqHJcTTYMVDc8Pb73c
mjDZDz9U91v+7j5oDLV/8khvLsHxi85/GA1+j7z77wWzrIwwNjY4YfbhF0HJZXQz
Xf3EHGxtSC01hJyfBjyXh5H8+NbyAYF8+d8y+uFu+GHhLS+Yag==
-----END CERTIFICATE-----

View File

@@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCn2MSVSyMzdZ5Q
cD0KMZAuhAmWE06qS/agpkG9OmvxsBAXjS8/2oaLCtmpF7Sx7XEJOaVUz8caty0L
Qjpovv5ZNqY0S/A+KahayTVXvNhjtwVb4kSYv8Tfv1H72CJAsHEi++DuFJ3uJyW5
zAblXpY96dCLKd4R6BOwQSAqpqAgCBJX3NiM1CtB6zvdYcGPxUJmFOxUSdU779WY
IloTDCi9lY/SWgt5tHb38jiCv8uZ9B+sgD6PXXqjnOnji+XXJb3zGrmyeSBvhgF+
bqlMcOXaufikzdq8v6wrW6zPsdDCYqtbpyku0pVz5JuN/czItS4fFGaQ+hjYnnq4
nU5YKGhjAgMBAAECggEAPlzd/ZJjS9VhswVgyI7NwVqxrR8TVVbQFbRwLHyuaqg9
8mI0sgbhgnvPj3INYyaTnxfaA/8HPTfd9pbu2MhN/Ju/eSLV6mLT+JdVyHmT9Mil
pxQU5KQr4+5T6bzOTTbBcnwfgJYMb9X/wF68GTDhpbNgFrTBm+mclxo7d11dlUiQ
bdA2KBhIM54kKcWEO90AP2FiC/cRSbv8afHVb0Fxy1SBAGVDSscUaEwgyHkRf6H7
Mmt4xhfBwLZZk5pVa97pZsas7L0sm4BtyYSs4elJesuo3DTpezZSzWHNreJcCL00
v7pm9ZWv9PYeUcQqSw6Ws2fhsydiN93o8s/fPpF0IQKBgQDQqvKPjGo3HLRnBmpc
nhUVQgjdh/QobJCGBjdxR/C3NkcatNLONddhefqovfb2jITdHmRKW+7XgRq/q457
mZ2JPvvryiWcVqf6D91tSoFYrVg5nU1zr6nTga1BpTH00/eNEXxjMAAguPj3m7Sw
3BP7L8yZZI1pOOjzVzuISEDlGwKBgQDN62ZXzRqorn5t+mDywTAJ2qAyZzoDAtUP
CjANOKBCECqE9cOW1qBDJcaEF7G+xyB22CMciOELjGXWw6ZwFWjc23tDCEkoHoia
5IWvwNs5ncdPCuOm3aay9w2dx9Idy+Eq5Vpe4XDlnZvEfzjwXX3gbQ5YS9kbNE2r
XtmgD/BmWQKBgBNKqrBA0BUWT0tzGWRErThQ6ZbpmdYe62Gos3mCqCuYFgzPCOpN
qgL2DwmIvotexG3ZAHardzJvWjS8PKkKs7jbnNjY0I9ap58D1nnjOIAlTpHNDDsU
04OdapI2Hp8+9ZUSN8jHyEs+Lq5ds9/iCOrhKW5JEJXY0BinSPa5j15fAoGBAM2I
dqCQollXwe34CaiD13UeeOOWUTsMKqlWW9v2d085X5dSzyTRmSksnVbfZ5SqoOa+
mV0z6pxiSIvywUACvqYjlIa10H9w6pzgF+fzMV3y9CsbDVtSxb7ABSFFf54qD9eH
EYq+rrchd4bMDYMtbiUB9V2AZ3VV4Wh5xfKTtjoRAoGAec/nBmX/oHiG/3T2LEmo
bTT+/eXc4LUzgHvFG6HW40PI8T2TfMTUGQH+90zkraryUF5PNoUF58tooDAcMWXm
DPEHFHeWn8T4obfuUjTw1mwj7Xjzr40HDitjIQa0bvBVwdYyEgiEw1CaeaUpq9Z8
ElGWoRHT87pfoZjbdPz7a4Y=
-----END PRIVATE KEY-----

View File

@@ -0,0 +1,357 @@
package system
import (
"context"
"fmt"
"io"
"net/http"
"testing"
"time"
"github.com/cortezaproject/corteza/server/pkg/id"
"github.com/cortezaproject/corteza/server/store"
"github.com/cortezaproject/corteza/server/system/service"
"github.com/cortezaproject/corteza/server/system/types"
"github.com/cortezaproject/corteza/server/tests/helpers"
jsonpath "github.com/steinfletcher/apitest-jsonpath"
)
func (h helper) repoMakeTemplate(ss ...string) *types.Template {
var res = &types.Template{
ID: id.Next(),
CreatedAt: time.Now(),
}
if len(ss) > 0 {
res.Handle = ss[0]
} else {
res.Handle = "n_" + rs()
}
if len(ss) > 1 {
res.Template = ss[1]
}
if len(ss) > 2 {
res.Type = types.DocumentType(ss[2])
}
h.a.NoError(store.CreateTemplate(context.Background(), service.DefaultStore, res))
return res
}
func (h helper) lookupTemplateByID(ID uint64) *types.Template {
res, err := store.LookupTemplateByID(context.Background(), service.DefaultStore, ID)
h.noError(err)
return res
}
func TestTemplateRead(t *testing.T) {
h := newHelper(t)
h.clearTemplates()
helpers.AllowMe(h, types.ComponentRbacResource(), "templates.search")
helpers.AllowMe(h, types.TemplateRbacResource(0), "read")
u := h.repoMakeTemplate()
h.apiInit().
Get(fmt.Sprintf("/template/%d", u.ID)).
Header("Accept", "application/json").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
Assert(jsonpath.Equal(`$.response.handle`, u.Handle)).
Assert(jsonpath.Equal(`$.response.templateID`, fmt.Sprintf("%d", u.ID))).
End()
}
func TestTemplateList(t *testing.T) {
h := newHelper(t)
h.clearTemplates()
helpers.AllowMe(h, types.ComponentRbacResource(), "templates.search")
helpers.AllowMe(h, types.TemplateRbacResource(0), "read")
h.repoMakeTemplate(rs())
h.repoMakeTemplate(rs())
h.apiInit().
Get("/template/").
Header("Accept", "application/json").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
Assert(jsonpath.Len(`$.response.set`, 2)).
End()
}
func TestTemplateList_filterForbidden(t *testing.T) {
h := newHelper(t)
h.clearTemplates()
// @todo this can be a problematic test because it leaves
// behind templates that are not denied this context
// db purge might be needed
h.repoMakeTemplate("template")
f := h.repoMakeTemplate()
helpers.AllowMe(h, types.ComponentRbacResource(), "templates.search")
helpers.DenyMe(h, f.RbacResource(), "read")
h.apiInit().
Get("/template/").
Header("Accept", "application/json").
Query("handle", f.Handle).
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
Assert(jsonpath.NotPresent(fmt.Sprintf(`$.response.set[? @.handle=="%s"]`, f.Handle))).
End()
}
func TestTemplateCreateForbidden(t *testing.T) {
h := newHelper(t)
h.clearTemplates()
h.apiInit().
Post("/template/").
Header("Accept", "application/json").
FormData("meta", fmt.Sprintf(`{"short": "%s"}`, rs())).
FormData("handle", "a"+rs()).
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertError("template.errors.notAllowedToCreate")).
End()
}
func TestTemplateCreate(t *testing.T) {
h := newHelper(t)
h.clearTemplates()
helpers.AllowMe(h, types.ComponentRbacResource(), "template.create")
h.apiInit().
Post("/template/").
Header("Accept", "application/json").
FormData("meta", fmt.Sprintf(`{"short": "%s"}`, rs())).
FormData("handle", "handle_"+rs()).
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
End()
}
func TestTemplateUpdateForbidden(t *testing.T) {
h := newHelper(t)
h.clearTemplates()
u := h.repoMakeTemplate()
h.apiInit().
Put(fmt.Sprintf("/template/%d", u.ID)).
Header("Accept", "application/json").
FormData("meta", fmt.Sprintf(`{"short": "%s"}`, rs())).
FormData("handle", "handle_"+rs()).
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertError("template.errors.notAllowedToUpdate")).
End()
}
func TestTemplateUpdate(t *testing.T) {
h := newHelper(t)
h.clearTemplates()
res := h.repoMakeTemplate()
helpers.AllowMe(h, types.TemplateRbacResource(0), "update")
newHandle := "updated-" + rs()
h.apiInit().
Put(fmt.Sprintf("/template/%d", res.ID)).
Header("Accept", "application/json").
FormData("meta", fmt.Sprintf(`{"short": "%s"}`, rs())).
FormData("handle", newHandle).
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
End()
res = h.lookupTemplateByID(res.ID)
h.a.NotNil(res)
h.a.Equal(newHandle, res.Handle)
}
func TestTemplateDeleteForbidden(t *testing.T) {
h := newHelper(t)
h.clearTemplates()
u := h.repoMakeTemplate()
helpers.AllowMe(h, types.ComponentRbacResource(), "templates.search")
h.apiInit().
Delete(fmt.Sprintf("/template/%d", u.ID)).
Header("Accept", "application/json").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertError("template.errors.notAllowedToDelete")).
End()
}
func TestTemplateDelete(t *testing.T) {
h := newHelper(t)
h.clearTemplates()
helpers.AllowMe(h, types.TemplateRbacResource(0), "delete")
res := h.repoMakeTemplate()
h.apiInit().
Delete(fmt.Sprintf("/template/%d", res.ID)).
Header("Accept", "application/json").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
End()
res = h.lookupTemplateByID(res.ID)
h.a.NotNil(res)
h.a.NotNil(res.DeletedAt)
}
func TestTemplateUndelete(t *testing.T) {
h := newHelper(t)
h.clearTemplates()
helpers.AllowMe(h, types.TemplateRbacResource(0), "delete")
res := h.repoMakeTemplate()
h.apiInit().
Post(fmt.Sprintf("/template/%d/undelete", res.ID)).
Header("Accept", "application/json").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
End()
res = h.lookupTemplateByID(res.ID)
h.a.NotNil(res)
h.a.Nil(res.DeletedAt)
}
func TestTemplateRenderForbidden(t *testing.T) {
h := newHelper(t)
h.clearTemplates()
helpers.AllowMe(h, types.ComponentRbacResource(), "templates.search")
helpers.AllowMe(h, types.TemplateRbacResource(0), "read")
helpers.DenyMe(h, types.TemplateRbacResource(0), "render")
res := h.repoMakeTemplate("rendering", "Hello, {{.interpolate}}", "text/plain")
h.apiInit().
Post(fmt.Sprintf("/template/%d/render/testing.txt", res.ID)).
JSON(`{"variables": {"interpolate": "world!"}}`).
Header("Accept", "application/json").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertError("template.errors.notAllowedToRender")).
End()
}
func TestTemplateRenderDriverUndefined(t *testing.T) {
h := newHelper(t)
h.clearTemplates()
helpers.AllowMe(h, types.ComponentRbacResource(), "templates.search")
helpers.AllowMe(h, types.TemplateRbacResource(0), "read")
helpers.AllowMe(h, types.TemplateRbacResource(0), "render")
res := h.repoMakeTemplate("rendering", "Hello, {{.interpolate}}", "text/notexisting")
h.apiInit().
Post(fmt.Sprintf("/template/%d/render/testing.txt", res.ID)).
JSON(`{"variables": {"interpolate": "world!"}}`).
Header("Accept", "application/json").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertError("rendering failed: driver not found")).
End()
}
func TestTemplateRenderPlain(t *testing.T) {
h := newHelper(t)
h.clearTemplates()
helpers.AllowMe(h, types.ComponentRbacResource(), "templates.search")
helpers.AllowMe(h, types.TemplateRbacResource(0), "read")
helpers.AllowMe(h, types.TemplateRbacResource(0), "render")
res := h.repoMakeTemplate("rendering", "Hello, {{.interpolate}}", "text/plain")
h.apiInit().
Post(fmt.Sprintf("/template/%d/render/testing.txt", res.ID)).
Header("Accept", "application/json").
JSON(`{"variables": {"interpolate": "world!"}}`).
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertBody("Hello, world!")).
End()
}
func TestTemplateRenderHTML(t *testing.T) {
h := newHelper(t)
h.clearTemplates()
helpers.AllowMe(h, types.ComponentRbacResource(), "templates.search")
helpers.AllowMe(h, types.TemplateRbacResource(0), "read")
helpers.AllowMe(h, types.TemplateRbacResource(0), "render")
res := h.repoMakeTemplate("rendering", "<h1>Hello, {{.interpolate}}</h1>", "text/html")
h.apiInit().
Post(fmt.Sprintf("/template/%d/render/testing.html", res.ID)).
Header("Accept", "application/json").
JSON(`{"variables": {"interpolate": "world!"}}`).
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertBody("<h1>Hello, world!</h1>")).
End()
}
func TestTemplateRenderAuxTemplates(t *testing.T) {
h := newHelper(t)
h.clearTemplates()
helpers.AllowMe(h, types.ComponentRbacResource(), "templates.search")
helpers.AllowMe(h, types.TemplateRbacResource(0), "read")
helpers.AllowMe(h, types.TemplateRbacResource(0), "render")
var (
ctx = h.secCtx()
header = h.repoMakeTemplate("header-tpl", "<header>from meta</header>", "text/html")
altHeader = h.repoMakeTemplate("alt-header-tpl", "<header>from argument</header>", "text/html")
footer = h.repoMakeTemplate("footer-tpl", "<footer>bye</footer>", "text/html")
body = h.repoMakeTemplate("body-tpl", "<h1>Hello</h1>", "text/html")
)
body.Meta.HeaderTemplateID = header.ID
h.a.NoError(store.UpdateTemplate(ctx, service.DefaultStore, body))
read := func(aux types.TemplateRenderAux) string {
doc, err := service.DefaultRenderer.Render(ctx, body.ID, "text/html", nil, nil, aux)
h.a.NoError(err)
out, err := io.ReadAll(doc)
h.a.NoError(err)
return string(out)
}
// no overrides, header comes from the template's own meta
out := read(types.TemplateRenderAux{})
h.a.Contains(out, "from meta")
h.a.NotContains(out, "from argument")
h.a.NotContains(out, "bye")
// header overridden, footer added
out = read(types.TemplateRenderAux{HeaderTemplateID: altHeader.ID, FooterTemplateID: footer.ID})
h.a.Contains(out, "from argument")
h.a.NotContains(out, "from meta")
h.a.Contains(out, "bye")
h.a.Contains(out, "<h1>Hello</h1>")
}

View File

@@ -0,0 +1,48 @@
{
"handle": "test_connection",
"type": "corteza::system:dal-connection",
"meta": {
"name": "Test Connection",
"location": {
"geometry": {
"type": "",
"coordinates": null
},
"properties": {
"name": ""
}
},
"ownership": ""
},
"config": {
"connection": {
"type": "corteza::dal:connection:dsn",
"params": {
"dsn": "sqlite3://file::memory:?cache=shared&mode=memory"
}
},
"privacy": {
"sensitivityLevelID": "0"
},
"dal": {
"properties": {},
"operations": [
"corteza::dal:operation:create",
"corteza::dal:operation:update",
"corteza::dal:operation:search",
"corteza::dal:operation:lookup",
"corteza::dal:operation:paging",
"corteza::dal:operation:stats",
"corteza::dal:operation:sorting",
"corteza::dal:operation:RBAC"
],
"modelIdent": "compose_record",
"attributeIdent": "values",
"partitionFormat": "compose_record_{{namespace}}_{{module}}",
"partitionIdentValidator": ""
}
}
}

View File

@@ -0,0 +1,48 @@
{
"handle": "test_connection",
"type": "corteza::system:dal-connection",
"meta": {
"name": "Test Connection",
"location": {
"geometry": {
"type": "",
"coordinates": null
},
"properties": {
"name": ""
}
},
"ownership": ""
},
"config": {
"connection": {
"type": "corteza::dal:connection:dsn",
"params": {
"dsn": "sqlite3://file::memory:?cache=shared&mode=memory"
}
},
"privacy": {
"sensitivityLevelID": "0"
},
"dal": {
"properties": {},
"operations": [
"corteza::dal:operation:create",
"corteza::dal:operation:update",
"corteza::dal:operation:search",
"corteza::dal:operation:lookup",
"corteza::dal:operation:paging",
"corteza::dal:operation:stats",
"corteza::dal:operation:sorting",
"corteza::dal:operation:RBAC"
],
"modelIdent": "compose_record",
"attributeIdent": "values",
"partitionFormat": "compose_record_{{namespace}}_{{module}}",
"partitionIdentValidator": ""
}
}
}

View File

@@ -0,0 +1,48 @@
{
"handle": "test_connection",
"type": "corteza::system:primary-dal-connection",
"meta": {
"name": "Test Connection",
"location": {
"geometry": {
"type": "",
"coordinates": null
},
"properties": {
"name": ""
}
},
"ownership": ""
},
"config": {
"connection": {
"type": "corteza::dal:connection:dsn",
"params": {
"dsn": "sqlite3://file::memory:?cache=shared&mode=memory"
}
},
"privacy": {
"sensitivityLevelID": "0"
},
"dal": {
"properties": {},
"operations": [
"corteza::dal:operation:create",
"corteza::dal:operation:update",
"corteza::dal:operation:search",
"corteza::dal:operation:lookup",
"corteza::dal:operation:paging",
"corteza::dal:operation:stats",
"corteza::dal:operation:sorting",
"corteza::dal:operation:RBAC"
],
"modelIdent": "compose_record",
"attributeIdent": "values",
"partitionFormat": "compose_record_{{namespace}}_{{module}}",
"partitionIdentValidator": ""
}
}
}

View File

@@ -0,0 +1,48 @@
{
"handle": "test_connection_edited",
"type": "corteza::system:dal-connection",
"meta": {
"name": "Test Connection EDITED",
"location": {
"geometry": {
"type": "",
"coordinates": null
},
"properties": {
"name": ""
}
},
"ownership": ""
},
"config": {
"connection": {
"type": "corteza::dal:connection:dsn",
"params": {
"dsn": "sqlite3://file::memory:?cache=shared&mode=memory"
}
},
"privacy": {
"sensitivityLevelID": "0"
},
"dal": {
"properties": {},
"operations": [
"corteza::dal:operation:create",
"corteza::dal:operation:update",
"corteza::dal:operation:search",
"corteza::dal:operation:lookup",
"corteza::dal:operation:paging",
"corteza::dal:operation:stats",
"corteza::dal:operation:sorting",
"corteza::dal:operation:RBAC"
],
"modelIdent": "compose_record",
"attributeIdent": "values",
"partitionFormat": "compose_record_{{namespace}}_{{module}}",
"partitionIdentValidator": ""
}
}
}

View File

@@ -0,0 +1,48 @@
{
"handle": "test_connection_edited",
"type": "corteza::system:dal-connection",
"meta": {
"name": "Test Connection EDITED",
"location": {
"geometry": {
"type": "",
"coordinates": null
},
"properties": {
"name": ""
}
},
"ownership": ""
},
"config": {
"connection": {
"type": "corteza::dal:connection:dsn",
"params": {
"dsn": "sqlite3://file::memory:?cache=shared&mode=memory"
}
},
"privacy": {
"sensitivityLevelID": "0"
},
"dal": {
"properties": {},
"operations": [
"corteza::dal:operation:create",
"corteza::dal:operation:update",
"corteza::dal:operation:search",
"corteza::dal:operation:lookup",
"corteza::dal:operation:paging",
"corteza::dal:operation:stats",
"corteza::dal:operation:sorting",
"corteza::dal:operation:RBAC"
],
"modelIdent": "compose_record",
"attributeIdent": "values",
"partitionFormat": "compose_record_{{namespace}}_{{module}}",
"partitionIdentValidator": ""
}
}
}

View File

@@ -0,0 +1,48 @@
{
"handle": "primary_connection",
"type": "corteza::system:primary-dal-connection",
"meta": {
"name": "Primary Connection EDITED",
"location": {
"geometry": {
"type": "",
"coordinates": null
},
"properties": {
"name": ""
}
},
"ownership": ""
},
"config": {
"connection": {
"type": "corteza::dal:connection:dsn",
"params": {
"dsn": "sqlite3://file::memory:?cache=shared&mode=memory"
}
},
"privacy": {
"sensitivityLevelID": "0"
},
"dal": {
"properties": {},
"operations": [
"corteza::dal:operation:create",
"corteza::dal:operation:update",
"corteza::dal:operation:search",
"corteza::dal:operation:lookup",
"corteza::dal:operation:paging",
"corteza::dal:operation:stats",
"corteza::dal:operation:sorting",
"corteza::dal:operation:RBAC"
],
"modelIdent": "compose_record",
"attributeIdent": "values",
"partitionFormat": "compose_record_{{namespace}}_{{module}}",
"partitionIdentValidator": ""
}
}
}

View File

@@ -0,0 +1,8 @@
{
"handle": "private",
"level": 1,
"meta": {
"name": "private sensitivity",
"description": "data is private to the owner"
}
}

View File

@@ -0,0 +1,8 @@
{
"handle": "private",
"level": 1,
"meta": {
"name": "private sensitivity",
"description": "data is private to the owner"
}
}

View File

@@ -0,0 +1,8 @@
{
"handle": "private_edited",
"level": 1,
"meta": {
"name": "private sensitivity",
"description": "data is private to the owner"
}
}

View File

@@ -0,0 +1,8 @@
{
"handle": "private_edited",
"level": 1,
"meta": {
"name": "private sensitivity",
"description": "data is private to the owner"
}
}

View File

@@ -0,0 +1,663 @@
package system
import (
"context"
"fmt"
"net/http"
"net/url"
"testing"
"time"
"github.com/cortezaproject/corteza/server/pkg/id"
labelTypes "github.com/cortezaproject/corteza/server/pkg/label/types"
"github.com/cortezaproject/corteza/server/store"
"github.com/cortezaproject/corteza/server/system/service"
"github.com/cortezaproject/corteza/server/system/types"
"github.com/cortezaproject/corteza/server/tests/helpers"
jsonpath "github.com/steinfletcher/apitest-jsonpath"
"github.com/stretchr/testify/require"
)
func (h helper) randEmail() string {
return fmt.Sprintf("%s@test.tld", rs())
}
func (h helper) createUserWithEmail(email string) *types.User {
return h.createUser(&types.User{Email: email})
}
func (h helper) createUser(user *types.User) *types.User {
if user.ID == 0 {
user.ID = id.Next()
}
if user.CreatedAt.IsZero() {
user.CreatedAt = time.Now()
}
h.a.NoError(service.DefaultStore.CreateUser(context.Background(), user))
return user
}
func (h helper) clearUsers() {
h.noError(store.TruncateUsers(context.Background(), service.DefaultStore))
}
func (h helper) lookupUserByEmail(email string) *types.User {
res, err := store.LookupUserByEmail(context.Background(), service.DefaultStore, email)
h.noError(err)
return res
}
func TestUserRead(t *testing.T) {
h := newHelper(t)
h.clearUsers()
helpers.AllowMe(h, types.UserRbacResource(0), "read")
service.CurrentSettings.Privacy.Mask.Email = true
service.CurrentSettings.Privacy.Mask.Name = true
defer func() {
service.CurrentSettings.Privacy.Mask.Email = false
service.CurrentSettings.Privacy.Mask.Name = false
}()
u := h.createUserWithEmail(h.randEmail())
h.apiInit().
Get(fmt.Sprintf("/users/%d", u.ID)).
Header("Accept", "application/json").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
Assert(jsonpath.Contains(`$.response.email`, "####")).
Assert(jsonpath.Equal(`$.response.userID`, fmt.Sprintf("%d", u.ID))).
End()
u = h.createUserWithEmail(h.randEmail())
helpers.AllowMe(h, types.UserRbacResource(0), "email.unmask")
h.apiInit().
Get(fmt.Sprintf("/users/%d", u.ID)).
Header("Accept", "application/json").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
Assert(jsonpath.Equal(`$.response.email`, u.Email)).
End()
}
func TestUserRead_forbidden(t *testing.T) {
h := newHelper(t)
h.clearUsers()
service.CurrentSettings.Privacy.Mask.Email = true
service.CurrentSettings.Privacy.Mask.Name = true
defer func() {
service.CurrentSettings.Privacy.Mask.Email = false
service.CurrentSettings.Privacy.Mask.Name = false
}()
u := h.createUser(&types.User{
Username: "test",
Email: "test@domain.tld",
Name: "Name Here",
Handle: "test_handle",
EmailConfirmed: true,
})
h.apiInit().
Get(fmt.Sprintf("/users/%d", u.ID)).
Header("Accept", "application/json").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertError("user.errors.notAllowedToRead")).
End()
}
func TestUserListAll(t *testing.T) {
h := newHelper(t)
h.clearUsers()
h.secCtx()
seedCount := 5
for i := 0; i < seedCount; i++ {
h.createUserWithEmail(h.randEmail())
}
helpers.AllowMe(h, types.ComponentRbacResource(), "users.search")
helpers.AllowMe(h, types.UserRbacResource(0), "read")
h.apiInit().
Get("/users/").
Header("Accept", "application/json").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
Assert(jsonpath.Present(`$.response.filter`)).
Assert(jsonpath.Present(`$.response.set`)).
End()
}
func TestUserListWithPaging(t *testing.T) {
h := newHelper(t)
h.clearUsers()
h.secCtx()
seedCount := 40
for i := 0; i < seedCount; i++ {
h.createUserWithEmail(h.randEmail())
}
helpers.AllowMe(h, types.ComponentRbacResource(), "users.search")
helpers.AllowMe(h, types.UserRbacResource(0), "read")
var aux = struct {
Response struct {
Filter struct {
NextPage *string
PrevPage *string
}
}
}{}
h.apiInit().
Get("/users/").
Query("limit", "13").
Header("Accept", "application/json").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
Assert(jsonpath.Present(`$.response.filter`)).
Assert(jsonpath.Present(`$.response.set`)).
Assert(jsonpath.Len(`$.response.set`, 13)).
Assert(jsonpath.Present(`$.response.filter.nextPage`)).
End().
JSON(&aux)
h.a.NotNil(aux.Response.Filter.NextPage)
h.apiInit().
Get("/users/").
Header("Accept", "application/json").
Query("limit", "13").
Query("pageCursor", *aux.Response.Filter.NextPage).
Expect(t).
Status(http.StatusOK).
Assert(jsonpath.Len(`$.response.set`, 13)).
Assert(jsonpath.Present(`$.response.filter.prevPage`)).
Assert(jsonpath.Present(`$.response.filter.nextPage`)).
End().
JSON(&aux)
h.a.NotNil(aux.Response.Filter.PrevPage)
h.a.NotNil(aux.Response.Filter.NextPage)
}
func TestUserList_filterForbidden(t *testing.T) {
h := newHelper(t)
h.clearUsers()
helpers.AllowMe(h, types.ComponentRbacResource(), "users.search")
helpers.AllowMe(h, types.UserRbacResource(0), "read")
h.createUserWithEmail("usr")
f := h.createUserWithEmail(h.randEmail())
helpers.DenyMe(h, f.RbacResource(), "read")
h.apiInit().
Get("/users/").
Header("Accept", "application/json").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
Assert(jsonpath.NotPresent(fmt.Sprintf(`$.response.set[? @.email=="%s"]`, f.Email))).
End()
}
func TestUserListQuery(t *testing.T) {
h := newHelper(t)
h.clearUsers()
h.secCtx()
helpers.AllowMe(h, types.ComponentRbacResource(), "users.search")
helpers.AllowMe(h, types.UserRbacResource(0), "read")
h.apiInit().
Get("/users/").
Header("Accept", "application/json").
Query("query", h.randEmail()).
Query("email", h.randEmail()).
Query("name", "John Doe").
Query("handle", "jdoe").
Query("username", "jdoe").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
Assert(jsonpath.Present(`$.response.filter`)).
End()
}
func TestUserListQueryEmail(t *testing.T) {
h := newHelper(t)
h.clearUsers()
h.secCtx()
helpers.AllowMe(h, types.ComponentRbacResource(), "users.search")
helpers.AllowMe(h, types.UserRbacResource(0), "read")
helpers.AllowMe(h, types.UserRbacResource(0), "email.unmask")
ee := h.randEmail()
h.createUserWithEmail(ee)
h.apiInit().
Get("/users/").
Header("Accept", "application/json").
Query("email", ee).
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
Assert(jsonpath.Present(`$.response.set != null`)).
End()
}
func TestUserListQueryUsername(t *testing.T) {
h := newHelper(t)
h.clearUsers()
h.secCtx()
helpers.AllowMe(h, types.ComponentRbacResource(), "users.search")
helpers.AllowMe(h, types.UserRbacResource(0), "read")
ee := h.randEmail()
h.createUser(&types.User{
Email: "test@test.tld",
Username: ee,
})
h.apiInit().
Get("/users/").
Header("Accept", "application/json").
Query("username", ee).
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
Assert(jsonpath.Present(`$.response.set != null`)).
End()
}
func TestUserListQueryHandle(t *testing.T) {
h := newHelper(t)
h.clearUsers()
h.secCtx()
helpers.AllowMe(h, types.UserRbacResource(0), "read")
helpers.AllowMe(h, types.ComponentRbacResource(), "users.search")
h.createUser(&types.User{
Email: "test@test.tld",
Handle: "johnDoe",
})
h.apiInit().
Get("/users/").
Query("handle", "johnDoe").
Header("Accept", "application/json").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
Assert(jsonpath.Present(`$.response.set != null`)).
End()
}
func TestUserListWithOneAllowed(t *testing.T) {
h := newHelper(t)
h.clearUsers()
h.secCtx()
newUserWeCanAccess := h.createUserWithEmail(h.randEmail())
helpers.AllowMe(h, types.ComponentRbacResource(), "users.search")
helpers.AllowMe(h, newUserWeCanAccess.RbacResource(), "read")
// And one we cannot access
h.createUserWithEmail(h.randEmail())
aux := struct {
Response *struct {
Filter *types.UserFilter
}
}{}
h.apiInit().
Get("/users/").
Header("Accept", "application/json").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
Assert(jsonpath.Present(`$.response.filter`)).
Assert(jsonpath.Present(`$.response.set`)).
Assert(jsonpath.Len(`$.response.set`, 1)).
End().
JSON(&aux)
}
func TestUserCreateForbidden(t *testing.T) {
h := newHelper(t)
h.clearUsers()
h.apiInit().
Post("/users/").
Header("Accept", "application/json").
FormData("email", h.randEmail()).
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertError("user.errors.notAllowedToCreate")).
End()
}
func TestUserCreate(t *testing.T) {
h := newHelper(t)
h.clearUsers()
helpers.AllowMe(h, types.ComponentRbacResource(), "user.create")
email := h.randEmail()
h.apiInit().
Post("/users/").
Header("Accept", "application/json").
FormData("email", email).
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
End()
}
func TestUserUpdateForbidden(t *testing.T) {
h := newHelper(t)
h.clearUsers()
u := h.createUserWithEmail(h.randEmail())
h.apiInit().
Put(fmt.Sprintf("/users/%d", u.ID)).
Header("Accept", "application/json").
FormData("email", h.randEmail()).
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertError("user.errors.notAllowedToUpdate")).
End()
}
func TestUserUpdate(t *testing.T) {
h := newHelper(t)
h.clearUsers()
u := h.createUserWithEmail(h.randEmail())
helpers.AllowMe(h, types.UserRbacResource(0), "update")
newEmail := h.randEmail()
h.apiInit().
Put(fmt.Sprintf("/users/%d", u.ID)).
Header("Accept", "application/json").
FormData("email", newEmail).
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
End()
}
func TestUserSetPassword(t *testing.T) {
h := newHelper(t)
h.clearUsers()
u := h.createUserWithEmail(h.randEmail())
helpers.AllowMe(h, types.UserRbacResource(0), "update")
h.apiInit().
Post(fmt.Sprintf("/users/%d/password", u.ID)).
FormData("password", "newPassword").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
End()
}
func TestUserSuspend(t *testing.T) {
h := newHelper(t)
h.clearUsers()
u := h.createUserWithEmail(h.randEmail())
helpers.AllowMe(h, types.UserRbacResource(0), "suspend")
h.apiInit().
Post(fmt.Sprintf("/users/%d/suspend", u.ID)).
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
End()
u = h.lookupUserByEmail(u.Email)
h.a.NotNil(u)
h.a.NotNil(u.SuspendedAt)
}
func TestUserUnsuspend(t *testing.T) {
h := newHelper(t)
h.clearUsers()
u := h.createUserWithEmail(h.randEmail())
helpers.AllowMe(h, types.UserRbacResource(0), "unsuspend")
h.apiInit().
Post(fmt.Sprintf("/users/%d/unsuspend", u.ID)).
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
End()
u = h.lookupUserByEmail(u.Email)
h.a.NotNil(u)
h.a.Nil(u.SuspendedAt)
}
func TestUserDeleteForbidden(t *testing.T) {
h := newHelper(t)
h.clearUsers()
u := h.createUserWithEmail(h.randEmail())
h.apiInit().
Delete(fmt.Sprintf("/users/%d", u.ID)).
Header("Accept", "application/json").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertError("user.errors.notAllowedToDelete")).
End()
}
func TestUserDelete(t *testing.T) {
h := newHelper(t)
h.clearUsers()
helpers.AllowMe(h, types.UserRbacResource(0), "delete")
u := h.createUserWithEmail(h.randEmail())
h.apiInit().
Delete(fmt.Sprintf("/users/%d", u.ID)).
Header("Accept", "application/json").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
End()
}
func TestUserUndelete(t *testing.T) {
h := newHelper(t)
h.clearUsers()
helpers.AllowMe(h, types.UserRbacResource(0), "delete")
u := h.createUserWithEmail(h.randEmail())
h.apiInit().
Post(fmt.Sprintf("/users/%d/undelete", u.ID)).
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
End()
u = h.lookupUserByEmail(u.Email)
h.a.NotNil(u)
h.a.Nil(u.DeletedAt)
}
func TestUserLabels(t *testing.T) {
h := newHelper(t)
h.clearUsers()
helpers.AllowMe(h, types.ComponentRbacResource(), "user.create")
helpers.AllowMe(h, types.ComponentRbacResource(), "users.search")
helpers.AllowMe(h, types.UserRbacResource(0), "read")
helpers.AllowMe(h, types.UserRbacResource(0), "update")
helpers.AllowMe(h, types.UserRbacResource(0), "delete")
var (
ID uint64
)
t.Run("create", func(t *testing.T) {
var (
req = require.New(t)
payload = &types.User{}
)
helpers.SetLabelsViaAPI(h.apiInit(), t,
"/users/",
types.User{Email: h.randEmail(), Labels: map[string]labelTypes.LabelValue{"foo": {Val: "bar"}, "bar": {Val: "42"}}},
payload,
)
req.NotZero(payload.ID)
h.a.Equal(payload.Labels["foo"].Val, "bar",
"labels must contain foo with value bar")
h.a.Equal(payload.Labels["bar"].Val, "42",
"labels must contain bar with value 42")
req.Equal(helpers.LabelsToStrings(payload.Labels), helpers.LabelsToStrings(helpers.LoadLabelsFromStore(t, service.DefaultStore, payload.LabelResourceKind(), payload.ID)),
"response must match stored labels")
ID = payload.ID
})
t.Run("update", func(t *testing.T) {
if ID == 0 {
t.Skip("label/create test not ran")
}
var (
req = require.New(t)
payload = &types.User{}
)
helpers.SetLabelsViaAPI(h.apiInit(), t,
fmt.Sprintf("PUT /users/%d", ID),
&types.User{ID: ID, Email: h.randEmail(), Labels: map[string]labelTypes.LabelValue{"foo": {Val: "baz"}, "baz": {Val: "123"}}},
payload,
)
req.NotZero(payload.ID)
//req.Nil(payload.UpdatedAt, "updatedAt must not change after changing labels")
req.Equal(payload.Labels["foo"].Val, "baz",
"labels must contain foo with value baz")
req.NotContains(payload.Labels, "bar",
"labels must not contain bar")
req.Equal(payload.Labels["baz"].Val, "123",
"labels must contain baz with value 123")
req.Equal(helpers.LabelsToStrings(payload.Labels), helpers.LabelsToStrings(helpers.LoadLabelsFromStore(t, service.DefaultStore, payload.LabelResourceKind(), payload.ID)),
"response must match stored labels")
})
t.Run("search", func(t *testing.T) {
if ID == 0 {
t.Skip("label/create test not ran")
}
var (
req = require.New(t)
set = types.UserSet{}
)
helpers.SearchWithLabelsViaAPI(h.apiInit(), t, "/users/", &set, url.Values{"labels": []string{"baz=123"}})
req.NotEmpty(set)
req.NotNil(set.FindByID(ID))
req.NotNil(set.FindByID(ID).Labels)
})
}
func TestUserMemberList(t *testing.T) {
h := newHelper(t)
h.clearUsers()
helpers.AllowMe(h, types.UserRbacResource(0), "read")
helpers.AllowMe(h, types.RoleRbacResource(0), "read")
u := h.createUserWithEmail(h.randEmail())
r1 := h.repoMakeRole(h.randEmail())
r2 := h.repoMakeRole(h.randEmail())
h.createRoleMember(u.ID, r1.ID)
h.createRoleMember(u.ID, r2.ID)
h.apiInit().
Get(fmt.Sprintf("/users/%d/membership", u.ID)).
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
Assert(jsonpath.Len(`$.response`, 2)).
End()
}
func TestUserMemberAdd(t *testing.T) {
h := newHelper(t)
h.clearUsers()
helpers.AllowMe(h, types.UserRbacResource(0), "read")
helpers.AllowMe(h, types.RoleRbacResource(0), "members.manage")
u := h.createUserWithEmail(h.randEmail())
r := h.repoMakeRole(h.randEmail())
h.apiInit().
Post(fmt.Sprintf("/users/%d/membership/%d", u.ID, r.ID)).
Header("Accept", "application/json").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
End()
}
func TestUserMemberRemove(t *testing.T) {
h := newHelper(t)
h.clearUsers()
helpers.AllowMe(h, types.UserRbacResource(0), "read")
helpers.AllowMe(h, types.RoleRbacResource(0), "members.manage")
u := h.createUserWithEmail(h.randEmail())
r := h.repoMakeRole(h.randEmail())
h.apiInit().
Delete(fmt.Sprintf("/users/%d/membership/%d", u.ID, r.ID)).
Header("Accept", "application/json").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
End()
}