Merge pull request #18 from gmemstr/permissions

Permissions
This commit is contained in:
Gabriel Simmer 2017-12-01 14:20:04 -08:00 committed by GitHub
commit fc292b497e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 111 additions and 45 deletions

View file

@ -7,32 +7,34 @@
package admin package admin
import ( import (
"database/sql"
"encoding/json"
"fmt" "fmt"
"golang.org/x/crypto/bcrypt"
"io" "io"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"os" "os"
"strings" "strings"
"encoding/json"
"golang.org/x/crypto/bcrypt"
"database/sql"
_ "github.com/mattn/go-sqlite3"
"github.com/gorilla/mux" "github.com/gorilla/mux"
_ "github.com/mattn/go-sqlite3"
"github.com/gmemstr/pogo/common" "github.com/gmemstr/pogo/common"
) )
type User struct { type User struct {
Id int `json:"id"` Id int `json:"id"`
Dbun string `json:"username"` Dbun string `json:"username"`
Dbrn string `json:"realname"` Dbrn string `json:"realname"`
Dbem string `json:"email"` Dbem string `json:"email"`
} }
type UserList struct { type UserList struct {
Users []User Users []User
} }
/* /*
* The following is a set of admin commands * The following is a set of admin commands
* that the average user probably shouldn't be * that the average user probably shouldn't be
* able to have access to, mostly user management. * able to have access to, mostly user management.
*/ */
@ -48,7 +50,7 @@ func AddUser() common.Handler {
StatusCode: http.StatusInternalServerError, StatusCode: http.StatusInternalServerError,
} }
} }
statement, err := db.Prepare("INSERT INTO users(username,hash,realname,email) VALUES (?,?,?,?)") statement, err := db.Prepare("INSERT INTO users(username,hash,realname,email,permissions) VALUES (?,?,?,?,?)")
if err != nil { if err != nil {
return &common.HTTPError{ return &common.HTTPError{
Message: fmt.Sprintf("error preparing sqlite3 statement: %v", err), Message: fmt.Sprintf("error preparing sqlite3 statement: %v", err),
@ -68,10 +70,11 @@ func AddUser() common.Handler {
password := strings.Join(r.Form["password"], "") password := strings.Join(r.Form["password"], "")
realname := strings.Join(r.Form["realname"], "") realname := strings.Join(r.Form["realname"], "")
email := strings.Join(r.Form["email"], "") email := strings.Join(r.Form["email"], "")
permissions := strings.Join(r.Form["permissions"], "")
hash, err := bcrypt.GenerateFromPassword([]byte(password), 4) hash, err := bcrypt.GenerateFromPassword([]byte(password), 4)
_, err = statement.Exec(username,hash,realname,email) _, err = statement.Exec(username, hash, realname, email, permissions)
if err != nil { if err != nil {
return &common.HTTPError{ return &common.HTTPError{
Message: fmt.Sprintf("error executing sqlite3 statement: %v", err), Message: fmt.Sprintf("error executing sqlite3 statement: %v", err),
@ -110,9 +113,10 @@ func EditUser() common.Handler {
newpassword := strings.Join(r.Form["newpw1"], "") newpassword := strings.Join(r.Form["newpw1"], "")
realname := strings.Join(r.Form["realname"], "") realname := strings.Join(r.Form["realname"], "")
email := strings.Join(r.Form["email"], "") email := strings.Join(r.Form["email"], "")
permissions := strings.Join(r.Form["permissions"], "")
pwhash, err := bcrypt.GenerateFromPassword([]byte(password), 4) pwhash, err := bcrypt.GenerateFromPassword([]byte(password), 4)
statement, err := db.Prepare("UPDATE users SET username=?, hash=?, realname=?, email=? WHERE id=?") statement, err := db.Prepare("UPDATE users SET username=?, hash=?, realname=?, email=?, permissions=? WHERE id=?")
if err != nil { if err != nil {
return &common.HTTPError{ return &common.HTTPError{
Message: fmt.Sprintf("error preparing sqlite3 statement: %v", err), Message: fmt.Sprintf("error preparing sqlite3 statement: %v", err),
@ -145,7 +149,7 @@ func EditUser() common.Handler {
Message: fmt.Sprintf("error executing sqlite3 statement: %v", err), Message: fmt.Sprintf("error executing sqlite3 statement: %v", err),
StatusCode: http.StatusInternalServerError, StatusCode: http.StatusInternalServerError,
} }
} }
} }
fmt.Println(hash) fmt.Println(hash)
if bcrypt.CompareHashAndPassword([]byte(hash), []byte(password)) != nil { if bcrypt.CompareHashAndPassword([]byte(hash), []byte(password)) != nil {
@ -158,9 +162,9 @@ func EditUser() common.Handler {
if newpassword != "" { if newpassword != "" {
pwhash, err = bcrypt.GenerateFromPassword([]byte(newpassword), 4) pwhash, err = bcrypt.GenerateFromPassword([]byte(newpassword), 4)
} }
_, err = statement.Exec(username,pwhash,realname,email,id) _, err = statement.Exec(username, pwhash, realname, email, id, permissions)
if err != nil { if err != nil {
return &common.HTTPError{ return &common.HTTPError{
Message: fmt.Sprintf("error executing sqlite3 statement: %v", err), Message: fmt.Sprintf("error executing sqlite3 statement: %v", err),
@ -328,15 +332,15 @@ func EditEpisode() common.Handler {
fmt.Println(filename) fmt.Println(filename)
description := strings.Join(r.Form["description"], "") description := strings.Join(r.Form["description"], "")
if ("./podcasts" + PreviousFilename + ".mp3" != filename) { if "./podcasts"+PreviousFilename+".mp3" != filename {
err = os.Rename("./podcasts/" + PreviousFilename + ".mp3", filename) err = os.Rename("./podcasts/"+PreviousFilename+".mp3", filename)
if err != nil { if err != nil {
return &common.HTTPError{ return &common.HTTPError{
Message: err.Error(), Message: err.Error(),
StatusCode: http.StatusBadRequest, StatusCode: http.StatusBadRequest,
} }
} }
err = os.Rename("./podcasts/" + PreviousFilename + "_SHOWNOTES.md", shownotes) err = os.Rename("./podcasts/"+PreviousFilename+"_SHOWNOTES.md", shownotes)
if err != nil { if err != nil {
return &common.HTTPError{ return &common.HTTPError{
Message: err.Error(), Message: err.Error(),

Binary file not shown.

View file

@ -91,6 +91,12 @@ const usernew = {
<label for="password">New Password</label> <label for="password">New Password</label>
<input type="password" id="password" name="password"> <input type="password" id="password" name="password">
<label for="permissions">Permission Level</label>
<select name="permissions">
<option value="0">Publishing only</option>
<option value="1">Publishing and Episode Management</option>
<option value="2">Publishing, Episode and User management</option>
</select>
<br /><br /> <br /><br />
<input type="submit" class="button" value="Save"></form> <input type="submit" class="button" value="Save"></form>
</div> </div>
@ -205,6 +211,7 @@ const episodemanagement = {
} else { } else {
var t = JSON.parse(items).items var t = JSON.parse(items).items
for (var i = t.length - 1; i >= 0; i--) { for (var i = t.length - 1; i >= 0; i--) {
console.log(i)
this.items.push({ this.items.push({
title: t[i].title, title: t[i].title,
url: t[i].url, url: t[i].url,
@ -312,10 +319,14 @@ const customcss = {
get("/admin/css", (err, css) => { get("/admin/css", (err, css) => {
this.loading = false this.loading = false
if (err) { if (css == "{}") {
this.error = err.toString() this.css = "You aren't allowed to edit this CSS!"
} else { } else {
this.css = css if (err) {
this.error = err.toString()
} else {
this.css = css
}
} }
}) })
} }
@ -348,8 +359,13 @@ const app = new Vue({
function get(url,callback) { function get(url,callback) {
var xmlHttp = new XMLHttpRequest(); var xmlHttp = new XMLHttpRequest();
xmlHttp.onreadystatechange = function() { xmlHttp.onreadystatechange = function() {
if (xmlHttp.readyState == 4 && xmlHttp.status == 200) if (xmlHttp.readyState == 4 && xmlHttp.status == 200) {
callback(null, xmlHttp.responseText) if (xmlHttp.responseText == "Unauthorized") {
callback(null, "{}")
} else {
callback(null, xmlHttp.responseText)
}
}
} }
xmlHttp.open("GET", url, true); xmlHttp.open("GET", url, true);
xmlHttp.send(null); xmlHttp.send(null);

View file

@ -6,6 +6,7 @@ import (
"crypto/hmac" "crypto/hmac"
"crypto/rand" "crypto/rand"
"crypto/sha256" "crypto/sha256"
"database/sql"
"encoding/base64" "encoding/base64"
"encoding/hex" "encoding/hex"
"encoding/json" "encoding/json"
@ -13,8 +14,10 @@ import (
"fmt" "fmt"
"log" "log"
"net/http" "net/http"
"strings"
"os" "os"
"strings"
_ "github.com/mattn/go-sqlite3"
"github.com/gmemstr/pogo/common" "github.com/gmemstr/pogo/common"
) )
@ -27,7 +30,39 @@ const (
cookieExpiry = 60 * 60 * 24 * 30 // 30 days in seconds cookieExpiry = 60 * 60 * 24 * 30 // 30 days in seconds
) )
func RequireAuthorization() common.Handler { func UserPermissions(username string, permission int) (bool, error) {
db, err := sql.Open("sqlite3", "assets/config/users.db")
defer db.Close()
isAllowed := false
if err != nil {
return isAllowed, err
}
statement, err := db.Prepare("SELECT permissions FROM users WHERE username=?")
if err != nil {
return isAllowed, err
}
rows, err := statement.Query(username)
if err != nil {
return isAllowed, err
}
var level int
for rows.Next() {
err = rows.Scan(&level)
if err != nil {
return isAllowed, err
}
if level >= permission {
isAllowed = true
}
}
return isAllowed, nil
}
func RequireAuthorization(permission int) common.Handler {
return func(rc *common.RouterContext, w http.ResponseWriter, r *http.Request) *common.HTTPError { return func(rc *common.RouterContext, w http.ResponseWriter, r *http.Request) *common.HTTPError {
usr, err := DecryptCookie(r) usr, err := DecryptCookie(r)
if err != nil { if err != nil {
@ -46,6 +81,17 @@ func RequireAuthorization() common.Handler {
} }
rc.User = usr rc.User = usr
username := rc.User.Username
hasPermission, err := UserPermissions(string(username), permission)
if !hasPermission {
return &common.HTTPError{
Message: "Unauthorized! Redirecting to /admin",
StatusCode: http.StatusUnauthorized,
}
}
return nil return nil
} }
} }

View file

@ -1,21 +1,21 @@
package router package router
import ( import (
"database/sql"
"encoding/json" "encoding/json"
"fmt" "fmt"
"golang.org/x/crypto/bcrypt"
"io/ioutil" "io/ioutil"
"log" "log"
"net/http" "net/http"
"strings" "strings"
"golang.org/x/crypto/bcrypt"
"database/sql"
_ "github.com/mattn/go-sqlite3" _ "github.com/mattn/go-sqlite3"
"github.com/gorilla/mux"
"github.com/gmemstr/pogo/admin" "github.com/gmemstr/pogo/admin"
"github.com/gmemstr/pogo/auth" "github.com/gmemstr/pogo/auth"
"github.com/gmemstr/pogo/common" "github.com/gmemstr/pogo/common"
"github.com/gorilla/mux"
) )
type NewConfig struct { type NewConfig struct {
@ -70,7 +70,7 @@ func Init() *mux.Router {
// Authenticated endpoints should be passed to BasicAuth() // Authenticated endpoints should be passed to BasicAuth()
// first // first
r.Handle("/admin", Handle( r.Handle("/admin", Handle(
auth.RequireAuthorization(), auth.RequireAuthorization(0),
adminHandler(), adminHandler(),
)).Methods("GET", "POST") )).Methods("GET", "POST")
@ -79,45 +79,45 @@ func Init() *mux.Router {
)).Methods("GET", "POST") )).Methods("GET", "POST")
r.Handle("/admin/publish", Handle( r.Handle("/admin/publish", Handle(
auth.RequireAuthorization(), auth.RequireAuthorization(0),
admin.CreateEpisode(), admin.CreateEpisode(),
)).Methods("POST") )).Methods("POST")
r.Handle("/admin/edituser", Handle( r.Handle("/admin/edituser", Handle(
auth.RequireAuthorization(), auth.RequireAuthorization(2),
admin.EditUser(), admin.EditUser(),
)).Methods("POST") )).Methods("POST")
r.Handle("/admin/newuser", Handle( r.Handle("/admin/newuser", Handle(
auth.RequireAuthorization(), auth.RequireAuthorization(2),
admin.AddUser(), admin.AddUser(),
)).Methods("POST") )).Methods("POST")
r.Handle("/admin/deleteuser/{id}", Handle( r.Handle("/admin/deleteuser/{id}", Handle(
auth.RequireAuthorization(), auth.RequireAuthorization(2),
admin.DeleteUser(), admin.DeleteUser(),
)).Methods("GET") )).Methods("GET")
r.Handle("/admin/edit", Handle( r.Handle("/admin/edit", Handle(
auth.RequireAuthorization(), auth.RequireAuthorization(1),
admin.EditEpisode(), admin.EditEpisode(),
)).Methods("POST") )).Methods("POST")
r.Handle("/admin/delete", Handle( r.Handle("/admin/delete", Handle(
auth.RequireAuthorization(), auth.RequireAuthorization(1),
admin.RemoveEpisode(), admin.RemoveEpisode(),
)).Methods("GET") )).Methods("GET")
r.Handle("/admin/css", Handle( r.Handle("/admin/css", Handle(
auth.RequireAuthorization(), auth.RequireAuthorization(1),
admin.CustomCss(), admin.CustomCss(),
)).Methods("GET", "POST") )).Methods("GET", "POST")
r.Handle("/admin/adduser", Handle( r.Handle("/admin/adduser", Handle(
auth.RequireAuthorization(), auth.RequireAuthorization(2),
admin.AddUser(), admin.AddUser(),
)).Methods("POST") )).Methods("POST")
r.Handle("/admin/listusers", Handle( r.Handle("/admin/listusers", Handle(
auth.RequireAuthorization(), auth.RequireAuthorization(1),
admin.ListUsers(), admin.ListUsers(),
)).Methods("GET") )).Methods("GET")
@ -169,13 +169,14 @@ func loginHandler() common.Handler {
StatusCode: http.StatusBadRequest, StatusCode: http.StatusBadRequest,
} }
} }
var id int var id int
var dbun string var dbun string
var dbhsh string var dbhsh string
var dbrn string var dbrn string
var dbem string var dbem string
var dbperm int
for rows.Next() { for rows.Next() {
err := rows.Scan(&id,&dbun,&dbhsh,&dbrn,&dbem) err := rows.Scan(&id, &dbun, &dbhsh, &dbrn, &dbem, &dbperm)
if err != nil { if err != nil {
return &common.HTTPError{ return &common.HTTPError{
Message: fmt.Sprintf("error in decoding sql data", err), Message: fmt.Sprintf("error in decoding sql data", err),
@ -186,7 +187,7 @@ func loginHandler() common.Handler {
} }
// Create a cookie here because the credentials are correct // Create a cookie here because the credentials are correct
if dbun == username && bcrypt.CompareHashAndPassword([]byte(dbhsh), []byte(password)) == nil { if dbun == username && bcrypt.CompareHashAndPassword([]byte(dbhsh), []byte(password)) == nil {
c, err := auth.CreateSession(&common.User{ c, err := auth.CreateSession(&common.User{
Username: username, Username: username,
}) })
if err != nil { if err != nil {
@ -204,7 +205,6 @@ func loginHandler() common.Handler {
return nil return nil
} }
return &common.HTTPError{ return &common.HTTPError{
Message: "Invalid credentials!", Message: "Invalid credentials!",
StatusCode: http.StatusUnauthorized, StatusCode: http.StatusUnauthorized,