From cbef1d5af17cb5ee166211f893249a390351672a Mon Sep 17 00:00:00 2001 From: Jamie Date: Fri, 16 Jan 2015 16:11:43 +1300 Subject: [PATCH 1/2] has Authenticated before allowing login return 403 otherwise --- api/helpers.go | 178 ++++++++++++++++++++++++++++++ api/shorelineApi.go | 220 ++++--------------------------------- clients/mockStoreClient.go | 5 + models/user.go | 4 + 4 files changed, 209 insertions(+), 198 deletions(-) diff --git a/api/helpers.go b/api/helpers.go index a3ba9a6a..fadcc36e 100644 --- a/api/helpers.go +++ b/api/helpers.go @@ -1,9 +1,15 @@ package api import ( + "encoding/base64" + "encoding/json" "log" "net/http" "strconv" + "strings" + + "./../models" + "github.com/tidepool-org/go-common/clients/status" ) const ( @@ -24,3 +30,175 @@ func tokenDuration(req *http.Request) (dur float64) { return dur } + +//Docode the http.Request parsing out the user details +func getUserDetail(req *http.Request) (ud *models.UserDetail) { + if req.ContentLength > 0 { + if err := json.NewDecoder(req.Body).Decode(&ud); err != nil { + log.Println("error trying to decode user detail ", err) + return ud + } + } + log.Printf("User details [%v]", ud) + return ud +} + +//Docode the http.Request parsing out the user details +func getGivenDetail(req *http.Request) (d map[string]string) { + if req.ContentLength > 0 { + if err := json.NewDecoder(req.Body).Decode(&d); err != nil { + log.Println("error trying to decode user detail ", err) + return nil + } + } + return d +} + +// Extract the username and password from the authorization +// line of an HTTP header. This function will handle the +// parsing and decoding of the line. +func unpackAuth(authLine string) (usr *models.User, pw string) { + if authLine != "" { + parts := strings.SplitN(authLine, " ", 2) + payload := parts[1] + if decodedPayload, err := base64.URLEncoding.DecodeString(payload); err != nil { + log.Print("Error unpacking authorization header [%s]", err.Error()) + } else { + details := strings.Split(string(decodedPayload), ":") + if details[0] != "" || details[1] != "" { + //Note the incoming `name` could infact be id, email or the username + return models.UserFromDetails(&models.UserDetail{Id: details[0], Name: details[0], Emails: []string{details[0]}}), details[1] + } + } + } + return nil, "" +} + +func sendModelsAsRes(res http.ResponseWriter, models ...interface{}) { + + res.Header().Set("content-type", "application/json") + res.WriteHeader(http.StatusOK) + + res.Write([]byte("[")) + for i := range models { + if jsonDetails, err := json.Marshal(models[i]); err != nil { + log.Println(err) + } else { + res.Write(jsonDetails) + } + } + res.Write([]byte("]")) + return +} + +func sendModelAsRes(res http.ResponseWriter, model interface{}) { + sendModelAsResWithStatus(res, model, http.StatusOK) + return +} + +func sendModelAsResWithStatus(res http.ResponseWriter, model interface{}, statusCode int) { + res.Header().Set("content-type", "application/json") + res.WriteHeader(statusCode) + + if jsonDetails, err := json.Marshal(model); err != nil { + log.Println(err) + } else { + res.Write(jsonDetails) + } + return +} + +func (a *Api) hasServerToken(tokenString string) bool { + + if td := a.getUnpackedToken(tokenString); td != nil { + return td.IsServer + } + return false +} + +//send metric +func (a *Api) logMetric(name, token string, params map[string]string) { + if token == "" { + log.Println("Missing token so couldn't log metric") + return + } + if params == nil { + params = make(map[string]string) + } + log.Printf("log metric name[%s] params[%v]", name, params) + //a.metrics.PostThisUser(name, token, params) + return +} + +//send metric +func (a *Api) logMetricAsServer(name, token string, params map[string]string) { + if token == "" { + log.Println("Missing token so couldn't log metric") + return + } + if params == nil { + params = make(map[string]string) + } + log.Printf("log metric as server name[%s] params[%v]", name, params) + //a.metrics.PostServer(name, token, params) + return +} + +//send metric +func (a *Api) logMetricForUser(id, name, token string, params map[string]string) { + if token == "" { + log.Println("Missing token so couldn't log metric") + return + } + if params == nil { + params = make(map[string]string) + } + log.Printf("log metric id[%s] name[%s] params[%v]", id, name, params) + //a.metrics.PostWithUser(id, name, token, params) + return +} + +//get the token from the req header +func (a *Api) getUnpackedToken(tokenString string) *models.TokenData { + if st := models.GetSessionToken(tokenString); st.Id != "" { + if td := st.UnpackAndVerify(a.Config.Secret); td != nil && td.Valid == true { + return td + } + } + return nil +} + +func (a *Api) addUserAndSendStatus(user *models.User, res http.ResponseWriter, req *http.Request) { + if err := a.Store.UpsertUser(user); err != nil { + log.Printf("addUserAndSendStatus %s err[%s]", STATUS_ERR_CREATING_USR, err.Error()) + sendModelAsResWithStatus(res, status.NewStatus(http.StatusInternalServerError, STATUS_ERR_CREATING_USR), http.StatusInternalServerError) + return + } + if sessionToken, err := a.createAndSaveToken(tokenDuration(req), user.Id, false); err != nil { + log.Printf("addUserAndSendStatus %s err[%s]", STATUS_ERR_GENERATING_TOKEN, err.Error()) + sendModelAsResWithStatus(res, status.NewStatus(http.StatusInternalServerError, STATUS_ERR_GENERATING_TOKEN), http.StatusInternalServerError) + return + } else { + a.logMetric("usercreated", sessionToken.Id, nil) + res.Header().Set(TP_SESSION_TOKEN, sessionToken.Id) + sendModelAsResWithStatus(res, user, http.StatusCreated) + return + } +} + +func (a *Api) createAndSaveToken(dur float64, id string, isServer bool) (*models.SessionToken, error) { + sessionToken, _ := models.NewSessionToken( + &models.TokenData{ + UserId: id, + IsServer: isServer, + DurationSecs: dur, + }, + a.Config.Secret, + ) + + if err := a.Store.AddToken(sessionToken); err == nil { + return sessionToken, nil + } else { + return nil, err + } +} diff --git a/api/shorelineApi.go b/api/shorelineApi.go index c399c686..6b67c989 100644 --- a/api/shorelineApi.go +++ b/api/shorelineApi.go @@ -1,12 +1,10 @@ package api import ( - "encoding/base64" "encoding/json" "log" "net/http" "strconv" - "strings" "./../clients" "./../models" @@ -46,6 +44,7 @@ const ( STATUS_ERROR_UPDATING_PW = "Error updating password" STATUS_MISSING_ID_PW = "Missing id and/or password" STATUS_NO_MATCH = "No user matched the given details" + STATUS_NOT_AUTHENTICATED = "The user hasn't authenticated this account yet" STATUS_NO_TOKEN_MATCH = "No token matched the given details" STATUS_PW_WRONG = "Wrong password" STATUS_ERR_SENDING_EMAIL = "Error sending email" @@ -94,178 +93,6 @@ func (h varsHandler) ServeHTTP(res http.ResponseWriter, req *http.Request) { h(res, req, vars) } -//Docode the http.Request parsing out the user details -func getUserDetail(req *http.Request) (ud *models.UserDetail) { - if req.ContentLength > 0 { - if err := json.NewDecoder(req.Body).Decode(&ud); err != nil { - log.Println("error trying to decode user detail ", err) - return ud - } - } - log.Printf("User details [%v]", ud) - return ud -} - -//Docode the http.Request parsing out the user details -func getGivenDetail(req *http.Request) (d map[string]string) { - if req.ContentLength > 0 { - if err := json.NewDecoder(req.Body).Decode(&d); err != nil { - log.Println("error trying to decode user detail ", err) - return nil - } - } - return d -} - -//send metric -func (a *Api) logMetric(name, token string, params map[string]string) { - if token == "" { - log.Println("Missing token so couldn't log metric") - return - } - if params == nil { - params = make(map[string]string) - } - log.Printf("log metric name[%s] params[%v]", name, params) - //a.metrics.PostThisUser(name, token, params) - return -} - -//send metric -func (a *Api) logMetricAsServer(name, token string, params map[string]string) { - if token == "" { - log.Println("Missing token so couldn't log metric") - return - } - if params == nil { - params = make(map[string]string) - } - log.Printf("log metric as server name[%s] params[%v]", name, params) - //a.metrics.PostServer(name, token, params) - return -} - -//send metric -func (a *Api) logMetricForUser(id, name, token string, params map[string]string) { - if token == "" { - log.Println("Missing token so couldn't log metric") - return - } - if params == nil { - params = make(map[string]string) - } - log.Printf("log metric id[%s] name[%s] params[%v]", id, name, params) - //a.metrics.PostWithUser(id, name, token, params) - return -} - -//get the token from the req header -func (a *Api) getUnpackedToken(tokenString string) *models.TokenData { - if st := models.GetSessionToken(tokenString); st.Id != "" { - if td := st.UnpackAndVerify(a.Config.Secret); td != nil && td.Valid == true { - return td - } - } - return nil -} - -// Extract the username and password from the authorization -// line of an HTTP header. This function will handle the -// parsing and decoding of the line. -func unpackAuth(authLine string) (usr *models.User, pw string) { - if authLine != "" { - parts := strings.SplitN(authLine, " ", 2) - payload := parts[1] - if decodedPayload, err := base64.URLEncoding.DecodeString(payload); err != nil { - log.Print("Error unpacking authorization header [%s]", err.Error()) - } else { - details := strings.Split(string(decodedPayload), ":") - if details[0] != "" || details[1] != "" { - //Note the incoming `name` could infact be id, email or the username - return models.UserFromDetails(&models.UserDetail{Id: details[0], Name: details[0], Emails: []string{details[0]}}), details[1] - } - } - } - return nil, "" -} - -func sendModelsAsRes(res http.ResponseWriter, models ...interface{}) { - - res.Header().Set("content-type", "application/json") - res.WriteHeader(http.StatusOK) - - res.Write([]byte("[")) - for i := range models { - if jsonDetails, err := json.Marshal(models[i]); err != nil { - log.Println(err) - } else { - res.Write(jsonDetails) - } - } - res.Write([]byte("]")) - return -} - -func sendModelAsRes(res http.ResponseWriter, model interface{}) { - sendModelAsResWithStatus(res, model, http.StatusOK) - return -} - -func sendModelAsResWithStatus(res http.ResponseWriter, model interface{}, statusCode int) { - res.Header().Set("content-type", "application/json") - res.WriteHeader(statusCode) - - if jsonDetails, err := json.Marshal(model); err != nil { - log.Println(err) - } else { - res.Write(jsonDetails) - } - return -} - -func (a *Api) addUserAndSendStatus(user *models.User, res http.ResponseWriter, req *http.Request) { - if err := a.Store.UpsertUser(user); err != nil { - log.Printf("addUserAndSendStatus %s err[%s]", STATUS_ERR_CREATING_USR, err.Error()) - sendModelAsResWithStatus(res, status.NewStatus(http.StatusInternalServerError, STATUS_ERR_CREATING_USR), http.StatusInternalServerError) - return - } - if sessionToken, err := a.createAndSaveToken(tokenDuration(req), user.Id, false); err != nil { - log.Printf("addUserAndSendStatus %s err[%s]", STATUS_ERR_GENERATING_TOKEN, err.Error()) - sendModelAsResWithStatus(res, status.NewStatus(http.StatusInternalServerError, STATUS_ERR_GENERATING_TOKEN), http.StatusInternalServerError) - return - } else { - a.logMetric("usercreated", sessionToken.Id, nil) - res.Header().Set(TP_SESSION_TOKEN, sessionToken.Id) - sendModelAsResWithStatus(res, user, http.StatusCreated) - return - } -} - -func (a *Api) hasServerToken(tokenString string) bool { - - if td := a.getUnpackedToken(tokenString); td != nil { - return td.IsServer - } - return false -} - -func (a *Api) createAndSaveToken(dur float64, id string, isServer bool) (*models.SessionToken, error) { - sessionToken, _ := models.NewSessionToken( - &models.TokenData{ - UserId: id, - IsServer: isServer, - DurationSecs: dur, - }, - a.Config.Secret, - ) - - if err := a.Store.AddToken(sessionToken); err == nil { - return sessionToken, nil - } else { - return nil, err - } -} - func (a *Api) GetStatus(res http.ResponseWriter, req *http.Request) { if err := a.Store.Ping(); err != nil { log.Println(err) @@ -467,17 +294,6 @@ func (a *Api) GetUserInfo(res http.ResponseWriter, req *http.Request, vars map[s a.logMetric("getuserinfo", req.Header.Get(TP_SESSION_TOKEN), map[string]string{"server": "false"}) } - /* - TODO: sort this out - if len(results) == 1 && usr.Pw != "" { - if results[0].PwsMatch(usr, a.Config.Salt) { - sendModelAsRes(res, results[0]) - return - } else { - res.WriteHeader(http.StatusNoContent) - return - } - } else*/ if len(results) == 1 { log.Printf("found user [%v]", results[0]) sendModelAsRes(res, results[0]) @@ -546,6 +362,7 @@ func (a *Api) DeleteUser(res http.ResponseWriter, req *http.Request, vars map[st // status: 200 TP_SESSION_TOKEN, // status: 400 STATUS_MISSING_ID_PW // status: 401 STATUS_NO_MATCH +// status: 403 STATUS_NOT_AUTHENTICATED // status: 500 STATUS_ERR_FINDING_USR // status: 500 STATUS_ERR_UPDATING_TOKEN func (a *Api) Login(res http.ResponseWriter, req *http.Request) { @@ -559,20 +376,27 @@ func (a *Api) Login(res http.ResponseWriter, req *http.Request) { if len(results) > 0 { for i := range results { if results[i] != nil && results[i].PwsMatch(pw, a.Config.Salt) { - if sessionToken, err := a.createAndSaveToken( - tokenDuration(req), - results[i].Id, - false, - ); err != nil { - log.Printf("Login %s [%s]", STATUS_ERR_UPDATING_TOKEN, err.Error()) - sendModelAsResWithStatus(res, status.NewStatus(http.StatusInternalServerError, STATUS_ERR_UPDATING_TOKEN), http.StatusInternalServerError) - return - } else { - a.logMetric("userlogin", sessionToken.Id, nil) - res.Header().Set(TP_SESSION_TOKEN, sessionToken.Id) - sendModelAsRes(res, results[i]) - return + + if results[i].IsAuthenticated() { + + if sessionToken, err := a.createAndSaveToken( + tokenDuration(req), + results[i].Id, + false, + ); err != nil { + log.Printf("Login %s [%s]", STATUS_ERR_UPDATING_TOKEN, err.Error()) + sendModelAsResWithStatus(res, status.NewStatus(http.StatusInternalServerError, STATUS_ERR_UPDATING_TOKEN), http.StatusInternalServerError) + return + } else { + a.logMetric("userlogin", sessionToken.Id, nil) + res.Header().Set(TP_SESSION_TOKEN, sessionToken.Id) + sendModelAsRes(res, results[i]) + return + } } + log.Printf("Login %s for [%s]", STATUS_NOT_AUTHENTICATED, usr.Id) + sendModelAsResWithStatus(res, status.NewStatus(http.StatusForbidden, STATUS_NOT_AUTHENTICATED), http.StatusForbidden) + return } log.Printf("Login %s [%s] from the [%d] users we found", STATUS_NO_MATCH, usr.Name, len(results)) sendModelAsResWithStatus(res, status.NewStatus(http.StatusUnauthorized, STATUS_NO_MATCH), http.StatusUnauthorized) diff --git a/clients/mockStoreClient.go b/clients/mockStoreClient.go index 7682ccfb..76d30795 100644 --- a/clients/mockStoreClient.go +++ b/clients/mockStoreClient.go @@ -44,9 +44,11 @@ func (d MockStoreClient) FindUsers(user *models.User) (found []*models.User, err if user.Name != "" { found, _ := models.NewUser(&models.UserDetail{Name: user.Name, Pw: "123youknoWm3", Emails: []string{}}, d.salt) + found.Authenticated = true return []*models.User{found}, nil } + user.Authenticated = true return []*models.User{user}, nil @@ -61,13 +63,16 @@ func (d MockStoreClient) FindUser(user *models.User) (found *models.User, err er if d.returnDifferent { other, _ := models.NewUser(&models.UserDetail{Name: "Some One Else", Pw: "123youknoWm3", Emails: []string{}}, d.salt) + other.Authenticated = true return other, nil } if user.Name != "" { found, _ := models.NewUser(&models.UserDetail{Name: user.Name, Pw: "123youknoWm3", Emails: []string{}}, d.salt) + found.Authenticated = true return found, nil } + user.Authenticated = true return user, nil } diff --git a/models/user.go b/models/user.go index 11f66ebc..4be2aa49 100644 --- a/models/user.go +++ b/models/user.go @@ -73,3 +73,7 @@ func (u *User) PwsMatch(pw, salt string) bool { } return false } + +func (u *User) IsAuthenticated() bool { + return u.Authenticated +} From b1a5b44cb8e39bd05cfd6281afd63f16efed68f4 Mon Sep 17 00:00:00 2001 From: Jamie Date: Wed, 21 Jan 2015 14:20:48 +1300 Subject: [PATCH 2/2] update from review also updated go-common version to latest --- Comedeps | 2 +- api/shorelineApi.go | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Comedeps b/Comedeps index fbf7cc80..7215536a 100644 --- a/Comedeps +++ b/Comedeps @@ -2,4 +2,4 @@ labix.org/v2/mgo bzr https://launchpad.net/mgo/v2 287 github.com/gorilla/mux git https://github.com/gorilla/mux.git 14cafe28513321476c73967a5a4f3454b6129c46 github.com/gorilla/context git https://github.com/gorilla/context.git 14f550f51af52180c2eefed15e5fd18d63c0a64a github.com/dgrijalva/jwt-go git https://github.com/dgrijalva/jwt-go.git v1.0.1 -github.com/tidepool-org/go-common git https://github.com/tidepool-org/go-common.git v0.0.8 +github.com/tidepool-org/go-common git https://github.com/tidepool-org/go-common.git v0.0.9 diff --git a/api/shorelineApi.go b/api/shorelineApi.go index 6b67c989..648b5677 100644 --- a/api/shorelineApi.go +++ b/api/shorelineApi.go @@ -44,7 +44,7 @@ const ( STATUS_ERROR_UPDATING_PW = "Error updating password" STATUS_MISSING_ID_PW = "Missing id and/or password" STATUS_NO_MATCH = "No user matched the given details" - STATUS_NOT_AUTHENTICATED = "The user hasn't authenticated this account yet" + STATUS_NOT_VERIFIED = "The user hasn't verified this account yet" STATUS_NO_TOKEN_MATCH = "No token matched the given details" STATUS_PW_WRONG = "Wrong password" STATUS_ERR_SENDING_EMAIL = "Error sending email" @@ -362,7 +362,7 @@ func (a *Api) DeleteUser(res http.ResponseWriter, req *http.Request, vars map[st // status: 200 TP_SESSION_TOKEN, // status: 400 STATUS_MISSING_ID_PW // status: 401 STATUS_NO_MATCH -// status: 403 STATUS_NOT_AUTHENTICATED +// status: 403 STATUS_NOT_VERIFIED // status: 500 STATUS_ERR_FINDING_USR // status: 500 STATUS_ERR_UPDATING_TOKEN func (a *Api) Login(res http.ResponseWriter, req *http.Request) { @@ -394,8 +394,8 @@ func (a *Api) Login(res http.ResponseWriter, req *http.Request) { return } } - log.Printf("Login %s for [%s]", STATUS_NOT_AUTHENTICATED, usr.Id) - sendModelAsResWithStatus(res, status.NewStatus(http.StatusForbidden, STATUS_NOT_AUTHENTICATED), http.StatusForbidden) + log.Printf("Login %s for [%s]", STATUS_NOT_VERIFIED, usr.Id) + sendModelAsResWithStatus(res, status.NewStatus(http.StatusForbidden, STATUS_NOT_VERIFIED), http.StatusForbidden) return } log.Printf("Login %s [%s] from the [%d] users we found", STATUS_NO_MATCH, usr.Name, len(results))