permissions

package module
Version: v0.0.0-...-5a3038e Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 28, 2021 License: MIT Imports: 14 Imported by: 12

README

Permissions2 Build GoDoc Go Report Card

Middleware for keeping track of users, login states and permissions.

Online API Documentation

godoc.org

Features and limitations

  • Uses secure cookies and stores user information in a Redis database.
  • Suitable for running a local Redis server, registering/confirming users and managing public/user/admin pages.
  • Also supports connecting to remote Redis servers.
  • Does not support SQL databases. For MariaDB/MySQL support, look into permissionsql.
  • For Bolt database support (no database host needed, uses a file), look into permissionbolt.
  • For PostgreSQL database support (using the HSTORE feature), look into pstore.
  • Supports registration and confirmation via generated confirmation codes.
  • Tries to keep things simple.
  • Only supports public, user and admin permissions out of the box, but offers functionality for implementing more fine grained permissions, if so desired.
  • The default permissions can be cleared with the Clear() function.
  • Supports Chi, Negroni, Martini, Gin, Goji and plain net/http.
  • Should also work with other frameworks, since the standard http.HandlerFunc is used everywhere.

Requirements

  • Redis >= 2.6.12
  • Go >= 1.8

Examples

There is more information after the examples.

Example for Chi
package main

import (
    "fmt"
    "log"
    "net/http"
    "strings"

    "github.com/go-chi/chi"
    "github.com/xyproto/permissions2"
)

func main() {
    m := chi.NewRouter()

    // New permissions middleware
    perm, err := permissions.New2()
    if err != nil {
        log.Fatalln(err)
    }

    // Blank slate, no default permissions
    //perm.Clear()

    // Get the userstate, used in the handlers below
    userstate := perm.UserState()

    // Set up the middleware handler for Chi
    m.Use(perm.Middleware)

    m.Get("/", func(w http.ResponseWriter, req *http.Request) {
        fmt.Fprintf(w, "Has user bob: %v\n", userstate.HasUser("bob"))
        fmt.Fprintf(w, "Logged in on server: %v\n", userstate.IsLoggedIn("bob"))
        fmt.Fprintf(w, "Is confirmed: %v\n", userstate.IsConfirmed("bob"))
        fmt.Fprintf(w, "Username stored in cookies (or blank): %v\n", userstate.Username(req))
        fmt.Fprintf(w, "Current user is logged in, has a valid cookie and *user rights*: %v\n", userstate.UserRights(req))
        fmt.Fprintf(w, "Current user is logged in, has a valid cookie and *admin rights*: %v\n", userstate.AdminRights(req))
        fmt.Fprintf(w, "\nTry: /register, /confirm, /remove, /login, /logout, /makeadmin, /clear, /data and /admin")
    })

    m.Get("/register", func(w http.ResponseWriter, r *http.Request) {
        userstate.AddUser("bob", "hunter1", "bob@zombo.com")
        fmt.Fprintf(w, "User bob was created: %v\n", userstate.HasUser("bob"))
    })

    m.Get("/confirm", func(w http.ResponseWriter, r *http.Request) {
        userstate.MarkConfirmed("bob")
        fmt.Fprintf(w, "User bob was confirmed: %v\n", userstate.IsConfirmed("bob"))
    })

    m.Get("/remove", func(w http.ResponseWriter, r *http.Request) {
        userstate.RemoveUser("bob")
        fmt.Fprintf(w, "User bob was removed: %v\n", !userstate.HasUser("bob"))
    })

    m.Get("/login", func(w http.ResponseWriter, r *http.Request) {
        userstate.Login(w, "bob")
        fmt.Fprintf(w, "bob is now logged in: %v\n", userstate.IsLoggedIn("bob"))
    })

    m.Get("/logout", func(w http.ResponseWriter, r *http.Request) {
        userstate.Logout("bob")
        fmt.Fprintf(w, "bob is now logged out: %v\n", !userstate.IsLoggedIn("bob"))
    })

    m.Get("/makeadmin", func(w http.ResponseWriter, r *http.Request) {
        userstate.SetAdminStatus("bob")
        fmt.Fprintf(w, "bob is now administrator: %v\n", userstate.IsAdmin("bob"))
    })

    m.Get("/clear", func(w http.ResponseWriter, r *http.Request) {
        userstate.ClearCookie(w)
        fmt.Fprintf(w, "Clearing cookie")
    })

    m.Get("/data", func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintf(w, "user page that only logged in users must see!")
    })

    m.Get("/admin", func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintf(w, "super secret information that only logged in administrators must see!\n\n")
        if usernames, err := userstate.AllUsernames(); err == nil {
            fmt.Fprintf(w, "list of all users: "+strings.Join(usernames, ", "))
        }
    })

    // Custom handler for when permissions are denied
    perm.SetDenyFunction(func(w http.ResponseWriter, req *http.Request) {
        http.Error(w, "Permission denied!", http.StatusForbidden)
    })

    // Serve
    http.ListenAndServe(":3000", m)
}
Example for Negroni
package main

import (
    "fmt"
    "net/http"
    "strings"
    "log"

    "github.com/urfave/negroni"
    "github.com/xyproto/permissions2"
)

func main() {
    n := negroni.Classic()
    mux := http.NewServeMux()

    // New permissions middleware
    perm, err := permissions.New2()
    if err != nil {
        log.Fatalln(err)
    }

    // Blank slate, no default permissions
    //perm.Clear()

    // Get the userstate, used in the handlers below
    userstate := perm.UserState()

    mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
        fmt.Fprintf(w, "Has user bob: %v\n", userstate.HasUser("bob"))
        fmt.Fprintf(w, "Logged in on server: %v\n", userstate.IsLoggedIn("bob"))
        fmt.Fprintf(w, "Is confirmed: %v\n", userstate.IsConfirmed("bob"))
        fmt.Fprintf(w, "Username stored in cookies (or blank): %v\n", userstate.Username(req))
        fmt.Fprintf(w, "Current user is logged in, has a valid cookie and *user rights*: %v\n", userstate.UserRights(req))
        fmt.Fprintf(w, "Current user is logged in, has a valid cookie and *admin rights*: %v\n", userstate.AdminRights(req))
        fmt.Fprintf(w, "\nTry: /register, /confirm, /remove, /login, /logout, /makeadmin, /clear, /data and /admin")
    })

    mux.HandleFunc("/register", func(w http.ResponseWriter, req *http.Request) {
        userstate.AddUser("bob", "hunter1", "bob@zombo.com")
        fmt.Fprintf(w, "User bob was created: %v\n", userstate.HasUser("bob"))
    })

    mux.HandleFunc("/confirm", func(w http.ResponseWriter, req *http.Request) {
        userstate.MarkConfirmed("bob")
        fmt.Fprintf(w, "User bob was confirmed: %v\n", userstate.IsConfirmed("bob"))
    })

    mux.HandleFunc("/remove", func(w http.ResponseWriter, req *http.Request) {
        userstate.RemoveUser("bob")
        fmt.Fprintf(w, "User bob was removed: %v\n", !userstate.HasUser("bob"))
    })

    mux.HandleFunc("/login", func(w http.ResponseWriter, req *http.Request) {
        userstate.Login(w, "bob")
        fmt.Fprintf(w, "bob is now logged in: %v\n", userstate.IsLoggedIn("bob"))
    })

    mux.HandleFunc("/logout", func(w http.ResponseWriter, req *http.Request) {
        userstate.Logout("bob")
        fmt.Fprintf(w, "bob is now logged out: %v\n", !userstate.IsLoggedIn("bob"))
    })

    mux.HandleFunc("/makeadmin", func(w http.ResponseWriter, req *http.Request) {
        userstate.SetAdminStatus("bob")
        fmt.Fprintf(w, "bob is now administrator: %v\n", userstate.IsAdmin("bob"))
    })

    mux.HandleFunc("/clear", func(w http.ResponseWriter, req *http.Request) {
        userstate.ClearCookie(w)
        fmt.Fprintf(w, "Clearing cookie")
    })

    mux.HandleFunc("/data", func(w http.ResponseWriter, req *http.Request) {
        fmt.Fprintf(w, "user page that only logged in users must see!")
    })

    mux.HandleFunc("/admin", func(w http.ResponseWriter, req *http.Request) {
        fmt.Fprintf(w, "super secret information that only logged in administrators must see!\n\n")
        if usernames, err := userstate.AllUsernames(); err == nil {
            fmt.Fprintf(w, "list of all users: "+strings.Join(usernames, ", "))
        }
    })

    // Custom handler for when permissions are denied
    perm.SetDenyFunction(func(w http.ResponseWriter, req *http.Request) {
        http.Error(w, "Permission denied!", http.StatusForbidden)
    })

    // Enable the permissions middleware
    n.Use(perm)

    // Use mux for routing, this goes last
    n.UseHandler(mux)

    // Serve
    n.Run(":3000")
}
Example for Martini
package main

import (
    "fmt"
    "net/http"
    "strings"
    "log"

    "github.com/go-martini/martini"
    "github.com/xyproto/permissions2"
)

func main() {
    m := martini.Classic()

    // New permissions middleware
    perm, err := permissions.New2()
    if err != nil {
        log.Fatalln(err)
    }

    // Blank slate, no default permissions
    //perm.Clear()

    // Get the userstate, used in the handlers below
    userstate := perm.UserState()

    m.Get("/", func(w http.ResponseWriter, req *http.Request) {
        fmt.Fprintf(w, "Has user bob: %v\n", userstate.HasUser("bob"))
        fmt.Fprintf(w, "Logged in on server: %v\n", userstate.IsLoggedIn("bob"))
        fmt.Fprintf(w, "Is confirmed: %v\n", userstate.IsConfirmed("bob"))
        fmt.Fprintf(w, "Username stored in cookies (or blank): %v\n", userstate.Username(req))
        fmt.Fprintf(w, "Current user is logged in, has a valid cookie and *user rights*: %v\n", userstate.UserRights(req))
        fmt.Fprintf(w, "Current user is logged in, has a valid cookie and *admin rights*: %v\n", userstate.AdminRights(req))
        fmt.Fprintf(w, "\nTry: /register, /confirm, /remove, /login, /logout, /makeadmin, /clear, /data and /admin")
    })

    m.Get("/register", func(w http.ResponseWriter) {
        userstate.AddUser("bob", "hunter1", "bob@zombo.com")
        fmt.Fprintf(w, "User bob was created: %v\n", userstate.HasUser("bob"))
    })

    m.Get("/confirm", func(w http.ResponseWriter) {
        userstate.MarkConfirmed("bob")
        fmt.Fprintf(w, "User bob was confirmed: %v\n", userstate.IsConfirmed("bob"))
    })

    m.Get("/remove", func(w http.ResponseWriter) {
        userstate.RemoveUser("bob")
        fmt.Fprintf(w, "User bob was removed: %v\n", !userstate.HasUser("bob"))
    })

    m.Get("/login", func(w http.ResponseWriter) {
        userstate.Login(w, "bob")
        fmt.Fprintf(w, "bob is now logged in: %v\n", userstate.IsLoggedIn("bob"))
    })

    m.Get("/logout", func(w http.ResponseWriter) {
        userstate.Logout("bob")
        fmt.Fprintf(w, "bob is now logged out: %v\n", !userstate.IsLoggedIn("bob"))
    })

    m.Get("/makeadmin", func(w http.ResponseWriter) {
        userstate.SetAdminStatus("bob")
        fmt.Fprintf(w, "bob is now administrator: %v\n", userstate.IsAdmin("bob"))
    })

    m.Get("/clear", func(w http.ResponseWriter) {
        userstate.ClearCookie(w)
        fmt.Fprintf(w, "Clearing cookie")
    })

    m.Get("/data", func(w http.ResponseWriter) {
        fmt.Fprintf(w, "user page that only logged in users must see!")
    })

    m.Get("/admin", func(w http.ResponseWriter) {
        fmt.Fprintf(w, "super secret information that only logged in administrators must see!\n\n")
        if usernames, err := userstate.AllUsernames(); err == nil {
            fmt.Fprintf(w, "list of all users: "+strings.Join(usernames, ", "))
        }
    })

    // Set up a middleware handler for Martini, with a custom "permission denied" message.
    permissionHandler := func(w http.ResponseWriter, req *http.Request, c martini.Context) {
        // Check if the user has the right admin/user rights
        if perm.Rejected(w, req) {
            // Deny the request
            http.Error(w, "Permission denied!", http.StatusForbidden)
            // Reject the request by not calling the next handler below
            return
        }
        // Call the next middleware handler
        c.Next()
    }

    // Enable the permissions middleware
    m.Use(permissionHandler)

    // Serve
    m.Run()
}
Example for Gin
package main

import (
    "fmt"
    "net/http"
    "strings"
    "log"

    "github.com/gin-gonic/gin"
    "github.com/xyproto/permissions2"
)

func main() {
    g := gin.New()

    // New permissions middleware
    perm, err := permissions.New2()
    if err != nil {
        log.Fatalln(err)
    }

    // Blank slate, no default permissions
    //perm.Clear()

    // Set up a middleware handler for Gin, with a custom "permission denied" message.
    permissionHandler := func(c *gin.Context) {
        // Check if the user has the right admin/user rights
        if perm.Rejected(c.Writer, c.Request) {
            // Deny the request, don't call other middleware handlers
            c.AbortWithStatus(http.StatusForbidden)
            fmt.Fprint(c.Writer, "Permission denied!")
            return
        }
        // Call the next middleware handler
        c.Next()
    }

    // Logging middleware
    g.Use(gin.Logger())

    // Enable the permissions middleware, must come before recovery
    g.Use(permissionHandler)

    // Recovery middleware
    g.Use(gin.Recovery())

    // Get the userstate, used in the handlers below
    userstate := perm.UserState()

    g.GET("/", func(c *gin.Context) {
        msg := ""
        msg += fmt.Sprintf("Has user bob: %v\n", userstate.HasUser("bob"))
        msg += fmt.Sprintf("Logged in on server: %v\n", userstate.IsLoggedIn("bob"))
        msg += fmt.Sprintf("Is confirmed: %v\n", userstate.IsConfirmed("bob"))
        msg += fmt.Sprintf("Username stored in cookies (or blank): %v\n", userstate.Username(c.Request))
        msg += fmt.Sprintf("Current user is logged in, has a valid cookie and *user rights*: %v\n", userstate.UserRights(c.Request))
        msg += fmt.Sprintf("Current user is logged in, has a valid cookie and *admin rights*: %v\n", userstate.AdminRights(c.Request))
        msg += fmt.Sprintln("\nTry: /register, /confirm, /remove, /login, /logout, /makeadmin, /clear, /data and /admin")
        c.String(http.StatusOK, msg)
    })

    g.GET("/register", func(c *gin.Context) {
        userstate.AddUser("bob", "hunter1", "bob@zombo.com")
        c.String(http.StatusOK, fmt.Sprintf("User bob was created: %v\n", userstate.HasUser("bob")))
    })

    g.GET("/confirm", func(c *gin.Context) {
        userstate.MarkConfirmed("bob")
        c.String(http.StatusOK, fmt.Sprintf("User bob was confirmed: %v\n", userstate.IsConfirmed("bob")))
    })

    g.GET("/remove", func(c *gin.Context) {
        userstate.RemoveUser("bob")
        c.String(http.StatusOK, fmt.Sprintf("User bob was removed: %v\n", !userstate.HasUser("bob")))
    })

    g.GET("/login", func(c *gin.Context) {
        // Headers will be written, for storing a cookie
        userstate.Login(c.Writer, "bob")
        c.String(http.StatusOK, fmt.Sprintf("bob is now logged in: %v\n", userstate.IsLoggedIn("bob")))
    })

    g.GET("/logout", func(c *gin.Context) {
        userstate.Logout("bob")
        c.String(http.StatusOK, fmt.Sprintf("bob is now logged out: %v\n", !userstate.IsLoggedIn("bob")))
    })

    g.GET("/makeadmin", func(c *gin.Context) {
        userstate.SetAdminStatus("bob")
        c.String(http.StatusOK, fmt.Sprintf("bob is now administrator: %v\n", userstate.IsAdmin("bob")))
    })

    g.GET("/clear", func(c *gin.Context) {
        userstate.ClearCookie(c.Writer)
        c.String(http.StatusOK, "Clearing cookie")
    })

    g.GET("/data", func(c *gin.Context) {
        c.String(http.StatusOK, "user page that only logged in users must see!")
    })

    g.GET("/admin", func(c *gin.Context) {
        c.String(http.StatusOK, "super secret information that only logged in administrators must see!\n\n")
        if usernames, err := userstate.AllUsernames(); err == nil {
            c.String(http.StatusOK, "list of all users: "+strings.Join(usernames, ", "))
        }
    })

    // Serve
    g.Run(":3000")
}
Example for Goji
package main

import (
    "fmt"
    "net/http"
    "strings"
    "log"

    "github.com/xyproto/permissions2"
    "github.com/zenazn/goji"
)

func main() {
    // New permissions middleware
    perm, err := permissions.New2()
    if err != nil {
        log.Fatalln(err)
    }

    // Blank slate, no default permissions
    //perm.Clear()

    // Get the userstate, used in the handlers below
    userstate := perm.UserState()

    goji.Get("/", func(w http.ResponseWriter, req *http.Request) {
        fmt.Fprintf(w, "Has user bob: %v\n", userstate.HasUser("bob"))
        fmt.Fprintf(w, "Logged in on server: %v\n", userstate.IsLoggedIn("bob"))
        fmt.Fprintf(w, "Is confirmed: %v\n", userstate.IsConfirmed("bob"))
        fmt.Fprintf(w, "Username stored in cookies (or blank): %v\n", userstate.Username(req))
        fmt.Fprintf(w, "Current user is logged in, has a valid cookie and *user rights*: %v\n", userstate.UserRights(req))
        fmt.Fprintf(w, "Current user is logged in, has a valid cookie and *admin rights*: %v\n", userstate.AdminRights(req))
        fmt.Fprintf(w, "\nTry: /register, /confirm, /remove, /login, /logout, /makeadmin, /clear, /data and /admin")
    })

    goji.Get("/register", func(w http.ResponseWriter, req *http.Request) {
        userstate.AddUser("bob", "hunter1", "bob@zombo.com")
        fmt.Fprintf(w, "User bob was created: %v\n", userstate.HasUser("bob"))
    })

    goji.Get("/confirm", func(w http.ResponseWriter, req *http.Request) {
        userstate.MarkConfirmed("bob")
        fmt.Fprintf(w, "User bob was confirmed: %v\n", userstate.IsConfirmed("bob"))
    })

    goji.Get("/remove", func(w http.ResponseWriter, req *http.Request) {
        userstate.RemoveUser("bob")
        fmt.Fprintf(w, "User bob was removed: %v\n", !userstate.HasUser("bob"))
    })

    goji.Get("/login", func(w http.ResponseWriter, req *http.Request) {
        userstate.Login(w, "bob")
        fmt.Fprintf(w, "bob is now logged in: %v\n", userstate.IsLoggedIn("bob"))
    })

    goji.Get("/logout", func(w http.ResponseWriter, req *http.Request) {
        userstate.Logout("bob")
        fmt.Fprintf(w, "bob is now logged out: %v\n", !userstate.IsLoggedIn("bob"))
    })

    goji.Get("/makeadmin", func(w http.ResponseWriter, req *http.Request) {
        userstate.SetAdminStatus("bob")
        fmt.Fprintf(w, "bob is now administrator: %v\n", userstate.IsAdmin("bob"))
    })

    goji.Get("/clear", func(w http.ResponseWriter, req *http.Request) {
        userstate.ClearCookie(w)
        fmt.Fprintf(w, "Clearing cookie")
    })

    goji.Get("/data", func(w http.ResponseWriter, req *http.Request) {
        fmt.Fprintf(w, "user page that only logged in users must see!")
    })

    goji.Get("/admin", func(w http.ResponseWriter, req *http.Request) {
        fmt.Fprintf(w, "super secret information that only logged in administrators must see!\n\n")
        if usernames, err := userstate.AllUsernames(); err == nil {
            fmt.Fprintf(w, "list of all users: "+strings.Join(usernames, ", "))
        }
    })

    // Custom "permissions denied" message
    perm.SetDenyFunction(func(w http.ResponseWriter, req *http.Request) {
        http.Error(w, "Permission denied!", http.StatusForbidden)
    })

    // Permissions middleware for Goji
    permissionHandler := func(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
            // Check if the user has the right admin/user rights
            if perm.Rejected(w, req) {
                // Deny the request
                perm.DenyFunction()(w, req)
                return
            }
            // Serve the requested page
            next.ServeHTTP(w, req)
        })
    }

    // Enable the permissions middleware
    goji.Use(permissionHandler)

    // Goji will listen to port 8000 by default
    goji.Serve()
}
Example for just net/http
package main

import (
    "fmt"
    "log"
    "net/http"
    "strings"
    "time"

    "github.com/xyproto/permissions2"
    "github.com/xyproto/pinterface"
)

type permissionHandler struct {
    // perm is a Permissions structure that can be used to deny requests
    // and acquire the UserState. By using `pinterface.IPermissions` instead
    // of `*permissions.Permissions`, the code is compatible with not only
    // `permissions2`, but also other modules that uses other database
    // backends, like `permissionbolt` which uses Bolt.
    perm pinterface.IPermissions

    // The HTTP multiplexer
    mux *http.ServeMux
}

// Implement the ServeHTTP method to make a permissionHandler a http.Handler
func (ph *permissionHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
    // Check if the user has the right admin/user rights
    if ph.perm.Rejected(w, req) {
        // Let the user know, by calling the custom "permission denied" function
        ph.perm.DenyFunction()(w, req)
        // Reject the request
        return
    }
    // Serve the requested page if permissions were granted
    ph.mux.ServeHTTP(w, req)
}

func main() {
    mux := http.NewServeMux()

    // New permissions middleware
    perm, err := permissions.New2()
    if err != nil {
        log.Fatalln(err)
    }

    // Blank slate, no default permissions
    //perm.Clear()

    // Get the userstate, used in the handlers below
    userstate := perm.UserState()

    mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
        fmt.Fprintf(w, "Has user bob: %v\n", userstate.HasUser("bob"))
        fmt.Fprintf(w, "Logged in on server: %v\n", userstate.IsLoggedIn("bob"))
        fmt.Fprintf(w, "Is confirmed: %v\n", userstate.IsConfirmed("bob"))
        fmt.Fprintf(w, "Username stored in cookies (or blank): %v\n", userstate.Username(req))
        fmt.Fprintf(w, "Current user is logged in, has a valid cookie and *user rights*: %v\n", userstate.UserRights(req))
        fmt.Fprintf(w, "Current user is logged in, has a valid cookie and *admin rights*: %v\n", userstate.AdminRights(req))
        fmt.Fprintf(w, "\nTry: /register, /confirm, /remove, /login, /logout, /makeadmin, /clear, /data and /admin")
    })

    mux.HandleFunc("/register", func(w http.ResponseWriter, req *http.Request) {
        userstate.AddUser("bob", "hunter1", "bob@zombo.com")
        fmt.Fprintf(w, "User bob was created: %v\n", userstate.HasUser("bob"))
    })

    mux.HandleFunc("/confirm", func(w http.ResponseWriter, req *http.Request) {
        userstate.MarkConfirmed("bob")
        fmt.Fprintf(w, "User bob was confirmed: %v\n", userstate.IsConfirmed("bob"))
    })

    mux.HandleFunc("/remove", func(w http.ResponseWriter, req *http.Request) {
        userstate.RemoveUser("bob")
        fmt.Fprintf(w, "User bob was removed: %v\n", !userstate.HasUser("bob"))
    })

    mux.HandleFunc("/login", func(w http.ResponseWriter, req *http.Request) {
        userstate.Login(w, "bob")
        fmt.Fprintf(w, "bob is now logged in: %v\n", userstate.IsLoggedIn("bob"))
    })

    mux.HandleFunc("/logout", func(w http.ResponseWriter, req *http.Request) {
        userstate.Logout("bob")
        fmt.Fprintf(w, "bob is now logged out: %v\n", !userstate.IsLoggedIn("bob"))
    })

    mux.HandleFunc("/makeadmin", func(w http.ResponseWriter, req *http.Request) {
        userstate.SetAdminStatus("bob")
        fmt.Fprintf(w, "bob is now administrator: %v\n", userstate.IsAdmin("bob"))
    })

    mux.HandleFunc("/clear", func(w http.ResponseWriter, req *http.Request) {
        userstate.ClearCookie(w)
        fmt.Fprintf(w, "Clearing cookie")
    })

    mux.HandleFunc("/data", func(w http.ResponseWriter, req *http.Request) {
        fmt.Fprintf(w, "user page that only logged in users must see!")
    })

    mux.HandleFunc("/admin", func(w http.ResponseWriter, req *http.Request) {
        fmt.Fprintf(w, "super secret information that only logged in administrators must see!\n\n")
        if usernames, err := userstate.AllUsernames(); err == nil {
            fmt.Fprintf(w, "list of all users: "+strings.Join(usernames, ", "))
        }
    })

    // Custom handler for when permissions are denied
    perm.SetDenyFunction(func(w http.ResponseWriter, req *http.Request) {
        http.Error(w, "Permission denied!", http.StatusForbidden)
    })

    // Configure the HTTP server and permissionHandler struct
    s := &http.Server{
        Addr:           ":3000",
        Handler:        &permissionHandler{perm, mux},
        ReadTimeout:    10 * time.Second,
        WriteTimeout:   10 * time.Second,
        MaxHeaderBytes: 1 << 20,
    }

    log.Println("Listening for requests on port 3000")

    // Start listening
    log.Fatal(s.ListenAndServe())
}

Default permissions

  • Visiting the /admin path prefix requires the user to be logged in with admin rights, by default.
  • These path prefixes requires the user to be logged in, by default: /repo and /data
  • These path prefixes are public by default: /, /login, /register, /style, /img, /js, /favicon.ico, /robots.txt and /sitemap_index.xml

The default permissions can be cleared with the Clear() function.

Password hashing

  • bcrypt is used by default for hashing passwords. sha256 is also supported.
  • By default, all new password will be hashed with bcrypt.
  • For backwards compatibility, old password hashes with the length of a sha256 hash will be checked with sha256. To disable this behavior, and only ever use bcrypt, add this line: userstate.SetPasswordAlgo("bcrypt")

Coding style

  • The code shall always be formatted with go fmt.

Setting and getting properties for users

  • Setting a property:
username := "bob"
propertyName := "clever"
propertyValue := "yes"

userstate.Users().Set(username, propertyName, propertyValue)
  • Getting a property:
username := "bob"
propertyName := "clever"
propertyValue, err := userstate.Users().Get(username, propertyName)
if err != nil {
    log.Print(err)
    return err
}
fmt.Printf("%s is %s: %s\n", username, propertyName, propertyValue)

This method can also be used for deleting users, by for example setting a deleted property to true.

Passing userstate between functions, files and to other Go packages

Using the pinterface.IUserState interface (from the pinterface package) makes it possible to pass UserState structs between functions, also in other packages. By using this, it is possible to seamlessly change the database backend from, for instance, Redis (permissions2) to BoltDB (permissionbolt).

pstore, permissionsql, permissionbolt and permissions2 are interchangeable.

Retrieving the underlying Redis database

Here is a short example application for retrieving the underlying Redis pool and connection:

package main

import (
    "fmt"
    "github.com/gomodule/redigo/redis"
    "github.com/xyproto/permissions2"
)

func main() {
    perm, err := permissions.New2()
    if err != nil {
        fmt.Println("Could not open Redis database")
        return
    }
    ustate := perm.UserState()

    // A bit of checking is needed, since the database backend is interchangeable
    pustate, ok := ustate.(*permissions.UserState)
    if !ok {
        fmt.Println("Not using the Redis database backend")
        return
    }

    // Convert from a simpleredis.ConnectionPool to a redis.Pool
    redisPool := redis.Pool(*pustate.Pool())
    fmt.Printf("Redis pool: %v (%T)\n", redisPool, redisPool)

    // Get the Redis connection as well
    redisConnection := redisPool.Get()
    fmt.Printf("Redis connection: %v (%T)\n", redisConnection, redisConnection)
}

Note that the redigo repository was recently moved to https://github.com/gomodule/redigo. The above code will not work if you use the old redigo package.

General information

  • Version: 2.6.2
  • License: MIT
  • Alexander F. Rødseth <xyproto@archlinux.org>

Documentation

Overview

Package permissions provides a way to keep track of users, login states and permissions.

Index

Constants

View Source
const (
	// Version number. Stable API within major version numbers.
	Version = 2.6
)

Variables

View Source
var (

	// ErrNotFound is returned if something is not found
	ErrNotFound = errors.New("not found")

	// ErrNoCookieUsername is returned if a username could not be retrieved from a cookie
	ErrNoCookieUsername = errors.New("could not retrieve the username from browser cookie")

	// ErrNoCookieEmptyUsername is returned if the cookie could not be set because the username was empty
	ErrNoCookieEmptyUsername = errors.New("can't set cookie for empty username")

	// ErrNoCookieMissingUser is returned if the user does not exist when about to store a cookie
	ErrNoCookieMissingUser = errors.New("can't store cookie for non-existing user")

	// ErrRedisConnectionFailure is returned if the Redis server is unreachable
	ErrRedisConnectionFailure = errors.New("unable to connect to Redis server on port 6379")

	// ErrRedisLostConnection is returned if the connection to the Redis server is lost
	ErrRedisLostConnection = errors.New("lost connection to Redis")

	// ErrUsersAlreadyConfirmed is returned if all users are confirmed and no confirmation can be done
	ErrUsersAlreadyConfirmed = errors.New("all existing users are already confirmed")

	// ErrConfirmationNoLongerValid is returned if the given confirmation code is invalid
	ErrConfirmationNoLongerValid = errors.New("the confirmation code is no longer valid")

	// ErrConfirmationUserMissing is returned if the confirmation fails because the user does not exist
	// on the list of users that are unconfirmed.
	ErrConfirmationUserMissing = errors.New("the user that is to be confirmed no longer exists")

	// ErrConfirmationNotUnique is returned if there are issues generating confirmation codes. This should normally not happen.
	ErrConfirmationNotUnique = errors.New("too many generated confirmation codes are not unique")

	// ErrInvalidUsername is returned if the given username contains characters that the default validator does not accept
	ErrInvalidUsername = errors.New("only numbers, underscore and some letters are allowed in usernames")

	// ErrSameUsernameAndPassword is returned if the username and password are equal
	ErrSameUsernameAndPassword = errors.New("username and password must be different, try another password")
)

Functions

func PermissionDenied

func PermissionDenied(w http.ResponseWriter, req *http.Request)

PermissionDenied is the default "permission denied" http handler.

func ValidUsernamePassword

func ValidUsernamePassword(username, password string) error

ValidUsernamePassword checks that the given username and password are different. Also check if the chosen username only contains letters, numbers and/or underscore. Use the "CorrectPassword" function for checking if the password is correct. Don't use this function if you wish to use e-mail addresses as usernames.

Types

type Permissions

type Permissions struct {
	// contains filtered or unexported fields
}

Permissions is a structure that keeps track of the permissions for various path prefixes

func New

func New() *Permissions

New will initialize a Permissions struct with all the default settings. This will also connect to the redis host at localhost:6379.

func New2

func New2() (*Permissions, error)

New2 will initialize a Permissions struct with all the default settings. This will also connect to the redis host at localhost:6379.

func NewPermissions

func NewPermissions(state *UserState) *Permissions

NewPermissions will initialize a Permissions struct with the given UserState and a few default paths for admin/user/public path prefixes.

func NewWithRedisConf

func NewWithRedisConf(dbindex int, hostPort string) *Permissions

NewWithRedisConf will initialize a Permissions struct with Redis DB index and host:port. Calls log.Fatal if something goes wrong.

func NewWithRedisConf2

func NewWithRedisConf2(dbindex int, hostPort string) (*Permissions, error)

NewWithRedisConf2 will initialize a Permissions struct with Redis DB index and host:port. Returns an error if something goes wrong.

func (*Permissions) AddAdminPath

func (perm *Permissions) AddAdminPath(prefix string)

AddAdminPath registers a path prefix for URLs that shall only be reached by logged in administrators

func (*Permissions) AddPublicPath

func (perm *Permissions) AddPublicPath(prefix string)

AddPublicPath registers a path prefix for URLs that can be reached by anyone

func (*Permissions) AddUserPath

func (perm *Permissions) AddUserPath(prefix string)

AddUserPath registers a path prefix for URLs that shall only be reached by logged in users

func (*Permissions) Clear

func (perm *Permissions) Clear()

Clear sets every URL path prefix permission to "public"

func (*Permissions) DenyFunction

func (perm *Permissions) DenyFunction() http.HandlerFunc

DenyFunction returns the current http.HandlerFunc, for when permissions are denied.

func (*Permissions) Middleware

func (perm *Permissions) Middleware(next http.Handler) http.Handler

Middleware handler (compatible with Chi)

func (*Permissions) Rejected

func (perm *Permissions) Rejected(w http.ResponseWriter, req *http.Request) bool

Rejected checks if a given request should be rejected.

func (*Permissions) ServeHTTP

func (perm *Permissions) ServeHTTP(w http.ResponseWriter, req *http.Request, next http.HandlerFunc)

Middleware handler (compatible with Negroni)

func (*Permissions) SetAdminPath

func (perm *Permissions) SetAdminPath(pathPrefixes []string)

SetAdminPath can be used for setting all URL path prefixes that are for the logged in administrator pages.

func (*Permissions) SetDenyFunction

func (perm *Permissions) SetDenyFunction(f http.HandlerFunc)

SetDenyFunction can be used for specifying a http.HandlerFunc that will be used when the permissions are denied.

func (*Permissions) SetPublicPath

func (perm *Permissions) SetPublicPath(pathPrefixes []string)

SetPublicPath can be used for setting all URL path prefixes that are for the public pages.

func (*Permissions) SetUserPath

func (perm *Permissions) SetUserPath(pathPrefixes []string)

SetUserPath can be used for setting all URL path prefixes that are for the logged in user pages.

func (*Permissions) UserState

func (perm *Permissions) UserState() pinterface.IUserState

UserState retrieves the UserState struct

type UserState

type UserState struct {
	// contains filtered or unexported fields
}

UserState is a struct for dealing with the user state, users and passwords. Can also be used for retrieving the underlying Redis connection pool. The default password hashing algorithm is "bcrypt+", which is the same as "bcrypt", but with backwards compatibility for checking sha256 hashes.

func NewUserState

func NewUserState(dbindex int, randomseed bool, redisHostPort string) *UserState

NewUserState will create a new *UserState that can be used for managing users. dbindex is the Redis database index (0 is a good default value). If randomseed is true, the random number generator will be seeded after generating the cookie secret (true is a good default value). redisHostPort is host:port for the desired Redis server (can be blank for localhost). Also creates a new ConnectionPool. Calls log.Fatal if things go wrong.

func NewUserState2

func NewUserState2(dbindex int, randomseed bool, redisHostPort string) (*UserState, error)

NewUserState2 will create a new *UserState that can be used for managing users. dbindex is the Redis database index (0 is a good default value). If randomseed is true, the random number generator will be seeded after generating the cookie secret (true is a good default value). redisHostPort is host:port for the desired Redis server (can be blank for localhost) Also creates a new ConnectionPool. Returns an error if things go wrong.

func NewUserStateSimple

func NewUserStateSimple() *UserState

NewUserStateSimple will create a new *UserState that can be used for managing users. The random number generator will be seeded after generating the cookie secret. A connection pool for the local Redis server (dbindex 0) will be created. Calls log.Fatal if things go wrong.

func NewUserStateSimple2

func NewUserStateSimple2() (*UserState, error)

NewUserStateSimple2 will create a new *UserState that can be used for managing users. The random number generator will be seeded after generating the cookie secret. A connection pool for the local Redis server (dbindex 0) will be created. Returns an error if things go wrong.

func NewUserStateWithPassword

func NewUserStateWithPassword(hostname, password string) *UserState

NewUserStateWithPassword is the same as NewUserStateSimple, but also takes a Redis hostname and a Redis password. Use NewUserState for control over the database index and port number. Calls log.Fatal if things go wrong.

func NewUserStateWithPassword2

func NewUserStateWithPassword2(hostname, password string) (*UserState, error)

NewUserStateWithPassword2 is the same as NewUserStateSimple2, but takes a hostname and a password. Use NewUserState2 for control over the database index and port number. Returns an error if things go wrong.

func (*UserState) AddUnconfirmed

func (state *UserState) AddUnconfirmed(username, confirmationCode string)

AddUnconfirmed adds a user that is registered but not confirmed.

func (*UserState) AddUser

func (state *UserState) AddUser(username, password, email string)

AddUser creates a user and hashes the password, does not check for rights. The given data must be valid.

func (*UserState) AdminRights

func (state *UserState) AdminRights(req *http.Request) bool

AdminRights checks if the current user is logged in and has administrator rights.

func (*UserState) AllUnconfirmedUsernames

func (state *UserState) AllUnconfirmedUsernames() ([]string, error)

AllUnconfirmedUsernames returns a list of all registered users that are not yet confirmed.

func (*UserState) AllUsernames

func (state *UserState) AllUsernames() ([]string, error)

AllUsernames retrieves a list of all usernames.

func (*UserState) AlreadyHasConfirmationCode

func (state *UserState) AlreadyHasConfirmationCode(confirmationCode string) bool

AlreadyHasConfirmationCode runs through all confirmation codes of all unconfirmed users and checks if this confirmationCode is already in use.

func (*UserState) BooleanField

func (state *UserState) BooleanField(username, fieldname string) bool

BooleanField returns the boolean value for a given username and field name. If the user or field is missing, false will be returned. Useful for states where it makes sense that the returned value is not true unless everything is in order.

func (*UserState) ClearCookie

func (state *UserState) ClearCookie(w http.ResponseWriter)

ClearCookie will try to clear the user cookie by setting it to expired. Some browsers *may* be configured to keep cookies even after this, but that is highly unusual.

func (*UserState) Close

func (state *UserState) Close()

Close the Redis connection pool.

func (*UserState) Confirm

func (state *UserState) Confirm(username string)

Confirm removes the username from the list of unconfirmed users and mark the user as confirmed.

func (*UserState) ConfirmUserByConfirmationCode

func (state *UserState) ConfirmUserByConfirmationCode(confirmationCode string) error

ConfirmUserByConfirmationCode takes a confirmation code and mark the corresponding unconfirmed user as confirmed.

func (*UserState) ConfirmationCode

func (state *UserState) ConfirmationCode(username string) (string, error)

ConfirmationCode gets the confirmation code for a specific user.

func (*UserState) CookieSecret

func (state *UserState) CookieSecret() string

CookieSecret returns the current cookie secret.

func (*UserState) CookieTimeout

func (state *UserState) CookieTimeout(username string) int64

CookieTimeout gets how long a login cookie should last, in seconds.

func (*UserState) CorrectPassword

func (state *UserState) CorrectPassword(username, password string) bool

CorrectPassword checks if a password is correct. username is needed because it is part of the hash.

func (*UserState) Creator

func (state *UserState) Creator() pinterface.ICreator

Creator returns a struct for creating data structures with

func (*UserState) DatabaseIndex

func (state *UserState) DatabaseIndex() int

DatabaseIndex gets the Redis database index.

func (*UserState) Email

func (state *UserState) Email(username string) (string, error)

Email returns the email address for the given username.

func (*UserState) FindUserByConfirmationCode

func (state *UserState) FindUserByConfirmationCode(confirmationCode string) (string, error)

FindUserByConfirmationCode can find the corresponding username in the list of unconfirmed users, given a unique confirmation code.

func (*UserState) GenerateUniqueConfirmationCode

func (state *UserState) GenerateUniqueConfirmationCode() (string, error)

GenerateUniqueConfirmationCode will generate a unique confirmation code that can be used for confirming users after users have registered.

func (*UserState) GetToken

func (state *UserState) GetToken(username string) (string, error)

GetToken retrieves the token for a user.

func (*UserState) HasEmail

func (state *UserState) HasEmail(email string) (string, error)

HasEmail finds the user that has a given e-mail address. Returns the username and nil if found or a blank string and ErrNotFound if not.

func (*UserState) HasUser

func (state *UserState) HasUser(username string) bool

HasUser checks if the given username exists.

func (*UserState) HasUser2

func (state *UserState) HasUser2(username string) (bool, error)

HasUser2 checks if the given username exists.

func (*UserState) HashPassword

func (state *UserState) HashPassword(username, password string) string

HashPassword will hash the password (takes a username as well, it can be used for salting when using sha256).

func (*UserState) Host

func (state *UserState) Host() pinterface.IHost

Host gets the Host (for qualifying for the IUserState interface)

func (*UserState) IsAdmin

func (state *UserState) IsAdmin(username string) bool

IsAdmin checks if the given username is an administrator.

func (*UserState) IsConfirmed

func (state *UserState) IsConfirmed(username string) bool

IsConfirmed checks if the given username is confirmed.

func (*UserState) IsLoggedIn

func (state *UserState) IsLoggedIn(username string) bool

IsLoggedIn checks if the given username is logged in.

func (*UserState) Login

func (state *UserState) Login(w http.ResponseWriter, username string) error

Login is a convenience function for logging a user in and storing the username in a cookie. Returns an error if the cookie could not be set.

func (*UserState) Logout

func (state *UserState) Logout(username string)

Logout is a convenience function for logging a user out. This is the same as SetLoggedOut.

func (*UserState) MarkConfirmed

func (state *UserState) MarkConfirmed(username string)

MarkConfirmed can mark a user as confirmed.

func (*UserState) PasswordAlgo

func (state *UserState) PasswordAlgo() string

PasswordAlgo gets the current password hashing algorithm.

func (*UserState) PasswordHash

func (state *UserState) PasswordHash(username string) (string, error)

PasswordHash returns the password hash for the given username.

func (*UserState) Pool

func (state *UserState) Pool() *simpleredis.ConnectionPool

Pool gets the Redis connection pool.

func (*UserState) Properties

func (state *UserState) Properties(username string) []string

Properties returns a list of user properties. Returns an empty list if the user has no properties, or if there are errors.

func (*UserState) RemoveAdminStatus

func (state *UserState) RemoveAdminStatus(username string)

RemoveAdminStatus can remove administrator status from a user.

func (*UserState) RemoveToken

func (state *UserState) RemoveToken(username string)

RemoveToken takes a username and removes the associated token.

func (*UserState) RemoveUnconfirmed

func (state *UserState) RemoveUnconfirmed(username string)

RemoveUnconfirmed removes a user that is registered but not confirmed.

func (*UserState) RemoveUser

func (state *UserState) RemoveUser(username string)

RemoveUser removes user and login status.

func (*UserState) SetAdminStatus

func (state *UserState) SetAdminStatus(username string)

SetAdminStatus can make a user an administrator.

func (*UserState) SetBooleanField

func (state *UserState) SetBooleanField(username, fieldname string, val bool)

SetBooleanField can store a boolean value for the given username and custom fieldname.

func (*UserState) SetCookieSecret

func (state *UserState) SetCookieSecret(cookieSecret string)

SetCookieSecret will set the secret that is used when generating secure cookies.

func (*UserState) SetCookieTimeout

func (state *UserState) SetCookieTimeout(cookieTime int64)

SetCookieTimeout will set how long a login cookie should last, in seconds.

func (*UserState) SetLoggedIn

func (state *UserState) SetLoggedIn(username string)

SetLoggedIn will mark the user as logged in. Use the Login function instead, unless cookies are not involved.

func (*UserState) SetLoggedOut

func (state *UserState) SetLoggedOut(username string)

SetLoggedOut will mark the user as logged out.

func (*UserState) SetMinimumConfirmationCodeLength

func (state *UserState) SetMinimumConfirmationCodeLength(length int)

SetMinimumConfirmationCodeLength will set the minimum length of the user confirmation code. The default is 20.

func (*UserState) SetPassword

func (state *UserState) SetPassword(username, password string)

SetPassword sets the password for a user. The given password string will be hashed. No validation or check of the given password is performed.

func (*UserState) SetPasswordAlgo

func (state *UserState) SetPasswordAlgo(algorithm string) error

SetPasswordAlgo can set the password hashing algorithm that should be used. The default is "bcrypt+". Possible values are:

bcrypt  -> Store and check passwords with the bcrypt hash.
sha256  -> Store and check passwords with the sha256 hash.
bcrypt+ -> Store passwords with bcrypt, but check with both
           bcrypt and sha256, for backwards compatibility
           with old passwords that has been stored as sha256.

func (*UserState) SetToken

func (state *UserState) SetToken(username, token string, expire time.Duration)

SetToken sets a token for a user, for a given expiry time.

func (*UserState) SetUsernameCookie

func (state *UserState) SetUsernameCookie(w http.ResponseWriter, username string) error

SetUsernameCookie tries to store the given username in a cookie in the browser.

*
* The user must exist. Returns an error if the username is empty or does not exist.
* Returns nil if the cookie has been attempted to be set.
* To check if the cookie has actually been set, one must try to read it.

func (*UserState) SetUsernameCookieOnlyHTTPS

func (state *UserState) SetUsernameCookieOnlyHTTPS(w http.ResponseWriter, username string) error

SetUsernameCookieOnlyHTTPS tries to store the given username in a cookie in the browser.

* This function will not set the cookie if over plain HTTP.
*
* The user must exist. Returns an error if the username is empty or does not exist.
* Returns nil if the cookie has been attempted to be set.
* To check if the cookie has actually been set, one must try to read it.

func (*UserState) UserRights

func (state *UserState) UserRights(req *http.Request) bool

UserRights checks if the current user is logged in and has user rights.

func (*UserState) Username

func (state *UserState) Username(req *http.Request) string

Username is a convenience function that will return a username (from the browser cookie) or an empty string.

func (*UserState) UsernameCookie

func (state *UserState) UsernameCookie(req *http.Request) (string, error)

UsernameCookie retrieves the username that is stored in a cookie in the browser, if available.

func (*UserState) Users

func (state *UserState) Users() pinterface.IHashMap

Users gets the users HashMap.

Directories

Path Synopsis
examples
chi
gin

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
t or T : Toggle theme light dark auto
y or Y : Canonical URL