1
0
mirror of https://github.com/rancher/norman.git synced 2025-09-06 01:30:20 +00:00

Refactor access control to return error not bool

This commit is contained in:
Darren Shepherd
2018-03-31 00:13:30 -07:00
parent 7edec77619
commit 4cf9f645cd
11 changed files with 145 additions and 109 deletions

View File

@@ -11,6 +11,7 @@ import (
"github.com/rancher/norman/api/writer" "github.com/rancher/norman/api/writer"
"github.com/rancher/norman/authorization" "github.com/rancher/norman/authorization"
"github.com/rancher/norman/httperror" "github.com/rancher/norman/httperror"
ehandler "github.com/rancher/norman/httperror/handler"
"github.com/rancher/norman/parse" "github.com/rancher/norman/parse"
"github.com/rancher/norman/store/wrapper" "github.com/rancher/norman/store/wrapper"
"github.com/rancher/norman/types" "github.com/rancher/norman/types"
@@ -64,7 +65,7 @@ func NewAPIServer() *Server {
LinkHandler: func(*types.APIContext, types.RequestHandler) error { LinkHandler: func(*types.APIContext, types.RequestHandler) error {
return httperror.NewAPIError(httperror.NotFound, "Link not found") return httperror.NewAPIError(httperror.NotFound, "Link not found")
}, },
ErrorHandler: httperror.ErrorHandler, ErrorHandler: ehandler.ErrorHandler,
}, },
StoreWrapper: wrapper.Wrap, StoreWrapper: wrapper.Wrap,
URLParser: parse.DefaultURLParser, URLParser: parse.DefaultURLParser,
@@ -187,31 +188,31 @@ func (s *Server) handle(rw http.ResponseWriter, req *http.Request) (*types.APICo
switch apiRequest.Method { switch apiRequest.Method {
case http.MethodGet: case http.MethodGet:
if apiRequest.ID == "" { if apiRequest.ID == "" {
if !apiRequest.AccessControl.CanList(apiRequest, apiRequest.Schema) { if err := apiRequest.AccessControl.CanList(apiRequest, apiRequest.Schema); err != nil {
return apiRequest, httperror.NewAPIError(httperror.PermissionDenied, "Can not list "+apiRequest.Schema.ID) return apiRequest, err
} }
} else { } else {
if !apiRequest.AccessControl.CanGet(apiRequest, apiRequest.Schema) { if err := apiRequest.AccessControl.CanGet(apiRequest, apiRequest.Schema); err != nil {
return apiRequest, httperror.NewAPIError(httperror.PermissionDenied, "Can not get "+apiRequest.Schema.ID) return apiRequest, err
} }
} }
handler = apiRequest.Schema.ListHandler handler = apiRequest.Schema.ListHandler
nextHandler = s.Defaults.ListHandler nextHandler = s.Defaults.ListHandler
case http.MethodPost: case http.MethodPost:
if !apiRequest.AccessControl.CanCreate(apiRequest, apiRequest.Schema) { if err := apiRequest.AccessControl.CanCreate(apiRequest, apiRequest.Schema); err != nil {
return apiRequest, httperror.NewAPIError(httperror.PermissionDenied, "Can not create "+apiRequest.Schema.ID) return apiRequest, err
} }
handler = apiRequest.Schema.CreateHandler handler = apiRequest.Schema.CreateHandler
nextHandler = s.Defaults.CreateHandler nextHandler = s.Defaults.CreateHandler
case http.MethodPut: case http.MethodPut:
if !apiRequest.AccessControl.CanUpdate(apiRequest, nil, apiRequest.Schema) { if err := apiRequest.AccessControl.CanUpdate(apiRequest, nil, apiRequest.Schema); err != nil {
return apiRequest, httperror.NewAPIError(httperror.PermissionDenied, "Can not update "+apiRequest.Schema.ID) return apiRequest, err
} }
handler = apiRequest.Schema.UpdateHandler handler = apiRequest.Schema.UpdateHandler
nextHandler = s.Defaults.UpdateHandler nextHandler = s.Defaults.UpdateHandler
case http.MethodDelete: case http.MethodDelete:
if !apiRequest.AccessControl.CanDelete(apiRequest, nil, apiRequest.Schema) { if err := apiRequest.AccessControl.CanDelete(apiRequest, nil, apiRequest.Schema); err != nil {
return apiRequest, httperror.NewAPIError(httperror.PermissionDenied, "Can not delete "+apiRequest.Schema.ID) return apiRequest, err
} }
handler = apiRequest.Schema.DeleteHandler handler = apiRequest.Schema.DeleteHandler
nextHandler = s.Defaults.DeleteHandler nextHandler = s.Defaults.DeleteHandler

View File

@@ -140,16 +140,16 @@ func (j *JSONResponseWriter) addLinks(b *builder.Builder, schema *types.Schema,
self := context.URLBuilder.ResourceLink(rawResource) self := context.URLBuilder.ResourceLink(rawResource)
rawResource.Links["self"] = self rawResource.Links["self"] = self
if context.AccessControl.CanUpdate(context, input, schema) { if context.AccessControl.CanUpdate(context, input, schema) == nil {
rawResource.Links["update"] = self rawResource.Links["update"] = self
} }
if context.AccessControl.CanDelete(context, input, schema) { if context.AccessControl.CanDelete(context, input, schema) == nil {
rawResource.Links["remove"] = self rawResource.Links["remove"] = self
} }
subContextVersion := context.Schemas.SubContextVersionForSchema(schema) subContextVersion := context.Schemas.SubContextVersionForSchema(schema)
for _, backRef := range context.Schemas.References(schema) { for _, backRef := range context.Schemas.References(schema) {
if !backRef.Schema.CanList(context) { if backRef.Schema.CanList(context) != nil {
continue continue
} }
@@ -162,7 +162,7 @@ func (j *JSONResponseWriter) addLinks(b *builder.Builder, schema *types.Schema,
if subContextVersion != nil { if subContextVersion != nil {
for _, subSchema := range context.Schemas.SchemasForVersion(*subContextVersion) { for _, subSchema := range context.Schemas.SchemasForVersion(*subContextVersion) {
if subSchema.CanList(context) { if subSchema.CanList(context) == nil {
rawResource.Links[subSchema.PluralName] = context.URLBuilder.SubContextCollection(schema, rawResource.ID, subSchema) rawResource.Links[subSchema.PluralName] = context.URLBuilder.SubContextCollection(schema, rawResource.ID, subSchema)
} }
} }
@@ -184,7 +184,7 @@ func newCollection(apiContext *types.APIContext) *types.GenericCollection {
} }
if apiContext.Method == http.MethodGet { if apiContext.Method == http.MethodGet {
if apiContext.AccessControl.CanCreate(apiContext, apiContext.Schema) { if apiContext.AccessControl.CanCreate(apiContext, apiContext.Schema) == nil {
result.CreateTypes[apiContext.Schema.ID] = apiContext.URLBuilder.Collection(apiContext.Schema, apiContext.Version) result.CreateTypes[apiContext.Schema.ID] = apiContext.URLBuilder.Collection(apiContext.Schema, apiContext.Version)
} }
} }

View File

@@ -3,6 +3,7 @@ package authorization
import ( import (
"net/http" "net/http"
"github.com/rancher/norman/httperror"
"github.com/rancher/norman/types" "github.com/rancher/norman/types"
"github.com/rancher/norman/types/slice" "github.com/rancher/norman/types/slice"
) )
@@ -10,24 +11,39 @@ import (
type AllAccess struct { type AllAccess struct {
} }
func (*AllAccess) CanCreate(apiContext *types.APIContext, schema *types.Schema) bool { func (*AllAccess) CanCreate(apiContext *types.APIContext, schema *types.Schema) error {
return slice.ContainsString(schema.CollectionMethods, http.MethodPost) if slice.ContainsString(schema.CollectionMethods, http.MethodPost) {
return nil
}
return httperror.NewAPIError(httperror.PermissionDenied, "can not create "+schema.ID)
} }
func (*AllAccess) CanGet(apiContext *types.APIContext, schema *types.Schema) bool { func (*AllAccess) CanGet(apiContext *types.APIContext, schema *types.Schema) error {
return slice.ContainsString(schema.ResourceMethods, http.MethodGet) if slice.ContainsString(schema.ResourceMethods, http.MethodGet) {
return nil
}
return httperror.NewAPIError(httperror.PermissionDenied, "can not get "+schema.ID)
} }
func (*AllAccess) CanList(apiContext *types.APIContext, schema *types.Schema) bool { func (*AllAccess) CanList(apiContext *types.APIContext, schema *types.Schema) error {
return slice.ContainsString(schema.CollectionMethods, http.MethodGet) if slice.ContainsString(schema.CollectionMethods, http.MethodGet) {
return nil
}
return httperror.NewAPIError(httperror.PermissionDenied, "can not list "+schema.ID)
} }
func (*AllAccess) CanUpdate(apiContext *types.APIContext, obj map[string]interface{}, schema *types.Schema) bool { func (*AllAccess) CanUpdate(apiContext *types.APIContext, obj map[string]interface{}, schema *types.Schema) error {
return slice.ContainsString(schema.ResourceMethods, http.MethodPut) if slice.ContainsString(schema.ResourceMethods, http.MethodPut) {
return nil
}
return httperror.NewAPIError(httperror.PermissionDenied, "can not update "+schema.ID)
} }
func (*AllAccess) CanDelete(apiContext *types.APIContext, obj map[string]interface{}, schema *types.Schema) bool { func (*AllAccess) CanDelete(apiContext *types.APIContext, obj map[string]interface{}, schema *types.Schema) error {
return slice.ContainsString(schema.ResourceMethods, http.MethodDelete) if slice.ContainsString(schema.ResourceMethods, http.MethodDelete) {
return nil
}
return httperror.NewAPIError(httperror.PermissionDenied, "can not delete "+schema.ID)
} }
func (*AllAccess) Filter(apiContext *types.APIContext, schema *types.Schema, obj map[string]interface{}, context map[string]string) map[string]interface{} { func (*AllAccess) Filter(apiContext *types.APIContext, schema *types.Schema, obj map[string]interface{}, context map[string]string) map[string]interface{} {

View File

@@ -34,40 +34,40 @@ var (
) )
type ErrorCode struct { type ErrorCode struct {
code string Code string
Status int Status int
} }
func (e ErrorCode) String() string { func (e ErrorCode) String() string {
return fmt.Sprintf("%s %d", e.code, e.Status) return fmt.Sprintf("%s %d", e.Code, e.Status)
} }
type APIError struct { type APIError struct {
code ErrorCode Code ErrorCode
message string Message string
Cause error Cause error
fieldName string FieldName string
} }
func NewAPIErrorLong(status int, code, message string) error { func NewAPIErrorLong(status int, code, message string) error {
return NewAPIError(ErrorCode{ return NewAPIError(ErrorCode{
code: code, Code: code,
Status: status, Status: status,
}, message) }, message)
} }
func NewAPIError(code ErrorCode, message string) error { func NewAPIError(code ErrorCode, message string) error {
return &APIError{ return &APIError{
code: code, Code: code,
message: message, Message: message,
} }
} }
func NewFieldAPIError(code ErrorCode, fieldName, message string) error { func NewFieldAPIError(code ErrorCode, fieldName, message string) error {
return &APIError{ return &APIError{
code: code, Code: code,
message: message, Message: message,
fieldName: fieldName, FieldName: fieldName,
} }
} }
@@ -76,9 +76,9 @@ func NewFieldAPIError(code ErrorCode, fieldName, message string) error {
func WrapFieldAPIError(err error, code ErrorCode, fieldName, message string) error { func WrapFieldAPIError(err error, code ErrorCode, fieldName, message string) error {
return &APIError{ return &APIError{
Cause: err, Cause: err,
code: code, Code: code,
message: message, Message: message,
fieldName: fieldName, FieldName: fieldName,
} }
} }
@@ -86,17 +86,17 @@ func WrapFieldAPIError(err error, code ErrorCode, fieldName, message string) err
// err WILL NOT be in the API response // err WILL NOT be in the API response
func WrapAPIError(err error, code ErrorCode, message string) error { func WrapAPIError(err error, code ErrorCode, message string) error {
return &APIError{ return &APIError{
code: code, Code: code,
message: message, Message: message,
Cause: err, Cause: err,
} }
} }
func (a *APIError) Error() string { func (a *APIError) Error() string {
if a.fieldName != "" { if a.FieldName != "" {
return fmt.Sprintf("%s=%s: %s", a.fieldName, a.code, a.message) return fmt.Sprintf("%s=%s: %s", a.FieldName, a.Code, a.Message)
} }
return fmt.Sprintf("%s: %s", a.code, a.message) return fmt.Sprintf("%s: %s", a.Code, a.Message)
} }
func IsAPIError(err error) bool { func IsAPIError(err error) bool {
@@ -106,7 +106,7 @@ func IsAPIError(err error) bool {
func IsConflict(err error) bool { func IsConflict(err error) bool {
if apiError, ok := err.(*APIError); ok { if apiError, ok := err.(*APIError); ok {
return apiError.code.Status == 409 return apiError.Code.Status == 409
} }
return false return false

View File

@@ -1,40 +0,0 @@
package httperror
import (
"github.com/rancher/norman/types"
"github.com/sirupsen/logrus"
)
func ErrorHandler(request *types.APIContext, err error) {
var error *APIError
if apiError, ok := err.(*APIError); ok {
if apiError.Cause != nil {
logrus.Errorf("API error response %v for %v %v. Cause: %v", apiError.code.Status, request.Request.Method,
request.Request.RequestURI, apiError.Cause)
}
error = apiError
} else {
logrus.Errorf("Unknown error: %v", err)
error = &APIError{
code: ServerError,
message: err.Error(),
}
}
data := toError(error)
request.WriteResponse(error.code.Status, data)
}
func toError(apiError *APIError) map[string]interface{} {
e := map[string]interface{}{
"type": "/meta/schemas/error",
"status": apiError.code.Status,
"code": apiError.code.code,
"message": apiError.message,
}
if apiError.fieldName != "" {
e["fieldName"] = apiError.fieldName
}
return e
}

View File

@@ -0,0 +1,41 @@
package handler
import (
"github.com/rancher/norman/httperror"
"github.com/rancher/norman/types"
"github.com/sirupsen/logrus"
)
func ErrorHandler(request *types.APIContext, err error) {
var error *httperror.APIError
if apiError, ok := err.(*httperror.APIError); ok {
if apiError.Cause != nil {
logrus.Errorf("API error response %v for %v %v. Cause: %v", apiError.Code.Status, request.Request.Method,
request.Request.RequestURI, apiError.Cause)
}
error = apiError
} else {
logrus.Errorf("Unknown error: %v", err)
error = &httperror.APIError{
Code: httperror.ServerError,
Message: err.Error(),
}
}
data := toError(error)
request.WriteResponse(error.Code.Status, data)
}
func toError(apiError *httperror.APIError) map[string]interface{} {
e := map[string]interface{}{
"type": "/meta/schemas/error",
"status": apiError.Code.Status,
"code": apiError.Code.Code,
"message": apiError.Message,
}
if apiError.FieldName != "" {
e["fieldName"] = apiError.FieldName
}
return e
}

View File

@@ -137,6 +137,8 @@ func (b *Builder) checkDefaultAndRequired(schema *types.Schema, input map[string
if op.IsList() && fieldMatchesOp(field, List) && definition.IsReferenceType(field.Type) && !hasKey { if op.IsList() && fieldMatchesOp(field, List) && definition.IsReferenceType(field.Type) && !hasKey {
result[fieldName] = nil result[fieldName] = nil
} else if op.IsList() && fieldMatchesOp(field, List) && !hasKey && field.Default != nil {
result[fieldName] = field.Default
} }
} }

View File

@@ -38,21 +38,21 @@ func (s *Store) ByID(apiContext *types.APIContext, schema *types.Schema, id stri
func (s *Store) modifyForAccessControl(context *types.APIContext, schema types.Schema) *types.Schema { func (s *Store) modifyForAccessControl(context *types.APIContext, schema types.Schema) *types.Schema {
var resourceMethods []string var resourceMethods []string
if slice.ContainsString(schema.ResourceMethods, http.MethodPut) && schema.CanUpdate(context) { if slice.ContainsString(schema.ResourceMethods, http.MethodPut) && schema.CanUpdate(context) == nil {
resourceMethods = append(resourceMethods, http.MethodPut) resourceMethods = append(resourceMethods, http.MethodPut)
} }
if slice.ContainsString(schema.ResourceMethods, http.MethodDelete) && schema.CanDelete(context) { if slice.ContainsString(schema.ResourceMethods, http.MethodDelete) && schema.CanDelete(context) == nil {
resourceMethods = append(resourceMethods, http.MethodDelete) resourceMethods = append(resourceMethods, http.MethodDelete)
} }
if slice.ContainsString(schema.ResourceMethods, http.MethodGet) && schema.CanGet(context) { if slice.ContainsString(schema.ResourceMethods, http.MethodGet) && schema.CanGet(context) == nil {
resourceMethods = append(resourceMethods, http.MethodGet) resourceMethods = append(resourceMethods, http.MethodGet)
} }
var collectionMethods []string var collectionMethods []string
if slice.ContainsString(schema.CollectionMethods, http.MethodPost) && schema.CanCreate(context) { if slice.ContainsString(schema.CollectionMethods, http.MethodPost) && schema.CanCreate(context) == nil {
collectionMethods = append(collectionMethods, http.MethodPost) collectionMethods = append(collectionMethods, http.MethodPost)
} }
if slice.ContainsString(schema.CollectionMethods, http.MethodGet) && schema.CanList(context) { if slice.ContainsString(schema.CollectionMethods, http.MethodGet) && schema.CanList(context) == nil {
collectionMethods = append(collectionMethods, http.MethodGet) collectionMethods = append(collectionMethods, http.MethodGet)
} }
@@ -78,7 +78,7 @@ func (s *Store) List(apiContext *types.APIContext, schema *types.Schema, opt *ty
continue continue
} }
if schema.CanList(apiContext) || schema.CanGet(apiContext) { if schema.CanList(apiContext) == nil || schema.CanGet(apiContext) == nil {
schemas = s.addSchema(apiContext, schema, schemaMap, schemas, included) schemas = s.addSchema(apiContext, schema, schemaMap, schemas, included)
} }
} }

View File

@@ -151,7 +151,7 @@ func (s *Schemas) importType(version *APIVersion, t reflect.Type, overrides ...r
mappers := s.mapper(&schema.Version, schema.ID) mappers := s.mapper(&schema.Version, schema.ID)
if s.DefaultMappers != nil { if s.DefaultMappers != nil {
if schema.CanList(nil) { if schema.CanList(nil) == nil {
mappers = append(s.DefaultMappers(), mappers...) mappers = append(s.DefaultMappers(), mappers...)
} }
} }
@@ -175,7 +175,7 @@ func (s *Schemas) importType(version *APIVersion, t reflect.Type, overrides ...r
mapper := &typeMapper{ mapper := &typeMapper{
Mappers: mappers, Mappers: mappers,
root: schema.CanList(nil), root: schema.CanList(nil) == nil,
} }
if err := mapper.ModifySchema(schema, s); err != nil { if err := mapper.ModifySchema(schema, s); err != nil {

View File

@@ -3,6 +3,7 @@ package types
import ( import (
"net/http" "net/http"
"github.com/rancher/norman/httperror"
"github.com/rancher/norman/types/slice" "github.com/rancher/norman/types/slice"
) )
@@ -21,37 +22,52 @@ func (v *APIVersion) Equals(other *APIVersion) bool {
v.Path == other.Path v.Path == other.Path
} }
func (s *Schema) CanList(context *APIContext) bool { func (s *Schema) CanList(context *APIContext) error {
if context == nil { if context == nil {
return slice.ContainsString(s.CollectionMethods, http.MethodGet) if slice.ContainsString(s.CollectionMethods, http.MethodGet) {
return nil
}
return httperror.NewAPIError(httperror.PermissionDenied, "can not list "+s.ID)
} }
return context.AccessControl.CanList(context, s) return context.AccessControl.CanList(context, s)
} }
func (s *Schema) CanGet(context *APIContext) bool { func (s *Schema) CanGet(context *APIContext) error {
if context == nil { if context == nil {
return slice.ContainsString(s.ResourceMethods, http.MethodGet) if slice.ContainsString(s.ResourceMethods, http.MethodGet) {
return nil
}
return httperror.NewAPIError(httperror.PermissionDenied, "can not get "+s.ID)
} }
return context.AccessControl.CanGet(context, s) return context.AccessControl.CanGet(context, s)
} }
func (s *Schema) CanCreate(context *APIContext) bool { func (s *Schema) CanCreate(context *APIContext) error {
if context == nil { if context == nil {
return slice.ContainsString(s.CollectionMethods, http.MethodPost) if slice.ContainsString(s.CollectionMethods, http.MethodPost) {
return nil
}
return httperror.NewAPIError(httperror.PermissionDenied, "can not create "+s.ID)
} }
return context.AccessControl.CanCreate(context, s) return context.AccessControl.CanCreate(context, s)
} }
func (s *Schema) CanUpdate(context *APIContext) bool { func (s *Schema) CanUpdate(context *APIContext) error {
if context == nil { if context == nil {
return slice.ContainsString(s.ResourceMethods, http.MethodPut) if slice.ContainsString(s.ResourceMethods, http.MethodPut) {
return nil
}
return httperror.NewAPIError(httperror.PermissionDenied, "can not update "+s.ID)
} }
return context.AccessControl.CanUpdate(context, nil, s) return context.AccessControl.CanUpdate(context, nil, s)
} }
func (s *Schema) CanDelete(context *APIContext) bool { func (s *Schema) CanDelete(context *APIContext) error {
if context == nil { if context == nil {
return slice.ContainsString(s.ResourceMethods, http.MethodDelete) if slice.ContainsString(s.ResourceMethods, http.MethodDelete) {
return nil
}
return httperror.NewAPIError(httperror.PermissionDenied, "can not delete "+s.ID)
} }
return context.AccessControl.CanDelete(context, nil, s) return context.AccessControl.CanDelete(context, nil, s)
} }

View File

@@ -69,11 +69,11 @@ type ResponseWriter interface {
} }
type AccessControl interface { type AccessControl interface {
CanCreate(apiContext *APIContext, schema *Schema) bool CanCreate(apiContext *APIContext, schema *Schema) error
CanList(apiContext *APIContext, schema *Schema) bool CanList(apiContext *APIContext, schema *Schema) error
CanGet(apiContext *APIContext, schema *Schema) bool CanGet(apiContext *APIContext, schema *Schema) error
CanUpdate(apiContext *APIContext, obj map[string]interface{}, schema *Schema) bool CanUpdate(apiContext *APIContext, obj map[string]interface{}, schema *Schema) error
CanDelete(apiContext *APIContext, obj map[string]interface{}, schema *Schema) bool CanDelete(apiContext *APIContext, obj map[string]interface{}, schema *Schema) error
Filter(apiContext *APIContext, schema *Schema, obj map[string]interface{}, context map[string]string) map[string]interface{} Filter(apiContext *APIContext, schema *Schema, obj map[string]interface{}, context map[string]string) map[string]interface{}
FilterList(apiContext *APIContext, schema *Schema, obj []map[string]interface{}, context map[string]string) []map[string]interface{} FilterList(apiContext *APIContext, schema *Schema, obj []map[string]interface{}, context map[string]string) []map[string]interface{}