Revert "remove is admin field"

This reverts commit 33ed68d3f3.
This commit is contained in:
Lars Hoogestraat 2018-11-21 17:56:54 +01:00
parent a853f227e1
commit cc5bd6f022
20 changed files with 264 additions and 102 deletions

View File

@ -26,7 +26,7 @@ Configuration
Create your first administrator account with create_user:
~~~
./createuser -admin -config {{BLOG_CONFIG}} -username test -email test@example.com -displayname "Hello World" -password secret1234
./createuser -admin -sqlite path_to_sqlite -username test -email test@example.com -displayname "Hello World" -password secret1234
~~~
Make sure -admin is set.

View File

@ -40,6 +40,7 @@ func main() {
password := flag.String("password", "", "Password for the admin user ")
email := flag.String("email", "", "Email for the created user ")
displayName := flag.String("displayname", "", "Display name for the admin user ")
isAdmin := flag.Bool("admin", false, "If set a new administrator will be created; otherwise a non-admin is created")
file := flag.String("sqlite", "", "Location to the sqlite3 database file")
flag.Parse()
@ -50,6 +51,7 @@ func main() {
password: *password,
email: *email,
displayName: *displayName,
admin: *isAdmin,
sqlite: *file,
}
@ -102,6 +104,7 @@ func (userFlags createUserFlag) CreateUser() error {
DisplayName: userFlags.displayName,
Email: userFlags.email,
PlainPassword: []byte(userFlags.password),
IsAdmin: userFlags.admin,
Active: true,
}

View File

@ -87,6 +87,7 @@ func (dbFlags initDatabaseFlags) initSQLite() error {
"display_name VARCHAR(191) NOT NULL, " +
"password CHAR(60) NOT NULL, " +
"salt CHAR(32) NOT NULL, " +
"is_admin boolean NOT NULL DEFAULT false, " +
"active boolean NOT NULL DEFAULT true, " +
"last_modified datetime NOT NULL," +
"CONSTRAINT user_email_key UNIQUE (username), " +

View File

@ -29,19 +29,22 @@ func AdminProfileHandler(ctx *middleware.AppContext, w http.ResponseWriter, r *h
//AdminProfilePostHandler handles the updating of the user profile
func AdminProfilePostHandler(ctx *middleware.AppContext, w http.ResponseWriter, r *http.Request) *middleware.Template {
ctxUser, _ := middleware.User(r)
ctxUser.PlainPassword = []byte(r.FormValue("current_password"))
u := &models.User{
ID: ctxUser.ID,
Username: r.FormValue("username"),
Email: r.FormValue("email"),
DisplayName: r.FormValue("displayname"),
Active: true,
ID: ctxUser.ID,
Username: r.FormValue("username"),
Email: r.FormValue("email"),
DisplayName: r.FormValue("displayname"),
Active: true,
IsAdmin: ctxUser.IsAdmin,
PlainPassword: []byte(r.FormValue("password")),
}
if _, err := ctx.UserService.Authenticate(ctxUser, ctx.ConfigService.LoginMethod, []byte(r.PostFormValue("current_password"))); err != nil {
if _, err := ctx.UserService.Authenticate(ctxUser, ctx.ConfigService.LoginMethod); err != nil {
return &middleware.Template{
Name: tplAdminProfile,
Err: httperror.New(http.StatusUnauthorized, "Your password is invalid.", err),
Err: httperror.New(http.StatusUnauthorized, "Your current password is invalid.", err),
Active: "profile",
Data: map[string]interface{}{
"user": u,
@ -51,12 +54,12 @@ func AdminProfilePostHandler(ctx *middleware.AppContext, w http.ResponseWriter,
changePassword := false
if len(r.FormValue("password")) > 0 {
if len(u.PlainPassword) > 0 {
changePassword = true
// Password change
u.Password = []byte(r.FormValue("password"))
u.PlainPassword = []byte(r.FormValue("password"))
if !bytes.Equal(u.Password, []byte(r.FormValue("retyped_password"))) {
if !bytes.Equal(u.PlainPassword, []byte(r.FormValue("retyped_password"))) {
return &middleware.Template{
Name: tplAdminProfile,
Active: "profile",

View File

@ -47,11 +47,12 @@ func LoginPostHandler(ctx *middleware.AppContext, rw http.ResponseWriter, r *htt
}
u := &models.User{
Username: username,
Email: username,
Username: username,
Email: username,
PlainPassword: password,
}
user, err := ctx.UserService.Authenticate(u, ctx.ConfigService.LoginMethod, password)
user, err := ctx.UserService.Authenticate(u, ctx.ConfigService.LoginMethod)
if err != nil {
return &middleware.Template{

View File

@ -17,8 +17,7 @@ import (
func AdminUsersHandler(ctx *middleware.AppContext, w http.ResponseWriter, r *http.Request) *middleware.Template {
page := getPageParam(r)
total, err := ctx.UserService.Count()
total, err := ctx.UserService.Count(models.All)
if err != nil {
return &middleware.Template{
Name: tplAdminUsers,
@ -44,17 +43,21 @@ func AdminUsersHandler(ctx *middleware.AppContext, w http.ResponseWriter, r *htt
}
}
userInvites, err := ctx.UserInviteService.List()
var userInvites []models.UserInvite
if err != nil {
return &middleware.Template{
Name: tplAdminUsers,
Err: err,
Active: "users",
Data: map[string]interface{}{
"users": users,
"pagination": p,
},
if cu, _ := middleware.User(r); cu.IsAdmin {
userInvites, err = ctx.UserInviteService.List()
if err != nil {
return &middleware.Template{
Name: tplAdminUsers,
Err: err,
Active: "users",
Data: map[string]interface{}{
"users": users,
"pagination": p,
},
}
}
}
@ -85,6 +88,7 @@ func AdminUserNewPostHandler(ctx *middleware.AppContext, w http.ResponseWriter,
Email: r.FormValue("email"),
PlainPassword: []byte(r.FormValue("password")),
Active: convertCheckbox(r, "active"),
IsAdmin: convertCheckbox(r, "admin"),
}
userID, err := ctx.UserService.Create(u)
@ -157,6 +161,7 @@ func AdminUserEditPostHandler(ctx *middleware.AppContext, w http.ResponseWriter,
Username: r.FormValue("username"),
PlainPassword: []byte(r.FormValue("password")),
Active: convertCheckbox(r, "active"),
IsAdmin: convertCheckbox(r, "admin"),
}
changePassword := false
@ -197,23 +202,15 @@ func AdminUserDeleteHandler(ctx *middleware.AppContext, w http.ResponseWriter, r
}
}
oneUser, err := ctx.UserService.OneUser()
oneAdmin, err := ctx.UserService.OneAdmin()
if err != nil {
return &middleware.Template{
RedirectPath: "admin/users",
Active: "users",
Err: err,
}
}
if oneUser {
if oneAdmin && user.IsAdmin {
return &middleware.Template{
RedirectPath: "admin/users",
Active: "users",
Err: httperror.New(http.StatusUnprocessableEntity,
"Could not remove user. No user would remain.",
fmt.Errorf("could not remove user %s no user would remain", user.Username)),
"Could not remove administrator. No Administrator would remain.",
fmt.Errorf("could not remove administrator %s no administrator would remain", user.Username)),
}
}

View File

@ -163,7 +163,9 @@ func (ctx AppContext) AuthHandler(handler http.Handler) http.Handler {
//RequireAdmin ensures that the user is an admin; if not next handler in chain is not called
func (ctx AppContext) RequireAdmin(handler http.Handler) http.Handler {
fn := func(rw http.ResponseWriter, r *http.Request) {
if _, err := User(r); err != nil {
u, err := User(r)
if err != nil {
logger.Log.Error(err)
ctx.Templates.ExecuteTemplate(rw, "admin/error", map[string]interface{}{
"ErrorMsg": "An internal server error occured",
@ -171,6 +173,14 @@ func (ctx AppContext) RequireAdmin(handler http.Handler) http.Handler {
return
}
if u.IsAdmin == false {
ctx.Templates.ExecuteTemplate(rw, "admin/error", map[string]interface{}{
"ErrorMsg": "You have not the permissions to execute this action",
"currentUser": u,
})
return
}
handler.ServeHTTP(rw, r)
}
return http.HandlerFunc(fn)

View File

@ -128,11 +128,17 @@ func (as ArticleService) Update(a *Article, u *User) error {
return err
}
_, err := as.Datasource.Get(a.ID, a.Author, All)
art, err := as.Datasource.Get(a.ID, a.Author, All)
if err != nil {
return err
}
if !u.IsAdmin {
if art.Author.ID != u.ID {
return httperror.PermissionDenied("update", "article", fmt.Errorf("could not update article %d user %d has no permission", a.ID, u.ID))
}
}
return as.Datasource.Update(a)
}
@ -144,6 +150,12 @@ func (as ArticleService) Publish(id int, u *User) error {
return err
}
if !u.IsAdmin {
if a.Author.ID != u.ID {
return httperror.PermissionDenied("publish", "article", fmt.Errorf("could not publish article %d user %d has no permission", a.ID, u.ID))
}
}
return as.Datasource.Publish(a)
}
@ -155,6 +167,12 @@ func (as ArticleService) Delete(id int, u *User) error {
return err
}
if !u.IsAdmin {
if a.Author.ID != u.ID {
return httperror.PermissionDenied("delete", "article", fmt.Errorf("could not delete article %d user %d has no permission", a.ID, u.ID))
}
}
return as.Datasource.Delete(a.ID)
}
@ -170,6 +188,14 @@ func (as ArticleService) GetBySlug(s string, u *User, pc PublishedCriteria) (*Ar
return nil, err
}
if u != nil {
if !u.IsAdmin {
if a.Author.ID != u.ID {
return nil, httperror.PermissionDenied("view", "article", fmt.Errorf("could not get article %s user %d has no permission", a.Slug, u.ID))
}
}
}
return a, nil
}
@ -185,6 +211,14 @@ func (as ArticleService) GetByID(id int, u *User, pc PublishedCriteria) (*Articl
return nil, err
}
if u != nil {
if !u.IsAdmin {
if a.Author.ID != u.ID {
return nil, httperror.PermissionDenied("get", "article", fmt.Errorf("could not get article %d user %d has no permission", a.ID, u.ID))
}
}
}
return a, nil
}

View File

@ -56,7 +56,7 @@ func (rdb SQLiteArticleDatasource) List(u *User, c *Category, p *Pagination, pc
var ru User
if err := rows.Scan(&a.ID, &a.Headline, &a.Teaser, &a.Content, &a.Published, &a.PublishedOn, &a.Slug, &a.LastModified, &ru.ID, &ru.DisplayName,
&ru.Email, &ru.Username, &a.CID, &a.CName); err != nil {
&ru.Email, &ru.Username, &ru.IsAdmin, &a.CID, &a.CName); err != nil {
return nil, err
}
@ -95,6 +95,13 @@ func (rdb SQLiteArticleDatasource) Count(u *User, c *Category, pc PublishedCrite
args = append(args, c.Name)
}
if u != nil {
if !u.IsAdmin {
stmt.WriteString("a.user_id=? AND ")
args = append(args, u.ID)
}
}
if pc == NotPublished {
stmt.WriteString("a.published = '0' ")
} else if pc == All {
@ -117,7 +124,7 @@ func (rdb SQLiteArticleDatasource) Get(articleID int, u *User, pc PublishedCrite
var ru User
if err := selectArticleStmt(rdb.SQLConn, articleID, "", u, pc).Scan(&a.ID, &a.Headline, &a.PublishedOn, &a.Published, &a.Slug, &a.Teaser, &a.Content,
&a.LastModified, &ru.ID, &ru.DisplayName, &ru.Email, &ru.Username); err != nil {
&a.LastModified, &ru.ID, &ru.DisplayName, &ru.Email, &ru.Username, &ru.IsAdmin); err != nil {
return nil, err
}
@ -133,7 +140,7 @@ func (rdb SQLiteArticleDatasource) GetBySlug(slug string, u *User, pc PublishedC
var ru User
if err := selectArticleStmt(rdb.SQLConn, -1, slug, u, pc).Scan(&a.ID, &a.Headline, &a.PublishedOn, &a.Published, &a.Slug, &a.Teaser, &a.Content,
&a.LastModified, &ru.ID, &ru.DisplayName, &ru.Email, &ru.Username); err != nil {
&a.LastModified, &ru.ID, &ru.DisplayName, &ru.Email, &ru.Username, &ru.IsAdmin); err != nil {
return nil, err
}
@ -182,7 +189,7 @@ func selectArticleStmt(db *sql.DB, articleID int, slug string, u *User, pc Publi
var args []interface{}
stmt.WriteString("SELECT a.id, a.headline, a.published_on, a.published, a.slug, a.teaser, a.content, a.last_modified, ")
stmt.WriteString("u.id, u.display_name, u.email, u.username ")
stmt.WriteString("u.id, u.display_name, u.email, u.username, u.is_admin ")
stmt.WriteString("FROM article a ")
stmt.WriteString("INNER JOIN user u ON (a.user_id = u.id) ")
stmt.WriteString("WHERE ")
@ -202,7 +209,12 @@ func selectArticleStmt(db *sql.DB, articleID int, slug string, u *User, pc Publi
stmt.WriteString("AND a.id=? ")
args = append(args, articleID)
}
if u != nil {
if !u.IsAdmin {
stmt.WriteString("AND a.user_id=? ")
args = append(args, u.ID)
}
}
stmt.WriteString("LIMIT 1")
return db.QueryRow(stmt.String(), args...)
}
@ -213,7 +225,7 @@ func selectArticlesStmt(db *sql.DB, u *User, c *Category, p *Pagination, pc Publ
var args []interface{}
stmt.WriteString("SELECT a.id, a.headline, a.teaser, a.content, a.published, a.published_on, a.slug, a.last_modified, ")
stmt.WriteString("u.id, u.display_name, u.email, u.username, ")
stmt.WriteString("u.id, u.display_name, u.email, u.username, u.is_admin, ")
stmt.WriteString("c.id, c.name ")
stmt.WriteString("FROM article a ")
stmt.WriteString("INNER JOIN user u ON (a.user_id = u.id) ")
@ -231,6 +243,13 @@ func selectArticlesStmt(db *sql.DB, u *User, c *Category, p *Pagination, pc Publ
args = append(args, c.Name)
}
if u != nil {
if !u.IsAdmin {
stmt.WriteString("a.user_id=? AND ")
args = append(args, u.ID)
}
}
if pc == NotPublished {
stmt.WriteString("a.published='0' ")
} else if pc == All {

View File

@ -38,7 +38,7 @@ func (rdb SQLiteCategoryDatasource) List(fc FilterCriteria) ([]Category, error)
var args []interface{}
stmt.WriteString("SELECT DISTINCT c.id, c.name, c.slug, c.last_modified, ")
stmt.WriteString("u.id, u.display_name, u.username, u.email ")
stmt.WriteString("u.id, u.display_name, u.username, u.email, u.is_admin ")
stmt.WriteString("FROM category as c ")
stmt.WriteString("INNER JOIN user as u ")
stmt.WriteString("ON c.user_id = u.id ")
@ -53,7 +53,7 @@ func (rdb SQLiteCategoryDatasource) List(fc FilterCriteria) ([]Category, error)
stmt.WriteString("WHERE a.categorie_id IS NULL ")
}
stmt.WriteString("ORDER BY c.name DESC ")
stmt.WriteString("ORDER BY c.last_modified DESC ")
rows, err := rdb.SQLConn.Query(stmt.String(), args...)
@ -69,7 +69,7 @@ func (rdb SQLiteCategoryDatasource) List(fc FilterCriteria) ([]Category, error)
var c Category
var ru User
if err := rows.Scan(&c.ID, &c.Name, &c.Slug, &c.LastModified, &ru.ID, &ru.DisplayName, &ru.Username, &ru.Email); err != nil {
if err := rows.Scan(&c.ID, &c.Name, &c.Slug, &c.LastModified, &ru.ID, &ru.DisplayName, &ru.Username, &ru.Email, &ru.IsAdmin); err != nil {
return nil, err
}
@ -99,7 +99,7 @@ func (rdb SQLiteCategoryDatasource) Get(categoryID int) (*Category, error) {
var stmt bytes.Buffer
stmt.WriteString("SELECT c.id, c.name, c.slug, c.last_modified, ")
stmt.WriteString("u.id, u.display_name, u.username, u.email ")
stmt.WriteString("u.id, u.display_name, u.username, u.email, u.is_admin ")
stmt.WriteString("FROM category as c ")
stmt.WriteString("INNER JOIN user as u ")
stmt.WriteString("ON u.id = c.user_id ")
@ -109,7 +109,7 @@ func (rdb SQLiteCategoryDatasource) Get(categoryID int) (*Category, error) {
var ru User
if err := rdb.SQLConn.QueryRow(stmt.String(), categoryID).Scan(&c.ID, &c.Name, &c.Slug, &c.LastModified, &ru.ID,
&ru.DisplayName, &ru.Username, &ru.Email); err != nil {
&ru.DisplayName, &ru.Username, &ru.Email, &ru.IsAdmin); err != nil {
return nil, err
}
@ -122,7 +122,7 @@ func (rdb SQLiteCategoryDatasource) GetBySlug(slug string) (*Category, error) {
var stmt bytes.Buffer
stmt.WriteString("SELECT c.id, c.name, c.slug, c.last_modified, ")
stmt.WriteString("u.id, u.display_name, u.username, u.email ")
stmt.WriteString("u.id, u.display_name, u.username, u.email, u.is_admin ")
stmt.WriteString("FROM category as c ")
stmt.WriteString("INNER JOIN user as u ")
stmt.WriteString("ON u.id = c.user_id ")
@ -132,7 +132,7 @@ func (rdb SQLiteCategoryDatasource) GetBySlug(slug string) (*Category, error) {
var ru User
if err := rdb.SQLConn.QueryRow(stmt.String(), slug).Scan(&c.ID, &c.Name, &c.Slug, &c.LastModified, &ru.ID,
&ru.DisplayName, &ru.Username, &ru.Email); err != nil {
&ru.DisplayName, &ru.Username, &ru.Email, &ru.IsAdmin); err != nil {
return nil, err
}

View File

@ -1,6 +1,7 @@
package models
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
@ -90,6 +91,12 @@ func (fs FileService) Delete(fileID int, location string, u *User) error {
return err
}
if !u.IsAdmin {
if file.Author.ID != u.ID {
return httperror.PermissionDenied("delete", "file", fmt.Errorf("could not remove file %d user %d has no permission", fileID, u.ID))
}
}
err = fs.Datasource.Delete(fileID)
if err != nil {

View File

@ -19,7 +19,7 @@ func (rdb SQLiteFileDatasource) GetByFilename(filename string, u *User) (*File,
var args []interface{}
stmt.WriteString("SELECT f.id, f.filename, f.content_type, f.size, f.last_modified, f.user_id, ")
stmt.WriteString("u.display_name, u.username, u.email ")
stmt.WriteString("u.display_name, u.username, u.email, u.is_admin ")
stmt.WriteString("FROM file as f ")
stmt.WriteString("INNER JOIN user as u ")
stmt.WriteString("ON u.id = f.user_id ")
@ -27,11 +27,18 @@ func (rdb SQLiteFileDatasource) GetByFilename(filename string, u *User) (*File,
args = append(args, filename)
if u != nil {
if !u.IsAdmin {
stmt.WriteString("AND f.user_id=? ")
args = append(args, u.ID)
}
}
var f File
var ru User
if err := rdb.SQLConn.QueryRow(stmt.String(), args...).Scan(&f.ID, &f.FullFilename, &f.ContentType, &f.Size, &f.LastModified, &ru.ID,
&ru.DisplayName, &ru.Username, &ru.Email); err != nil {
&ru.DisplayName, &ru.Username, &ru.Email, &ru.IsAdmin); err != nil {
return nil, err
}
@ -48,7 +55,7 @@ func (rdb SQLiteFileDatasource) Get(fileID int, u *User) (*File, error) {
var args []interface{}
stmt.WriteString("SELECT f.id, f.filename, f.content_type, f.size, f.last_modified, f.user_id, ")
stmt.WriteString("u.display_name, u.username, u.email ")
stmt.WriteString("u.display_name, u.username, u.email, u.is_admin ")
stmt.WriteString("FROM file as f ")
stmt.WriteString("INNER JOIN user as u ")
stmt.WriteString("ON u.id = f.user_id ")
@ -56,11 +63,18 @@ func (rdb SQLiteFileDatasource) Get(fileID int, u *User) (*File, error) {
args = append(args, fileID)
if u != nil {
if !u.IsAdmin {
stmt.WriteString("AND f.user_id=? ")
args = append(args, u.ID)
}
}
var f File
var ru User
if err := rdb.SQLConn.QueryRow(stmt.String(), args...).Scan(&f.ID, &f.FullFilename, &f.ContentType, &f.Size, &f.LastModified, &ru.ID,
&ru.DisplayName, &ru.Username, &ru.Email); err != nil {
&ru.DisplayName, &ru.Username, &ru.Email, &ru.IsAdmin); err != nil {
return nil, err
}
@ -95,10 +109,18 @@ func (rdb SQLiteFileDatasource) List(u *User, p *Pagination) ([]File, error) {
var args []interface{}
stmt.WriteString("SELECT f.id, f.filename, f.content_type, f.size, f.last_modified, ")
stmt.WriteString("u.id, u.display_name, u.username, u.email ")
stmt.WriteString("u.id, u.display_name, u.username, u.email, u.is_admin ")
stmt.WriteString("FROM file as f ")
stmt.WriteString("INNER JOIN user as u ")
stmt.WriteString("ON f.user_id = u.id ")
if u != nil {
if !u.IsAdmin {
stmt.WriteString("WHERE f.user_id=? ")
args = append(args, u.ID)
}
}
stmt.WriteString("ORDER BY f.last_modified DESC ")
if p != nil {
@ -121,7 +143,7 @@ func (rdb SQLiteFileDatasource) List(u *User, p *Pagination) ([]File, error) {
for rows.Next() {
if err = rows.Scan(&f.ID, &f.FullFilename, &f.ContentType, &f.Size, &f.LastModified, &us.ID, &us.DisplayName,
&us.Username, &us.Email); err != nil {
&us.Username, &us.Email, &u.IsAdmin); err != nil {
return nil, err
}
@ -147,6 +169,13 @@ func (rdb SQLiteFileDatasource) Count(u *User) (int, error) {
stmt.WriteString("SELECT count(id) FROM file ")
if u != nil {
if !u.IsAdmin {
stmt.WriteString("WHERE user_id = ?")
args = append(args, u.ID)
}
}
var total int
if err := rdb.SQLConn.QueryRow(stmt.String(), args...).Scan(&total); err != nil {

View File

@ -6,6 +6,7 @@ package models
import (
"database/sql"
"errors"
"fmt"
"net/http"
"strings"
@ -24,7 +25,7 @@ type UserDatasourceService interface {
List(p *Pagination) ([]User, error)
Get(userID int) (*User, error)
Update(u *User, changePassword bool) error
Count() (int, error)
Count(ac AdminCriteria) (int, error)
GetByMail(mail string) (*User, error)
GetByUsername(username string) (*User, error)
Remove(userID int) error
@ -41,6 +42,7 @@ type User struct {
Salt []byte
LastModified time.Time
Active bool
IsAdmin bool
}
const (
@ -161,8 +163,8 @@ func (us UserService) duplicateUsername(username string) error {
}
//Count returns the amount of users
func (us UserService) Count() (int, error) {
return us.Datasource.Count()
func (us UserService) Count(a AdminCriteria) (int, error) {
return us.Datasource.Count(a)
}
//List returns a list of users. Limits the amount based on the defined pagination
@ -261,7 +263,14 @@ func (us UserService) Update(u *User, changePassword bool) error {
return err
}
if !oldUser.IsAdmin {
if oldUser.ID != u.ID {
return httperror.PermissionDenied("update", "user", fmt.Errorf("permission denied user %d is not granted to update user %d", oldUser.ID, u.ID))
}
}
if us.UserInterceptor != nil {
if err := us.UserInterceptor.PreUpdate(oldUser, u); err != nil {
return httperror.InternalServerError(fmt.Errorf("error while executing user interceptor 'PreUpdate' error %v", err))
}
@ -285,17 +294,18 @@ func (us UserService) Update(u *User, changePassword bool) error {
return err
}
oneAdmin, err := us.OneUser()
oneAdmin, err := us.OneAdmin()
if err != nil {
return err
}
if oneAdmin && !u.Active {
return httperror.New(http.StatusUnprocessableEntity,
"Could not update user, because no user would remain",
fmt.Errorf("could not update user %s action, because no administrator would remain", oldUser.Username))
if oneAdmin {
if (oldUser.IsAdmin && !u.IsAdmin) || (oldUser.IsAdmin && !u.Active) {
return httperror.New(http.StatusUnprocessableEntity,
"Could not update user, because no administrator would remain",
fmt.Errorf("could not update user %s action, because no administrator would remain", oldUser.Username))
}
}
if changePassword {
@ -327,25 +337,33 @@ func (us UserService) Update(u *User, changePassword bool) error {
// Authenticate authenticates the user by the given login method (email or username)
// if the user was found but the password is wrong the found user and an error will be returned
func (us UserService) Authenticate(u *User, loginMethod settings.LoginMethod, password []byte) (*User, error) {
func (us UserService) Authenticate(u *User, loginMethod settings.LoginMethod) (*User, error) {
var err error
if len(u.Username) == 0 || len(u.PlainPassword) == 0 {
return nil, httperror.New(http.StatusUnauthorized, "Your username or password is invalid.", errors.New("no username or password were given"))
}
var password = u.PlainPassword
if loginMethod == settings.EMail {
u, err = us.Datasource.GetByMail(u.Email)
} else {
u, err = us.Datasource.GetByUsername(u.Username)
}
u.PlainPassword = password
if err != nil {
if err == sql.ErrNoRows {
//Do some extra work
bcrypt.CompareHashAndPassword([]byte("$2a$12$bQlRnXTNZMp6kCyoAlnf3uZW5vtmSj9CHP7pYplRUVK2n0C5xBHBa"), password)
bcrypt.CompareHashAndPassword([]byte("$2a$12$bQlRnXTNZMp6kCyoAlnf3uZW5vtmSj9CHP7pYplRUVK2n0C5xBHBa"), u.PlainPassword)
return nil, httperror.New(http.StatusUnauthorized, "Your username or password is invalid.", err)
}
return nil, err
}
if err := u.comparePassword(password); err != nil {
if err := u.comparePassword(); err != nil {
return u, httperror.New(http.StatusUnauthorized, "Your username or password is invalid.", err)
}
@ -355,6 +373,10 @@ func (us UserService) Authenticate(u *User, loginMethod settings.LoginMethod, pa
fmt.Errorf("the user with id %d tried to logged in but the account is deactivated", u.ID))
}
u.PlainPassword = nil
u.Password = nil
u.Salt = nil
return u, nil
}
@ -366,16 +388,18 @@ func (us UserService) Remove(u *User) error {
}
}
oneAdmin, err := us.OneUser()
oneAdmin, err := us.OneAdmin()
if err != nil {
return err
}
if oneAdmin {
return httperror.New(http.StatusUnprocessableEntity,
"Could not remove administrator. No Administrator would remain.",
fmt.Errorf("could not remove administrator %s no administrator would remain", u.Username))
if u.IsAdmin {
return httperror.New(http.StatusUnprocessableEntity,
"Could not remove administrator. No Administrator would remain.",
fmt.Errorf("could not remove administrator %s no administrator would remain", u.Username))
}
}
err = us.Datasource.Remove(u.ID)
@ -390,8 +414,8 @@ func (us UserService) Remove(u *User) error {
}
//OneAdmin returns true if there is only one admin
func (us UserService) OneUser() (bool, error) {
c, err := us.Datasource.Count()
func (us UserService) OneAdmin() (bool, error) {
c, err := us.Datasource.Count(OnlyAdmins)
if err != nil {
return true, err
@ -404,6 +428,6 @@ func (us UserService) OneUser() (bool, error) {
return false, nil
}
func (u User) comparePassword(password []byte) error {
return bcrypt.CompareHashAndPassword(u.Password, utils.AppendBytes(password, u.Salt))
func (u User) comparePassword() error {
return bcrypt.CompareHashAndPassword(u.Password, utils.AppendBytes(u.PlainPassword, u.Salt))
}

View File

@ -25,6 +25,7 @@ func (ui UserInvite) Copy() *User {
Username: ui.Username,
Email: ui.Email,
DisplayName: ui.DisplayName,
IsAdmin: ui.IsAdmin,
}
}

View File

@ -16,7 +16,7 @@ func (rdb SQLiteUserDatasource) List(p *Pagination) ([]User, error) {
var stmt bytes.Buffer
var args []interface{}
stmt.WriteString("SELECT id, username, email, display_name, last_modified, active FROM user ORDER BY username ASC ")
stmt.WriteString("SELECT id, username, email, display_name, last_modified, active, is_admin FROM user ORDER BY username ASC ")
if p != nil {
stmt.WriteString("LIMIT ? OFFSET ? ")
@ -36,7 +36,7 @@ func (rdb SQLiteUserDatasource) List(p *Pagination) ([]User, error) {
var u User
for rows.Next() {
if err = rows.Scan(&u.ID, &u.Username, &u.Email, &u.DisplayName, &u.LastModified, &u.Active); err != nil {
if err = rows.Scan(&u.ID, &u.Username, &u.Email, &u.DisplayName, &u.LastModified, &u.Active, &u.IsAdmin); err != nil {
return nil, err
}
@ -54,10 +54,10 @@ func (rdb SQLiteUserDatasource) List(p *Pagination) ([]User, error) {
func (rdb SQLiteUserDatasource) Get(userID int) (*User, error) {
var u User
if err := rdb.SQLConn.QueryRow("SELECT u.id, u.username, u.email, u.display_name, u.last_modified, u.active, u.salt "+
if err := rdb.SQLConn.QueryRow("SELECT u.id, u.username, u.email, u.display_name, u.last_modified, u.active, u.is_admin, u.salt "+
"FROM user as u "+
"WHERE u.id=? ", userID).
Scan(&u.ID, &u.Username, &u.Email, &u.DisplayName, &u.LastModified, &u.Active, &u.Salt); err != nil {
Scan(&u.ID, &u.Username, &u.Email, &u.DisplayName, &u.LastModified, &u.Active, &u.IsAdmin, &u.Salt); err != nil {
return nil, err
}
@ -68,8 +68,8 @@ func (rdb SQLiteUserDatasource) Get(userID int) (*User, error) {
func (rdb SQLiteUserDatasource) GetByMail(mail string) (*User, error) {
var u User
if err := rdb.SQLConn.QueryRow("SELECT id, active, display_name, username, email, salt, password FROM user WHERE email=? ", mail).
Scan(&u.ID, &u.Active, &u.DisplayName, &u.Username, &u.Email, &u.Salt, &u.Password); err != nil {
if err := rdb.SQLConn.QueryRow("SELECT id, is_admin, active, display_name, username, email, salt, password FROM user WHERE email=? ", mail).
Scan(&u.ID, &u.IsAdmin, &u.Active, &u.DisplayName, &u.Username, &u.Email, &u.Salt, &u.Password); err != nil {
return nil, err
}
return &u, nil
@ -79,8 +79,8 @@ func (rdb SQLiteUserDatasource) GetByMail(mail string) (*User, error) {
func (rdb SQLiteUserDatasource) GetByUsername(username string) (*User, error) {
var u User
if err := rdb.SQLConn.QueryRow("SELECT id, active, display_name, username, email, salt, password FROM user WHERE username=? ", username).
Scan(&u.ID, &u.Active, &u.DisplayName, &u.Username, &u.Email, &u.Salt, &u.Password); err != nil {
if err := rdb.SQLConn.QueryRow("SELECT id, is_admin, active, display_name, username, email, salt, password FROM user WHERE username=? ", username).
Scan(&u.ID, &u.IsAdmin, &u.Active, &u.DisplayName, &u.Username, &u.Email, &u.Salt, &u.Password); err != nil {
return nil, err
}
return &u, nil
@ -88,8 +88,8 @@ func (rdb SQLiteUserDatasource) GetByUsername(username string) (*User, error) {
//Create creates an new user
func (rdb SQLiteUserDatasource) Create(u *User) (int, error) {
res, err := rdb.SQLConn.Exec("INSERT INTO user (salt, password, username, email, display_name, last_modified, active) VALUES(?, ?, ?, ?, ?, ?, ?);",
u.Salt, u.Password, u.Username, u.Email, u.DisplayName, time.Now(), u.Active)
res, err := rdb.SQLConn.Exec("INSERT INTO user (salt, password, username, email, display_name, last_modified, active, is_admin) VALUES(?, ?, ?, ?, ?, ?, ?, ?);",
u.Salt, u.Password, u.Username, u.Email, u.DisplayName, time.Now(), u.Active, u.IsAdmin)
if err != nil {
return -1, err
@ -109,8 +109,8 @@ func (rdb SQLiteUserDatasource) Update(u *User, changePassword bool) error {
var args []interface{}
stmt.WriteString("UPDATE user SET display_name=?, username=?, email=?, last_modified=?, active=? ")
args = append(args, u.DisplayName, u.Username, u.Email, time.Now(), u.Active)
stmt.WriteString("UPDATE user SET display_name=?, username=?, email=?, last_modified=?, active=?, is_admin=? ")
args = append(args, u.DisplayName, u.Username, u.Email, time.Now(), u.Active, u.IsAdmin)
if changePassword {
stmt.WriteString(", salt=?, password=? ")
@ -129,10 +129,20 @@ func (rdb SQLiteUserDatasource) Update(u *User, changePassword bool) error {
}
//Count retuns the amount of users matches the AdminCriteria
func (rdb SQLiteUserDatasource) Count() (int, error) {
func (rdb SQLiteUserDatasource) Count(ac AdminCriteria) (int, error) {
var stmt bytes.Buffer
stmt.WriteString("SELECT count(id) FROM user ")
if ac == OnlyAdmins {
stmt.WriteString("WHERE is_admin = '1'")
} else if ac == NoAdmins {
stmt.WriteString("WHERE is_admin = '0'")
}
var total int
if err := rdb.SQLConn.QueryRow("SELECT count(id) FROM user ").Scan(&total); err != nil {
if err := rdb.SQLConn.QueryRow(stmt.String()).Scan(&total); err != nil {
return 0, err
}

View File

@ -10,6 +10,15 @@
{{with .article}}
<form action="/admin/article/edit/{{.ID}}" method="post">
<label for="category">Category</label>
<select name="categoryID">
<option></option>
{{range .categories}}
<option value="{{.ID}}">{{.Name}}</option>
{{end}}
</select>
<label for="headline">Headline</label>
<input type="text" value="{{.Headline}}" id="headline" name="headline" placeholder="Headline..." required>

View File

@ -4,11 +4,12 @@
<li>
<a{{if .active}}{{if eq .active "articles"}} class="active" {{end}}{{end}} href="/admin/articles">Articles</a>
</li>
<li>
<a{{if .active}}{{if eq .active "categories"}} class="active" {{end}}{{end}} href="/admin/categories">Categories</a>
</li>
{{if .currentUser.IsAdmin}}
<li>
<a{{if .active}}{{if eq .active "users"}} class="active" {{end}}{{end}} href="/admin/users">Users</a>
</li>
@ -16,6 +17,7 @@
<li>
<a{{if .active}}{{if eq .active "sites"}} class="active" {{end}}{{end}} href="/admin/sites">Sites</a>
</li>
{{end}}
<li>
<a{{if .active}}{{if eq .active "files"}} class="active" {{end}}{{end}} href="/admin/files">Files</a>

View File

@ -20,6 +20,10 @@
<label for="password">Password</label>
<input type="password" id="password" name="password" placeholder="Password..." required>
<div class="checkbox">
<label><input type="checkbox" id="admin" name="admin" value="on"{{if .IsAdmin}} checked{{end}}>Admin?</label>
</div>
<div class="checkbox">
<label><input type="checkbox" id="active" name="active" value="on"{{if .Active}} checked{{end}}>Is activated?</label>
</div>

View File

@ -11,15 +11,19 @@
<form action="/admin/user/edit/{{.ID}}" method="post">
<label for="username">Username</label>
<input type="username" name="username" value="{{.Username}}" id="username" placeholder="Username..." required>
<label for="email">Email</label>
<input type="email" name="email" value="{{.Email}}" id="email" placeholder="Email..." required>
<label for="displayname">Display name</label>
<input type="text" value="{{.DisplayName}}" id="displayname" name="displayname" placeholder="Display name..." required>
<label for="password">New password</label>
<input type="password" id="password" name="password" placeholder="Password...">
<div class="checkbox">
<label><input type="checkbox" id="admin" name="admin" value="on"{{if .IsAdmin}} checked{{end}}>Admin?</label>
</div>
<div class="checkbox">
<label><input type="checkbox" id="active" name="active" value="on"{{if .Active}} checked{{end}}>Is activated?</label>

View File

@ -4,14 +4,14 @@
{{template "admin/navigation" .}}
<main>
{{template "skel/flash" .}}
<h2>User management</h2>
<p><a href="/admin/user/new">Add an user</a> | <a href="/admin/user-invite/new">Invite an user</a></p>
{{with .user_invites}}
<h3>Open Invites</h3>
<table>
<thead>
<tr>
@ -19,6 +19,7 @@
<th>Username</th>
<th>E-mail</th>
<th>Display name</th>
<th>Admin</th>
<th>Invited by</th>
<th>Actions</th>
</tr>
@ -30,12 +31,13 @@
<td>{{.Username}}</td>
<td>{{.Email}}</td>
<td>{{.DisplayName}}</td>
<td>{{.IsAdmin | BoolToIcon}}</td>
<td>{{.CreatedBy.DisplayName}}</td>
<td class="action-data">
<form method="post" action="/admin/user-invite/resend/{{.ID}}">
<button type="submit" name="direction" value="resendinvite">
Resend invite link
</button>
</button>
{{$.csrfField}}
</form>
<a href="/admin/user-invite/delete/{{.ID}}" title="Remove">Remove</a>
@ -46,9 +48,9 @@
</table>
<br>
{{end}}
<h3>Users</h3>
<table>
<thead>
<tr>
@ -57,6 +59,7 @@
<th>E-mail</th>
<th>Display name</th>
<th>Active</th>
<th>Admin</th>
<th>Actions</th>
</tr>
</thead>
@ -68,6 +71,7 @@
<td>{{.Email}}</td>
<td>{{.DisplayName}}</td>
<td>{{.Active | BoolToIcon}}</td>
<td>{{.IsAdmin | BoolToIcon}}</td>
<td class="action-data">
<a href="/admin/user/edit/{{.ID}}" title="Edit">Edit</a>
<a href="/admin/user/delete/{{.ID}}" title="Remove">Remove</a>