mirror of
https://github.com/casdoor/casdoor.git
synced 2025-05-22 18:25:47 +08:00
261 lines
7.4 KiB
Go
261 lines
7.4 KiB
Go
![]() |
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
|
||
|
//
|
||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||
|
// you may not use this file except in compliance with the License.
|
||
|
// You may obtain a copy of the License at
|
||
|
//
|
||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||
|
//
|
||
|
// Unless required by applicable law or agreed to in writing, software
|
||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
|
// See the License for the specific language governing permissions and
|
||
|
// limitations under the License.
|
||
|
|
||
|
package scim
|
||
|
|
||
|
import (
|
||
|
"fmt"
|
||
|
"net/http"
|
||
|
|
||
|
"github.com/casdoor/casdoor/object"
|
||
|
"github.com/elimity-com/scim"
|
||
|
"github.com/elimity-com/scim/errors"
|
||
|
)
|
||
|
|
||
|
type UserResourceHandler struct{}
|
||
|
|
||
|
// https://github.com/elimity-com/scim/blob/master/resource_handler_test.go Example in-memory resource handler
|
||
|
// https://datatracker.ietf.org/doc/html/rfc7644#section-3.4 How to query/update resources
|
||
|
|
||
|
func (h UserResourceHandler) Create(r *http.Request, attrs scim.ResourceAttributes) (scim.Resource, error) {
|
||
|
resource := &scim.Resource{Attributes: attrs}
|
||
|
err := AddScimUser(resource)
|
||
|
return *resource, err
|
||
|
}
|
||
|
|
||
|
func (h UserResourceHandler) Get(r *http.Request, id string) (scim.Resource, error) {
|
||
|
resource, err := GetScimUser(id)
|
||
|
if err != nil {
|
||
|
return scim.Resource{}, err
|
||
|
}
|
||
|
if resource == nil {
|
||
|
return scim.Resource{}, errors.ScimErrorResourceNotFound(id)
|
||
|
}
|
||
|
return *resource, nil
|
||
|
}
|
||
|
|
||
|
func (h UserResourceHandler) Delete(r *http.Request, id string) error {
|
||
|
user, err := object.GetUserByUserIdOnly(id)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
if user == nil {
|
||
|
return errors.ScimErrorResourceNotFound(id)
|
||
|
}
|
||
|
_, err = object.DeleteUser(user)
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
func (h UserResourceHandler) GetAll(r *http.Request, params scim.ListRequestParams) (scim.Page, error) {
|
||
|
if params.Count == 0 {
|
||
|
count, err := object.GetGlobalUserCount("", "")
|
||
|
if err != nil {
|
||
|
return scim.Page{}, err
|
||
|
}
|
||
|
return scim.Page{TotalResults: int(count)}, nil
|
||
|
}
|
||
|
|
||
|
resources := make([]scim.Resource, 0)
|
||
|
// startIndex is 1-based index
|
||
|
users, err := object.GetPaginationGlobalUsers(params.StartIndex-1, params.Count, "", "", "", "")
|
||
|
if err != nil {
|
||
|
return scim.Page{}, err
|
||
|
}
|
||
|
for _, user := range users {
|
||
|
resources = append(resources, *user2resource(user))
|
||
|
}
|
||
|
return scim.Page{
|
||
|
TotalResults: len(resources),
|
||
|
Resources: resources,
|
||
|
}, nil
|
||
|
}
|
||
|
|
||
|
func (h UserResourceHandler) Patch(r *http.Request, id string, operations []scim.PatchOperation) (scim.Resource, error) {
|
||
|
user, err := object.GetUserByUserIdOnly(id)
|
||
|
if err != nil {
|
||
|
return scim.Resource{}, err
|
||
|
}
|
||
|
if user == nil {
|
||
|
return scim.Resource{}, errors.ScimErrorResourceNotFound(id)
|
||
|
}
|
||
|
return UpdateScimUserByPatchOperation(id, operations)
|
||
|
}
|
||
|
|
||
|
func (h UserResourceHandler) Replace(r *http.Request, id string, attrs scim.ResourceAttributes) (scim.Resource, error) {
|
||
|
user, err := object.GetUserByUserIdOnly(id)
|
||
|
if err != nil {
|
||
|
return scim.Resource{}, err
|
||
|
}
|
||
|
if user == nil {
|
||
|
return scim.Resource{}, errors.ScimErrorResourceNotFound(id)
|
||
|
}
|
||
|
resource := &scim.Resource{Attributes: attrs}
|
||
|
err = UpdateScimUser(id, resource)
|
||
|
return *resource, err
|
||
|
}
|
||
|
|
||
|
func GetScimUser(id string) (*scim.Resource, error) {
|
||
|
user, err := object.GetUserByUserIdOnly(id)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
if user == nil {
|
||
|
return nil, nil
|
||
|
}
|
||
|
r := user2resource(user)
|
||
|
return r, nil
|
||
|
}
|
||
|
|
||
|
func AddScimUser(r *scim.Resource) error {
|
||
|
newUser, err := resource2user(r.Attributes)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
// Check whether the user exists.
|
||
|
oldUser, err := object.GetUser(newUser.GetId())
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
if oldUser != nil {
|
||
|
return errors.ScimErrorUniqueness
|
||
|
}
|
||
|
|
||
|
affect, err := object.AddUser(newUser)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
if !affect {
|
||
|
return fmt.Errorf("add new user failed")
|
||
|
}
|
||
|
|
||
|
r.Attributes = user2resource(newUser).Attributes
|
||
|
r.ID = newUser.Id
|
||
|
r.ExternalID = buildExternalId(newUser)
|
||
|
r.Meta = buildMeta(newUser)
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func UpdateScimUser(id string, r *scim.Resource) error {
|
||
|
oldUser, err := object.GetUserByUserIdOnly(id)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
if oldUser == nil {
|
||
|
return errors.ScimErrorResourceNotFound(id)
|
||
|
}
|
||
|
newUser, err := resource2user(r.Attributes)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
_, err = object.UpdateUser(oldUser.GetId(), newUser, nil, true)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
r.ID = newUser.Id
|
||
|
r.ExternalID = buildExternalId(newUser)
|
||
|
r.Meta = buildMeta(newUser)
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// https://datatracker.ietf.org/doc/html/rfc7644#section-3.5.2 Modifying with PATCH
|
||
|
func UpdateScimUserByPatchOperation(id string, ops []scim.PatchOperation) (r scim.Resource, err error) {
|
||
|
user, err := object.GetUserByUserIdOnly(id)
|
||
|
if err != nil {
|
||
|
return scim.Resource{}, err
|
||
|
}
|
||
|
if user == nil {
|
||
|
return scim.Resource{}, errors.ScimErrorResourceNotFound(id)
|
||
|
}
|
||
|
defer func() {
|
||
|
if r := recover(); r != nil {
|
||
|
err = fmt.Errorf("invalid patch op value: %v", r)
|
||
|
}
|
||
|
}()
|
||
|
old := user.GetId()
|
||
|
for _, op := range ops {
|
||
|
value := op.Value
|
||
|
if op.Op == scim.PatchOperationRemove {
|
||
|
value = nil
|
||
|
}
|
||
|
// PatchOperationAdd and PatchOperationReplace is same in Casdoor, just replace the value
|
||
|
switch op.Path.String() {
|
||
|
case "userName":
|
||
|
user.Name = ToString(value, "")
|
||
|
case "password":
|
||
|
user.Password = ToString(value, "")
|
||
|
case "externalId":
|
||
|
user.ExternalId = ToString(value, "")
|
||
|
case "displayName":
|
||
|
user.DisplayName = ToString(value, "")
|
||
|
case "profileUrl":
|
||
|
user.Homepage = ToString(value, "")
|
||
|
case "userType":
|
||
|
user.Type = ToString(value, "")
|
||
|
case "name.givenName":
|
||
|
user.FirstName = ToString(value, "")
|
||
|
case "name.familyName":
|
||
|
user.LastName = ToString(value, "")
|
||
|
case "name":
|
||
|
defaultV := AnyMap{"givenName": "", "familyName": ""}
|
||
|
v := ToAnyMap(value, defaultV) // e.g. {"givenName": "AA", "familyName": "BB"}
|
||
|
user.FirstName = ToString(v["givenName"], user.FirstName)
|
||
|
user.LastName = ToString(v["familyName"], user.LastName)
|
||
|
case "emails":
|
||
|
defaultV := AnyArray{AnyMap{"value": ""}}
|
||
|
vs := ToAnyArray(value, defaultV) // e.g. [{"value": "test@casdoor"}]
|
||
|
if len(vs) > 0 {
|
||
|
v := ToAnyMap(vs[0])
|
||
|
user.Email = ToString(v["value"], user.Email)
|
||
|
}
|
||
|
case "phoneNumbers":
|
||
|
defaultV := AnyArray{AnyMap{"value": ""}}
|
||
|
vs := ToAnyArray(value, defaultV) // e.g. [{"value": "18750004417"}]
|
||
|
if len(vs) > 0 {
|
||
|
v := ToAnyMap(vs[0])
|
||
|
user.Phone = ToString(v["value"], user.Phone)
|
||
|
}
|
||
|
case "photos":
|
||
|
defaultV := AnyArray{AnyMap{"value": ""}}
|
||
|
vs := ToAnyArray(value, defaultV) // e.g. [{"value": "https://cdn.casbin.org/img/casbin.svg"}]
|
||
|
if len(vs) > 0 {
|
||
|
v := ToAnyMap(vs[0])
|
||
|
user.Avatar = ToString(v["value"], user.Avatar)
|
||
|
}
|
||
|
case "addresses":
|
||
|
defaultV := AnyArray{AnyMap{"locality": "", "region": "", "country": ""}}
|
||
|
vs := ToAnyArray(value, defaultV) // e.g. [{"locality": "Hollywood", "region": "CN", "country": "USA"}]
|
||
|
if len(vs) > 0 {
|
||
|
v := ToAnyMap(vs[0])
|
||
|
user.Location = ToString(v["locality"], user.Location)
|
||
|
user.Region = ToString(v["region"], user.Region)
|
||
|
user.CountryCode = ToString(v["country"], user.CountryCode)
|
||
|
}
|
||
|
case UserExtensionKey:
|
||
|
defaultV := AnyMap{"organization": user.Owner}
|
||
|
v := ToAnyMap(value, defaultV) // e.g. {"organization": "org1"}
|
||
|
user.Owner = ToString(v["organization"], user.Owner)
|
||
|
case fmt.Sprintf("%v.%v", UserExtensionKey, "organization"):
|
||
|
user.Owner = ToString(value, user.Owner)
|
||
|
}
|
||
|
}
|
||
|
_, err = object.UpdateUser(old, user, nil, true)
|
||
|
if err != nil {
|
||
|
return scim.Resource{}, err
|
||
|
}
|
||
|
r = *user2resource(user)
|
||
|
return r, nil
|
||
|
}
|