mirror of
https://github.com/gravitl/netmaker.git
synced 2024-11-10 17:48:25 +08:00
478 lines
13 KiB
Go
478 lines
13 KiB
Go
package logic
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/go-playground/validator/v10"
|
|
"golang.org/x/crypto/bcrypt"
|
|
|
|
"github.com/gravitl/netmaker/database"
|
|
"github.com/gravitl/netmaker/logger"
|
|
"github.com/gravitl/netmaker/logic/pro"
|
|
"github.com/gravitl/netmaker/models"
|
|
"github.com/gravitl/netmaker/models/promodels"
|
|
"github.com/gravitl/netmaker/servercfg"
|
|
)
|
|
|
|
// HasAdmin - checks if server has an admin
|
|
func HasAdmin() (bool, error) {
|
|
|
|
collection, err := database.FetchRecords(database.USERS_TABLE_NAME)
|
|
if err != nil {
|
|
if database.IsEmptyRecord(err) {
|
|
return false, nil
|
|
} else {
|
|
return true, err
|
|
}
|
|
}
|
|
for _, value := range collection { // filter for isadmin true
|
|
var user models.User
|
|
err = json.Unmarshal([]byte(value), &user)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
if user.IsAdmin {
|
|
return true, nil
|
|
}
|
|
}
|
|
|
|
return false, err
|
|
}
|
|
|
|
// GetReturnUser - gets a user
|
|
func GetReturnUser(username string) (models.ReturnUser, error) {
|
|
|
|
var user models.ReturnUser
|
|
record, err := database.FetchRecord(database.USERS_TABLE_NAME, username)
|
|
if err != nil {
|
|
return user, err
|
|
}
|
|
if err = json.Unmarshal([]byte(record), &user); err != nil {
|
|
return models.ReturnUser{}, err
|
|
}
|
|
return user, err
|
|
}
|
|
|
|
// GetUsers - gets users
|
|
func GetUsers() ([]models.ReturnUser, error) {
|
|
|
|
var users []models.ReturnUser
|
|
|
|
collection, err := database.FetchRecords(database.USERS_TABLE_NAME)
|
|
|
|
if err != nil {
|
|
return users, err
|
|
}
|
|
|
|
for _, value := range collection {
|
|
|
|
var user models.ReturnUser
|
|
err = json.Unmarshal([]byte(value), &user)
|
|
if err != nil {
|
|
continue // get users
|
|
}
|
|
users = append(users, user)
|
|
}
|
|
|
|
return users, err
|
|
}
|
|
|
|
// CreateUser - creates a user
|
|
func CreateUser(user *models.User) error {
|
|
// check if user exists
|
|
if _, err := GetUser(user.UserName); err == nil {
|
|
return errors.New("user exists")
|
|
}
|
|
var err = ValidateUser(user)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// encrypt that password so we never see it again
|
|
hash, err := bcrypt.GenerateFromPassword([]byte(user.Password), 5)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// set password to encrypted password
|
|
user.Password = string(hash)
|
|
|
|
tokenString, _ := CreateProUserJWT(user.UserName, user.Networks, user.Groups, user.IsAdmin)
|
|
if tokenString == "" {
|
|
// logic.ReturnErrorResponse(w, r, errorResponse)
|
|
return err
|
|
}
|
|
|
|
SetUserDefaults(user)
|
|
|
|
// connect db
|
|
data, err := json.Marshal(user)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = database.Insert(user.UserName, string(data), database.USERS_TABLE_NAME)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// == PRO == Add user to every network as network user ==
|
|
currentNets, err := GetNetworks()
|
|
if err != nil {
|
|
currentNets = []models.Network{}
|
|
}
|
|
for i := range currentNets {
|
|
newUser := promodels.NetworkUser{
|
|
ID: promodels.NetworkUserID(user.UserName),
|
|
Clients: []string{},
|
|
Nodes: []string{},
|
|
}
|
|
|
|
pro.AddProNetDefaults(¤tNets[i])
|
|
if pro.IsUserAllowed(¤tNets[i], user.UserName, user.Groups) {
|
|
newUser.AccessLevel = currentNets[i].ProSettings.DefaultAccessLevel
|
|
newUser.ClientLimit = currentNets[i].ProSettings.DefaultUserClientLimit
|
|
newUser.NodeLimit = currentNets[i].ProSettings.DefaultUserNodeLimit
|
|
} else {
|
|
newUser.AccessLevel = pro.NO_ACCESS
|
|
newUser.ClientLimit = 0
|
|
newUser.NodeLimit = 0
|
|
}
|
|
|
|
// legacy
|
|
if StringSliceContains(user.Networks, currentNets[i].NetID) {
|
|
if !servercfg.Is_EE {
|
|
newUser.AccessLevel = pro.NET_ADMIN
|
|
}
|
|
}
|
|
userErr := pro.CreateNetworkUser(¤tNets[i], &newUser)
|
|
if userErr != nil {
|
|
logger.Log(0, "failed to add network user data on network", currentNets[i].NetID, "for user", user.UserName)
|
|
}
|
|
}
|
|
// == END PRO ==
|
|
|
|
return nil
|
|
}
|
|
|
|
// CreateAdmin - creates an admin user
|
|
func CreateAdmin(admin *models.User) error {
|
|
hasadmin, err := HasAdmin()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if hasadmin {
|
|
return errors.New("admin user already exists")
|
|
}
|
|
admin.IsAdmin = true
|
|
return CreateUser(admin)
|
|
}
|
|
|
|
// VerifyAuthRequest - verifies an auth request
|
|
func VerifyAuthRequest(authRequest models.UserAuthParams) (string, error) {
|
|
var result models.User
|
|
if authRequest.UserName == "" {
|
|
return "", errors.New("username can't be empty")
|
|
} else if authRequest.Password == "" {
|
|
return "", errors.New("password can't be empty")
|
|
}
|
|
// Search DB for node with Mac Address. Ignore pending nodes (they should not be able to authenticate with API until approved).
|
|
record, err := database.FetchRecord(database.USERS_TABLE_NAME, authRequest.UserName)
|
|
if err != nil {
|
|
return "", errors.New("error retrieving user from db: " + err.Error())
|
|
}
|
|
if err = json.Unmarshal([]byte(record), &result); err != nil {
|
|
return "", errors.New("error unmarshalling user json: " + err.Error())
|
|
}
|
|
|
|
// 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...
|
|
// TODO: Consider a way of hashing the password client side before sending, or using certificates
|
|
if err = bcrypt.CompareHashAndPassword([]byte(result.Password), []byte(authRequest.Password)); err != nil {
|
|
return "", errors.New("incorrect credentials")
|
|
}
|
|
|
|
// Create a new JWT for the node
|
|
tokenString, _ := CreateProUserJWT(authRequest.UserName, result.Networks, result.Groups, result.IsAdmin)
|
|
return tokenString, nil
|
|
}
|
|
|
|
// UpdateUserNetworks - updates the networks of a given user
|
|
func UpdateUserNetworks(newNetworks, newGroups []string, isadmin bool, currentUser *models.ReturnUser) error {
|
|
// check if user exists
|
|
returnedUser, err := GetUser(currentUser.UserName)
|
|
if err != nil {
|
|
return err
|
|
} else if returnedUser.IsAdmin {
|
|
return fmt.Errorf("can not make changes to an admin user, attempted to change %s", returnedUser.UserName)
|
|
}
|
|
if isadmin {
|
|
currentUser.IsAdmin = true
|
|
currentUser.Networks = nil
|
|
} else {
|
|
// == PRO ==
|
|
currentUser.Groups = newGroups
|
|
for _, n := range newNetworks {
|
|
if !StringSliceContains(currentUser.Networks, n) {
|
|
// make net admin of any network not previously assigned
|
|
pro.MakeNetAdmin(n, currentUser.UserName)
|
|
}
|
|
}
|
|
// Compare networks, find networks not in previous
|
|
for _, n := range currentUser.Networks {
|
|
if !StringSliceContains(newNetworks, n) {
|
|
// if user was removed from a network, re-assign access to net default level
|
|
if network, err := GetNetwork(n); err == nil {
|
|
if network.ProSettings != nil {
|
|
ok := pro.AssignAccessLvl(n, currentUser.UserName, network.ProSettings.DefaultAccessLevel)
|
|
if ok {
|
|
logger.Log(0, "changed", currentUser.UserName, "access level on network", network.NetID, "to", fmt.Sprintf("%d", network.ProSettings.DefaultAccessLevel))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if err := AdjustGroupPermissions(currentUser); err != nil {
|
|
logger.Log(0, "failed to update user", currentUser.UserName, "after group update", err.Error())
|
|
}
|
|
// == END PRO ==
|
|
|
|
currentUser.Networks = newNetworks
|
|
}
|
|
|
|
userChange := models.User{
|
|
UserName: currentUser.UserName,
|
|
Networks: currentUser.Networks,
|
|
IsAdmin: currentUser.IsAdmin,
|
|
Password: "",
|
|
Groups: currentUser.Groups,
|
|
}
|
|
|
|
_, err = UpdateUser(&userChange, returnedUser)
|
|
|
|
return err
|
|
}
|
|
|
|
// UpdateUser - updates a given user
|
|
func UpdateUser(userchange, user *models.User) (*models.User, error) {
|
|
// check if user exists
|
|
if _, err := GetUser(user.UserName); err != nil {
|
|
return &models.User{}, err
|
|
}
|
|
|
|
queryUser := user.UserName
|
|
|
|
if userchange.UserName != "" {
|
|
user.UserName = userchange.UserName
|
|
}
|
|
if len(userchange.Networks) > 0 {
|
|
user.Networks = userchange.Networks
|
|
}
|
|
if len(userchange.Groups) > 0 {
|
|
user.Groups = userchange.Groups
|
|
}
|
|
if userchange.Password != "" {
|
|
// encrypt that password so we never see it again
|
|
hash, err := bcrypt.GenerateFromPassword([]byte(userchange.Password), 5)
|
|
|
|
if err != nil {
|
|
return userchange, err
|
|
}
|
|
// set password to encrypted password
|
|
userchange.Password = string(hash)
|
|
|
|
user.Password = userchange.Password
|
|
}
|
|
|
|
if (userchange.IsAdmin != user.IsAdmin) && !user.IsAdmin {
|
|
user.IsAdmin = userchange.IsAdmin
|
|
}
|
|
|
|
err := ValidateUser(user)
|
|
if err != nil {
|
|
return &models.User{}, err
|
|
}
|
|
|
|
if err = database.DeleteRecord(database.USERS_TABLE_NAME, queryUser); err != nil {
|
|
return &models.User{}, err
|
|
}
|
|
data, err := json.Marshal(&user)
|
|
if err != nil {
|
|
return &models.User{}, err
|
|
}
|
|
if err = database.Insert(user.UserName, string(data), database.USERS_TABLE_NAME); err != nil {
|
|
return &models.User{}, err
|
|
}
|
|
logger.Log(1, "updated user", queryUser)
|
|
return user, nil
|
|
}
|
|
|
|
// ValidateUser - validates a user model
|
|
func ValidateUser(user *models.User) error {
|
|
|
|
v := validator.New()
|
|
_ = v.RegisterValidation("in_charset", func(fl validator.FieldLevel) bool {
|
|
isgood := user.NameInCharSet()
|
|
return isgood
|
|
})
|
|
err := v.Struct(user)
|
|
|
|
if err != nil {
|
|
for _, e := range err.(validator.ValidationErrors) {
|
|
logger.Log(2, e.Error())
|
|
}
|
|
}
|
|
|
|
return err
|
|
}
|
|
|
|
// DeleteUser - deletes a given user
|
|
func DeleteUser(user string) (bool, error) {
|
|
|
|
if userRecord, err := database.FetchRecord(database.USERS_TABLE_NAME, user); err != nil || len(userRecord) == 0 {
|
|
return false, errors.New("user does not exist")
|
|
}
|
|
|
|
err := database.DeleteRecord(database.USERS_TABLE_NAME, user)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
|
|
// == pro - remove user from all network user instances ==
|
|
currentNets, err := GetNetworks()
|
|
if err != nil {
|
|
if database.IsEmptyRecord(err) {
|
|
currentNets = []models.Network{}
|
|
} else {
|
|
return true, err
|
|
}
|
|
}
|
|
|
|
for i := range currentNets {
|
|
netID := currentNets[i].NetID
|
|
if err = pro.DeleteNetworkUser(netID, user); err != nil {
|
|
logger.Log(0, "failed to remove", user, "as network user from network", netID, err.Error())
|
|
}
|
|
}
|
|
|
|
return true, nil
|
|
}
|
|
|
|
// FetchAuthSecret - manages secrets for oauth
|
|
func FetchAuthSecret(key string, secret string) (string, error) {
|
|
var record, err = database.FetchRecord(database.GENERATED_TABLE_NAME, key)
|
|
if err != nil {
|
|
if err = database.Insert(key, secret, database.GENERATED_TABLE_NAME); err != nil {
|
|
return "", err
|
|
} else {
|
|
return secret, nil
|
|
}
|
|
}
|
|
return record, nil
|
|
}
|
|
|
|
// GetState - gets an SsoState from DB, if expired returns error
|
|
func GetState(state string) (*models.SsoState, error) {
|
|
var s models.SsoState
|
|
record, err := database.FetchRecord(database.SSO_STATE_CACHE, state)
|
|
if err != nil {
|
|
return &s, err
|
|
}
|
|
|
|
if err = json.Unmarshal([]byte(record), &s); err != nil {
|
|
return &s, err
|
|
}
|
|
|
|
if s.IsExpired() {
|
|
return &s, fmt.Errorf("state expired")
|
|
}
|
|
|
|
return &s, nil
|
|
}
|
|
|
|
// SetState - sets a state with new expiration
|
|
func SetState(state string) error {
|
|
s := models.SsoState{
|
|
Value: state,
|
|
Expiration: time.Now().Add(models.DefaultExpDuration),
|
|
}
|
|
|
|
data, err := json.Marshal(&s)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return database.Insert(state, string(data), database.SSO_STATE_CACHE)
|
|
}
|
|
|
|
// IsStateValid - checks if given state is valid or not
|
|
// deletes state after call is made to clean up, should only be called once per sign-in
|
|
func IsStateValid(state string) (string, bool) {
|
|
s, err := GetState(state)
|
|
if err != nil {
|
|
logger.Log(2, "error retrieving oauth state:", err.Error())
|
|
return "", false
|
|
}
|
|
if s.Value != "" {
|
|
if err = delState(state); err != nil {
|
|
logger.Log(2, "error deleting oauth state:", err.Error())
|
|
return "", false
|
|
}
|
|
}
|
|
return s.Value, true
|
|
}
|
|
|
|
// delState - removes a state from cache/db
|
|
func delState(state string) error {
|
|
return database.DeleteRecord(database.SSO_STATE_CACHE, state)
|
|
}
|
|
|
|
// PRO
|
|
|
|
// AdjustGroupPermissions - adjusts a given user's network access based on group changes
|
|
func AdjustGroupPermissions(user *models.ReturnUser) error {
|
|
networks, err := GetNetworks()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// UPDATE
|
|
// go through all networks and see if new group is in
|
|
// if access level of current user is greater (value) than network's default
|
|
// assign network's default
|
|
// DELETE
|
|
// if user not allowed on network a
|
|
for i := range networks {
|
|
AdjustNetworkUserPermissions(user, &networks[i])
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// AdjustNetworkUserPermissions - adjusts a given user's network access based on group changes
|
|
func AdjustNetworkUserPermissions(user *models.ReturnUser, network *models.Network) error {
|
|
networkUser, err := pro.GetNetworkUser(
|
|
network.NetID,
|
|
promodels.NetworkUserID(user.UserName),
|
|
)
|
|
if err == nil && network.ProSettings != nil {
|
|
if pro.IsUserAllowed(network, user.UserName, user.Groups) {
|
|
if networkUser.AccessLevel > network.ProSettings.DefaultAccessLevel {
|
|
networkUser.AccessLevel = network.ProSettings.DefaultAccessLevel
|
|
}
|
|
if networkUser.NodeLimit < network.ProSettings.DefaultUserNodeLimit {
|
|
networkUser.NodeLimit = network.ProSettings.DefaultUserNodeLimit
|
|
}
|
|
if networkUser.ClientLimit < network.ProSettings.DefaultUserClientLimit {
|
|
networkUser.ClientLimit = network.ProSettings.DefaultUserClientLimit
|
|
}
|
|
} else {
|
|
networkUser.AccessLevel = pro.NO_ACCESS
|
|
networkUser.NodeLimit = 0
|
|
networkUser.ClientLimit = 0
|
|
}
|
|
pro.UpdateNetworkUser(network.NetID, networkUser)
|
|
}
|
|
return err
|
|
}
|