From 041b641e17a295fd2c32a6e5d5c6a520af6a9157 Mon Sep 17 00:00:00 2001 From: afeiszli Date: Fri, 2 Jul 2021 00:03:46 -0400 Subject: [PATCH] multitenancy working --- controllers/.userHttpController.go.swp | Bin 0 -> 16384 bytes controllers/networkHttpController.go | 11 +- controllers/nodeHttpController.go | 22 +++- controllers/serverHttpController.go | 2 +- controllers/userHttpController.go | 151 ++++++++++++++++++++++--- functions/helpers.go | 10 ++ functions/jwt.go | 13 ++- models/structs.go | 2 + 8 files changed, 184 insertions(+), 27 deletions(-) create mode 100644 controllers/.userHttpController.go.swp diff --git a/controllers/.userHttpController.go.swp b/controllers/.userHttpController.go.swp new file mode 100644 index 0000000000000000000000000000000000000000..f3d93afa76ec991b8b2c3fb4da3b08e99492fa2c GIT binary patch literal 16384 zcmeHNL2M*P6)i{>m<_uLa6 z>T27=%8&!f!fAtqLT({Gazq)>P>-5F>1^%En!SK0@vG~fHU;X6A6?^=FBP>Y7HWeTA*^+7_ z(}9jbCMVDl)pAo|L>yyn`Qf=gR-B074;7l_18!0i{kpLW}s%E zW}s%EW}s%EW}s%EW}s%EW}s%EW}s%^T`&;fz(0m|uhDrM_y6Vj|L=D)_BY^d;CH|; zfu92}11|#41K$Il1rlHxXactZZybjl@Esrm&H*#PG{AvR0lz=S*mr?h;7=cBEC-GQ zFWkY{9Prnpj9ml%0z3=M19QMX8;t!3_%`rm;5cvxa1?m!LyWxyJO?}l%mH5n9s)iO z+zGt%LB_5EKLmaNyZ}50%mddx0K0%UfY*T+foFgP;1j@6;Mx(!{s_DYyaId+xC|hT z!X5;CU>x|z?Tq~jcoldB*Z~AE1AGLyej7Lfehl0PTt@=tdEhKC1xx@OcpdXXbMxCG zA9A=UZtSo|LkXR$lsnEi7fMN$d5dDpbuQo?3-0hw^B~vj%i_sgWZI*`#h@!t?C?}- z-j;b9IZf7x+tiw$IK$I8;g?t=5^X35gESP0+hnY<87L!ihKKYTM@a91c<16YI-d$R z)Z62{m7!$el0&a|r0B(&+_UqYrZ*!(8433uC9*ux=-#xqo$*QPA=P4Aduy>?UyZv$ z=GraG&IaK|N0HrbbDU3{9@7x;R-iGBNK<7TDeB6!BTGfkeA>Qk%zp1_K6KzKq^0cZA~fd3i_D|V_Mv$V2G z?QW@i9HTW0t1z$>K=67&|^B374g2-a??V^&lz7Hl&!`GL3i(tANAH z_`_?f)>U@KVQr(Flu3TW{FlW>h8SLz8zPC?~JUjmgYRHKS zmdY+UEo9?Qg8*;t* zVvbjY!t}yMsmA%Jo6qZeGZW7HGRHLHAM8Bj&Bfk02oVF1HL6M?uy#mv$n?^&X_?Uq zv5}8D;&hED3LVlx8p{|(n`#zk zI6DBg3nJXcu!iS`(gOb}<2b%<#Qz zr-OpI?=NCmn+{5K8P7A?M`PI8D|b0sQCN{G7MlY0P|P!1a%q_f-yZD^6j11+yg9Sj z&SC3u9Y5&B=B3jNA-`-S4CPVRHC9=1W#l_TQ(e|DVLKb`X!pzvdby=8FbIEmQ4}4OPHhMpxYB|2kUC2(e#I%Y+5a%| zI34oILn&GhvQi6vj;gS3ZI?B9aIp?DSMjlKzU?jJ(HpZ%PhRkTrz4Xf?RZjk{B4-n z?`i3`%s1iZni};u%fe*$;RmfAlBi!+)_&)$Y0j>fs0hfWN0S#op7(i$KBf#yz&v35)DR3Eh z8fXCl@CYyod=B_5a1U@da2IeKI0pO%d4OL4&j43|r+_vffCqrj04IU#$Pv5+yb4I5 z4TL}oxEnYLIKZDVp0|Nt11|$tf$sxd;4xqgSOqB8Kx3Z;>QBu;%|OjS%|OjS%|OjS z%|Ok-;TSNvXLH+v2l688ij-IbPwCzg64g3MU4dg#gYFByf&?3-qZAHI$j>X~xz*#p zchMdMa27K8A*8yI8XC>gu?AX^icRE4RUT@WPH51-X$s4;gt_lQV#}u4ZK*kvyRs*g z$&K(bk4_4PQ%+4X4942TdYL(!r9%eNI$KT8{&agLA^+ImYUgotg=7;Ak%yeZA<~3f zNCk~k;%3j>>x@ZSAg@{^f028J-rc#(!3D>OLXx;EGQZUiLu;9ca`)_cG_$*#{z8A1 zDYYijX?_nvhyqIPS*j=jh~x7`scPAKnyChytRAZu8qw0QQos-&f{L;Y2kfh1TZ5N`Pb zv1BA7HSGYGa>Ww0l$bfAK8v-9%dVD`iJ?Z?gv%hEdGm!y8k)@HJn?7HqAal&8SKCK C#sDq= literal 0 HcmV?d00001 diff --git a/controllers/networkHttpController.go b/controllers/networkHttpController.go index 4e5f3886..a0174715 100644 --- a/controllers/networkHttpController.go +++ b/controllers/networkHttpController.go @@ -78,8 +78,15 @@ func SecurityCheck(netname, token string) error { } //all endpoints here require master so not as complicated if !hasBearer || !authenticateMaster(authToken) { - _, isadmin, err := functions.VerifyUserToken(authToken) - if err != nil || !isadmin { + _, networks, isadmin, err := functions.VerifyUserToken(authToken) + if err != nil { + return errors.New("Error verifying user token") + } + if !isadmin && netname != ""{ + if !functions.SliceContains(networks, netname){ + return errors.New("You are unauthorized to access this endpoint") + } + } else if !isadmin { return errors.New("You are unauthorized to access this endpoint") } } diff --git a/controllers/nodeHttpController.go b/controllers/nodeHttpController.go index 6d7c4d71..d12bcb43 100644 --- a/controllers/nodeHttpController.go +++ b/controllers/nodeHttpController.go @@ -186,8 +186,9 @@ func authorize(networkCheck bool, authNetwork string, next http.Handler) http.Ha var isAuthorized = false var macaddress = "" - _, isadmin, errN := functions.VerifyUserToken(authToken) - if errN == nil && isadmin { + _, networks, isadmin, errN := functions.VerifyUserToken(authToken) + isnetadmin := isadmin + if errN == nil && isadmin { macaddress = "mastermac" isAuthorized = true } else { @@ -201,6 +202,11 @@ func authorize(networkCheck bool, authNetwork string, next http.Handler) http.Ha } macaddress = mac } + if !isadmin && params["network"] != ""{ + if functions.SliceContains(networks, params["network"]){ + isnetadmin = true + } + } //The mastermac (login with masterkey from config) can do everything!! May be dangerous. if macaddress == "mastermac" { isAuthorized = true @@ -212,8 +218,11 @@ func authorize(networkCheck bool, authNetwork string, next http.Handler) http.Ha case "all": isAuthorized = true case "nodes": - isAuthorized = (macaddress != "") + isAuthorized = (macaddress != "") || isnetadmin case "network": + if isnetadmin { + isAuthorized = true + } else { node, err := functions.GetNodeByMacAddress(params["network"], macaddress) if err != nil { errorResponse = models.ErrorResponse{ @@ -223,8 +232,13 @@ func authorize(networkCheck bool, authNetwork string, next http.Handler) http.Ha return } isAuthorized = (node.Network == params["network"]) + } case "node": - isAuthorized = (macaddress == params["macaddress"]) + if isnetadmin { + isAuthorized = true + } else { + isAuthorized = (macaddress == params["macaddress"]) + } case "master": isAuthorized = (macaddress == "mastermac") default: diff --git a/controllers/serverHttpController.go b/controllers/serverHttpController.go index 9a1885e0..cb5ce49b 100644 --- a/controllers/serverHttpController.go +++ b/controllers/serverHttpController.go @@ -42,7 +42,7 @@ func securityCheckServer(next http.Handler) http.HandlerFunc { } //all endpoints here require master so not as complicated //still might not be a good way of doing this - _, isadmin, _ := functions.VerifyUserToken(authToken) + _, _, isadmin, _ := functions.VerifyUserToken(authToken) if !isadmin && !authenticateMasterServer(authToken) { errorResponse = models.ErrorResponse{ diff --git a/controllers/userHttpController.go b/controllers/userHttpController.go index 1645859f..1cd23b7f 100644 --- a/controllers/userHttpController.go +++ b/controllers/userHttpController.go @@ -26,8 +26,11 @@ func userHandlers(r *mux.Router) { r.HandleFunc("/api/users/adm/createadmin", createAdmin).Methods("POST") r.HandleFunc("/api/users/adm/authenticate", authenticateUser).Methods("POST") r.HandleFunc("/api/users/{username}", authorizeUser(http.HandlerFunc(updateUser))).Methods("PUT") + r.HandleFunc("/api/users/{username}/adm", authorizeUserAdm(http.HandlerFunc(updateUserAdm))).Methods("PUT") + r.HandleFunc("/api/users/{username}", authorizeUserAdm(http.HandlerFunc(createUser))).Methods("POST") r.HandleFunc("/api/users/{username}", authorizeUser(http.HandlerFunc(deleteUser))).Methods("DELETE") r.HandleFunc("/api/users/{username}", authorizeUser(http.HandlerFunc(getUser))).Methods("GET") + r.HandleFunc("/api/users", authorizeUserAdm(http.HandlerFunc(getUsers))).Methods("GET") } //Node authenticates using its password and retrieves a JWT for authorization. @@ -96,9 +99,9 @@ func VerifyAuthRequest(authRequest models.UserAuthParams) (string, error) { return "", errors.New("User " + authRequest.UserName + " not found") } // This is a a useless test as cannot create user that is not an an admin - if !result.IsAdmin { - return "", errors.New("User is not an admin") - } + //if !result.IsAdmin { + // return "", errors.New("User is not an admin") + //} //compare password from request to stored password in database //might be able to have a common hash (certificates?) and compare those so that a password isn't passed in in plain text... @@ -109,7 +112,7 @@ func VerifyAuthRequest(authRequest models.UserAuthParams) (string, error) { } //Create a new JWT for the node - tokenString, _ := functions.CreateUserJWT(authRequest.UserName, true) + tokenString, _ := functions.CreateUserJWT(authRequest.UserName, result.Networks, result.IsAdmin) return tokenString, nil } @@ -123,10 +126,11 @@ func VerifyAuthRequest(authRequest models.UserAuthParams) (string, error) { func authorizeUser(next http.Handler) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") + var params = mux.Vars(r) //get the auth token bearerToken := r.Header.Get("Authorization") - err := ValidateUserToken(bearerToken) + err := ValidateUserToken(bearerToken, params["username"], false) if err != nil { returnErrorResponse(w, r, formatError(err, "unauthorized")) return @@ -135,7 +139,24 @@ func authorizeUser(next http.Handler) http.HandlerFunc { } } -func ValidateUserToken(token string) error { +func authorizeUserAdm(next http.Handler) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + var params = mux.Vars(r) + + //get the auth token + bearerToken := r.Header.Get("Authorization") + err := ValidateUserToken(bearerToken, params["username"], true) + if err != nil { + returnErrorResponse(w, r, formatError(err, "unauthorized")) + return + } + next.ServeHTTP(w, r) + } +} + + +func ValidateUserToken(token string, user string, adminonly bool) error { var tokenSplit = strings.Split(token, " ") //I put this in in case the user doesn't put in a token at all (in which case it's empty) @@ -148,12 +169,16 @@ func ValidateUserToken(token string) error { return errors.New("Missing Auth Token.") } - username, _, err := functions.VerifyUserToken(authToken) + username, _, isadmin, err := functions.VerifyUserToken(authToken) if err != nil { return errors.New("Error Verifying Auth Token") } - - isAuthorized := username != "" + isAuthorized := false + if adminonly { + isAuthorized = isadmin + } else { + isAuthorized = username == user || isadmin + } if !isAuthorized { return errors.New("You are unauthorized to access this endpoint.") } @@ -214,6 +239,42 @@ func GetUser(username string) (models.User, error) { return user, err } +func GetUsers() ([]models.User, error) { + + var users []models.User + + collection := mongoconn.Client.Database("netmaker").Collection("users") + + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + + cur, err := collection.Find(ctx, bson.M{}, options.Find().SetProjection(bson.M{"_id": 0})) + + if err != nil { + return users, err + } + + defer cancel() + + for cur.Next(context.TODO()) { + + var user models.User + err := cur.Decode(&user) + if err != nil { + return users, err + } + + // add network our array + users = append(users, user) + } + + if err := cur.Err(); err != nil { + return users, err + } + + return users, err +} + + //Get an individual node. Nothin fancy here folks. func getUser(w http.ResponseWriter, r *http.Request) { // set header. @@ -231,13 +292,27 @@ func getUser(w http.ResponseWriter, r *http.Request) { json.NewEncoder(w).Encode(user) } +//Get an individual node. Nothin fancy here folks. +func getUsers(w http.ResponseWriter, r *http.Request) { + // set header. + w.Header().Set("Content-Type", "application/json") + + users, err := GetUsers() + + if err != nil { + returnErrorResponse(w, r, formatError(err, "internal")) + return + } + + json.NewEncoder(w).Encode(users) +} + + func CreateUser(user models.User) (models.User, error) { hasadmin, err := HasAdmin() - if hasadmin { + if hasadmin && user.IsAdmin { return models.User{}, errors.New("Admin already Exists") } - - user.IsAdmin = true err = ValidateUser("create", user) if err != nil { return models.User{}, err @@ -251,7 +326,7 @@ func CreateUser(user models.User) (models.User, error) { //set password to encrypted password user.Password = string(hash) - tokenString, _ := functions.CreateUserJWT(user.UserName, user.IsAdmin) + tokenString, _ := functions.CreateUserJWT(user.UserName,user.Networks, user.IsAdmin) if tokenString == "" { //returnErrorResponse(w, r, errorResponse) @@ -275,7 +350,7 @@ func createAdmin(w http.ResponseWriter, r *http.Request) { var admin models.User //get node from body of request _ = json.NewDecoder(r.Body).Decode(&admin) - + admin.IsAdmin = true admin, err := CreateUser(admin) if err != nil { @@ -286,6 +361,24 @@ func createAdmin(w http.ResponseWriter, r *http.Request) { json.NewEncoder(w).Encode(admin) } +func createUser(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + + var user models.User + //get node from body of request + _ = json.NewDecoder(r.Body).Decode(&user) + + user, err := CreateUser(user) + + if err != nil { + returnErrorResponse(w, r, formatError(err, "badrequest")) + return + } + + json.NewEncoder(w).Encode(user) +} + + func UpdateUser(userchange models.User, user models.User) (models.User, error) { err := ValidateUser("update", userchange) @@ -298,6 +391,9 @@ func UpdateUser(userchange models.User, user models.User) (models.User, error) { if userchange.UserName != "" { user.UserName = userchange.UserName } + if len(userchange.Networks) > 0 { + user.Networks = userchange.Networks + } if userchange.Password != "" { //encrypt that password so we never see it again hash, err := bcrypt.GenerateFromPassword([]byte(userchange.Password), 5) @@ -325,6 +421,7 @@ func UpdateUser(userchange models.User, user models.User) (models.User, error) { {"$set", bson.D{ {"username", user.UserName}, {"password", user.Password}, + {"networks", user.Networks}, {"isadmin", user.IsAdmin}, }}, } @@ -360,6 +457,7 @@ func updateUser(w http.ResponseWriter, r *http.Request) { returnErrorResponse(w, r, formatError(err, "internal")) return } + userchange.Networks = nil user, err = UpdateUser(userchange, user) if err != nil { returnErrorResponse(w, r, formatError(err, "badrequest")) @@ -368,6 +466,31 @@ func updateUser(w http.ResponseWriter, r *http.Request) { json.NewEncoder(w).Encode(user) } +func updateUserAdm(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + var params = mux.Vars(r) + var user models.User + //start here + user, err := GetUser(params["username"]) + if err != nil { + returnErrorResponse(w, r, formatError(err, "internal")) + return + } + var userchange models.User + // we decode our body request params + err = json.NewDecoder(r.Body).Decode(&userchange) + if err != nil { + returnErrorResponse(w, r, formatError(err, "internal")) + return + } + user, err = UpdateUser(userchange, user) + if err != nil { + returnErrorResponse(w, r, formatError(err, "badrequest")) + return + } + json.NewEncoder(w).Encode(user) +} + func DeleteUser(user string) (bool, error) { deleted := false diff --git a/functions/helpers.go b/functions/helpers.go index d95d4c52..b2c9215f 100644 --- a/functions/helpers.go +++ b/functions/helpers.go @@ -26,6 +26,16 @@ import ( //Takes in an arbitrary field and value for field and checks to see if any other //node has that value for the same field within the network +func SliceContains(slice []string, item string) bool { + set := make(map[string]struct{}, len(slice)) + for _, s := range slice { + set[s] = struct{}{} + } + + _, ok := set[item] + return ok +} + func CreateServerToken(netID string) (string, error) { var network models.Network var accesskey models.AccessKey diff --git a/functions/jwt.go b/functions/jwt.go index 5beee30c..225232bf 100644 --- a/functions/jwt.go +++ b/functions/jwt.go @@ -28,11 +28,12 @@ func CreateJWT(macaddress string, network string) (response string, err error) { return "", err } -func CreateUserJWT(username string, isadmin bool) (response string, err error) { +func CreateUserJWT(username string, networks []string, isadmin bool) (response string, err error) { expirationTime := time.Now().Add(60 * time.Minute) claims := &models.UserClaims{ UserName: username, - IsAdmin: isadmin, + Networks: networks, + IsAdmin: isadmin, StandardClaims: jwt.StandardClaims{ ExpiresAt: expirationTime.Unix(), }, @@ -47,11 +48,11 @@ func CreateUserJWT(username string, isadmin bool) (response string, err error) { } // VerifyToken func will used to Verify the JWT Token while using APIS -func VerifyUserToken(tokenString string) (username string, isadmin bool, err error) { +func VerifyUserToken(tokenString string) (username string, networks []string, isadmin bool, err error) { claims := &models.UserClaims{} if tokenString == servercfg.GetMasterKey() { - return "masteradministrator", true, nil + return "masteradministrator", nil, true, nil } token, err := jwt.ParseWithClaims(tokenString, claims, func(token *jwt.Token) (interface{}, error) { @@ -59,9 +60,9 @@ func VerifyUserToken(tokenString string) (username string, isadmin bool, err err }) if token != nil { - return claims.UserName, claims.IsAdmin, nil + return claims.UserName, claims.Networks, claims.IsAdmin, nil } - return "", false, err + return "", nil, false, err } // VerifyToken func will used to Verify the JWT Token while using APIS diff --git a/models/structs.go b/models/structs.go index 1a8b7c09..37e27e6f 100644 --- a/models/structs.go +++ b/models/structs.go @@ -10,6 +10,7 @@ type AuthParams struct { type User struct { UserName string `json:"username" bson:"username" validate:"alphanum,min=3"` Password string `json:"password" bson:"password" validate:"required,min=5"` + Networks []string `json:"networks" bson:"networks"` IsAdmin bool `json:"isadmin" bson:"isadmin"` } @@ -21,6 +22,7 @@ type UserAuthParams struct { type UserClaims struct { IsAdmin bool UserName string + Networks []string jwt.StandardClaims }