dev mode mixed with normal auth via basic

This commit is contained in:
Umputun
2018-02-17 02:06:57 -06:00
parent ed4c58cbe5
commit 24fefc1c3a
6 changed files with 71 additions and 63 deletions
+5 -3
View File
@@ -27,8 +27,9 @@ var opts struct {
RemarkURL string `long:"url" env:"REMARK_URL" default:"https://remark42.com" description:"url to remark"`
Admins []string `long:"admin" env:"ADMIN" description:"admin(s) names" env-delim:","`
DevMode bool `long:"dev" env:"DEV" description:"development mode, no auth enforced"`
Dbg bool `long:"dbg" env:"DEBUG" description:"debug mode"`
DevMode bool `long:"dev" env:"DEV" description:"development mode, no auth enforced"`
DevPasswd string `long:"dev-passwd" env:"DEV_PASSWD" default:"password" description:"development mode password"`
Dbg bool `long:"dbg" env:"DEBUG" description:"debug mode"`
BackupLocation string `long:"backup" env:"BACKUP_PATH" default:"./var" description:"backups location"`
MaxBackupFiles int `long:"max-back" env:"MAX_BACKUP_FILES" default:"10" description:"max backups to keep"`
@@ -108,13 +109,14 @@ func main() {
srv := server.Rest{
Version: revision,
DataService: dataService,
DevMode: opts.DevMode,
Exporter: &exporter,
Authenticator: auth.Authenticator{
Admins: opts.Admins,
SessionStore: sessionStore,
Providers: makeAuthProviders(sessionStore, avatarProxy),
AvatarProxy: avatarProxy,
DevEnabled: opts.DevMode,
DevPasswd: opts.DevPasswd,
},
Cache: rest.NewLoadingCache(4*time.Hour, 15*time.Minute, postFlushFn),
Notifier: notifier.NewNoOperation(),
+42 -26
View File
@@ -2,7 +2,9 @@ package auth
import (
"context"
"encoding/base64"
"net/http"
"strings"
"github.com/gorilla/sessions"
@@ -16,18 +18,11 @@ type Authenticator struct {
AvatarProxy *AvatarProxy
Admins []string
Providers []Provider
DevEnabled bool
DevPasswd string
}
// Mode defines behavior of Auth middleware
type Mode int
// auth modes
const (
Anonymous Mode = iota // propagates user info only, doesn't protect resource
Developer // fake dev auth, admin too
Full // real auth
)
var devUser = store.User{
ID: "dev",
Name: "developer one",
@@ -37,22 +32,13 @@ var devUser = store.User{
}
// Auth middleware adds auth from session and populates user info
func (a *Authenticator) Auth(modes []Mode) func(http.Handler) http.Handler {
inModes := func(mode Mode) bool {
for _, m := range modes {
if m == mode {
return true
}
}
return false
}
func (a *Authenticator) Auth(reqAuth bool) func(http.Handler) http.Handler {
f := func(h http.Handler) http.Handler {
fn := func(w http.ResponseWriter, r *http.Request) {
// for dev mode skip all real auth, make dev admin user
if inModes(Developer) {
// dev user - skip regular auth check and populate dev to context
if a.basicDevUser(w, r) {
user := devUser
ctx := r.Context()
ctx = context.WithValue(ctx, rest.ContextKey("user"), user)
@@ -62,18 +48,18 @@ func (a *Authenticator) Auth(modes []Mode) func(http.Handler) http.Handler {
}
session, err := a.SessionStore.Get(r, "remark")
if err != nil && inModes(Full) { // in full auth lack of session causes Unauthorized
http.Error(w, err.Error(), http.StatusUnauthorized)
if err != nil && reqAuth { // in full auth lack of session causes Unauthorized
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
if err != nil { // in any other mode just pass it to next handler
if err != nil { // in anonymous mode just pass it to next handler
h.ServeHTTP(w, r)
return
}
uinfoData, ok := session.Values["uinfo"]
if !ok && inModes(Full) { // return StatusUnauthorized for full auth mode only
if !ok && reqAuth {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
@@ -117,3 +103,33 @@ func (a *Authenticator) AdminOnly(next http.Handler) http.Handler {
}
return http.HandlerFunc(fn)
}
func (a *Authenticator) basicDevUser(w http.ResponseWriter, r *http.Request) bool {
if a.DevPasswd == "" || !a.DevEnabled {
return false
}
w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)
s := strings.SplitN(r.Header.Get("Authorization"), " ", 2)
if len(s) != 2 {
return false
}
b, err := base64.StdEncoding.DecodeString(s[1])
if err != nil {
return false
}
pair := strings.SplitN(string(b), ":", 2)
if len(pair) != 2 {
return false
}
if pair[0] != "dev" || pair[1] != a.DevPasswd {
return false
}
return true
}
+2 -4
View File
@@ -130,10 +130,8 @@ func (a *admin) checkBlocked(siteID string, user store.User) bool {
func (a *admin) maskInfo(comments []store.Comment, r *http.Request) (res []store.Comment) {
res = make([]store.Comment, len(comments))
isAdmin := false
if user, err := rest.GetUserInfo(r); err == nil && user.Admin { // make seprate cache key for admins
isAdmin = true
}
user, err := rest.GetUserInfo(r)
isAdmin := (err == nil && user.Admin) // make seprate cache key for admins
for i, c := range comments {
+5 -6
View File
@@ -29,7 +29,8 @@ func TestAdmin_Delete(t *testing.T) {
client := http.Client{}
req, err := http.NewRequest(http.MethodDelete,
fmt.Sprintf("http://127.0.0.1:%d/api/v1/admin/comment/%s?site=radio-t&url=https://radio-t.com/blah", port, id1), nil)
fmt.Sprintf("http://dev:password@127.0.0.1:%d/api/v1/admin/comment/%s?site=radio-t&url=https://radio-t.com/blah",
port, id1), nil)
assert.Nil(t, err)
resp, err := client.Do(req)
assert.Nil(t, err)
@@ -60,8 +61,7 @@ func TestAdmin_Pin(t *testing.T) {
pin := func(val int) int {
client := http.Client{}
req, err := http.NewRequest(http.MethodPut,
fmt.Sprintf("http://127.0.0.1:%d/api/v1/admin/pin/%s?site=radio-t&url=https://radio-t.com/blah&pin=%d", port, id1, val),
nil)
fmt.Sprintf("http://dev:password@127.0.0.1:%d/api/v1/admin/pin/%s?site=radio-t&url=https://radio-t.com/blah&pin=%d", port, id1, val), nil)
assert.Nil(t, err)
resp, err := client.Do(req)
assert.Nil(t, err)
@@ -106,8 +106,7 @@ func TestAdmin_Block(t *testing.T) {
block := func(val int) (code int, body []byte) {
client := http.Client{}
req, err := http.NewRequest(http.MethodPut,
fmt.Sprintf("http://127.0.0.1:%d/api/v1/admin/user/%s?site=radio-t&block=%d",
port, "user1", val), nil)
fmt.Sprintf("http://dev:password@127.0.0.1:%d/api/v1/admin/user/%s?site=radio-t&block=%d", port, "user1", val), nil)
assert.Nil(t, err)
resp, err := client.Do(req)
require.Nil(t, err)
@@ -154,7 +153,7 @@ func TestAdmin_Export(t *testing.T) {
addComment(t, c1, port)
addComment(t, c2, port)
body, code := get(t, fmt.Sprintf("http://127.0.0.1:%d/api/v1/admin/export?site=radio-t&mode=stream", port))
body, code := get(t, fmt.Sprintf("http://dev:password@127.0.0.1:%d/api/v1/admin/export?site=radio-t&mode=stream", port))
assert.Equal(t, 200, code)
assert.Equal(t, 2, strings.Count(body, "\n"))
assert.Equal(t, 2, strings.Count(body, "\"text\""))
+2 -12
View File
@@ -31,7 +31,6 @@ import (
// Rest is a rest access server
type Rest struct {
Version string
DevMode bool
DataService store.Service
Authenticator auth.Authenticator
@@ -47,15 +46,6 @@ type Rest struct {
func (s *Rest) Run(port int) {
log.Print("[INFO] activate rest server")
// add auth.Developer flag if dev mode is active
maybeWithDevMode := func(mode auth.Mode) (modes []auth.Mode) {
modes = append(modes, mode)
if s.DevMode {
modes = append(modes, auth.Developer)
}
return modes
}
if len(s.Authenticator.Admins) > 0 {
log.Printf("[DEBUG] admins %+v", s.Authenticator.Admins)
}
@@ -66,7 +56,7 @@ func (s *Rest) Run(port int) {
router.Use(tollbooth_chi.LimitHandler(tollbooth.NewLimiter(10, nil)))
// all request by default allow anonymous access
router.Use(s.Authenticator.Auth(maybeWithDevMode(auth.Anonymous)))
router.Use(s.Authenticator.Auth(false))
router.Use(AppInfo("remark42", s.Version), Ping, Logger(LogAll))
router.Use(context.ClearHandler) // if you aren't using gorilla/mux, you need to wrap your handlers with context.ClearHandler
@@ -97,7 +87,7 @@ func (s *Rest) Run(port int) {
rapi.Get("/config", s.configCtrl)
// protected routes, require auth
rapi.With(s.Authenticator.Auth(maybeWithDevMode(auth.Full))).Group(func(rauth chi.Router) {
rapi.With(s.Authenticator.Auth(true)).Group(func(rauth chi.Router) {
rauth.Post("/comment", s.createCommentCtrl)
rauth.Put("/comment/{id}", s.updateCommentCtrl)
rauth.Get("/user", s.userInfoCtrl)
+15 -12
View File
@@ -13,6 +13,7 @@ import (
"testing"
"time"
"github.com/gorilla/sessions"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@@ -40,7 +41,7 @@ func TestServer_Create(t *testing.T) {
defer cleanup(srv)
r := strings.NewReader(`{"text": "test 123", "locator":{"url": "https://radio-t.com/blah1", "site": "radio-t"}}`)
resp, err := http.Post(fmt.Sprintf("http://127.0.0.1:%d/api/v1/comment", port), "application/json", r)
resp, err := http.Post(fmt.Sprintf("http://dev:password@127.0.0.1:%d/api/v1/comment", port), "application/json", r)
assert.Nil(t, err)
assert.Equal(t, http.StatusCreated, resp.StatusCode)
@@ -62,7 +63,7 @@ func TestServer_CreateAndGet(t *testing.T) {
// create comment
r := strings.NewReader(`{"text": "**test** *123* http://radio-t.com", "locator":{"url": "https://radio-t.com/blah1", "site": "radio-t"}}`)
resp, err := http.Post(fmt.Sprintf("http://127.0.0.1:%d/api/v1/comment", port), "application/json", r)
resp, err := http.Post(fmt.Sprintf("http://dev:password@127.0.0.1:%d/api/v1/comment", port), "application/json", r)
assert.Nil(t, err)
assert.Equal(t, http.StatusCreated, resp.StatusCode)
b, err := ioutil.ReadAll(resp.Body)
@@ -74,7 +75,7 @@ func TestServer_CreateAndGet(t *testing.T) {
id := c["id"].(string)
// get created comment by id
res, code := get(t, fmt.Sprintf("http://127.0.0.1:%d/api/v1/id/%s?site=radio-t&url=https://radio-t.com/blah1", port, id))
res, code := get(t, fmt.Sprintf("http://dev:password@127.0.0.1:%d/api/v1/id/%s?site=radio-t&url=https://radio-t.com/blah1", port, id))
assert.Equal(t, 200, code)
comment := store.Comment{}
err = json.Unmarshal([]byte(res), &comment)
@@ -135,7 +136,7 @@ func TestServer_Update(t *testing.T) {
client := http.Client{}
req, err := http.NewRequest(http.MethodPut,
fmt.Sprintf("http://127.0.0.1:%d/api/v1/comment/"+id+"?site=radio-t&url=https://radio-t.com/blah1", port),
fmt.Sprintf("http://dev:password@127.0.0.1:%d/api/v1/comment/"+id+"?site=radio-t&url=https://radio-t.com/blah1", port),
strings.NewReader(`{"text":"updated text", "summary":"my edit"}`))
assert.Nil(t, err)
b, err := client.Do(req)
@@ -154,7 +155,7 @@ func TestServer_Update(t *testing.T) {
assert.True(t, time.Since(c2.Edit.Timestamp) < 1*time.Second)
// read updated comment
res, code := get(t, fmt.Sprintf("http://127.0.0.1:%d/api/v1/id/%s?site=radio-t&url=https://radio-t.com/blah1", port, id))
res, code := get(t, fmt.Sprintf("http://dev:password@127.0.0.1:%d/api/v1/id/%s?site=radio-t&url=https://radio-t.com/blah1", port, id))
assert.Equal(t, 200, code)
c3 := store.Comment{}
err = json.Unmarshal([]byte(res), &c3)
@@ -230,7 +231,7 @@ func TestServer_UserInfo(t *testing.T) {
assert.NotNil(t, srv)
defer cleanup(srv)
body, code := get(t, fmt.Sprintf("http://127.0.0.1:%d/api/v1/user?site=radio-t", port))
body, code := get(t, fmt.Sprintf("http://dev:password@127.0.0.1:%d/api/v1/user?site=radio-t", port))
assert.Equal(t, 200, code)
user := store.User{}
err := json.Unmarshal([]byte(body), &user)
@@ -256,8 +257,8 @@ func TestServer_Vote(t *testing.T) {
vote := func(val int) int {
client := http.Client{}
req, err := http.NewRequest(http.MethodPut,
fmt.Sprintf("http://127.0.0.1:%d/api/v1/vote/%s?site=radio-t&url=https://radio-t.com/blah&vote=%d", port, id1, val),
nil)
fmt.Sprintf("http://dev:password@127.0.0.1:%d/api/v1/vote/%s?site=radio-t&url=https://radio-t.com/blah&vote=%d",
port, id1, val), nil)
assert.Nil(t, err)
resp, err := client.Do(req)
assert.Nil(t, err)
@@ -344,10 +345,12 @@ func prep(t *testing.T) (srv *Rest, port int) {
require.Nil(t, err)
srv = &Rest{
DataService: store.Service{Interface: dataStore, EditDuration: 5 * time.Minute},
DevMode: true,
Authenticator: auth.Authenticator{
Providers: nil,
AvatarProxy: &auth.AvatarProxy{StorePath: "/tmp", RoutePath: "/api/v1/avatar"},
SessionStore: sessions.NewFilesystemStore("/tmp", []byte("blah")),
DevEnabled: true,
DevPasswd: "password",
Providers: nil,
AvatarProxy: &auth.AvatarProxy{StorePath: "/tmp", RoutePath: "/api/v1/avatar"},
},
Exporter: &migrator.Remark{DataStore: dataStore},
Cache: &mockCache{},
@@ -374,7 +377,7 @@ func addComment(t *testing.T, c store.Comment, port int) string {
b, err := json.Marshal(c)
assert.Nil(t, err, "can't marshal comment %+v", c)
resp, err := http.Post(fmt.Sprintf("http://127.0.0.1:%d/api/v1/comment", port), "application/json", bytes.NewBuffer(b))
resp, err := http.Post(fmt.Sprintf("http://dev:password@127.0.0.1:%d/api/v1/comment", port), "application/json", bytes.NewBuffer(b))
assert.Nil(t, err)
assert.Equal(t, http.StatusCreated, resp.StatusCode)
b, err = ioutil.ReadAll(resp.Body)