Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Put in stubs for dynamic access control on schemas #54

Merged
merged 2 commits into from
Jan 17, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions api/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -195,12 +195,12 @@ func (s *Server) handle(rw http.ResponseWriter, req *http.Request) (*types.APICo
}
handler = apiRequest.Schema.CreateHandler
case http.MethodPut:
if !apiRequest.AccessControl.CanUpdate(apiRequest, apiRequest.Schema) {
if !apiRequest.AccessControl.CanUpdate(apiRequest, nil, apiRequest.Schema) {
return apiRequest, httperror.NewAPIError(httperror.PermissionDenied, "Can not update "+apiRequest.Schema.Type)
}
handler = apiRequest.Schema.UpdateHandler
case http.MethodDelete:
if !apiRequest.AccessControl.CanDelete(apiRequest, apiRequest.Schema) {
if !apiRequest.AccessControl.CanDelete(apiRequest, nil, apiRequest.Schema) {
return apiRequest, httperror.NewAPIError(httperror.PermissionDenied, "Can not delete "+apiRequest.Schema.Type)
}
handler = apiRequest.Schema.DeleteHandler
Expand Down
6 changes: 3 additions & 3 deletions api/writer/json.go
Original file line number Diff line number Diff line change
Expand Up @@ -127,15 +127,15 @@ func (j *JSONResponseWriter) addLinks(b *builder.Builder, schema *types.Schema,

self := context.URLBuilder.ResourceLink(rawResource)
rawResource.Links["self"] = self
if schema.CanUpdate() {
if context.AccessControl.CanUpdate(context, input, schema) {
rawResource.Links["update"] = self
}
if schema.CanDelete() {
if context.AccessControl.CanDelete(context, input, schema) {
rawResource.Links["remove"] = self
}

for _, backRef := range context.Schemas.References(schema) {
if !backRef.Schema.CanList() {
if !backRef.Schema.CanList(context) {
continue
}

Expand Down
4 changes: 2 additions & 2 deletions authorization/all.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@ func (*AllAccess) CanList(apiContext *types.APIContext, schema *types.Schema) bo
return slice.ContainsString(schema.CollectionMethods, http.MethodGet)
}

func (*AllAccess) CanUpdate(apiContext *types.APIContext, schema *types.Schema) bool {
func (*AllAccess) CanUpdate(apiContext *types.APIContext, obj map[string]interface{}, schema *types.Schema) bool {
return slice.ContainsString(schema.ResourceMethods, http.MethodPut)
}

func (*AllAccess) CanDelete(apiContext *types.APIContext, schema *types.Schema) bool {
func (*AllAccess) CanDelete(apiContext *types.APIContext, obj map[string]interface{}, schema *types.Schema) bool {
return slice.ContainsString(schema.ResourceMethods, http.MethodDelete)
}

Expand Down
92 changes: 58 additions & 34 deletions condition/condition.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,14 @@ func (c Cond) IsTrue(obj runtime.Object) bool {
return getStatus(obj, string(c)) == "True"
}

func (c Cond) LastUpdated(obj runtime.Object, ts string) {
setTS(obj, string(c), ts)
}

func (c Cond) GetLastUpdated(obj runtime.Object) string {
return getTS(obj, string(c))
}

func (c Cond) False(obj runtime.Object) {
setStatus(obj, string(c), "False")
}
Expand All @@ -28,10 +36,22 @@ func (c Cond) IsFalse(obj runtime.Object) bool {
return getStatus(obj, string(c)) == "False"
}

func (c Cond) GetStatus(obj runtime.Object) string {
return getStatus(obj, string(c))
}

func (c Cond) Unknown(obj runtime.Object) {
setStatus(obj, string(c), "Unknown")
}

func (c Cond) CreateUnknownIfNotExists(obj runtime.Object) {
condSlice := getValue(obj, "Status", "Conditions")
cond := findCond(condSlice, string(c))
if cond == nil {
c.Unknown(obj)
}
}

func (c Cond) IsUnknown(obj runtime.Object) bool {
return getStatus(obj, string(c)) == "Unknown"
}
Expand All @@ -43,7 +63,7 @@ func (c Cond) Reason(obj runtime.Object, reason string) {

func (c Cond) Message(obj runtime.Object, message string) {
cond := findOrCreateCond(obj, string(c))
getFieldValue(cond, "Message").SetString(message)
setValue(cond, "Message", message)
}

func (c Cond) GetMessage(obj runtime.Object) string {
Expand Down Expand Up @@ -76,59 +96,53 @@ func (c Cond) Once(obj runtime.Object, f func() (runtime.Object, error)) (runtim
}
}

if c.IsTrue(obj) {
return obj, nil
}

c.Unknown(obj)
newObj, err := f()
if newObj != nil && !reflect.ValueOf(newObj).IsNil() {
obj = newObj
}

if err != nil {
c.False(obj)
c.ReasonAndMessageFromError(obj, err)
return obj, err
}
c.True(obj)
return obj, nil
return c.DoUntilTrue(obj, f)
}

func (c Cond) DoUntilTrue(obj runtime.Object, f func() (runtime.Object, error)) (runtime.Object, error) {
if c.IsTrue(obj) {
return obj, nil
}

c.Unknown(obj)
newObj, err := f()
if newObj != nil && !reflect.ValueOf(newObj).IsNil() {
obj = newObj
}

if err != nil {
c.ReasonAndMessageFromError(obj, err)
return obj, err
}
c.True(obj)
c.Reason(obj, "")
c.Message(obj, "")
return obj, nil
return c.do(obj, f)
}

func (c Cond) Do(obj runtime.Object, f func() (runtime.Object, error)) (runtime.Object, error) {
return c.do(obj, f)
}

func (c Cond) do(obj runtime.Object, f func() (runtime.Object, error)) (runtime.Object, error) {
c.Unknown(obj)
status := c.GetStatus(obj)
ts := c.GetLastUpdated(obj)
reason := c.GetReason(obj)
message := c.GetMessage(obj)

obj, err := c.doInternal(obj, f)

// This is to prevent non stop flapping of states and update
if status == c.GetStatus(obj) &&
reason == c.GetReason(obj) &&
message == c.GetMessage(obj) {
c.LastUpdated(obj, ts)
}

return obj, err
}

func (c Cond) doInternal(obj runtime.Object, f func() (runtime.Object, error)) (runtime.Object, error) {
if !c.IsFalse(obj) {
c.Unknown(obj)
}

newObj, err := f()
if newObj != nil && !reflect.ValueOf(newObj).IsNil() {
obj = newObj
}

if err != nil {
c.False(obj)
if _, ok := err.(*controller.ForgetError); !ok {
c.False(obj)
}
c.ReasonAndMessageFromError(obj, err)
return obj, err
}
Expand All @@ -148,6 +162,16 @@ func getStatus(obj interface{}, condName string) string {
return getFieldValue(cond, "Status").String()
}

func setTS(obj interface{}, condName, ts string) {
cond := findOrCreateCond(obj, condName)
getFieldValue(cond, "LastUpdateTime").SetString(ts)
}

func getTS(obj interface{}, condName string) string {
cond := findOrCreateCond(obj, condName)
return getFieldValue(cond, "LastUpdateTime").String()
}

func setStatus(obj interface{}, condName, status string) {
cond := findOrCreateCond(obj, condName)
setValue(cond, "Status", status)
Expand Down
2 changes: 1 addition & 1 deletion store/crd/crd_store.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ func (c *Store) AddSchemas(ctx context.Context, schemas ...*types.Schema) error
var allSchemas []*types.Schema

for _, schema := range schemas {
if schema.Store != nil || !schema.CanList() {
if schema.Store != nil || !schema.CanList(nil) {
continue
}

Expand Down
44 changes: 34 additions & 10 deletions store/schema/schema_store.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@ package schema

import (
"encoding/json"

"net/http"
"strings"

"github.com/rancher/norman/store/empty"
"github.com/rancher/norman/types"
"github.com/rancher/norman/types/definition"
"github.com/rancher/norman/types/slice"
)

type Store struct {
Expand All @@ -23,7 +24,7 @@ func (s *Store) ByID(apiContext *types.APIContext, schema *types.Schema, id stri
if strings.EqualFold(schema.ID, id) {
schemaData := map[string]interface{}{}

data, err := json.Marshal(schema)
data, err := json.Marshal(s.modifyForAccessControl(apiContext, *schema))
if err != nil {
return nil, err
}
Expand All @@ -34,6 +35,29 @@ func (s *Store) ByID(apiContext *types.APIContext, schema *types.Schema, id stri
return nil, nil
}

func (s *Store) modifyForAccessControl(context *types.APIContext, schema types.Schema) *types.Schema {
var resourceMethods []string
if slice.ContainsString(schema.ResourceMethods, http.MethodPut) && schema.CanUpdate(context) {
resourceMethods = append(resourceMethods, http.MethodPut)
}
if slice.ContainsString(schema.ResourceMethods, http.MethodDelete) && schema.CanDelete(context) {
resourceMethods = append(resourceMethods, http.MethodDelete)
}

var collectionMethods []string
if slice.ContainsString(schema.CollectionMethods, http.MethodPost) && schema.CanCreate(context) {
collectionMethods = append(collectionMethods, http.MethodPost)
}
if slice.ContainsString(schema.CollectionMethods, http.MethodGet) && schema.CanList(context) {
collectionMethods = append(collectionMethods, http.MethodGet)
}

schema.ResourceMethods = resourceMethods
schema.CollectionMethods = collectionMethods

return &schema
}

func (s *Store) Watch(apiContext *types.APIContext, schema *types.Schema, opt *types.QueryOptions) (chan map[string]interface{}, error) {
return nil, nil
}
Expand All @@ -50,8 +74,8 @@ func (s *Store) List(apiContext *types.APIContext, schema *types.Schema, opt *ty
continue
}

if schema.CanList() {
schemas = addSchema(schema, schemaMap, schemas, included)
if schema.CanList(apiContext) {
schemas = s.addSchema(apiContext, schema, schemaMap, schemas, included)
}
}

Expand All @@ -63,14 +87,14 @@ func (s *Store) List(apiContext *types.APIContext, schema *types.Schema, opt *ty
return schemaData, json.Unmarshal(data, &schemaData)
}

func addSchema(schema *types.Schema, schemaMap map[string]*types.Schema, schemas []*types.Schema, included map[string]bool) []*types.Schema {
func (s *Store) addSchema(apiContext *types.APIContext, schema *types.Schema, schemaMap map[string]*types.Schema, schemas []*types.Schema, included map[string]bool) []*types.Schema {
included[schema.ID] = true
schemas = traverseAndAdd(schema, schemaMap, schemas, included)
schemas = append(schemas, schema)
schemas = s.traverseAndAdd(apiContext, schema, schemaMap, schemas, included)
schemas = append(schemas, s.modifyForAccessControl(apiContext, *schema))
return schemas
}

func traverseAndAdd(schema *types.Schema, schemaMap map[string]*types.Schema, schemas []*types.Schema, included map[string]bool) []*types.Schema {
func (s *Store) traverseAndAdd(apiContext *types.APIContext, schema *types.Schema, schemaMap map[string]*types.Schema, schemas []*types.Schema, included map[string]bool) []*types.Schema {
for _, field := range schema.ResourceFields {
t := ""
subType := field.Type
Expand All @@ -80,7 +104,7 @@ func traverseAndAdd(schema *types.Schema, schemaMap map[string]*types.Schema, sc
}

if refSchema, ok := schemaMap[t]; ok && !included[t] {
schemas = addSchema(refSchema, schemaMap, schemas, included)
schemas = s.addSchema(apiContext, refSchema, schemaMap, schemas, included)
}
}

Expand All @@ -91,7 +115,7 @@ func traverseAndAdd(schema *types.Schema, schemaMap map[string]*types.Schema, sc
}

if refSchema, ok := schemaMap[t]; ok && !included[t] {
schemas = addSchema(refSchema, schemaMap, schemas, included)
schemas = s.addSchema(apiContext, refSchema, schemaMap, schemas, included)
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions types/reflection.go
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ func (s *Schemas) importType(version *APIVersion, t reflect.Type, overrides ...r

mappers := s.mapper(&schema.Version, schema.ID)
if s.DefaultMappers != nil {
if schema.CanList() {
if schema.CanList(nil) {
mappers = append(s.DefaultMappers(), mappers...)
}
}
Expand All @@ -179,7 +179,7 @@ func (s *Schemas) importType(version *APIVersion, t reflect.Type, overrides ...r

mapper := &typeMapper{
Mappers: mappers,
root: schema.CanList(),
root: schema.CanList(nil),
}

if err := mapper.ModifySchema(schema, s); err != nil {
Expand Down
28 changes: 20 additions & 8 deletions types/schema_funcs.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,18 +21,30 @@ func (v *APIVersion) Equals(other *APIVersion) bool {
v.Path == other.Path
}

func (s *Schema) CanList() bool {
return slice.ContainsString(s.CollectionMethods, http.MethodGet)
func (s *Schema) CanList(context *APIContext) bool {
if context == nil {
return slice.ContainsString(s.CollectionMethods, http.MethodGet)
}
return context.AccessControl.CanList(context, s)
}

func (s *Schema) CanCreate() bool {
return slice.ContainsString(s.CollectionMethods, http.MethodPost)
func (s *Schema) CanCreate(context *APIContext) bool {
if context == nil {
return slice.ContainsString(s.CollectionMethods, http.MethodPost)
}
return context.AccessControl.CanCreate(context, s)
}

func (s *Schema) CanUpdate() bool {
return slice.ContainsString(s.ResourceMethods, http.MethodPut)
func (s *Schema) CanUpdate(context *APIContext) bool {
if context == nil {
return slice.ContainsString(s.ResourceMethods, http.MethodPut)
}
return context.AccessControl.CanUpdate(context, nil, s)
}

func (s *Schema) CanDelete() bool {
return slice.ContainsString(s.ResourceMethods, http.MethodDelete)
func (s *Schema) CanDelete(context *APIContext) bool {
if context == nil {
return slice.ContainsString(s.ResourceMethods, http.MethodDelete)
}
return context.AccessControl.CanDelete(context, nil, s)
}
4 changes: 2 additions & 2 deletions types/server_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,8 @@ type ResponseWriter interface {
type AccessControl interface {
CanCreate(apiContext *APIContext, schema *Schema) bool
CanList(apiContext *APIContext, schema *Schema) bool
CanUpdate(apiContext *APIContext, schema *Schema) bool
CanDelete(apiContext *APIContext, schema *Schema) bool
CanUpdate(apiContext *APIContext, obj map[string]interface{}, schema *Schema) bool
CanDelete(apiContext *APIContext, obj map[string]interface{}, schema *Schema) bool

Filter(apiContext *APIContext, obj map[string]interface{}, context map[string]string) map[string]interface{}
FilterList(apiContext *APIContext, obj []map[string]interface{}, context map[string]string) []map[string]interface{}
Expand Down