Skip to content

Instantly share code, notes, and snippets.

@pkieltyka
Created January 4, 2017 21:59
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save pkieltyka/ac40ab917470e6d0c8e951b49b22d497 to your computer and use it in GitHub Desktop.
Save pkieltyka/ac40ab917470e6d0c8e951b49b22d497 to your computer and use it in GitHub Desktop.
Example of acl mux on top of chi router
package acl
import (
"net/http"
"reflect"
"regexp"
"runtime"
"strings"
"github.com/pressly/chi"
)
var nonalphanum = regexp.MustCompile(`[^a-zA-Z0-9_-]+`)
type Mux struct {
Mux *chi.Mux
}
type Router interface {
chi.Router
}
var _ Router = &Mux{}
func NewRouter(route string) *Mux {
r := &Mux{chi.NewRouter()}
if route != "" {
r.Mux.Use(Route(route))
}
return r
}
func (r *Mux) Use(middlewares ...func(http.Handler) http.Handler) {
r.Mux.Use(middlewares...)
}
func (r *Mux) Handle(pattern string, handler http.Handler) {
r.with().Handle(pattern, handler)
}
func (r *Mux) HandleFunc(pattern string, handler http.HandlerFunc) {
r.with().HandleFunc(pattern, handler)
}
func (r *Mux) Connect(pattern string, handler http.HandlerFunc) {
r.with().Connect(pattern, handler)
}
func (r *Mux) Head(pattern string, handler http.HandlerFunc) {
r.with().Head(pattern, handler)
}
func (r *Mux) Get(pattern string, handler http.HandlerFunc) {
r.with().Get(pattern, handler)
}
func (r *Mux) Post(pattern string, handler http.HandlerFunc) {
r.with().Post(pattern, handler)
}
func (r *Mux) Put(pattern string, handler http.HandlerFunc) {
r.with().Put(pattern, handler)
}
func (r *Mux) Patch(pattern string, handler http.HandlerFunc) {
r.with().Patch(pattern, handler)
}
func (r *Mux) Delete(pattern string, handler http.HandlerFunc) {
r.with().Delete(pattern, handler)
}
func (r *Mux) Trace(pattern string, handler http.HandlerFunc) {
r.with().Trace(pattern, handler)
}
func (r *Mux) Options(pattern string, handler http.HandlerFunc) {
r.with().Options(pattern, handler)
}
func (r *Mux) NotFound(handlerFn http.HandlerFunc) {
r.Mux.NotFound(handlerFn)
}
func (r *Mux) ServeHTTP(w http.ResponseWriter, req *http.Request) {
r.Mux.ServeHTTP(w, req)
}
// This method will inject ResourceControl at the top of the middleware chain,
// but it will also check existing Middlewares for a ResourceControl from a
// previous With() call or Group().
//
// TODO: perhaps there is a cleaner way to search for a function in a slice..?
// but I don't know, so I used reflection.
func (r *Mux) with(middlewares ...func(http.Handler) http.Handler) chi.Router {
rcFn := runtime.FuncForPC(reflect.ValueOf(ResourceControl).Pointer()).Name()
var found bool
for _, mw := range r.Mux.Middlewares() {
mwFn := runtime.FuncForPC(reflect.ValueOf(mw).Pointer()).Name()
if rcFn == mwFn {
found = true
break
}
}
var mws chi.Middlewares
if found {
mws = chi.Middlewares{}
} else {
mws = chi.Middlewares{ResourceControl}
}
mws = append(mws, middlewares...)
mx := r.Mux.With(mws...).(*chi.Mux)
return mx
}
func (r *Mux) With(middlewares ...func(http.Handler) http.Handler) chi.Router {
mx := r.with(middlewares...).(*chi.Mux)
return &Mux{mx}
}
// TODO/NOTE: this will call Group() on the chi.Mux, and not our own
// mux. As a result, the routes defined on a Group will not have ResourceControl
// middleware set so you'll have to do it yourself. The Group() method below
// fixes it, and adds ResourceControl automatically to each route, but, it's
// overkill and causes some missing role errors, or something.
// So for now, just set r.Use(ResourceControl) in a group manually
func (r *Mux) Group(fn func(r chi.Router)) chi.Router {
mx := r.Mux.With().(*chi.Mux)
if fn != nil {
fn(mx)
}
return mx
}
// NOTE: the following method will add a ResourceControl() for anytime a route is
// used with Group(), but in fact, we may not always want this.. it requires
// turning on and going through the API to ensure the acl logic and flow makes sense.
// func (r *Mux) Group(fn func(r chi.Router)) chi.Router {
// mx := r.Mux.With().(*chi.Mux)
// im := &Mux{mx}
// if fn != nil {
// fn(im)
// }
// return im
// }
func (r *Mux) Route(pattern string, fn func(r chi.Router)) chi.Router {
subRouter := NewRouter("")
if fn != nil {
fn(subRouter)
}
r.Mount(pattern, subRouter)
return subRouter
}
func (r *Mux) Mount(pattern string, handler http.Handler) {
r.Mux.Mount(pattern, handler)
}
func (r *Mux) Middlewares() chi.Middlewares {
return r.Mux.Middlewares()
}
func (r *Mux) Routes() []chi.Route {
return r.Mux.Routes()
}
// pathToACLRoute parses route fragment to generate ACL route fragment to be appended
// this is only used for .Get, .Post, .Put, etc methods to add the last part of ACL route
// the remaining part of the route is built when subrouters are created
// Example: .Get("/user_list") will add ".user_list" to ACL route for that path.
func pathToACLRoute(s string) string {
s = nonalphanum.ReplaceAllString(s, ".")
if strings.HasPrefix(s, ".") {
return s[1:len(s)]
}
return s
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment