diff --git a/config/method.go b/config/method.go index d912156..ba7ce0a 100644 --- a/config/method.go +++ b/config/method.go @@ -1 +1,17 @@ package config + +import ( + "fmt" + "git.xdrm.io/go/aicra/middleware" +) + +// CheckScope returns whether a given scope matches the +// method configuration +// +// format is: [ [a,b], [c], [d,e] ] +// > level 1 is OR +// > level 2 is AND +func (m *Method) CheckScope(scope middleware.Scope) bool { + fmt.Printf("Scope: %v\n", m.Permission) + return false +} diff --git a/middleware/public.go b/middleware/public.go new file mode 100644 index 0000000..f96c43c --- /dev/null +++ b/middleware/public.go @@ -0,0 +1,110 @@ +package middleware + +import ( + "fmt" + "io/ioutil" + "log" + "net/http" + "plugin" + "strings" +) + +// CreateRegistry creates an empty middleware registry +// - if loadDir is set -> load all available middlewares +// inside the local ./middleware folder +func CreateRegistry(loadDir ...string) *MiddlewareRegistry { + + /* (1) Create registry */ + reg := &MiddlewareRegistry{ + Middlewares: make([]MiddleWare, 0), + } + + /* (2) If no default to use -> empty registry */ + if len(loadDir) < 1 { + return reg + } + + /* (3) List types */ + plugins, err := ioutil.ReadDir(loadDir[0]) + if err != nil { + log.Fatal(err) + } + + /* (4) Else try to load each given default */ + for _, file := range plugins { + + // ignore non .so files + if !strings.HasSuffix(file.Name(), ".so") { + continue + } + + err := reg.Add(file.Name()) + if err != nil { + log.Fatalf("Cannot load plugin '%s'", file.Name()) + } + + } + + return reg +} + +// Add adds a middleware to the registry; it must be a +// valid and existing plugin name with or without the .so extension +// it must be located in the relative directory .build/middleware +func (tr *MiddlewareRegistry) Add(pluginName string) error { + + /* (1) Check plugin name */ + if len(pluginName) < 1 { + return fmt.Errorf("Plugin name must not be empty") + } + + /* (2) Check if valid plugin name */ + if strings.ContainsAny(pluginName, "/") { + return fmt.Errorf("'%s' can only be a name, not a path", pluginName) + } + + /* (3) Check plugin extension */ + if !strings.HasSuffix(pluginName, ".so") { + pluginName = fmt.Sprintf("%s.so", pluginName) + } + + /* (4) Try to load the plugin */ + p, err := plugin.Open(fmt.Sprintf(".build/middleware/%s", pluginName)) + if err != nil { + return err + } + + /* (5) Export wanted properties */ + inspect, err := p.Lookup("Inspect") + if err != nil { + return fmt.Errorf("Missing method 'Inspect()'; %s", err) + } + + /* (6) Cast Inspect */ + inspectCast, ok := inspect.(func(http.Request, Scope)) + if !ok { + return fmt.Errorf("Inspect() is malformed") + } + + /* (7) Add type to registry */ + tr.Middlewares = append(tr.Middlewares, MiddleWare{ + Inspect: inspectCast, + }) + + return nil +} + +// Runs all middlewares (default browse order) +func (mr MiddlewareRegistry) Run(req http.Request) Scope { + + /* (1) Initialise scope */ + scope := Scope{} + + /* (2) Execute each middleware */ + for _, m := range mr.Middlewares { + m.Inspect(req, scope) + } + + return scope + +} diff --git a/middleware/types.go b/middleware/types.go new file mode 100644 index 0000000..f2ab731 --- /dev/null +++ b/middleware/types.go @@ -0,0 +1,26 @@ +package middleware + +import ( + "net/http" +) + +// Scope represents a list of scope processed by middlewares +// and used by the router to block/allow some uris +// it is also passed to controllers +type Scope []string + +// Inspector updates the @Scope passed to it according to +// the @http.Request +type Inspector func(http.Request, Scope) + +// Middleware contains all necessary methods +// for a Middleware provided by user/developer +type MiddleWare struct { + Inspect func(http.Request, Scope) +} + +// MiddlewareRegistry represents a registry containing all registered +// middlewares to be processed before routing any request +type MiddlewareRegistry struct { + Middlewares []MiddleWare +} diff --git a/server.go b/server.go index 1cbb731..9c0d5cc 100644 --- a/server.go +++ b/server.go @@ -6,16 +6,15 @@ import ( "git.xdrm.io/go/aicra/config" e "git.xdrm.io/go/aicra/err" "git.xdrm.io/go/aicra/implement" + "git.xdrm.io/go/aicra/middleware" "git.xdrm.io/go/aicra/request" "log" "net/http" ) // Init initilises a new framework instance -// - path is the configuration path -// - if typeChecker is nil, defaults will be used (all *.so files -// inside ./.build/types local directory) -func Init(path string, typeChecker ...*checker.TypeRegistry) (*Server, error) { +// - path is the configuration file +func New(path string) (*Server, error) { /* (1) Init instance */ inst := &Server{ @@ -30,20 +29,30 @@ func Init(path string, typeChecker ...*checker.TypeRegistry) (*Server, error) { } inst.config = config - /* (3) Store registry if given */ - if len(typeChecker) > 0 && typeChecker[0] != nil { - inst.Checker = typeChecker[0] - return inst, nil - } + /* (3) Default type registry */ + inst.SetTypeFolder(".build/type") - /* (4) Default registry creation */ - inst.Checker = checker.CreateRegistry(".build/type") + /* (4) Default middleware registry */ + inst.SetMiddlewareFolder(".build/middleware") return inst, nil + +} + +// Create the type (checker) registry from +// a given folder +func (s *Server) SetTypeFolder(path string) { + s.Checker = checker.CreateRegistry(path) +} + +// Create the middleware registry from +// a given folder +func (s *Server) SetMiddlewareFolder(path string) { + s.Middleware = middleware.CreateRegistry(path) } // Listens and binds the server to the given port -func (s *Server) Launch(port uint16) error { +func (s *Server) Listen(port uint16) error { /* (1) Bind router */ http.HandleFunc("/", s.routeRequest) @@ -63,10 +72,13 @@ func (s *Server) routeRequest(res http.ResponseWriter, httpReq *http.Request) { } /* (2) Middleware: authentication */ - // TODO: Auth + scope := s.Middleware.Run(*httpReq) /* (3) Find a matching controller */ controller := s.findController(req) + if controller == nil { + return + } /* (4) Check if matching method exists */ var method = controller.Method(httpReq.Method) @@ -76,6 +88,12 @@ func (s *Server) routeRequest(res http.ResponseWriter, httpReq *http.Request) { return } + /* (5) Check scope permissions */ + if !method.CheckScope(scope) { + httpError(res, e.Permission) + return + } + /* (4) Check parameters ---------------------------------------------------------*/ parameters, paramError := s.ExtractParameters(req, method.Parameters) @@ -96,16 +114,19 @@ func (s *Server) routeRequest(res http.ResponseWriter, httpReq *http.Request) { /* (6) Execute and get response ---------------------------------------------------------*/ - /* (1) Show Authorization header into controller */ + /* (1) Give Authorization header into controller */ authHeader := httpReq.Header.Get("Authorization") if len(authHeader) > 0 { parameters["_AUTHORIZATION_"] = authHeader } - /* (2) Execute */ + /* (2) Give Scope into controller */ + parameters["_SCOPE_"] = scope + + /* (3) Execute */ response := callable(parameters, implement.NewResponse()) - /* (3) Extract http headers */ + /* (4) Extract http headers */ for k, v := range response.Dump() { if k == "_REDIRECT_" { if newLocation, ok := v.(string); ok { @@ -115,7 +136,7 @@ func (s *Server) routeRequest(res http.ResponseWriter, httpReq *http.Request) { } } - /* (4) Build JSON response */ + /* (5) Build JSON response */ httpPrint(res, response) return diff --git a/types.go b/types.go index 364cbd2..429736e 100644 --- a/types.go +++ b/types.go @@ -3,10 +3,12 @@ package aicra import ( "git.xdrm.io/go/aicra/checker" "git.xdrm.io/go/aicra/config" + "git.xdrm.io/go/aicra/middleware" ) type Server struct { - config *config.Controller - Params map[string]interface{} - Checker *checker.TypeRegistry // type check + config *config.Controller + Params map[string]interface{} + Checker *checker.TypeRegistry // type check + Middleware *middleware.MiddlewareRegistry // middlewares }