This is an automated email from the git hooks/post-receive script. New commit to branch bow-v2-go in repository bow. See https://gitlab.nuiton.org/chorem/bow.git commit 8fefd8e248efb10e053426435678c1ae12329150 Author: Benjamin <poussin@codelutin.com> Date: Mon May 18 00:05:33 2020 +0200 ajout de la notion d'admin qui est le seul a pouvoir demander les stats --- migrate/001_init_schema.sql | 3 ++- pkg/constant/const.go | 5 +++++ pkg/http/router.go | 17 +++++++++++++---- pkg/http/userResource.go | 2 +- pkg/model/user.go | 15 ++++++++------- pkg/repository/userRepository.go | 8 ++++---- pkg/utils/stats.go | 27 +++++++++++++-------------- pkg/utils/utils.go | 2 +- 8 files changed, 47 insertions(+), 32 deletions(-) diff --git a/migrate/001_init_schema.sql b/migrate/001_init_schema.sql index bdec668..81b09c7 100644 --- a/migrate/001_init_schema.sql +++ b/migrate/001_init_schema.sql @@ -41,6 +41,7 @@ CREATE TABLE bowUser ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), creationDate TIMESTAMP WITH TIME ZONE DEFAULT current_timestamp, updateDate TIMESTAMP WITH TIME ZONE DEFAULT current_timestamp, + admin BOOLEAN DEFAULT false, login TEXT, password TEXT, tokens jsonb, -- [{name: sring, token: string, expire: date}] @@ -166,7 +167,7 @@ CREATE USER nobody WITH NOINHERIT CREATEROLE LOGIN PASSWORD '{{.nobody_password} CREATE ROLE person; -- l'utilisateur nobody a le droit d'inserer des users (creation de compte) et c'est lui qui visite les pages -GRANT INSERT ON bowUser TO nobody; +GRANT SELECT, INSERT ON bowUser TO nobody; GRANT INSERT ON pageHistory TO nobody; -- l'utilisateur nobody a le droit de lire les alias public et l'uri pour permettre la recheche et donc l'utilisation des alias public GRANT SELECT(uri, publicAlias) ON bookmark TO nobody; diff --git a/pkg/constant/const.go b/pkg/constant/const.go index b55f6ba..2f870f9 100644 --- a/pkg/constant/const.go +++ b/pkg/constant/const.go @@ -21,6 +21,11 @@ Token le nom utiliser pour le token dans les cookies et les parameters de query */ const Token = "bow-token" +/* +AuthFields la liste des champs necessaire pour le token d'authentification +*/ +var AuthFields = []string{"id", "admin", "login", "creationdate", "maxtagincloud", "maxresult"} + /* TokenHeader le nom utiliser pour mettre dans le header de requete http (fallback Authorization) */ diff --git a/pkg/http/router.go b/pkg/http/router.go index c7c7c81..33d0fea 100644 --- a/pkg/http/router.go +++ b/pkg/http/router.go @@ -126,11 +126,15 @@ func withoutAuthenticationEndpoint(r *http.Request) bool { result := !strings.HasPrefix(r.URL.Path, "/api") || // no auth for SPA (html, css, ...) strings.HasSuffix(r.URL.Path, "/auth") || // no auth to create/delete auth strings.HasSuffix(r.URL.Path, "/system/liveness") || // no auth to test if server is up - strings.HasSuffix(r.URL.Path, "/system/stats") || // no auth to test if server is up strings.HasSuffix(r.URL.Path, "/users") // no auth for creation of user return result } +func needAdminEndpoint(r *http.Request) bool { + result := strings.HasSuffix(r.URL.Path, "/system/stats") + return result +} + func convertCurrentToId(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { id := mux.Vars(r)["id"] @@ -150,7 +154,7 @@ func authentication(next http.Handler) http.Handler { return } - authStat := stats.StartStat("authentication") + authStat := stats.StartStat("authentication", r.URL.String()) canBeAppToken := false @@ -196,7 +200,7 @@ func authentication(next http.Handler) http.Handler { user, err = utils.JwtVerify(token) if err != nil && canBeAppToken { // try as application token - user, err = repository.UserFromToken(token, "id", "login", "creationdate", "maxtagincloud", "maxresult", "actions") + user, err = repository.UserFromToken(token, constant.AuthFields...) if err != nil { utils.Throw(w, utils.NewHTTPError500(err, constant.Nobody)) return @@ -219,8 +223,13 @@ func authentication(next http.Handler) http.Handler { r = r.WithContext(ctx) authStat.Stop() + if needAdminEndpoint(r) && !user.Admin { + utils.Throw(w, utils.NewHTTPError("Need admin user", user, 403)) + return + } + routeName := getRouteName(r) - restStat := stats.StartStat(fmt.Sprintf("rest(%v)", routeName)) + restStat := stats.StartStat(fmt.Sprintf("rest(%v)", routeName), r.URL.String()) next.ServeHTTP(w, r) restStat.Stop() }) diff --git a/pkg/http/userResource.go b/pkg/http/userResource.go index 9c5b75a..ee5b317 100644 --- a/pkg/http/userResource.go +++ b/pkg/http/userResource.go @@ -42,7 +42,7 @@ func createAuth(w http.ResponseWriter, r *http.Request) { pseudoUser := model.BowUser{ID: id} // on ne met plus les actions dans le cookie car il fini par etre trop gros et plus accepte par les navigateur (>4ko) - userJSON, err := repository.UserJSON(pseudoUser, id, "id", "login", "creationdate", "maxtagincloud", "maxresult") + userJSON, err := repository.UserJSON(pseudoUser, id, constant.AuthFields...) if err != nil { utils.Throw(w, utils.NewHTTPError500(err, pseudoUser)) return diff --git a/pkg/model/user.go b/pkg/model/user.go index 235fda6..41e913b 100644 --- a/pkg/model/user.go +++ b/pkg/model/user.go @@ -14,12 +14,13 @@ type BowUser struct { ID string `json:"id,omitempty"` CreationDate time.Time `json:"creationdate,omitempty"` UpdateDate time.Time `json:"updatedate,omitempty"` + Admin bool `json:"admin,omitempty"` Login string `json:"login,omitempty"` Password string `json:"password,omitempty"` Tokens []Token `json:"tokens,omitempty"` Emails []string `json:"emails,omitempty"` UnconfirmedEmails []UnconfirmedEmails `json:"unconfirmedemails,omitempty"` - AuthenticationInfo AuthenticationInfo `json:"authenticationinfo,omitempty"` + AuthenticationInfo *AuthenticationInfo `json:"authenticationinfo,omitempty"` AutoScreenshot bool `json:"autoscreenshot"` AutoFavicon bool `json:"autofavicon"` MaxTagInCloud int16 `json:"maxtagincloud,omitempty"` @@ -28,15 +29,15 @@ type BowUser struct { } type Token struct { - Name string - Token string - Expiration time.Time + Name string `json:"name,omitempty"` + Token string `json:"token,omitempty"` + Expiration *time.Time `json:"expiration,omitempty"` } type UnconfirmedEmails struct { - Email string - Token string - CreationDate time.Time + Email string `json:"email,omitempty"` + Token string `json:"token,omitempty"` + CreationDate time.Time `json:"creationdate,omitempty"` } /* diff --git a/pkg/repository/userRepository.go b/pkg/repository/userRepository.go index 24d4acb..cf5a448 100644 --- a/pkg/repository/userRepository.go +++ b/pkg/repository/userRepository.go @@ -23,7 +23,7 @@ all field are send except: func UserJSON(currentUser model.BowUser, id string, fields ...string) (string, error) { var askedFields string if len(fields) == 0 { - askedFields = "id, creationdate, updatedate, login, tokens, emails, unconfirmedemails, authenticationinfo, autoscreenshot, autofavicon, maxtagincloud, maxresult, actions" + askedFields = "id, creationdate, updatedate, admin, login, tokens, emails, unconfirmedemails, authenticationinfo, autoscreenshot, autofavicon, maxtagincloud, maxresult, actions" } else { askedFields = strings.Join(fields, ", ") } @@ -77,7 +77,7 @@ func UserFromToken(token string, fields ...string) (model.BowUser, error) { var allFields string if len(fields) == 0 { - allFields = "id, creationdate, updatedate, login, tokens, emails, authenticationinfo, autoscreenshot, autofavicon, maxtagincloud, maxresult, actions" + allFields = "id, creationdate, updatedate, admin, login, tokens, emails, authenticationinfo, autoscreenshot, autofavicon, maxtagincloud, maxresult, actions" } else { allFields = strings.Join(fields, ",") } @@ -157,7 +157,7 @@ func CreateUser(login string, password string) (string, error) { } currentUser := model.BowUser{ - ID: uuid, Login: login, Password: hashPassword, MaxTagInCloud: 20, MaxResult: 20, AutoFavicon: false, AutoScreenshot: false, AuthenticationInfo: model.AuthenticationInfo{DomainComponent: 2, MaxLength: 15}} + ID: uuid, Login: login, Password: hashPassword, MaxTagInCloud: 20, MaxResult: 20, AutoFavicon: false, AutoScreenshot: false, AuthenticationInfo: &model.AuthenticationInfo{DomainComponent: 2, MaxLength: 15}} userAsJSON, err := json.Marshal(currentUser) if err != nil { return "", utils.NewHTTPError500(err, currentUser) @@ -203,7 +203,7 @@ func UpdateUserPassword(currentUser model.BowUser, id string, password string, o /* AddUserToken ajout un tocken d'authentification pour l'utilisateur */ -func AddUserToken(currentUser model.BowUser, id string, name string, expiration time.Time) (string, error) { +func AddUserToken(currentUser model.BowUser, id string, name string, expiration *time.Time) (string, error) { token, err := utils.GenUUID() if err != nil { return "", err diff --git a/pkg/utils/stats.go b/pkg/utils/stats.go index 0d282d5..0d37308 100644 --- a/pkg/utils/stats.go +++ b/pkg/utils/stats.go @@ -1,9 +1,8 @@ package utils import ( - "fmt" + "encoding/json" "math" - "strings" "sync" "time" ) @@ -11,15 +10,15 @@ import ( type Stat struct { Nb int64 `json:"call"` Min time.Duration `json:"min"` + MinInfo string Max time.Duration `json:"max"` + MaxInfo string Avg time.Duration `json:"avg"` StdDeviation time.Duration `json:"stddeviation"` - variance float64 - // pour l'ecart type (standard deviation) delta int64 - M2 int64 + m2 int64 // pour que les requetes concurrente de corrompe pas les donnees sync.Mutex @@ -34,6 +33,7 @@ type Stats struct { type OneCall struct { name string + info string start time.Time all *Stats } @@ -43,15 +43,12 @@ func NewStats() *Stats { } func (all *Stats) String() string { - var result []string - for name, s := range all.Values { - result = append(result, fmt.Sprintf(`%v: {"call": %v, "min": %q, "max": %q, "avg": %q, "stdderivation": %v}`, name, s.Nb, s.Min, s.Max, s.Avg, s.StdDeviation)) - } - return fmt.Sprintf("{%v}", strings.Join(result, ",")) + result, _ := json.Marshal(all) + return string(result) } -func (s *Stats) StartStat(name string) *OneCall { - return &OneCall{name: name, start: time.Now(), all: s} +func (s *Stats) StartStat(name string, info string) *OneCall { + return &OneCall{name: name, info: info, start: time.Now(), all: s} } func (call *OneCall) Stop() { @@ -73,16 +70,18 @@ func (call *OneCall) Stop() { s.Nb++ if s.Min == 0 || s.Min > duration { s.Min = duration + s.MinInfo = call.info } if s.Max < duration { s.Max = duration + s.MaxInfo = call.info } s.delta = duration.Nanoseconds() - s.Avg.Nanoseconds() s.Avg = time.Duration(s.Avg.Nanoseconds() + s.delta/s.Nb) - s.M2 = s.M2 + s.delta*(duration.Nanoseconds()-s.Avg.Nanoseconds()) + s.m2 = s.m2 + s.delta*(duration.Nanoseconds()-s.Avg.Nanoseconds()) - variance := float64(s.M2) / math.Max(1, float64(s.Nb-1)) + variance := float64(s.m2) / math.Max(1, float64(s.Nb-1)) s.StdDeviation = time.Duration(int64(math.Sqrt(variance))) } diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go index 4cffc0d..19338ef 100644 --- a/pkg/utils/utils.go +++ b/pkg/utils/utils.go @@ -12,7 +12,7 @@ RemoveTagReturn remove all \n and \t in the string func RemoveTagReturn(s string) string { return strings.Map(func(c rune) rune { if c == '\n' || c == '\t' { - return -1 + return ' ' } return c }, s) -- To stop receiving notification emails like this one, please contact chorem.org SCM administrator <admin+scm@chorem.org>.