Пример #1
0
func TestHandlerDeleteItemEtag(t *testing.T) {
	s := mem.NewHandler()
	s.Insert(context.TODO(), []*resource.Item{
		{ID: "1", ETag: "a", Payload: map[string]interface{}{"id": "1"}},
	})
	index := resource.NewIndex()
	test := index.Bind("test", schema.Schema{
		Fields: schema.Fields{"foo": {Filterable: true}},
	}, s, resource.DefaultConf)
	r, _ := http.NewRequest("DELETE", "/test/2", nil)
	r.Header.Set("If-Match", "a")
	rm := &RouteMatch{
		ResourcePath: []*ResourcePathComponent{
			&ResourcePathComponent{
				Name:     "test",
				Field:    "id",
				Value:    "1",
				Resource: test,
			},
		},
	}
	status, headers, body := itemDelete(context.TODO(), r, rm)
	assert.Equal(t, http.StatusNoContent, status)
	assert.Nil(t, headers)
	assert.Nil(t, body)

	l, err := s.Find(context.TODO(), resource.NewLookup(), 1, -1)
	assert.NoError(t, err)
	assert.Len(t, l.Items, 0)
}
Пример #2
0
// Lookup builds a Lookup object from the matched route
func (r RouteMatch) Lookup() (*resource.Lookup, *Error) {
	l := resource.NewLookup()
	// Append route fields to the query
	for _, rp := range r.ResourcePath {
		if rp.Value != nil {
			l.AddQuery(schema.Query{schema.Equal{Field: rp.Field, Value: rp.Value}})
		}
	}
	// Parse query string params
	if sort := r.Params.Get("sort"); sort != "" {
		if err := l.SetSort(sort, r.Resource().Validator()); err != nil {
			return nil, &Error{422, fmt.Sprintf("Invalid `sort` paramter: %s", err), nil}
		}
	}
	if filters, found := r.Params["filter"]; found {
		// If several filter parameters are present, merge them using $and (see lookup.addFilter)
		for _, filter := range filters {
			if err := l.AddFilter(filter, r.Resource().Validator()); err != nil {
				return nil, &Error{422, fmt.Sprintf("Invalid `filter` parameter: %s", err), nil}
			}
		}
	}
	if fields := r.Params.Get("fields"); fields != "" {
		if err := l.SetSelector(fields, r.Resource()); err != nil {
			return nil, &Error{422, fmt.Sprintf("Invalid `fields` paramter: %s", err), nil}
		}
	}
	return l, nil
}
Пример #3
0
func TestHandlerDeleteList(t *testing.T) {
	s := mem.NewHandler()
	s.Insert(context.TODO(), []*resource.Item{
		{ID: "1", Payload: map[string]interface{}{}},
		{ID: "2", Payload: map[string]interface{}{}},
		{ID: "3", Payload: map[string]interface{}{}},
		{ID: "4", Payload: map[string]interface{}{}},
		{ID: "5", Payload: map[string]interface{}{}},
	})
	index := resource.NewIndex()
	test := index.Bind("test", schema.Schema{}, s, resource.DefaultConf)
	r, _ := http.NewRequest("DELETE", "/test", bytes.NewBufferString("{}"))
	rm := &RouteMatch{
		ResourcePath: []*ResourcePathComponent{
			&ResourcePathComponent{
				Name:     "test",
				Resource: test,
			},
		},
	}
	status, headers, body := listDelete(context.TODO(), r, rm)
	assert.Equal(t, http.StatusNoContent, status)
	assert.Equal(t, http.Header{"X-Total": []string{"5"}}, headers)
	assert.Nil(t, body)

	l, err := s.Find(context.TODO(), resource.NewLookup(), 1, -1)
	assert.NoError(t, err)
	assert.Len(t, l.Items, 0)
}
Пример #4
0
// ParentsExist checks if the each intermediate parents in the path exist and
// return either a ErrNotFound or an error returned by on of the intermediate
// resource.
func (p ResourcePath) ParentsExist(ctx context.Context) error {
	// First we check that we have no field conflict on the path (i.e.: two path
	// components defining the same field with a different value)
	fields := map[string]interface{}{}
	for _, rp := range p {
		if val, found := fields[rp.Field]; found && val != rp.Value {
			return &Error{404, "Resource Path Conflict", nil}
		}
		fields[rp.Field] = rp.Value
	}

	// Check parents existence
	parents := len(p) - 1
	if parents <= 0 {
		return nil
	}
	q := schema.Query{}
	wait := sync.WaitGroup{}

	defer wait.Wait()
	c := make(chan error, parents)
	for i := 0; i < parents; i++ {
		if p[i].Value == nil {
			continue
		}
		// Create a lookup with the parent path fields + the current path id
		l := resource.NewLookup()
		lq := append(q[:], schema.Equal{Field: "id", Value: p[i].Value})
		l.AddQuery(lq)
		// Execute all intermediate checkes in concurence
		wait.Add(1)
		go func(index int) {
			defer wait.Done()
			// Check if the resource exists
			list, err := p[index].Resource.Find(ctx, l, 1, 1)
			if err != nil {
				c <- err
			} else if len(list.Items) == 0 {
				c <- &Error{404, "Parent Resource Not Found", nil}
			} else {
				c <- nil
			}
		}(i)
		// Push the resource field=value for the next hops
		q = append(q, schema.Equal{Field: p[i].Field, Value: p[i].Value})
	}
	// Fail on first error
	for i := 0; i < parents; i++ {
		if err := <-c; err != nil {
			return err
		}
	}
	return nil
}
Пример #5
0
// itemGet handles GET and HEAD resquests on an item URL
func (r *request) itemGet(ctx context.Context, route *RouteMatch) (status int, headers http.Header, body interface{}) {
	lookup, e := route.Lookup()
	if e != nil {
		return e.Code, nil, e
	}
	list, err := route.Resource().Find(ctx, lookup, 1, 1)
	if err != nil {
		e = NewError(err)
		return e.Code, nil, e
	} else if len(list.Items) == 0 {
		return ErrNotFound.Code, nil, ErrNotFound
	}
	item := list.Items[0]
	// Handle conditional request: If-None-Match
	if compareEtag(r.req.Header.Get("If-None-Match"), item.ETag) {
		return 304, nil, nil
	}
	// Handle conditional request: If-Modified-Since
	if r.req.Header.Get("If-Modified-Since") != "" {
		if ifModTime, err := time.Parse(time.RFC1123, r.req.Header.Get("If-Modified-Since")); err != nil {
			return 400, nil, &Error{400, "Invalid If-Modified-Since header", nil}
		} else if item.Updated.Equal(ifModTime) || item.Updated.Before(ifModTime) {
			return 304, nil, nil
		}
	}
	item.Payload, err = lookup.ApplySelector(route.Resource(), item.Payload, func(path string, value interface{}) (*resource.Resource, map[string]interface{}, error) {
		router, ok := IndexFromContext(ctx)
		if !ok {
			return nil, nil, errors.New("router not available in context")
		}
		rsrc, _, found := router.GetResource(path)
		if !found {
			return nil, nil, fmt.Errorf("invalid resource reference: %s", path)
		}
		l := resource.NewLookup()
		l.AddQuery(schema.Query{schema.Equal{Field: "id", Value: value}})
		list, _ := rsrc.Find(ctx, l, 1, 1)
		if len(list.Items) == 1 {
			item := list.Items[0]
			return rsrc, item.Payload, nil
		}
		// If no item found, just return an empty dict so we don't error the main request
		return rsrc, map[string]interface{}{}, nil
	})
	if err != nil {
		e = NewError(err)
		return e.Code, nil, e
	}
	return 200, nil, item
}
Пример #6
0
func listParamResolver(r *resource.Resource, p graphql.ResolveParams, params url.Values) (lookup *resource.Lookup, page int, perPage int, err error) {
	page = 1
	// Default value on non HEAD request for perPage is -1 (pagination disabled)
	perPage = -1
	if l := r.Conf().PaginationDefaultLimit; l > 0 {
		perPage = l
	}
	if p, ok := p.Args["page"].(string); ok && p != "" {
		i, err := strconv.ParseUint(p, 10, 32)
		if err != nil {
			return nil, 0, 0, errors.New("invalid `limit` parameter")
		}
		page = int(i)
	}
	if l, ok := p.Args["limit"].(string); ok && l != "" {
		i, err := strconv.ParseUint(l, 10, 32)
		if err != nil {
			return nil, 0, 0, errors.New("invalid `limit` parameter")
		}
		perPage = int(i)
	}
	if perPage == -1 && page != 1 {
		return nil, 0, 0, errors.New("cannot use `page' parameter with no `limit' paramter on a resource with no default pagination size")
	}
	lookup = resource.NewLookup()
	if sort, ok := p.Args["sort"].(string); ok && sort != "" {
		if err := lookup.SetSort(sort, r.Validator()); err != nil {
			return nil, 0, 0, fmt.Errorf("invalid `sort` parameter: %v", err)
		}
	}
	if filter, ok := p.Args["filter"].(string); ok && filter != "" {
		if err := lookup.AddFilter(filter, r.Validator()); err != nil {
			return nil, 0, 0, fmt.Errorf("invalid `filter` parameter: %v", err)
		}
	}
	if params != nil {
		if filter := params.Get("filter"); filter != "" {
			if err := lookup.AddFilter(filter, r.Validator()); err != nil {
				return nil, 0, 0, fmt.Errorf("invalid `filter` parameter: %v", err)
			}
		}
	}
	return
}
Пример #7
0
func TestHandlerDeleteItemFilterCondition(t *testing.T) {
	s := mem.NewHandler()
	s.Insert(context.TODO(), []*resource.Item{
		{ID: "1", Payload: map[string]interface{}{"id": "1", "foo": "bar"}},
		{ID: "2", Payload: map[string]interface{}{"id": "2", "foo": "bar"}},
		{ID: "3", Payload: map[string]interface{}{"id": "3", "foo": "bar"}},
	})
	index := resource.NewIndex()
	test := index.Bind("test", schema.Schema{
		Fields: schema.Fields{"foo": {Filterable: true}},
	}, s, resource.DefaultConf)
	r, _ := http.NewRequest("DELETE", "/test/2", nil)
	rm := &RouteMatch{
		ResourcePath: []*ResourcePathComponent{
			&ResourcePathComponent{
				Name:     "test",
				Field:    "id",
				Value:    "2",
				Resource: test,
			},
		},
		Params: url.Values{
			"filter": []string{`{"foo": "baz"}`},
		},
	}
	status, headers, body := itemDelete(context.TODO(), r, rm)
	assert.Equal(t, http.StatusNotFound, status)
	assert.Nil(t, headers)
	if assert.IsType(t, body, &Error{}) {
		err := body.(*Error)
		assert.Equal(t, http.StatusNotFound, err.Code)
		assert.Equal(t, "Not Found", err.Message)
	}

	l, err := s.Find(context.TODO(), resource.NewLookup(), 1, -1)
	assert.NoError(t, err)
	assert.Len(t, l.Items, 3)
}
Пример #8
0
// checkReferences checks that fields with the Reference validator reference an existing object
func (r *request) checkReferences(ctx context.Context, payload map[string]interface{}, s schema.Validator) *Error {
	for name, value := range payload {
		field := s.GetField(name)
		if field == nil {
			continue
		}
		// Check reference if validator is of type Reference
		if field.Validator != nil {
			if ref, ok := field.Validator.(*schema.Reference); ok {
				router, ok := IndexFromContext(ctx)
				if !ok {
					return &Error{500, "Router not available in context", nil}
				}
				rsrc, _, found := router.GetResource(ref.Path)
				if !found {
					return &Error{500, fmt.Sprintf("Invalid resource reference for field `%s': %s", name, ref.Path), nil}
				}
				l := resource.NewLookup()
				l.AddQuery(schema.Query{schema.Equal{Field: "id", Value: value}})
				list, _ := rsrc.Find(ctx, l, 1, 1)
				if len(list.Items) == 0 {
					return &Error{404, fmt.Sprintf("Resource reference not found for field `%s'", name), nil}
				}
			}
		}
		// Check sub-schema if any
		if field.Schema != nil && value != nil {
			if subPayload, ok := value.(map[string]interface{}); ok {
				if err := r.checkReferences(ctx, subPayload, field.Schema); err != nil {
					return err
				}
			}
		}
	}
	return nil
}
Пример #9
0
// listGet handles GET resquests on a resource URL
func (r *request) listGet(ctx context.Context, route *RouteMatch) (status int, headers http.Header, body interface{}) {
	page := 1
	perPage := 0
	if route.Method != "HEAD" {
		if l := route.Resource().Conf().PaginationDefaultLimit; l > 0 {
			perPage = l
		} else {
			// Default value on non HEAD request for perPage is -1 (pagination disabled)
			perPage = -1
		}
		if p := r.req.URL.Query().Get("page"); p != "" {
			i, err := strconv.ParseUint(p, 10, 32)
			if err != nil {
				return 422, nil, &Error{422, "Invalid `page` paramter", nil}
			}
			page = int(i)
		}
		if l := r.req.URL.Query().Get("limit"); l != "" {
			i, err := strconv.ParseUint(l, 10, 32)
			if err != nil {
				return 422, nil, &Error{422, "Invalid `limit` paramter", nil}
			}
			perPage = int(i)
		}
		if perPage == -1 && page != 1 {
			return 422, nil, &Error{422, "Cannot use `page' parameter with no `limit' paramter on a resource with no default pagination size", nil}
		}
	}
	lookup, e := route.Lookup()
	if e != nil {
		return e.Code, nil, e
	}
	list, err := route.Resource().Find(ctx, lookup, page, perPage)
	if err != nil {
		e = NewError(err)
		return e.Code, nil, e
	}
	for _, item := range list.Items {
		item.Payload, err = lookup.ApplySelector(route.Resource(), item.Payload, func(path string, value interface{}) (*resource.Resource, map[string]interface{}, error) {
			router, ok := IndexFromContext(ctx)
			if !ok {
				return nil, nil, errors.New("router not available in context")
			}
			rsrc, _, found := router.GetResource(path)
			if !found {
				return nil, nil, fmt.Errorf("invalid resource reference: %s", path)
			}
			l := resource.NewLookup()
			l.AddQuery(schema.Query{schema.Equal{Field: "id", Value: value}})
			list, _ := rsrc.Find(ctx, l, 1, 1)
			if len(list.Items) == 1 {
				item := list.Items[0]
				return rsrc, item.Payload, nil
			}
			// If no item found, just return an empty dict so we don't error the main request
			return rsrc, map[string]interface{}{}, nil
		})
		if err != nil {
			e = NewError(err)
			return e.Code, nil, e
		}
	}
	return 200, nil, list
}
Пример #10
0
func callGetSort(s string, v schema.Validator) []string {
	l := resource.NewLookup()
	l.SetSort(s, v)
	return getSort(l)
}
Пример #11
0
func callGetQuery(q schema.Query) (bson.M, error) {
	l := resource.NewLookup()
	l.AddQuery(q)
	return getQuery(l)
}
Пример #12
0
				},
			},
		},
	}
)

func SetAuthUserResource(us *resource.Resource) {
	field, _ := AuthSchema["access"]
	field.HookParams[0].Param = us
}

var CheckAccess = func(value interface{}, params []interface{}) interface{} {
	users, users_ok := params[0].(*resource.Resource)
	username, u_ok := params[1].(string)
	password, p_ok := params[2].(string)

	if users_ok && u_ok && p_ok {
		l := resource.NewLookup()
		l.AddQuery(schema.Query{schema.Equal{Field: "username", Value: username}})
		list, err := users.Find(context.Background(), l, 1, 1)
		if err == nil && len(list.Items) == 1 {
			user := list.Items[0]
			if schema.VerifyPassword(user.Payload["password"], []byte(password)) {
				return true
			}
		}
	}

	return false
}
Пример #13
0
func TestFind(t *testing.T) {
	if testing.Short() {
		t.Skip("skipping test in short mode.")
	}
	s, err := mgo.Dial("")
	if !assert.NoError(t, err) {
		return
	}
	defer cleanup(s, "testfind")()
	h := NewHandler(s, "testfind", "test")
	h2 := NewHandler(s, "testfind", "test2")
	items := []*resource.Item{
		{ID: "1", Payload: map[string]interface{}{"id": "1", "name": "a", "age": 1}},
		{ID: "2", Payload: map[string]interface{}{"id": "2", "name": "b", "age": 2}},
		{ID: "3", Payload: map[string]interface{}{"id": "3", "name": "c", "age": 3}},
		{ID: "4", Payload: map[string]interface{}{"id": "4", "name": "d", "age": 4}},
	}
	ctx := context.Background()
	assert.NoError(t, h.Insert(ctx, items))
	assert.NoError(t, h2.Insert(ctx, items))

	lookup := resource.NewLookup()
	l, err := h.Find(ctx, lookup, 1, -1)
	if assert.NoError(t, err) {
		assert.Equal(t, 1, l.Page)
		assert.Equal(t, -1, l.Total) // Mongo doesn't support total counting
		assert.Len(t, l.Items, 4)
		// Do not check result's content as its order is unpredictable
	}

	lookup = resource.NewLookupWithQuery(schema.Query{
		schema.Equal{Field: "name", Value: "c"},
	})
	l, err = h.Find(ctx, lookup, 1, 100)
	if assert.NoError(t, err) {
		assert.Equal(t, 1, l.Page)
		assert.Equal(t, -1, l.Total) // Mongo doesn't support total counting
		if assert.Len(t, l.Items, 1) {
			item := l.Items[0]
			assert.Equal(t, "3", item.ID)
			assert.Equal(t, map[string]interface{}{"id": "3", "name": "c", "age": 3}, item.Payload)
		}
	}

	lookup = resource.NewLookupWithQuery(schema.Query{
		schema.In{Field: "name", Values: []schema.Value{"c", "d"}},
	})
	lookup.SetSorts([]string{"name"})
	l, err = h.Find(ctx, lookup, 1, 100)
	if assert.NoError(t, err) {
		assert.Equal(t, 1, l.Page)
		assert.Equal(t, -1, l.Total) // Mongo doesn't support total counting
		if assert.Len(t, l.Items, 2) {
			item := l.Items[0]
			assert.Equal(t, "3", item.ID)
			assert.Equal(t, map[string]interface{}{"id": "3", "name": "c", "age": 3}, item.Payload)
			item = l.Items[1]
			assert.Equal(t, "4", item.ID)
			assert.Equal(t, map[string]interface{}{"id": "4", "name": "d", "age": 4}, item.Payload)
		}
	}

	lookup = resource.NewLookupWithQuery(schema.Query{
		schema.Equal{Field: "id", Value: "3"},
	})
	l, err = h.Find(ctx, lookup, 1, 1)
	if assert.NoError(t, err) {
		assert.Equal(t, 1, l.Page)
		assert.Equal(t, -1, l.Total) // Mongo doesn't support total counting
		if assert.Len(t, l.Items, 1) {
			item := l.Items[0]
			assert.Equal(t, "3", item.ID)
			assert.Equal(t, map[string]interface{}{"id": "3", "name": "c", "age": 3}, item.Payload)
		}
	}

	lookup = resource.NewLookupWithQuery(schema.Query{
		schema.Equal{Field: "id", Value: "10"},
	})
	l, err = h.Find(ctx, lookup, 1, 1)
	if assert.NoError(t, err) {
		assert.Equal(t, 1, l.Page)
		assert.Equal(t, -1, l.Total) // Mongo doesn't support total counting
		assert.Len(t, l.Items, 0)
	}

	lookup = resource.NewLookupWithQuery(schema.Query{
		schema.In{Field: "id", Values: []schema.Value{"3", "4", "10"}},
	})
	l, err = h.Find(ctx, lookup, 1, -1)
	if assert.NoError(t, err) {
		assert.Equal(t, 1, l.Page)
		assert.Equal(t, -1, l.Total) // Mongo doesn't support total counting
		assert.Len(t, l.Items, 2)
	}
}
Пример #14
0
// findRoute recursively route a (sub)resource request
func findRoute(ctx context.Context, path string, index resource.Index, route *RouteMatch) *Error {
	// Split the path into path components
	c := strings.Split(strings.Trim(path, "/"), "/")

	// Shift the resource name from the path components
	name, c := c[0], c[1:]

	resourcePath := name
	if prefix := route.ResourcePath.Path(); prefix != "" {
		resourcePath = strings.Join([]string{prefix, name}, ".")
	}

	// First component must match a resource
	if rsrc, _, found := index.GetResource(resourcePath); found {
		rp := ResourcePathComponent{
			Name:     name,
			Resource: rsrc,
		}
		if len(c) >= 1 {
			// If there are some components left, the path targets an item or an alias

			// Shift the item id from the path components
			var id string
			id, c = c[0], c[1:]

			// Handle sub-resources (/resource1/id1/resource2/id2)
			if len(c) >= 1 {
				subResourcePath := strings.Join([]string{resourcePath, c[0]}, ".")
				if _, field, found := index.GetResource(subResourcePath); found {
					// Check if the current (intermediate) item exists before going farther
					l := resource.NewLookup()
					q := schema.Query{}
					for _, rp := range route.ResourcePath {
						if rp.Value != nil {
							q = append(q, schema.Equal{Field: rp.Field, Value: rp.Value})
						}
					}
					q = append(q, schema.Equal{Field: "id", Value: id})
					l.AddQuery(q)
					list, err := rsrc.Find(ctx, l, 1, 1)
					if err != nil {
						return NewError(err)
					} else if len(list.Items) == 0 {
						return ErrNotFound
					}
					rp.Field = field
					rp.Value = id
					route.ResourcePath = append(route.ResourcePath, rp)
					// Recurse to match the sub-path
					path = strings.Join(c, "/")
					if err := findRoute(ctx, path, index, route); err != nil {
						return err
					}
				} else {
					route.ResourcePath = ResourcePath{}
					return &Error{404, "Resource Not Found", nil}
				}
				return nil
			}

			// Handle aliases (/resource/alias or /resource1/id1/resource2/alias)
			if alias, found := rsrc.GetAlias(id); found {
				// Apply aliases query to the request
				for key, values := range alias {
					for _, value := range values {
						route.Params.Add(key, value)
					}
				}
			} else {
				// Set the id route field
				rp.Field = "id"
				rp.Value = id
			}
		}
		route.ResourcePath = append(route.ResourcePath, rp)
		return nil
	}
	route.ResourcePath = ResourcePath{}
	return &Error{404, "Resource Not Found", nil}
}