zitadel/internal/api/scim/resources/resource_list.go

149 lines
4.4 KiB
Go
Raw Normal View History

package resources
import (
"encoding/json"
"net/http"
zhttp "github.com/zitadel/zitadel/internal/api/http"
"github.com/zitadel/zitadel/internal/api/scim/resources/filter"
"github.com/zitadel/zitadel/internal/api/scim/schemas"
"github.com/zitadel/zitadel/internal/api/scim/serrors"
"github.com/zitadel/zitadel/internal/query"
"github.com/zitadel/zitadel/internal/zerrors"
)
type ListRequest struct {
// Count An integer indicating the desired maximum number of query results per page.
Count int64 `json:"count" schema:"count"`
// StartIndex An integer indicating the 1-based index of the first query result.
StartIndex int64 `json:"startIndex" schema:"startIndex"`
// Filter a scim filter expression to filter the query result.
Filter *filter.Filter `json:"filter,omitempty" schema:"filter"`
// SortBy attribute path to the sort attribute
SortBy string `json:"sortBy" schema:"sortBy"`
SortOrder ListRequestSortOrder `json:"sortOrder" schema:"sortOrder"`
}
type ListResponse[T ResourceHolder] struct {
Schemas []schemas.ScimSchemaType `json:"schemas"`
ItemsPerPage uint64 `json:"itemsPerPage"`
TotalResults uint64 `json:"totalResults"`
StartIndex uint64 `json:"startIndex"`
Resources []T `json:"Resources"` // according to the rfc this is the only field in PascalCase...
}
type ListRequestSortOrder string
const (
ListRequestSortOrderAsc ListRequestSortOrder = "ascending"
ListRequestSortOrderDsc ListRequestSortOrder = "descending"
defaultListCount = 100
maxListCount = 100
)
var parser = zhttp.NewParser()
func (o ListRequestSortOrder) isDefined() bool {
switch o {
case ListRequestSortOrderAsc, ListRequestSortOrderDsc:
return true
default:
return false
}
}
func (o ListRequestSortOrder) IsAscending() bool {
return o == ListRequestSortOrderAsc
}
func newListResponse[T ResourceHolder](totalResultCount uint64, q query.SearchRequest, resources []T) *ListResponse[T] {
return &ListResponse[T]{
Schemas: []schemas.ScimSchemaType{schemas.IdListResponse},
ItemsPerPage: q.Limit,
TotalResults: totalResultCount,
StartIndex: q.Offset + 1, // start index is 1 based
Resources: resources,
}
}
func readListRequest(r *http.Request) (*ListRequest, error) {
request := &ListRequest{
Count: defaultListCount,
StartIndex: 1,
SortOrder: ListRequestSortOrderAsc,
}
switch r.Method {
case http.MethodGet:
if err := parser.Parse(r, request); err != nil {
err = parser.UnwrapParserError(err)
if serrors.IsScimOrZitadelError(err) {
return nil, err
}
return nil, zerrors.ThrowInvalidArgument(nil, "SCIM-ullform", "Could not decode form: "+err.Error())
}
case http.MethodPost:
if err := json.NewDecoder(r.Body).Decode(request); err != nil {
if serrors.IsScimOrZitadelError(err) {
return nil, err
}
return nil, zerrors.ThrowInvalidArgument(nil, "SCIM-ulljson", "Could not decode json: "+err.Error())
}
// json deserialization initializes this field if an empty string is provided
// to not special case this in the resource implementation,
// set it to nil here.
if request.Filter.IsZero() {
request.Filter = nil
}
}
return request, request.validate()
}
func (r *ListRequest) toSearchRequest(defaultSortCol query.Column, fieldPathColumnMapping filter.FieldPathMapping) (query.SearchRequest, error) {
sr := query.SearchRequest{
Offset: uint64(r.StartIndex - 1), // start index is 1 based
Limit: uint64(r.Count),
Asc: r.SortOrder.IsAscending(),
}
if r.SortBy == "" {
// set a default sort to ensure consistent results
sr.SortingColumn = defaultSortCol
} else if sortCol, err := fieldPathColumnMapping.Resolve(r.SortBy); err != nil {
return sr, serrors.ThrowInvalidValue(zerrors.ThrowInvalidArgument(err, "SCIM-SRT1", "SortBy field is unknown or not supported"))
} else {
sr.SortingColumn = sortCol.Column
}
return sr, nil
}
func (r *ListRequest) validate() error {
// according to the spec values < 1 are treated as 1
if r.StartIndex < 1 {
r.StartIndex = 1
}
// according to the spec values < 0 are treated as 0
if r.Count < 0 {
r.Count = 0
} else if r.Count > maxListCount {
return zerrors.ThrowInvalidArgumentf(nil, "SCIM-ucr", "Limit count exceeded, set a count <= %v", maxListCount)
}
if !r.SortOrder.isDefined() {
return zerrors.ThrowInvalidArgument(nil, "SCIM-ucx", "Invalid sort order")
}
return nil
}