385 lines
9.5 KiB
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}$`)
|
|
}
|