Allow completely private instances, part 1
This is the start of all changes needed to support entirely private instances, where all blogs are only visible to other authenticated users on an instance (ref T576). It begins by changing how Handler methods check an endpoint's permissions. - Renames UserLevelLEVEL consts to UserLevelLEVELType - Adds UserLevelLEVEL funcs with same names as previous consts. Each returns a UserLevel - Adds a new UserLevelReader that restricts access based on app configuration. This is now used on collections and posts. - Changes routing a bit so static files are always accessible
This commit is contained in:
parent
161f7a8de2
commit
b3a36a3be7
|
@ -76,7 +76,9 @@ type (
|
|||
// Federation
|
||||
Federation bool `ini:"federation"`
|
||||
PublicStats bool `ini:"public_stats"`
|
||||
Private bool `ini:"private"`
|
||||
|
||||
// Access
|
||||
Private bool `ini:"private"`
|
||||
|
||||
// Additional functions
|
||||
LocalTimeline bool `ini:"local_timeline"`
|
||||
|
|
77
handle.go
77
handle.go
|
@ -23,24 +23,52 @@ import (
|
|||
"github.com/gorilla/sessions"
|
||||
"github.com/writeas/impart"
|
||||
"github.com/writeas/web-core/log"
|
||||
"github.com/writeas/writefreely/config"
|
||||
"github.com/writeas/writefreely/page"
|
||||
)
|
||||
|
||||
// UserLevel represents the required user level for accessing an endpoint
|
||||
type UserLevel int
|
||||
|
||||
const (
|
||||
UserLevelNone UserLevel = iota // user or not -- ignored
|
||||
UserLevelOptional // user or not -- object fetched if user
|
||||
UserLevelNoneRequired // non-user (required)
|
||||
UserLevelUser // user (required)
|
||||
UserLevelNoneType UserLevel = iota // user or not -- ignored
|
||||
UserLevelOptionalType // user or not -- object fetched if user
|
||||
UserLevelNoneRequiredType // non-user (required)
|
||||
UserLevelUserType // user (required)
|
||||
)
|
||||
|
||||
func UserLevelNone(cfg *config.Config) UserLevel {
|
||||
return UserLevelNoneType
|
||||
}
|
||||
|
||||
func UserLevelOptional(cfg *config.Config) UserLevel {
|
||||
return UserLevelOptionalType
|
||||
}
|
||||
|
||||
func UserLevelNoneRequired(cfg *config.Config) UserLevel {
|
||||
return UserLevelNoneRequiredType
|
||||
}
|
||||
|
||||
func UserLevelUser(cfg *config.Config) UserLevel {
|
||||
return UserLevelUserType
|
||||
}
|
||||
|
||||
// UserLevelReader returns the permission level required for any route where
|
||||
// users can read published content.
|
||||
func UserLevelReader(cfg *config.Config) UserLevel {
|
||||
if cfg.App.Private {
|
||||
return UserLevelUserType
|
||||
}
|
||||
return UserLevelOptionalType
|
||||
}
|
||||
|
||||
type (
|
||||
handlerFunc func(app *App, w http.ResponseWriter, r *http.Request) error
|
||||
userHandlerFunc func(app *App, u *User, w http.ResponseWriter, r *http.Request) error
|
||||
userApperHandlerFunc func(apper Apper, u *User, w http.ResponseWriter, r *http.Request) error
|
||||
dataHandlerFunc func(app *App, w http.ResponseWriter, r *http.Request) ([]byte, string, error)
|
||||
authFunc func(app *App, r *http.Request) (*User, error)
|
||||
UserLevelFunc func(cfg *config.Config) UserLevel
|
||||
)
|
||||
|
||||
type Handler struct {
|
||||
|
@ -307,7 +335,7 @@ func (h *Handler) Page(n string) http.HandlerFunc {
|
|||
}, UserLevelOptional)
|
||||
}
|
||||
|
||||
func (h *Handler) WebErrors(f handlerFunc, ul UserLevel) http.HandlerFunc {
|
||||
func (h *Handler) WebErrors(f handlerFunc, ul UserLevelFunc) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
// TODO: factor out this logic shared with Web()
|
||||
h.handleHTTPError(w, r, func() error {
|
||||
|
@ -331,21 +359,21 @@ func (h *Handler) WebErrors(f handlerFunc, ul UserLevel) http.HandlerFunc {
|
|||
|
||||
var session *sessions.Session
|
||||
var err error
|
||||
if ul != UserLevelNone {
|
||||
if ul(h.app.App().cfg) != UserLevelNoneType {
|
||||
session, err = h.sessionStore.Get(r, cookieName)
|
||||
if err != nil && (ul == UserLevelNoneRequired || ul == UserLevelUser) {
|
||||
if err != nil && (ul(h.app.App().cfg) == UserLevelNoneRequiredType || ul(h.app.App().cfg) == UserLevelUserType) {
|
||||
// Cookie is required, but we can ignore this error
|
||||
log.Error("Handler: Unable to get session (for user permission %d); ignoring: %v", ul, err)
|
||||
}
|
||||
|
||||
_, gotUser := session.Values[cookieUserVal].(*User)
|
||||
if ul == UserLevelNoneRequired && gotUser {
|
||||
if ul(h.app.App().cfg) == UserLevelNoneRequiredType && gotUser {
|
||||
to := correctPageFromLoginAttempt(r)
|
||||
log.Info("Handler: Required NO user, but got one. Redirecting to %s", to)
|
||||
err := impart.HTTPError{http.StatusFound, to}
|
||||
status = err.Status
|
||||
return err
|
||||
} else if ul == UserLevelUser && !gotUser {
|
||||
} else if ul(h.app.App().cfg) == UserLevelUserType && !gotUser {
|
||||
log.Info("Handler: Required a user, but DIDN'T get one. Sending not logged in.")
|
||||
err := ErrNotLoggedIn
|
||||
status = err.Status
|
||||
|
@ -380,9 +408,18 @@ func (h *Handler) WebErrors(f handlerFunc, ul UserLevel) http.HandlerFunc {
|
|||
}
|
||||
}
|
||||
|
||||
func (h *Handler) CollectionPostOrStatic(w http.ResponseWriter, r *http.Request) {
|
||||
if strings.Contains(r.URL.Path, ".") && !isRaw(r) {
|
||||
// Serve static file
|
||||
h.app.App().shttp.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
h.Web(viewCollectionPost, UserLevelReader)(w, r)
|
||||
}
|
||||
|
||||
// Web handles requests made in the web application. This provides user-
|
||||
// friendly HTML pages and actions that work in the browser.
|
||||
func (h *Handler) Web(f handlerFunc, ul UserLevel) http.HandlerFunc {
|
||||
func (h *Handler) Web(f handlerFunc, ul UserLevelFunc) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
h.handleHTTPError(w, r, func() error {
|
||||
var status int
|
||||
|
@ -404,21 +441,21 @@ func (h *Handler) Web(f handlerFunc, ul UserLevel) http.HandlerFunc {
|
|||
log.Info("\"%s %s\" %d %s \"%s\"", r.Method, r.RequestURI, status, time.Since(start), r.UserAgent())
|
||||
}()
|
||||
|
||||
if ul != UserLevelNone {
|
||||
if ul(h.app.App().cfg) != UserLevelNoneType {
|
||||
session, err := h.sessionStore.Get(r, cookieName)
|
||||
if err != nil && (ul == UserLevelNoneRequired || ul == UserLevelUser) {
|
||||
if err != nil && (ul(h.app.App().cfg) == UserLevelNoneRequiredType || ul(h.app.App().cfg) == UserLevelUserType) {
|
||||
// Cookie is required, but we can ignore this error
|
||||
log.Error("Handler: Unable to get session (for user permission %d); ignoring: %v", ul, err)
|
||||
}
|
||||
|
||||
_, gotUser := session.Values[cookieUserVal].(*User)
|
||||
if ul == UserLevelNoneRequired && gotUser {
|
||||
if ul(h.app.App().cfg) == UserLevelNoneRequiredType && gotUser {
|
||||
to := correctPageFromLoginAttempt(r)
|
||||
log.Info("Handler: Required NO user, but got one. Redirecting to %s", to)
|
||||
err := impart.HTTPError{http.StatusFound, to}
|
||||
status = err.Status
|
||||
return err
|
||||
} else if ul == UserLevelUser && !gotUser {
|
||||
} else if ul(h.app.App().cfg) == UserLevelUserType && !gotUser {
|
||||
log.Info("Handler: Required a user, but DIDN'T get one. Sending not logged in.")
|
||||
err := ErrNotLoggedIn
|
||||
status = err.Status
|
||||
|
@ -478,7 +515,7 @@ func (h *Handler) All(f handlerFunc) http.HandlerFunc {
|
|||
}
|
||||
}
|
||||
|
||||
func (h *Handler) Download(f dataHandlerFunc, ul UserLevel) http.HandlerFunc {
|
||||
func (h *Handler) Download(f dataHandlerFunc, ul UserLevelFunc) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
h.handleHTTPError(w, r, func() error {
|
||||
var status int
|
||||
|
@ -523,27 +560,27 @@ func (h *Handler) Download(f dataHandlerFunc, ul UserLevel) http.HandlerFunc {
|
|||
}
|
||||
}
|
||||
|
||||
func (h *Handler) Redirect(url string, ul UserLevel) http.HandlerFunc {
|
||||
func (h *Handler) Redirect(url string, ul UserLevelFunc) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
h.handleHTTPError(w, r, func() error {
|
||||
start := time.Now()
|
||||
|
||||
var status int
|
||||
if ul != UserLevelNone {
|
||||
if ul(h.app.App().cfg) != UserLevelNoneType {
|
||||
session, err := h.sessionStore.Get(r, cookieName)
|
||||
if err != nil && (ul == UserLevelNoneRequired || ul == UserLevelUser) {
|
||||
if err != nil && (ul(h.app.App().cfg) == UserLevelNoneRequiredType || ul(h.app.App().cfg) == UserLevelUserType) {
|
||||
// Cookie is required, but we can ignore this error
|
||||
log.Error("Handler: Unable to get session (for user permission %d); ignoring: %v", ul, err)
|
||||
}
|
||||
|
||||
_, gotUser := session.Values[cookieUserVal].(*User)
|
||||
if ul == UserLevelNoneRequired && gotUser {
|
||||
if ul(h.app.App().cfg) == UserLevelNoneRequiredType && gotUser {
|
||||
to := correctPageFromLoginAttempt(r)
|
||||
log.Info("Handler: Required NO user, but got one. Redirecting to %s", to)
|
||||
err := impart.HTTPError{http.StatusFound, to}
|
||||
status = err.Status
|
||||
return err
|
||||
} else if ul == UserLevelUser && !gotUser {
|
||||
} else if ul(h.app.App().cfg) == UserLevelUserType && !gotUser {
|
||||
log.Info("Handler: Required a user, but DIDN'T get one. Sending not logged in.")
|
||||
err := ErrNotLoggedIn
|
||||
status = err.Status
|
||||
|
|
16
posts.go
16
posts.go
|
@ -1190,6 +1190,16 @@ func getRawCollectionPost(app *App, slug, collAlias string) *RawPost {
|
|||
}
|
||||
}
|
||||
|
||||
func isRaw(r *http.Request) bool {
|
||||
vars := mux.Vars(r)
|
||||
slug := vars["slug"]
|
||||
|
||||
isJSON := strings.HasSuffix(slug, ".json")
|
||||
isXML := strings.HasSuffix(slug, ".xml")
|
||||
isMarkdown := strings.HasSuffix(slug, ".md")
|
||||
return strings.HasSuffix(slug, ".txt") || isJSON || isXML || isMarkdown
|
||||
}
|
||||
|
||||
func viewCollectionPost(app *App, w http.ResponseWriter, r *http.Request) error {
|
||||
vars := mux.Vars(r)
|
||||
slug := vars["slug"]
|
||||
|
@ -1199,12 +1209,6 @@ func viewCollectionPost(app *App, w http.ResponseWriter, r *http.Request) error
|
|||
isMarkdown := strings.HasSuffix(slug, ".md")
|
||||
isRaw := strings.HasSuffix(slug, ".txt") || isJSON || isXML || isMarkdown
|
||||
|
||||
if strings.Contains(r.URL.Path, ".") && !isRaw {
|
||||
// Serve static file
|
||||
app.shttp.ServeHTTP(w, r)
|
||||
return nil
|
||||
}
|
||||
|
||||
cr := &collectionReq{}
|
||||
err := processCollectionRequest(cr, vars, w, r)
|
||||
if err != nil {
|
||||
|
|
28
routes.go
28
routes.go
|
@ -14,6 +14,7 @@ import (
|
|||
"github.com/gorilla/mux"
|
||||
"github.com/writeas/go-webfinger"
|
||||
"github.com/writeas/web-core/log"
|
||||
"github.com/writeas/writefreely/config"
|
||||
"github.com/writefreely/go-nodeinfo"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
|
@ -152,8 +153,13 @@ func InitRoutes(apper Apper, r *mux.Router) *mux.Router {
|
|||
write.HandleFunc("/login", handler.Web(viewLogin, UserLevelNoneRequired))
|
||||
write.HandleFunc("/invite/{code}", handler.Web(handleViewInvite, UserLevelNoneRequired)).Methods("GET")
|
||||
// TODO: show a reader-specific 404 page if the function is disabled
|
||||
// TODO: change this based on configuration for either public or private-to-this-instance
|
||||
readPerm := UserLevelOptional
|
||||
readPerm := func(cfg *config.Config) UserLevel {
|
||||
if cfg.App.Private {
|
||||
// Private instance, so only allow users to access Reader routes
|
||||
return UserLevelUserType
|
||||
}
|
||||
return UserLevelOptionalType
|
||||
}
|
||||
|
||||
write.HandleFunc("/read", handler.Web(viewLocalTimeline, readPerm))
|
||||
RouteRead(handler, readPerm, write.PathPrefix("/read").Subrouter())
|
||||
|
@ -173,8 +179,8 @@ func InitRoutes(apper Apper, r *mux.Router) *mux.Router {
|
|||
if apper.App().cfg.App.SingleUser {
|
||||
RouteCollections(handler, write.PathPrefix("/").Subrouter())
|
||||
} else {
|
||||
write.HandleFunc("/{prefix:[@~$!\\-+]}{collection}", handler.Web(handleViewCollection, UserLevelOptional))
|
||||
write.HandleFunc("/{collection}/", handler.Web(handleViewCollection, UserLevelOptional))
|
||||
write.HandleFunc("/{prefix:[@~$!\\-+]}{collection}", handler.Web(handleViewCollection, UserLevelReader))
|
||||
write.HandleFunc("/{collection}/", handler.Web(handleViewCollection, UserLevelReader))
|
||||
RouteCollections(handler, write.PathPrefix("/{prefix:[@~$!\\-+]?}{collection}").Subrouter())
|
||||
// Posts
|
||||
}
|
||||
|
@ -184,19 +190,19 @@ func InitRoutes(apper Apper, r *mux.Router) *mux.Router {
|
|||
}
|
||||
|
||||
func RouteCollections(handler *Handler, r *mux.Router) {
|
||||
r.HandleFunc("/page/{page:[0-9]+}", handler.Web(handleViewCollection, UserLevelOptional))
|
||||
r.HandleFunc("/tag:{tag}", handler.Web(handleViewCollectionTag, UserLevelOptional))
|
||||
r.HandleFunc("/tag:{tag}/feed/", handler.Web(ViewFeed, UserLevelOptional))
|
||||
r.HandleFunc("/tags/{tag}", handler.Web(handleViewCollectionTag, UserLevelOptional))
|
||||
r.HandleFunc("/page/{page:[0-9]+}", handler.Web(handleViewCollection, UserLevelReader))
|
||||
r.HandleFunc("/tag:{tag}", handler.Web(handleViewCollectionTag, UserLevelReader))
|
||||
r.HandleFunc("/tag:{tag}/feed/", handler.Web(ViewFeed, UserLevelReader))
|
||||
r.HandleFunc("/tags/{tag}", handler.Web(handleViewCollectionTag, UserLevelReader))
|
||||
r.HandleFunc("/sitemap.xml", handler.All(handleViewSitemap))
|
||||
r.HandleFunc("/feed/", handler.All(ViewFeed))
|
||||
r.HandleFunc("/{slug}", handler.Web(viewCollectionPost, UserLevelOptional))
|
||||
r.HandleFunc("/{slug}", handler.CollectionPostOrStatic)
|
||||
r.HandleFunc("/{slug}/edit", handler.Web(handleViewPad, UserLevelUser))
|
||||
r.HandleFunc("/{slug}/edit/meta", handler.Web(handleViewMeta, UserLevelUser))
|
||||
r.HandleFunc("/{slug}/", handler.Web(handleCollectionPostRedirect, UserLevelOptional)).Methods("GET")
|
||||
r.HandleFunc("/{slug}/", handler.Web(handleCollectionPostRedirect, UserLevelReader)).Methods("GET")
|
||||
}
|
||||
|
||||
func RouteRead(handler *Handler, readPerm UserLevel, r *mux.Router) {
|
||||
func RouteRead(handler *Handler, readPerm UserLevelFunc, r *mux.Router) {
|
||||
r.HandleFunc("/api/posts", handler.Web(viewLocalTimelineAPI, readPerm))
|
||||
r.HandleFunc("/p/{page}", handler.Web(viewLocalTimeline, readPerm))
|
||||
r.HandleFunc("/feed/", handler.Web(viewLocalTimelineFeed, readPerm))
|
||||
|
|
Loading…
Reference in New Issue