func TestHandlerTest(t *testing.T) { cfg, err := conf.Parse(test.TcConfigName()) if err != nil { t.Fatalf("invalid behavior") } err = cfg.Validate() if err != nil { t.Fatalf("invalid behavior") } ctx, cancel := context.WithCancel(conf.NewContext(cfg)) defer cancel() r := &http.Request{} ctx, _ = auth.CheckToken(ctx, r, false) s, err := db.NewSession(cfg.Conn, true) if err != nil { t.Fatal(err) } defer s.Close() ctx = db.NewContext(ctx, s) ctx, err = auth.Authenticate(ctx) if err != nil { t.Fatal(err) } w := httptest.NewRecorder() if err := HandlerTest(ctx, w, r); err.Err != nil { t.Error(err.Err) } if w.Code != http.StatusOK { t.Error("invalid behavior") } }
func BenchmarkContext(b *testing.B) { cfg, err := conf.Parse(test.TcConfigName()) if err != nil { b.Fatal("invalid behavior") } err = cfg.Validate() if err != nil { b.Fatal("invalid behavior") } ctx := conf.NewContext(cfg) s, err := NewSession(cfg.Conn, false) if err != nil { b.Fatal(err) } defer s.Close() ctx = NewContext(ctx, s) b.ResetTimer() for i := 0; i < b.N; i++ { if _, err := C(ctx, "test"); err != nil { b.Error(err) } } }
func TestNewSession(t *testing.T) { cfg, err := conf.Parse(test.TcConfigName()) if err != nil { t.Fatalf("invalid behavior") } err = cfg.Validate() if err != nil { t.Fatalf("invalid behavior") } s, err := NewSession(cfg.Conn, true) if err != nil { t.Fatalf("invalid db init") } defer s.Close() ctx := conf.NewContext(cfg) if _, err := CtxSession(ctx); err == nil { t.Fatalf("invalid behavior") } if _, err := C(ctx, "tests"); err == nil { t.Fatalf("invalid behavior") } ctx = NewContext(ctx, s) if _, err := CtxSession(ctx); err != nil { t.Error(err) } if _, err := C(ctx, "test-bad"); err == nil { t.Fatalf("invalid behavior") } coll, err := C(ctx, "tests") if err != nil { t.Error(err) } _, err = coll.Count() if err != nil { t.Error(err) } }
func TestToken(t *testing.T) { cfg, err := conf.Parse(test.TcConfigName()) if err != nil { t.Fatalf("invalid behavior") } err = cfg.Validate() if err != nil { t.Fatalf("invalid behavior") } ctx, cancel := context.WithCancel(conf.NewContext(cfg)) defer cancel() // check form auth r := &http.Request{PostForm: url.Values{"token": {""}}} if _, err := CheckToken(ctx, r, false); err != ErrAnonymous { t.Error("invalid behavior") } r = &http.Request{PostForm: url.Values{"token": {"bad"}}} if _, err := CheckToken(ctx, r, false); err == nil || err == ErrAnonymous { t.Error("invalid behavior") } p1, p2, err := genToken(cfg) if err != nil { t.Fatal(err) } r = &http.Request{PostForm: url.Values{"token": {p1 + p2}}} ctxToken, err := CheckToken(ctx, r, false) if err != nil { t.Errorf("invalid behavior: %v", err) } strToken, err := ExtractTokenKey(ctxToken) if err != nil { t.Errorf("invalid behavior: %v", err) } if p2 != strToken { t.Errorf("invalid behavior: %v != %v", p2, strToken) } // check API auth r = &http.Request{} if _, err := CheckToken(ctx, r, true); err != ErrAnonymous { t.Error("invalid behavior") } r = &http.Request{Header: http.Header{}} r.Header.Set("Authorization", "bad") if _, err := CheckToken(ctx, r, true); err == nil { t.Error("invalid behavior") } r = &http.Request{Header: http.Header{}} r.Header.Set("Authorization", "Bearer") if _, err := CheckToken(ctx, r, true); err == nil { t.Error("invalid behavior") } r = &http.Request{Header: http.Header{}} r.Header.Set("Authorization", "Bearer1234") if _, err := CheckToken(ctx, r, true); err == nil { t.Error("invalid behavior") } r = &http.Request{Header: http.Header{}} r.Header.Set("Authorization", "Bearer"+p1+p2) if _, err := CheckToken(ctx, r, true); err != nil { t.Errorf("invalid behavior: %v", err) } }
func TestCreateUser(t *testing.T) { const userName = "******" cfg, err := conf.Parse(test.TcConfigName()) if err != nil { t.Fatalf("invalid behavior") } err = cfg.Validate() if err != nil { t.Fatalf("invalid behavior") } ctx, cancel := context.WithCancel(conf.NewContext(cfg)) defer cancel() s, err := db.NewSession(cfg.Conn, true) if err != nil { t.Fatal(err) } defer s.Close() ctx = db.NewContext(ctx, s) coll, err := db.Coll(s, "users") if err != nil { t.Fatal(err) } _, err = coll.RemoveAll(nil) if err != nil { t.Fatal(err) } err = InitUsers(cfg) if err != nil { t.Fatal(err) } if n, err := coll.Count(); err != nil || n != 2 { t.Errorf("n=%v, err=%v", n, err) } users, err := CreateUsers(ctx, []string{userName}) if err != nil { t.Fatal(err) } if users, err := CreateUsers(ctx, []string{userName}); err == nil { if users[0].Err == "" { t.Error("invalid behavior") } } r := &http.Request{PostForm: url.Values{"token": {users[0].T}}} ctx, err = CheckToken(ctx, r, false) if err != nil { t.Errorf("invalid behavior: %v", err) } ctx, err = Authenticate(ctx) if err != nil { t.Fatal(err) } u, err := ExtractUser(ctx) if err != nil { t.Fatal(err) } if u.String() != userName { t.Error("invalid behavior") } if !u.HasRole("user") { t.Error("invalid behavior") } if u.IsAnonymous() { t.Error("invalid behavior") } _, err = ChangeUsers(ctx, []string{userName}) if err != nil { t.Fatal(err) } if result, err := ChangeUsers(ctx, []string{"bad"}); err != nil { if result[0].Err == "" { t.Error("invalid behavior") } } _, err = DisableUsers(ctx, []string{userName}) if err != nil { t.Fatal(err) } if result, err := DisableUsers(ctx, []string{"bad"}); err != nil { if result[0].Err == "" { t.Error("invalid behavior") } } }
func main() { var err error defer func() { if r := recover(); r != nil { fmt.Printf("abnormal termination [%v]: %v\n", Version, r) } }() version := flag.Bool("version", false, "show version") config := flag.String("config", Config, "configuration file") flag.Parse() if *version { fmt.Printf("%v: %v\n\trevision: %v %v\n\tbuild date: %v\n", Name, Version, Revision, runtime.Version(), BuildDate) return } // configuration initialization cfg, err := conf.Parse(*config) if err != nil { log.Panicf("init config error [%v]", err) } if err := cfg.Validate(); err != nil { log.Panicf("config validate error [%v]", err) } // check db connection s, err := db.NewSession(cfg.Conn, true) if err != nil { log.Panic(err) } s.Close() defer cfg.Close() // init users if err := auth.InitUsers(cfg); err != nil { log.Panic(err) } // set init context mainCtx := conf.NewContext(cfg) mainCtx, err = core.RunWorkers(mainCtx) if err != nil { log.Panic(err) } go core.CleanWorker(cfg) errc := make(chan error) go func() { errc <- interrupt() }() listener := net.JoinHostPort(cfg.Listener.Host, fmt.Sprint(cfg.Listener.Port)) cfg.L.Info.Printf("%v running (debug=%v):\n\tlisten: %v\n\tgo version: %v\n\tversion=%v [%v %v]", Name, cfg.Debug, listener, GoVersion, Version, Revision, BuildDate, ) server := &http.Server{ Addr: listener, Handler: http.DefaultServeMux, ReadTimeout: time.Duration(cfg.Listener.Timeout) * time.Second, WriteTimeout: time.Duration(cfg.Listener.Timeout) * time.Second, MaxHeaderBytes: 1 << 20, ErrorLog: cfg.L.Error, } maxSize := cfg.Settings.MaxReqSize << 20 // static files staticDir, _ := cfg.StaticDir() http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir(staticDir)))) // keys should not match to trim.IsShortURL pattern (short URLs set) handlers := map[string]Handler{ "/": {F: core.HandlerIndex, Auth: false, API: false, Method: "ANY"}, "/test/t": {F: core.HandlerTest, Auth: false, API: false, Method: "ANY"}, "/error/notfoud": {F: core.HandlerNotFound, Auth: false, API: false, Method: "GET"}, "/error/common": {F: core.HandlerError, Auth: false, API: false, Method: "GET"}, "/api/noweb": {F: core.HandlerNoWebIndex, Auth: false, API: false, Method: "ANY"}, "/api/info": {F: api.HandlerInfo, Auth: false, API: true, Method: "GET"}, "/api/add": {F: api.HandlerAdd, Auth: false, API: true, Method: "POST"}, "/api/get": {F: api.HandlerGet, Auth: false, API: true, Method: "POST"}, "/api/user/add": {F: api.HandlerUserAdd, Auth: true, API: true, Method: "POST"}, "/api/user/pwd": {F: api.HandlerPwd, Auth: true, API: true, Method: "POST"}, "/api/user/del": {F: api.HandlerUserDel, Auth: true, API: true, Method: "POST"}, "/api/import": {F: api.HandlerImport, Auth: true, API: true, Method: "POST"}, "/api/export": {F: api.HandlerExport, Auth: true, API: true, Method: "POST"}, // "/api/stats" } http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { path := "/" if r.URL.Path != path { path = strings.TrimRight(r.URL.Path, "/") } start, code, isAPI := time.Now(), http.StatusOK, false ctx, cancel := context.WithCancel(mainCtx) defer func() { cancel() switch { case code == http.StatusNotFound && !isAPI: core.HandlerNotFound(ctx, w, r) case code != http.StatusOK && !isAPI: core.HandlerError(ctx, w, r) case code != http.StatusOK: if err := api.HandlerError(w, code); err != nil { cfg.L.Error.Println(err) } } cfg.L.Info.Printf("%-5v %v\t%-12v\t%v", r.Method, code, time.Since(start), path) }() rh, ok := handlers[path] if ok { isAPI = rh.API if (rh.Method != "ANY") && (rh.Method != r.Method) { code = http.StatusMethodNotAllowed return } if r.ContentLength > maxSize { code = http.StatusRequestEntityTooLarge return } // API accepts only JSON requests if ct := r.Header.Get("Content-Type"); isAPI && !strings.HasPrefix(ct, "application/json") { code = http.StatusBadRequest return } // pre-authentication: quickly check a token value ctx, err := auth.CheckToken(ctx, r, isAPI) // anonymous request should be allowed/denied here authRequired := rh.Auth || !cfg.Settings.Anonymous if err != nil && (authRequired || err != auth.ErrAnonymous) { cfg.L.Debug.Printf("authentication error [required=%v]: %v", rh.Auth, err) code = http.StatusUnauthorized return } // open new database session s, err := db.NewSession(cfg.Conn, true) if err != nil { cfg.L.Error.Println(err) code = http.StatusInternalServerError return } defer s.Close() ctx = db.NewContext(ctx, s) // authentication ctx, err = auth.Authenticate(ctx) if err != nil { cfg.L.Error.Println(err) code = http.StatusUnauthorized return } // call a found handler if err := rh.F(ctx, w, r); err.Err != nil { cfg.L.Error.Println(err) code = err.Status return } return } else if link, ok := trim.IsShort(path); ok { // it's a short URL candidate if r.Method != "GET" { code = http.StatusMethodNotAllowed return } origURL, err := core.HandlerRedirect(ctx, link, r) switch { case err == nil: code = http.StatusFound http.Redirect(w, r, origURL, code) case err == mgo.ErrNotFound: code = http.StatusNotFound default: cfg.L.Error.Println(err) code = http.StatusInternalServerError } return } code = http.StatusNotFound }) // run server go func() { errc <- server.ListenAndServe() }() cfg.L.Info.Printf("%v termination, reason[%v]: %v [%v]\n", Name, <-errc, Version, Revision) }