package main import ( "crypto/hmac" "crypto/sha512" "encoding/base64" "encoding/json" "errors" "fmt" "io" "log" "net/http" "strconv" "time" "github.com/julienschmidt/httprouter" "git.nemunai.re/srs/adlin/libadlin" ) var router = httprouter.New() func Router() *httprouter.Router { return router } type DispatchFunction func(httprouter.Params, []byte) (interface{}, error) func remoteValidatorHandler(f func(http.ResponseWriter, *http.Request, httprouter.Params)) func(http.ResponseWriter, *http.Request, httprouter.Params) { return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { expectedMAC := hmac.New(sha512.New, []byte(adlin.SharedSecret)).Sum([]byte(fmt.Sprintf("%d", time.Now().Unix()/10))) previousMAC := hmac.New(sha512.New, []byte(adlin.SharedSecret)).Sum([]byte(fmt.Sprintf("%d", time.Now().Unix()/10-1))) if aauth, err := base64.StdEncoding.DecodeString(r.Header.Get("X-ADLIN-Authentication")); err != nil { http.Error(w, fmt.Sprintf("{\"errmsg\":%q}\n", err), http.StatusUnauthorized) } else if !hmac.Equal(expectedMAC, aauth) && !hmac.Equal(previousMAC, aauth) { http.Error(w, fmt.Sprintf("{\"errmsg\":%q}\n", "Bad authentication header"), http.StatusUnauthorized) } else { f(w, r, ps) } } } func rawHandler(f func(http.ResponseWriter, *http.Request, httprouter.Params, []byte), access ...func(*adlin.Student, *http.Request) error) func(http.ResponseWriter, *http.Request, httprouter.Params) { return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { if addr := r.Header.Get("X-Forwarded-For"); addr != "" { r.RemoteAddr = addr } log.Printf("%s \"%s %s\" [%s]\n", r.RemoteAddr, r.Method, r.URL.Path, r.UserAgent()) w.Header().Set("Content-Type", "application/json") // Read Authorization header var student *adlin.Student = nil if cookie, err := r.Cookie("auth"); err == nil { if sessionid, err := base64.StdEncoding.DecodeString(cookie.Value); err != nil { http.Error(w, fmt.Sprintf(`{"errmsg": %q}`, err), http.StatusNotAcceptable) return } else if session, err := adlin.GetSession(sessionid); err != nil { http.Error(w, fmt.Sprintf(`{"errmsg": %q}`, err), http.StatusUnauthorized) return } else if session.IdStudent == nil { student = nil } else if std, err := adlin.GetStudent(int(*session.IdStudent)); err != nil { http.Error(w, fmt.Sprintf(`{"errmsg": %q}`, err), http.StatusUnauthorized) return } else { student = std } } // Check access limitation for _, a := range access { if err := a(student, r); err != nil { http.Error(w, fmt.Sprintf(`{"errmsg":%q}`, err), http.StatusForbidden) return } } // Read the body if r.ContentLength < 0 || r.ContentLength > 6553600 { http.Error(w, "{errmsg:\"Request too large or request size unknown\"}", http.StatusRequestEntityTooLarge) return } var body []byte if r.ContentLength > 0 { tmp := make([]byte, 1024) for { n, err := r.Body.Read(tmp) for j := 0; j < n; j++ { body = append(body, tmp[j]) } if err != nil || n <= 0 { break } } } f(w, r, ps, body) } } func responseHandler(f func(*http.Request, httprouter.Params, []byte) (interface{}, error)) func(http.ResponseWriter, *http.Request, httprouter.Params, []byte) { return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params, body []byte) { ret, err := f(r, ps, body) // Format response resStatus := http.StatusOK if err != nil { ret = map[string]string{"errmsg": err.Error()} resStatus = http.StatusBadRequest log.Println(r.RemoteAddr, resStatus, err.Error()) } if ret == nil { ret = map[string]string{"errmsg": "Page not found"} resStatus = http.StatusNotFound } if str, found := ret.(string); found { w.WriteHeader(resStatus) io.WriteString(w, str) w.Write([]byte("\n")) } else if bts, found := ret.([]byte); found { w.WriteHeader(resStatus) w.Write(bts) } else if j, err := json.Marshal(ret); err != nil { http.Error(w, fmt.Sprintf("{\"errmsg\":%q}\n", err), http.StatusInternalServerError) } else { w.WriteHeader(resStatus) w.Write(j) w.Write([]byte("\n")) } } } func challengeHandler(f func(*http.Request, []byte, int) (interface{}, error)) func(*http.Request, httprouter.Params, []byte) (interface{}, error) { return func(r *http.Request, ps httprouter.Params, body []byte) (interface{}, error) { return f(r, body, 0) } } func definedChallengeHandler(f func(*http.Request, []byte, int) (interface{}, error), chid int) func(*http.Request, httprouter.Params, []byte) (interface{}, error) { return func(r *http.Request, ps httprouter.Params, body []byte) (interface{}, error) { return f(r, body, chid) } } func apiRawHandler(f func(http.ResponseWriter, httprouter.Params, []byte) (interface{}, error), access ...func(*adlin.Student, *http.Request) error) func(http.ResponseWriter, *http.Request, httprouter.Params) { return rawHandler(func(w http.ResponseWriter, r *http.Request, ps httprouter.Params, b []byte) { responseHandler(func(_ *http.Request, ps httprouter.Params, b []byte) (interface{}, error) { return f(w, ps, b) })(w, r, ps, b) }, access...) } func apiHandler(f DispatchFunction, access ...func(*adlin.Student, *http.Request) error) func(http.ResponseWriter, *http.Request, httprouter.Params) { return rawHandler(responseHandler(func(_ *http.Request, ps httprouter.Params, b []byte) (interface{}, error) { return f(ps, b) }), access...) } func apiAuthHandler(f func(*adlin.Student, httprouter.Params, []byte) (interface{}, error), access ...func(*adlin.Student, *http.Request) error) func(http.ResponseWriter, *http.Request, httprouter.Params) { return rawHandler(responseHandler(func(r *http.Request, ps httprouter.Params, b []byte) (interface{}, error) { if cookie, err := r.Cookie("auth"); err != nil { return nil, errors.New("Authorization required") } else if sessionid, err := base64.StdEncoding.DecodeString(cookie.Value); err != nil { return nil, err } else if session, err := adlin.GetSession(sessionid); err != nil { return nil, err } else if session.IdStudent == nil { return nil, errors.New("Authorization required") } else if std, err := adlin.GetStudent(int(*session.IdStudent)); err != nil { return nil, err } else { return f(std, ps, b) } }), access...) } func studentHandler(f func(*adlin.Student, []byte) (interface{}, error)) func(httprouter.Params, []byte) (interface{}, error) { return func(ps httprouter.Params, body []byte) (interface{}, error) { if sid, err := strconv.Atoi(string(ps.ByName("sid"))); err != nil { if student, err := adlin.GetStudentByLogin(ps.ByName("sid")); err != nil { return nil, err } else { return f(student, body) } } else if student, err := adlin.GetStudent(sid); err != nil { return nil, err } else { return f(student, body) } } }