diff --git a/config/config.go b/config/config.go index add5447..8009208 100644 --- a/config/config.go +++ b/config/config.go @@ -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"` diff --git a/handle.go b/handle.go index 81a4823..c70859e 100644 --- a/handle.go +++ b/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 diff --git a/posts.go b/posts.go index 0efa5ec..7fd6635 100644 --- a/posts.go +++ b/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 { diff --git a/routes.go b/routes.go index 13dd3a5..58bd455 100644 --- a/routes.go +++ b/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))