added user independent sections and started db migration work
This commit is contained in:
parent
6a2dc8146c
commit
16ea95bf09
24 changed files with 622 additions and 122 deletions
11
pkg/db/db.go
11
pkg/db/db.go
|
|
@ -57,9 +57,18 @@ func New(cfg core.Config) (db *DB, err error) {
|
|||
if err != nil {
|
||||
return
|
||||
}
|
||||
if db.SectionExists(core.WikiSection) != nil {
|
||||
err = db.CreateSection(core.WikiSection, []string{defUser}, false)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
_, err = db.CreatePage(core.IndexPage, core.WikiSection, welcome, defUser, core.Public)
|
||||
return
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return db.RunMigrations()
|
||||
}
|
||||
|
||||
func (db *DB) Dump(w io.Writer) error {
|
||||
|
|
|
|||
|
|
@ -4,10 +4,11 @@ package db
|
|||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"git.giftfish.de/ston1th/gowiki/pkg/core"
|
||||
"time"
|
||||
)
|
||||
|
||||
var reservedUsers = []string{
|
||||
var reservedNames = []string{
|
||||
"blacklist",
|
||||
"login",
|
||||
"logout",
|
||||
|
|
@ -27,13 +28,18 @@ func hasTrimPrefix(s, prefix string) (string, bool) {
|
|||
return s, false
|
||||
}
|
||||
|
||||
func reservedUser(username string) bool {
|
||||
for _, v := range reservedUsers {
|
||||
if username == v {
|
||||
return true
|
||||
}
|
||||
func addOrRemove(s string, a []string) []string {
|
||||
i, _ := core.Contains(s, a)
|
||||
switch i {
|
||||
case -1:
|
||||
return append(a, s)
|
||||
case 0:
|
||||
return a[1:]
|
||||
case len(a):
|
||||
return a[:i]
|
||||
default:
|
||||
return append(a[:i], a[i+1:]...)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func validatePassword(pw string) (b []byte) {
|
||||
|
|
|
|||
72
pkg/db/migration.go
Normal file
72
pkg/db/migration.go
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
package db
|
||||
|
||||
import (
|
||||
"git.giftfish.de/ston1th/gowiki/pkg/core"
|
||||
"git.giftfish.de/ston1th/gowiki/pkg/log"
|
||||
)
|
||||
|
||||
const (
|
||||
versionKey = "version/version"
|
||||
currentVersion = 2
|
||||
)
|
||||
|
||||
type migrator func(*DB) (int, error)
|
||||
|
||||
var migrators = []migrator{
|
||||
func(db *DB) (int, error) {
|
||||
return 0, nil
|
||||
},
|
||||
func(db *DB) (version int, err error) {
|
||||
version = 1
|
||||
return
|
||||
},
|
||||
func(db *DB) (version int, err error) {
|
||||
version = 2
|
||||
// recreate all pages with new format
|
||||
var pages []string
|
||||
db.store.ForEach(func(k string, _ []byte) error {
|
||||
if trim, ok := hasTrimPrefix(k, pagePrefix); ok {
|
||||
pages = append(pages, trim)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
var p core.Page
|
||||
for _, v := range pages {
|
||||
k := pagePrefix + v
|
||||
err := db.store.Get(k, &p)
|
||||
if err != nil {
|
||||
log.Printf("db: migrator[%d]: pageGet %s: %s", version, k, err)
|
||||
continue
|
||||
}
|
||||
err = db.store.Set(k, &p)
|
||||
if err != nil {
|
||||
log.Printf("db: migrator[%d]: pageSet %s: %s", version, k, err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
return
|
||||
},
|
||||
}
|
||||
|
||||
func (db *DB) RunMigrations() (err error) {
|
||||
var version int
|
||||
err = db.store.Get(versionKey, &version)
|
||||
if err == ErrKeyNotFound {
|
||||
err = db.store.Set(versionKey, 0)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
log.Printf("db: detected database version %d", version)
|
||||
for version < currentVersion {
|
||||
log.Printf("db: running migration %d", version)
|
||||
version, err = migrators[version](db)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = db.store.Set(versionKey, version)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -14,15 +14,14 @@ import (
|
|||
const pagePrefix = "page/"
|
||||
|
||||
var (
|
||||
errPageNotFound = store.ErrKeyNotFound
|
||||
errPermissionDenied = errors.New("db: permission denied")
|
||||
errInvalidPermission = errors.New("db: invalid permission")
|
||||
errPrivateWikiPage = errors.New("db: private wiki pages are not allowed")
|
||||
errNonPublicIndexPage = errors.New("db: internal/private index page is not allowed")
|
||||
errDeleteIndexPage = errors.New("db: index page can not be deleted")
|
||||
errPageNotFound = store.ErrKeyNotFound
|
||||
errPermissionDenied = errors.New("db: permission denied")
|
||||
errInvalidPermission = errors.New("db: invalid permission")
|
||||
errPrivateWikiPage = errors.New("db: private wiki pages are not allowed")
|
||||
errDeleteIndexPage = errors.New("db: index page can not be deleted")
|
||||
)
|
||||
|
||||
func (db *DB) GetAllSections(username string) (sections core.Sections) {
|
||||
func (db *DB) GetAllSections(username string) (sections core.SectionPages) {
|
||||
m := make(map[string]core.UpdatedPages)
|
||||
db.store.ForEach(func(k string, _ []byte) error {
|
||||
if trim, ok := hasTrimPrefix(k, pagePrefix); ok {
|
||||
|
|
@ -46,7 +45,7 @@ func (db *DB) GetAllSections(username string) (sections core.Sections) {
|
|||
if l > 5 {
|
||||
l = 5
|
||||
}
|
||||
sections = append(sections, core.Section{s, p[:l]})
|
||||
sections = append(sections, core.SectionPage{s, p[:l]})
|
||||
}
|
||||
sort.Sort(sections)
|
||||
return
|
||||
|
|
@ -86,7 +85,7 @@ func (db *DB) ValidatePage(st, username string) bool {
|
|||
db.cache.Add(st, p)
|
||||
return true
|
||||
}
|
||||
return core.ReadPerm(username, p)
|
||||
return db.ReadPerm(st, username, p.Perm)
|
||||
}
|
||||
|
||||
func (db *DB) GetPage(section, title, username string) (p *core.Page, err error) {
|
||||
|
|
@ -97,7 +96,7 @@ func (db *DB) GetPage(section, title, username string) (p *core.Page, err error)
|
|||
db.cache.Add(st, p)
|
||||
return
|
||||
}
|
||||
if !core.ReadPerm(username, p) {
|
||||
if !db.ReadPerm(section, username, p.Perm) {
|
||||
err = errPageNotFound
|
||||
}
|
||||
return
|
||||
|
|
@ -109,7 +108,7 @@ func (db *DB) getPage(title, username string) (p *core.Page, err error) {
|
|||
if err != nil {
|
||||
return
|
||||
}
|
||||
if !core.ReadPerm(username, p) {
|
||||
if !db.ReadPerm(title, username, p.Perm) {
|
||||
err = errPageNotFound
|
||||
}
|
||||
return
|
||||
|
|
@ -120,13 +119,9 @@ func (db *DB) CreatePage(title, section, markdown, username string, p core.Permi
|
|||
err = errInvalidPermission
|
||||
return
|
||||
}
|
||||
owner := username
|
||||
if section == core.WikiSection {
|
||||
owner = core.WikiSection
|
||||
if p == core.Private {
|
||||
err = errPrivateWikiPage
|
||||
return
|
||||
}
|
||||
if section == core.WikiSection && p == core.Private {
|
||||
err = errPrivateWikiPage
|
||||
return
|
||||
}
|
||||
st := render.StoreTitle(section, render.Title(title))
|
||||
_, err = db.getPage(st, username)
|
||||
|
|
@ -139,7 +134,6 @@ func (db *DB) CreatePage(title, section, markdown, username string, p core.Permi
|
|||
StoreTitle: st,
|
||||
Markdown: markdown,
|
||||
Created: created(username),
|
||||
Owner: owner,
|
||||
Perm: p,
|
||||
}
|
||||
search := render.Render(page)
|
||||
|
|
@ -166,17 +160,12 @@ func (db *DB) UpdatePage(title, section, markdown, username string, p core.Permi
|
|||
err = errPrivateWikiPage
|
||||
return
|
||||
}
|
||||
if section == core.WikiSection && title == core.IndexPage &&
|
||||
(p == core.Internal || p == core.Private) {
|
||||
err = errNonPublicIndexPage
|
||||
return
|
||||
}
|
||||
st := render.StoreTitle(section, title)
|
||||
page, err := db.getPage(st, username)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if !core.WritePerm(username, section, page) {
|
||||
if !db.WritePerm(title, username, page.Perm) {
|
||||
return errPermissionDenied
|
||||
}
|
||||
|
||||
|
|
@ -216,7 +205,7 @@ func (db *DB) DeletePage(title, section, username string) (err error) {
|
|||
if page, err = db.getPage(st, username); err != nil {
|
||||
return
|
||||
}
|
||||
if !core.WritePerm(username, section, page) {
|
||||
if !db.WritePerm(section, username, page.Perm) {
|
||||
return errPermissionDenied
|
||||
}
|
||||
return db.deletePage(st)
|
||||
|
|
|
|||
26
pkg/db/permission.go
Normal file
26
pkg/db/permission.go
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
// Copyright (C) 2019 Marius Schellenberger
|
||||
|
||||
package db
|
||||
|
||||
import (
|
||||
"git.giftfish.de/ston1th/gowiki/pkg/core"
|
||||
"git.giftfish.de/ston1th/gowiki/pkg/render"
|
||||
)
|
||||
|
||||
func (db *DB) ReadPerm(st, username string, p core.Permission) bool {
|
||||
section, _ := render.SplitStoreTitle(st)
|
||||
s, err := db.GetSection(section)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return core.ReadPerm(username, p, s)
|
||||
}
|
||||
|
||||
func (db *DB) WritePerm(st, username string, p core.Permission) bool {
|
||||
section, _ := render.SplitStoreTitle(st)
|
||||
s, err := db.GetSection(section)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return core.WritePerm(username, p, s)
|
||||
}
|
||||
103
pkg/db/section.go
Normal file
103
pkg/db/section.go
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
// Copyright (C) 2019 Marius Schellenberger
|
||||
|
||||
package db
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"git.giftfish.de/ston1th/gowiki/pkg/core"
|
||||
"git.giftfish.de/ston1th/gowiki/pkg/log"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const sectionPrefix = "section/"
|
||||
|
||||
var (
|
||||
errSectionExists = errors.New("db: section already exist")
|
||||
)
|
||||
|
||||
func (db *DB) GetSections() (secs core.Sections, err error) {
|
||||
err = db.store.ForEach(func(k string, _ []byte) error {
|
||||
if trim, ok := hasTrimPrefix(k, sectionPrefix); ok {
|
||||
s, err := db.GetSection(trim)
|
||||
if err == nil {
|
||||
secs = append(secs, s)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
sort.Sort(secs)
|
||||
return
|
||||
}
|
||||
|
||||
func (db *DB) GetSection(section string) (s core.Section, err error) {
|
||||
err = db.store.Get(sectionPrefix+section, &s)
|
||||
return
|
||||
}
|
||||
|
||||
func (db *DB) SectionExists(section string) error {
|
||||
return db.store.Get(sectionPrefix+section, nil)
|
||||
}
|
||||
|
||||
func (db *DB) CreateSection(section string, members []string, user bool) error {
|
||||
section = userRe.ReplaceAllString(section, "")
|
||||
if err := db.SectionExists(section); err == nil {
|
||||
return errSectionExists
|
||||
}
|
||||
return db.UpdateSection(section, members, user)
|
||||
}
|
||||
|
||||
func (db *DB) UpdateSection(section string, members []string, user bool) error {
|
||||
section = userRe.ReplaceAllString(section, "")
|
||||
if _, ok := core.Contains(section, reservedNames); ok {
|
||||
return errNameReserved
|
||||
}
|
||||
var m []string
|
||||
if user {
|
||||
m = append(m, section)
|
||||
} else {
|
||||
for _, v := range members {
|
||||
if v == "" {
|
||||
continue
|
||||
}
|
||||
if err := db.UserExists(v); err == nil {
|
||||
m = append(m, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
return db.store.Set(sectionPrefix+section, &core.Section{section, user, m})
|
||||
}
|
||||
|
||||
func (db *DB) DeleteSection(section string, user bool) error {
|
||||
if section == core.WikiSection {
|
||||
return errors.New("section '" + core.WikiSection + "' cannot be deleted")
|
||||
}
|
||||
s, err := db.GetSection(section)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = db.store.Delete(sectionPrefix + section)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !s.User && !user {
|
||||
return nil
|
||||
}
|
||||
section += "/"
|
||||
var pages []string
|
||||
db.store.ForEach(func(k string, _ []byte) error {
|
||||
if trim, ok := hasTrimPrefix(k, pagePrefix); ok {
|
||||
if strings.HasPrefix(trim, section) {
|
||||
pages = append(pages, trim)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
for _, p := range pages {
|
||||
err := db.deletePage(p)
|
||||
if err != nil {
|
||||
log.Debugf("DeleteSection %s, %s", section, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
@ -5,14 +5,12 @@ package db
|
|||
import (
|
||||
"errors"
|
||||
"git.giftfish.de/ston1th/gowiki/pkg/core"
|
||||
"git.giftfish.de/ston1th/gowiki/pkg/log"
|
||||
"git.giftfish.de/ston1th/gowiki/pkg/otp"
|
||||
"git.giftfish.de/ston1th/jwt/v3"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const userPrefix = "user/"
|
||||
|
|
@ -20,7 +18,7 @@ const userPrefix = "user/"
|
|||
var (
|
||||
errUserNotFound = errors.New("db: user not found")
|
||||
errUserExists = errors.New("db: user already exist")
|
||||
errUserReserved = errors.New("db: username is reserved")
|
||||
errNameReserved = errors.New("db: name is reserved")
|
||||
|
||||
userRe = regexp.MustCompile("[^a-zA-Z0-9]+")
|
||||
)
|
||||
|
|
@ -64,8 +62,8 @@ func (db *DB) UserExists(username string) error {
|
|||
|
||||
func (db *DB) CreateUser(username, password string, admin bool) error {
|
||||
username = userRe.ReplaceAllString(username, "")
|
||||
if reservedUser(username) {
|
||||
return errUserReserved
|
||||
if _, ok := core.Contains(username, reservedNames); ok {
|
||||
return errNameReserved
|
||||
}
|
||||
if err := db.UserExists(username); err == nil {
|
||||
return errUserExists
|
||||
|
|
@ -74,6 +72,10 @@ func (db *DB) CreateUser(username, password string, admin bool) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = db.CreateSection(username, nil, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return db.store.Set(userPrefix+username, &core.User{
|
||||
Username: username,
|
||||
Password: string(hash),
|
||||
|
|
@ -218,21 +220,5 @@ func (db *DB) DeleteUser(username string) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
username += "/"
|
||||
var pages []string
|
||||
db.store.ForEach(func(k string, _ []byte) error {
|
||||
if trim, ok := hasTrimPrefix(k, pagePrefix); ok {
|
||||
if strings.HasPrefix(trim, username) {
|
||||
pages = append(pages, trim)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
for _, p := range pages {
|
||||
err := db.deletePage(p)
|
||||
if err != nil {
|
||||
log.Debugf("DeleteUser %s, %s", username, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
return db.DeleteSection(username, true)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue