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
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:
399
server/tests/system/scim_test.go
Normal file
399
server/tests/system/scim_test.go
Normal 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}$`)
|
||||
}
|
||||
Reference in New Issue
Block a user