3
0
corteza/tests/system/scim_test.go

385 lines
9.5 KiB
Go

package system
import (
"context"
"fmt"
"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"
"github.com/steinfletcher/apitest"
jsonpath "github.com/steinfletcher/apitest-jsonpath"
"net/http"
"regexp"
"testing"
)
// 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()
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, UserID: 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":[{"value":%q}]}],"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":[{"value":%q}]},{"op":"remove","path":"members","value":[{"value":%q}]}],"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}$`)
}