From 7e0be1489229af0706da77958955b8b6e971dbf3 Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Tue, 21 May 2024 20:05:25 +0530 Subject: [PATCH 001/139] user mgmt models --- database/database.go | 3 +++ logic/user_mgmt.go | 1 + models/user_mgmt.go | 36 ++++++++++++++++++++++++++++++++++++ 3 files changed, 40 insertions(+) create mode 100644 logic/user_mgmt.go create mode 100644 models/user_mgmt.go diff --git a/database/database.go b/database/database.go index dd2b2af13..b29aaec11 100644 --- a/database/database.go +++ b/database/database.go @@ -25,6 +25,8 @@ const ( DELETED_NODES_TABLE_NAME = "deletednodes" // USERS_TABLE_NAME - users table USERS_TABLE_NAME = "users" + // USER_PERMISSIONS_TABLE_NAME - user permissions table + USER_PERMISSIONS_TABLE_NAME = "user_permissions" // CERTS_TABLE_NAME - certificates table CERTS_TABLE_NAME = "certs" // DNS_TABLE_NAME - dns table @@ -146,6 +148,7 @@ func createTables() { CreateTable(ENROLLMENT_KEYS_TABLE_NAME) CreateTable(HOST_ACTIONS_TABLE_NAME) CreateTable(PENDING_USERS_TABLE_NAME) + CreateTable(USER_PERMISSIONS_TABLE_NAME) } func CreateTable(tableName string) error { diff --git a/logic/user_mgmt.go b/logic/user_mgmt.go new file mode 100644 index 000000000..4c79103d6 --- /dev/null +++ b/logic/user_mgmt.go @@ -0,0 +1 @@ +package logic diff --git a/models/user_mgmt.go b/models/user_mgmt.go new file mode 100644 index 000000000..779dba57b --- /dev/null +++ b/models/user_mgmt.go @@ -0,0 +1,36 @@ +package models + +type NetworkID string +type RsrcID string + +const ( + HostRsrc RsrcID = "host" + RelayRsrc RsrcID = "relay" + RemoteAccessGwRsrc RsrcID = "remote_access_gw" + InetGwRsrc RsrcID = "inet_gw" + EgressGwRsrc RsrcID = "egress" +) + +type NetworkRsrcPermissions struct { + All bool `json:"all"` + Create bool `json:"create"` + Read bool `json:"read"` + Update bool `json:"update"` + Delete bool `json:"delete"` +} + +type NetworkAccessControls struct { + NetworkID string `json:"network_id"` + FullAccess bool `json:"full_access"` + NetworkRsrcPermissionsList map[RsrcID]NetworkRsrcPermissions `json:"network_permissions_list"` +} + +type DashboardAccessControls struct { + FullAccess bool `json:"full_access"` + NetworkLevelAccess map[NetworkID]NetworkAccessControls `json:"network_access_controls"` +} + +type UserPermissionTemplate struct { + ID string `json:"id"` + DashBoardAcls DashboardAccessControls `json:"dashboard_access_controls"` +} From 33375bba0f4e4569e7b065e15f7f549473745aca Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Wed, 22 May 2024 08:51:55 +0530 Subject: [PATCH 002/139] define user roles --- logic/user_mgmt.go | 51 +++++++++++++++++++++++++++++++++++++++++++++ models/user_mgmt.go | 23 ++++++++++++++++---- 2 files changed, 70 insertions(+), 4 deletions(-) diff --git a/logic/user_mgmt.go b/logic/user_mgmt.go index 4c79103d6..ccb603afe 100644 --- a/logic/user_mgmt.go +++ b/logic/user_mgmt.go @@ -1 +1,52 @@ package logic + +import ( + "encoding/json" + + "github.com/gravitl/netmaker/database" + "github.com/gravitl/netmaker/models" +) + +// Pre-Define Permission Templates for default Roles +var SuperAdminPermissionTemplate = models.UserPermissionTemplate{ + ID: models.SuperAdminRole, + Default: true, + DashBoardAcls: models.DashboardAccessControls{ + FullAccess: true, + }, +} +var AdminPermissionTemplate = models.UserPermissionTemplate{ + ID: models.AdminRole, + Default: true, + DashBoardAcls: models.DashboardAccessControls{ + FullAccess: true, + }, +} + +var NetworkAdminPermissionTemplate = models.UserPermissionTemplate{ + ID: models.NetworkAdmin, + Default: true, + DashBoardAcls: models.DashboardAccessControls{ + NetworkLevelAccess: make(map[models.NetworkID]models.NetworkAccessControls), + }, +} + +var NetworkUserPermissionTemplate = models.UserPermissionTemplate{ + ID: models.NetworkUser, + Default: true, + DashBoardAcls: models.DashboardAccessControls{ + DenyDashboardAccess: true, + NetworkLevelAccess: make(map[models.NetworkID]models.NetworkAccessControls), + }, +} + +func init() { + d, _ := json.Marshal(SuperAdminPermissionTemplate) + database.Insert(SuperAdminPermissionTemplate.ID.String(), string(d), database.USER_PERMISSIONS_TABLE_NAME) + d, _ = json.Marshal(AdminPermissionTemplate) + database.Insert(AdminPermissionTemplate.ID.String(), string(d), database.USER_PERMISSIONS_TABLE_NAME) + d, _ = json.Marshal(NetworkAdminPermissionTemplate) + database.Insert(NetworkAdminPermissionTemplate.ID.String(), string(d), database.USER_PERMISSIONS_TABLE_NAME) + d, _ = json.Marshal(NetworkUserPermissionTemplate) + database.Insert(NetworkUserPermissionTemplate.ID.String(), string(d), database.USER_PERMISSIONS_TABLE_NAME) +} diff --git a/models/user_mgmt.go b/models/user_mgmt.go index 779dba57b..78a0ea555 100644 --- a/models/user_mgmt.go +++ b/models/user_mgmt.go @@ -2,6 +2,7 @@ package models type NetworkID string type RsrcID string +type UserRole string const ( HostRsrc RsrcID = "host" @@ -11,8 +12,20 @@ const ( EgressGwRsrc RsrcID = "egress" ) +// Pre-Defined User Roles + +const ( + SuperAdminRole UserRole = "super_admin" + AdminRole UserRole = "admin" + NetworkAdmin UserRole = "network_admin" + NetworkUser UserRole = "network_user" +) + +func (r UserRole) String() string { + return string(r) +} + type NetworkRsrcPermissions struct { - All bool `json:"all"` Create bool `json:"create"` Read bool `json:"read"` Update bool `json:"update"` @@ -26,11 +39,13 @@ type NetworkAccessControls struct { } type DashboardAccessControls struct { - FullAccess bool `json:"full_access"` - NetworkLevelAccess map[NetworkID]NetworkAccessControls `json:"network_access_controls"` + FullAccess bool `json:"full_access"` + DenyDashboardAccess bool `json:"deny_dashboard_access"` + NetworkLevelAccess map[NetworkID]NetworkAccessControls `json:"network_access_controls"` } type UserPermissionTemplate struct { - ID string `json:"id"` + ID UserRole `json:"id"` + Default bool `json:"default"` DashBoardAcls DashboardAccessControls `json:"dashboard_access_controls"` } From 0ed44422b3f99dcaa0deaf17fa8d795e538e6c15 Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Fri, 24 May 2024 15:10:09 +0530 Subject: [PATCH 003/139] define models for new user mgmt and groups --- controllers/user.go | 26 ++++++++++++++++++ logic/user_mgmt.go | 26 +++++++++++++++--- models/structs.go | 33 ---------------------- models/user_groups.go | 7 +++++ models/user_mgmt.go | 64 ++++++++++++++++++++++++++++++++++++------- 5 files changed, 109 insertions(+), 47 deletions(-) create mode 100644 models/user_groups.go diff --git a/controllers/user.go b/controllers/user.go index ea2d4a825..418a4a61b 100644 --- a/controllers/user.go +++ b/controllers/user.go @@ -37,6 +37,32 @@ func userHandlers(r *mux.Router) { r.HandleFunc("/api/users_pending/user/{username}", logic.SecurityCheck(true, http.HandlerFunc(deletePendingUser))).Methods(http.MethodDelete) r.HandleFunc("/api/users_pending/user/{username}", logic.SecurityCheck(true, http.HandlerFunc(approvePendingUser))).Methods(http.MethodPost) + // User Mgmt handlers + r.HandleFunc("/api/v1/users/roles", logic.SecurityCheck(true, http.HandlerFunc(getUserRoles))).Methods(http.MethodGet) + +} + +// swagger:route GET /api/v1/users/roles user getUserRoles +// +// Get user role permission templates. +// +// Schemes: https +// +// Security: +// oauth +// +// Responses: +// 200: userBodyResponse +func getUserRoles(w http.ResponseWriter, r *http.Request) { + roles, err := logic.ListRoles() + if err != nil { + logic.ReturnErrorResponse(w, r, models.ErrorResponse{ + Code: http.StatusInternalServerError, + Message: err.Error(), + }) + return + } + logic.ReturnSuccessResponseWithJson(w, r, roles, "successfully fetched user roles permission templates") } // swagger:route POST /api/users/adm/authenticate authenticate authenticateUser diff --git a/logic/user_mgmt.go b/logic/user_mgmt.go index ccb603afe..9902933f2 100644 --- a/logic/user_mgmt.go +++ b/logic/user_mgmt.go @@ -8,14 +8,14 @@ import ( ) // Pre-Define Permission Templates for default Roles -var SuperAdminPermissionTemplate = models.UserPermissionTemplate{ +var SuperAdminPermissionTemplate = models.UserRolePermissionTemplate{ ID: models.SuperAdminRole, Default: true, DashBoardAcls: models.DashboardAccessControls{ FullAccess: true, }, } -var AdminPermissionTemplate = models.UserPermissionTemplate{ +var AdminPermissionTemplate = models.UserRolePermissionTemplate{ ID: models.AdminRole, Default: true, DashBoardAcls: models.DashboardAccessControls{ @@ -23,7 +23,7 @@ var AdminPermissionTemplate = models.UserPermissionTemplate{ }, } -var NetworkAdminPermissionTemplate = models.UserPermissionTemplate{ +var NetworkAdminPermissionTemplate = models.UserRolePermissionTemplate{ ID: models.NetworkAdmin, Default: true, DashBoardAcls: models.DashboardAccessControls{ @@ -31,7 +31,7 @@ var NetworkAdminPermissionTemplate = models.UserPermissionTemplate{ }, } -var NetworkUserPermissionTemplate = models.UserPermissionTemplate{ +var NetworkUserPermissionTemplate = models.UserRolePermissionTemplate{ ID: models.NetworkUser, Default: true, DashBoardAcls: models.DashboardAccessControls{ @@ -50,3 +50,21 @@ func init() { d, _ = json.Marshal(NetworkUserPermissionTemplate) database.Insert(NetworkUserPermissionTemplate.ID.String(), string(d), database.USER_PERMISSIONS_TABLE_NAME) } + +// ListRoles - lists user roles permission templates +func ListRoles() ([]models.UserRolePermissionTemplate, error) { + data, err := database.FetchRecords(database.USER_PERMISSIONS_TABLE_NAME) + if err != nil { + return []models.UserRolePermissionTemplate{}, err + } + userRoles := []models.UserRolePermissionTemplate{} + for _, dataI := range data { + userRole := models.UserRolePermissionTemplate{} + err := json.Unmarshal([]byte(dataI), &userRole) + if err != nil { + continue + } + userRoles = append(userRoles, userRole) + } + return userRoles, nil +} diff --git a/models/structs.go b/models/structs.go index 9706428d3..38b09499a 100644 --- a/models/structs.go +++ b/models/structs.go @@ -23,39 +23,6 @@ type AuthParams struct { Password string `json:"password"` } -// User struct - struct for Users -type User struct { - UserName string `json:"username" bson:"username" validate:"min=3,max=40,in_charset|email"` - Password string `json:"password" bson:"password" validate:"required,min=5"` - IsAdmin bool `json:"isadmin" bson:"isadmin"` - IsSuperAdmin bool `json:"issuperadmin"` - RemoteGwIDs map[string]struct{} `json:"remote_gw_ids"` - LastLoginTime time.Time `json:"last_login_time"` -} - -// ReturnUser - return user struct -type ReturnUser struct { - UserName string `json:"username"` - IsAdmin bool `json:"isadmin"` - IsSuperAdmin bool `json:"issuperadmin"` - RemoteGwIDs map[string]struct{} `json:"remote_gw_ids"` - LastLoginTime time.Time `json:"last_login_time"` -} - -// UserAuthParams - user auth params struct -type UserAuthParams struct { - UserName string `json:"username"` - Password string `json:"password"` -} - -// UserClaims - user claims struct -type UserClaims struct { - IsAdmin bool - IsSuperAdmin bool - UserName string - jwt.RegisteredClaims -} - // IngressGwUsers - struct to hold users on a ingress gw type IngressGwUsers struct { NodeID string `json:"node_id"` diff --git a/models/user_groups.go b/models/user_groups.go new file mode 100644 index 000000000..2f2215daf --- /dev/null +++ b/models/user_groups.go @@ -0,0 +1,7 @@ +package models + +type UserGroup struct { + ID string `json:"id"` + PermissionTemplate UserRolePermissionTemplate `json:"role_permission_template"` + MetaData string `json:"meta_data"` +} diff --git a/models/user_mgmt.go b/models/user_mgmt.go index 78a0ea555..d99de0559 100644 --- a/models/user_mgmt.go +++ b/models/user_mgmt.go @@ -1,15 +1,23 @@ package models +import ( + "time" + + jwt "github.com/golang-jwt/jwt/v4" +) + type NetworkID string type RsrcID string type UserRole string const ( - HostRsrc RsrcID = "host" - RelayRsrc RsrcID = "relay" - RemoteAccessGwRsrc RsrcID = "remote_access_gw" - InetGwRsrc RsrcID = "inet_gw" - EgressGwRsrc RsrcID = "egress" + HostRsrcID RsrcID = "all_host" + RelayRsrcID RsrcID = "all_relay" + RemoteAccessGwRsrcID RsrcID = "all_remote_access_gw" + InetGwRsrcID RsrcID = "all_inet_gw" + EgressGwRsrcID RsrcID = "all_egress" + NetworkRsrcID RsrcID = "all_network" + EnrollmentKeysRsrcID RsrcID = "all_enrollment_key" ) // Pre-Defined User Roles @@ -25,7 +33,7 @@ func (r UserRole) String() string { return string(r) } -type NetworkRsrcPermissions struct { +type RsrcPermissions struct { Create bool `json:"create"` Read bool `json:"read"` Update bool `json:"update"` @@ -33,19 +41,55 @@ type NetworkRsrcPermissions struct { } type NetworkAccessControls struct { - NetworkID string `json:"network_id"` - FullAccess bool `json:"full_access"` - NetworkRsrcPermissionsList map[RsrcID]NetworkRsrcPermissions `json:"network_permissions_list"` + NetworkID string `json:"network_id"` + FullAccess bool `json:"full_access"` + NetworkRsrcPermissionsList map[RsrcID]RsrcPermissions `json:"network_permissions_list"` } type DashboardAccessControls struct { FullAccess bool `json:"full_access"` DenyDashboardAccess bool `json:"deny_dashboard_access"` NetworkLevelAccess map[NetworkID]NetworkAccessControls `json:"network_access_controls"` + GlobalLevelAccess map[RsrcID]RsrcPermissions `json:"global_level_access"` } -type UserPermissionTemplate struct { +type UserRolePermissionTemplate struct { ID UserRole `json:"id"` Default bool `json:"default"` DashBoardAcls DashboardAccessControls `json:"dashboard_access_controls"` } + +// User struct - struct for Users +type User struct { + UserName string `json:"username" bson:"username" validate:"min=3,max=40,in_charset|email"` + Password string `json:"password" bson:"password" validate:"required,min=5"` + IsAdmin bool `json:"isadmin" bson:"isadmin"` + IsSuperAdmin bool `json:"issuperadmin"` + RemoteGwIDs map[string]struct{} `json:"remote_gw_ids"` + GroupID string `json:"group_id"` + PermissionTemplate UserRolePermissionTemplate `json:"role_permission_template"` + LastLoginTime time.Time `json:"last_login_time"` +} + +// ReturnUser - return user struct +type ReturnUser struct { + UserName string `json:"username"` + IsAdmin bool `json:"isadmin"` + IsSuperAdmin bool `json:"issuperadmin"` + RemoteGwIDs map[string]struct{} `json:"remote_gw_ids"` + LastLoginTime time.Time `json:"last_login_time"` +} + +// UserAuthParams - user auth params struct +type UserAuthParams struct { + UserName string `json:"username"` + Password string `json:"password"` +} + +// UserClaims - user claims struct +type UserClaims struct { + IsAdmin bool + IsSuperAdmin bool + UserName string + jwt.RegisteredClaims +} From 50c7de1608215c893d3c4e2e4a72d6ab986591f5 Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Fri, 24 May 2024 16:16:48 +0530 Subject: [PATCH 004/139] oauth debug log --- pro/auth/google.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pro/auth/google.go b/pro/auth/google.go index 6bf8af29e..bdfb35dab 100644 --- a/pro/auth/google.go +++ b/pro/auth/google.go @@ -151,6 +151,7 @@ func getGoogleUserInfo(state string, code string) (*OAuthUser, error) { if err != nil { return nil, fmt.Errorf("failed reading response body: %s", err.Error()) } + logger.Log(0, fmt.Sprintf("---------------> USERINFO: %v", string(contents))) var userInfo = &OAuthUser{} if err = json.Unmarshal(contents, userInfo); err != nil { return nil, fmt.Errorf("failed parsing email from response data: %s", err.Error()) From e856c769d258749901c41ee53f0c81046069431c Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Fri, 24 May 2024 16:46:33 +0530 Subject: [PATCH 005/139] initialize user role after db conn --- logic/user_mgmt.go | 2 +- main.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/logic/user_mgmt.go b/logic/user_mgmt.go index 9902933f2..a03549cf9 100644 --- a/logic/user_mgmt.go +++ b/logic/user_mgmt.go @@ -40,7 +40,7 @@ var NetworkUserPermissionTemplate = models.UserRolePermissionTemplate{ }, } -func init() { +func UserRolesInit() { d, _ := json.Marshal(SuperAdminPermissionTemplate) database.Insert(SuperAdminPermissionTemplate.ID.String(), string(d), database.USER_PERMISSIONS_TABLE_NAME) d, _ = json.Marshal(AdminPermissionTemplate) diff --git a/main.go b/main.go index 9b8f5eea6..1cb8b5c62 100644 --- a/main.go +++ b/main.go @@ -89,7 +89,7 @@ func initialize() { // Client Mode Prereq Check migrate.Run() logic.SetJWTSecret() - + logic.UserRolesInit() err = serverctl.SetDefaults() if err != nil { logger.FatalLog("error setting defaults: ", err.Error()) From f70e27b64f2b7873bd522962a057a43fa8445c08 Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Fri, 24 May 2024 16:54:47 +0530 Subject: [PATCH 006/139] print oauth token in debug log --- pro/auth/google.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pro/auth/google.go b/pro/auth/google.go index bdfb35dab..64d503e73 100644 --- a/pro/auth/google.go +++ b/pro/auth/google.go @@ -151,7 +151,7 @@ func getGoogleUserInfo(state string, code string) (*OAuthUser, error) { if err != nil { return nil, fmt.Errorf("failed reading response body: %s", err.Error()) } - logger.Log(0, fmt.Sprintf("---------------> USERINFO: %v", string(contents))) + logger.Log(0, fmt.Sprintf("---------------> USERINFO: %v, token: %s", string(contents), token.AccessToken)) var userInfo = &OAuthUser{} if err = json.Unmarshal(contents, userInfo); err != nil { return nil, fmt.Errorf("failed parsing email from response data: %s", err.Error()) From 01c50b345786e1c462b92c3306304c7fc6394dee Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Mon, 27 May 2024 00:48:03 +0530 Subject: [PATCH 007/139] user roles CRUD apis --- controllers/user.go | 95 +++++++++++++++++++++++++++++++++++++++++++-- logic/user_mgmt.go | 61 +++++++++++++++++++++++++++++ 2 files changed, 152 insertions(+), 4 deletions(-) diff --git a/controllers/user.go b/controllers/user.go index 418a4a61b..38397cadb 100644 --- a/controllers/user.go +++ b/controllers/user.go @@ -37,12 +37,16 @@ func userHandlers(r *mux.Router) { r.HandleFunc("/api/users_pending/user/{username}", logic.SecurityCheck(true, http.HandlerFunc(deletePendingUser))).Methods(http.MethodDelete) r.HandleFunc("/api/users_pending/user/{username}", logic.SecurityCheck(true, http.HandlerFunc(approvePendingUser))).Methods(http.MethodPost) - // User Mgmt handlers - r.HandleFunc("/api/v1/users/roles", logic.SecurityCheck(true, http.HandlerFunc(getUserRoles))).Methods(http.MethodGet) + // User Role handlers + r.HandleFunc("/api/v1/users/roles", logic.SecurityCheck(true, http.HandlerFunc(listRoles))).Methods(http.MethodGet) + r.HandleFunc("/api/v1/users/role", logic.SecurityCheck(true, http.HandlerFunc(createRole))).Methods(http.MethodPost) + r.HandleFunc("/api/v1/users/role", logic.SecurityCheck(true, http.HandlerFunc(updateRole))).Methods(http.MethodPut) + r.HandleFunc("/api/v1/users/role", logic.SecurityCheck(true, http.HandlerFunc(deleteRole))).Methods(http.MethodDelete) + // User Group Handlers } -// swagger:route GET /api/v1/users/roles user getUserRoles +// swagger:route GET /api/v1/users/roles user listRoles // // Get user role permission templates. // @@ -53,7 +57,7 @@ func userHandlers(r *mux.Router) { // // Responses: // 200: userBodyResponse -func getUserRoles(w http.ResponseWriter, r *http.Request) { +func listRoles(w http.ResponseWriter, r *http.Request) { roles, err := logic.ListRoles() if err != nil { logic.ReturnErrorResponse(w, r, models.ErrorResponse{ @@ -65,6 +69,89 @@ func getUserRoles(w http.ResponseWriter, r *http.Request) { logic.ReturnSuccessResponseWithJson(w, r, roles, "successfully fetched user roles permission templates") } +// swagger:route POST /api/v1/users/role user createRole +// +// Create user role permission template. +// +// Schemes: https +// +// Security: +// oauth +// +// Responses: +// 200: userBodyResponse +func createRole(w http.ResponseWriter, r *http.Request) { + var userRole models.UserRolePermissionTemplate + err := json.NewDecoder(r.Body).Decode(&userRole) + if err != nil { + slog.Error("error decoding request body", "error", + err.Error()) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) + return + } + err = logic.CreateRole(userRole) + if err != nil { + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) + return + } + logic.ReturnSuccessResponseWithJson(w, r, userRole, "created user role") +} + +// swagger:route PUT /api/v1/users/role user updateRole +// +// Update user role permission template. +// +// Schemes: https +// +// Security: +// oauth +// +// Responses: +// 200: userBodyResponse +func updateRole(w http.ResponseWriter, r *http.Request) { + var userRole models.UserRolePermissionTemplate + err := json.NewDecoder(r.Body).Decode(&userRole) + if err != nil { + slog.Error("error decoding request body", "error", + err.Error()) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) + return + } + err = logic.UpdateRole(userRole) + if err != nil { + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) + return + } + logic.ReturnSuccessResponseWithJson(w, r, userRole, "updated user role") +} + +// swagger:route DELETE /api/v1/users/role user deleteRole +// +// Delete user role permission template. +// +// Schemes: https +// +// Security: +// oauth +// +// Responses: +// 200: userBodyResponse +func deleteRole(w http.ResponseWriter, r *http.Request) { + var userRole models.UserRolePermissionTemplate + var params = mux.Vars(r) + rid := params["role_id"] + if rid == "" { + logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("role is required"), "badrequest")) + return + } + err := logic.DeleteRole(models.UserRole(rid)) + if err != nil { + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) + return + } + logic.ReturnSuccessResponseWithJson(w, r, userRole, "created user role") +} + // swagger:route POST /api/users/adm/authenticate authenticate authenticateUser // // User authenticates using its password and retrieves a JWT for authorization. diff --git a/logic/user_mgmt.go b/logic/user_mgmt.go index a03549cf9..6edacc0ed 100644 --- a/logic/user_mgmt.go +++ b/logic/user_mgmt.go @@ -2,6 +2,7 @@ package logic import ( "encoding/json" + "errors" "github.com/gravitl/netmaker/database" "github.com/gravitl/netmaker/models" @@ -68,3 +69,63 @@ func ListRoles() ([]models.UserRolePermissionTemplate, error) { } return userRoles, nil } + +// CreateRole - inserts new role into DB +func CreateRole(r models.UserRolePermissionTemplate) error { + // check if role already exists + _, err := database.FetchRecord(database.USER_PERMISSIONS_TABLE_NAME, r.ID.String()) + if err == nil { + return errors.New("role already exists") + } + d, err := json.Marshal(r) + if err != nil { + return err + } + return database.Insert(r.ID.String(), string(d), database.USER_PERMISSIONS_TABLE_NAME) +} + +// GetRole - fetches role template by id +func GetRole(roleID string) (models.UserRolePermissionTemplate, error) { + // check if role already exists + data, err := database.FetchRecord(database.USER_PERMISSIONS_TABLE_NAME, roleID) + if err != nil { + return models.UserRolePermissionTemplate{}, errors.New("role already exists") + } + ur := models.UserRolePermissionTemplate{} + err = json.Unmarshal([]byte(data), &ur) + if err != nil { + return ur, err + } + return ur, nil +} + +// UpdateRole - updates role template +func UpdateRole(r models.UserRolePermissionTemplate) error { + _, err := database.FetchRecord(database.USER_PERMISSIONS_TABLE_NAME, r.ID.String()) + if err != nil { + return err + } + d, err := json.Marshal(r) + if err != nil { + return err + } + return database.Insert(r.ID.String(), string(d), database.USER_PERMISSIONS_TABLE_NAME) +} + +// DeleteRole - deletes user role +func DeleteRole(rid models.UserRole) error { + users, err := GetUsersDB() + if err != nil { + return err + } + for _, user := range users { + if user.GroupID != "" { + // TODO - get permission template of the group + continue + } + if user.PermissionTemplate.ID == rid { + errors.New("active roles cannot be deleted.switch existing users to a new role before deleting") + } + } + return database.DeleteRecord(database.USER_PERMISSIONS_TABLE_NAME, rid.String()) +} From beaaefb871a803c868543cf3eb60e68955ecc889 Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Mon, 27 May 2024 01:28:17 +0530 Subject: [PATCH 008/139] user groups CRUD Apis --- controllers/user.go | 190 +++++++++++++++++++++++++++++++++++++++--- logic/user_mgmt.go | 85 ++++++++++++++++++- models/user_groups.go | 7 -- models/user_mgmt.go | 6 ++ 4 files changed, 268 insertions(+), 20 deletions(-) delete mode 100644 models/user_groups.go diff --git a/controllers/user.go b/controllers/user.go index 38397cadb..558ebe5e1 100644 --- a/controllers/user.go +++ b/controllers/user.go @@ -38,17 +38,157 @@ func userHandlers(r *mux.Router) { r.HandleFunc("/api/users_pending/user/{username}", logic.SecurityCheck(true, http.HandlerFunc(approvePendingUser))).Methods(http.MethodPost) // User Role handlers - r.HandleFunc("/api/v1/users/roles", logic.SecurityCheck(true, http.HandlerFunc(listRoles))).Methods(http.MethodGet) - r.HandleFunc("/api/v1/users/role", logic.SecurityCheck(true, http.HandlerFunc(createRole))).Methods(http.MethodPost) - r.HandleFunc("/api/v1/users/role", logic.SecurityCheck(true, http.HandlerFunc(updateRole))).Methods(http.MethodPut) - r.HandleFunc("/api/v1/users/role", logic.SecurityCheck(true, http.HandlerFunc(deleteRole))).Methods(http.MethodDelete) + r.HandleFunc("/api/v1/user/roles", logic.SecurityCheck(true, http.HandlerFunc(listRoles))).Methods(http.MethodGet) + r.HandleFunc("/api/v1/user/role", logic.SecurityCheck(true, http.HandlerFunc(getRole))).Methods(http.MethodGet) + r.HandleFunc("/api/v1/user/role", logic.SecurityCheck(true, http.HandlerFunc(createRole))).Methods(http.MethodPost) + r.HandleFunc("/api/v1/user/role", logic.SecurityCheck(true, http.HandlerFunc(updateRole))).Methods(http.MethodPut) + r.HandleFunc("/api/v1/user/role", logic.SecurityCheck(true, http.HandlerFunc(deleteRole))).Methods(http.MethodDelete) // User Group Handlers + r.HandleFunc("/api/v1/user/groups", logic.SecurityCheck(true, http.HandlerFunc(listUserGroups))).Methods(http.MethodGet) + r.HandleFunc("/api/v1/user/group", logic.SecurityCheck(true, http.HandlerFunc(getUserGroup))).Methods(http.MethodGet) + r.HandleFunc("/api/v1/user/group", logic.SecurityCheck(true, http.HandlerFunc(createUserGroup))).Methods(http.MethodPost) + r.HandleFunc("/api/v1/user/group", logic.SecurityCheck(true, http.HandlerFunc(updateUserGroup))).Methods(http.MethodPut) + r.HandleFunc("/api/v1/user/group", logic.SecurityCheck(true, http.HandlerFunc(deleteUserGroup))).Methods(http.MethodDelete) } -// swagger:route GET /api/v1/users/roles user listRoles +// swagger:route GET /api/v1/user/groups user listUserGroups // -// Get user role permission templates. +// Get all user groups. +// +// Schemes: https +// +// Security: +// oauth +// +// Responses: +// 200: userBodyResponse +func listUserGroups(w http.ResponseWriter, r *http.Request) { + groups, err := logic.ListUserGroups() + if err != nil { + logic.ReturnErrorResponse(w, r, models.ErrorResponse{ + Code: http.StatusInternalServerError, + Message: err.Error(), + }) + return + } + logic.ReturnSuccessResponseWithJson(w, r, groups, "successfully fetched user groups") +} + +// swagger:route GET /api/v1/user/group user getUserGroup +// +// Get user group. +// +// Schemes: https +// +// Security: +// oauth +// +// Responses: +// 200: userBodyResponse +func getUserGroup(w http.ResponseWriter, r *http.Request) { + var params = mux.Vars(r) + gid := params["group_id"] + if gid == "" { + logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("group id is required"), "badrequest")) + return + } + group, err := logic.GetUserGroup(gid) + if err != nil { + logic.ReturnErrorResponse(w, r, models.ErrorResponse{ + Code: http.StatusInternalServerError, + Message: err.Error(), + }) + return + } + logic.ReturnSuccessResponseWithJson(w, r, group, "successfully fetched user group") +} + +// swagger:route POST /api/v1/user/group user createUserGroup +// +// Create user groups. +// +// Schemes: https +// +// Security: +// oauth +// +// Responses: +// 200: userBodyResponse +func createUserGroup(w http.ResponseWriter, r *http.Request) { + var userGroup models.UserGroup + err := json.NewDecoder(r.Body).Decode(&userGroup) + if err != nil { + slog.Error("error decoding request body", "error", + err.Error()) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) + return + } + err = logic.CreateUserGroup(userGroup) + if err != nil { + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) + return + } + logic.ReturnSuccessResponseWithJson(w, r, userGroup, "created user group") +} + +// swagger:route PUT /api/v1/user/group user updateUserGroup +// +// Update user group. +// +// Schemes: https +// +// Security: +// oauth +// +// Responses: +// 200: userBodyResponse +func updateUserGroup(w http.ResponseWriter, r *http.Request) { + var userGroup models.UserGroup + err := json.NewDecoder(r.Body).Decode(&userGroup) + if err != nil { + slog.Error("error decoding request body", "error", + err.Error()) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) + return + } + err = logic.UpdateUserGroup(userGroup) + if err != nil { + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) + return + } + logic.ReturnSuccessResponseWithJson(w, r, userGroup, "updated user group") +} + +// swagger:route DELETE /api/v1/user/group user deleteUserGroup +// +// delete user group. +// +// Schemes: https +// +// Security: +// oauth +// +// Responses: +// 200: userBodyResponse +func deleteUserGroup(w http.ResponseWriter, r *http.Request) { + var params = mux.Vars(r) + gid := params["group_id"] + if gid == "" { + logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("role is required"), "badrequest")) + return + } + err := logic.DeleteUserGroup(gid) + if err != nil { + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) + return + } + logic.ReturnSuccessResponseWithJson(w, r, nil, "deleted user group") +} + +// swagger:route GET /api/v1/user/roles user listRoles +// +// lists all user roles. // // Schemes: https // @@ -69,7 +209,36 @@ func listRoles(w http.ResponseWriter, r *http.Request) { logic.ReturnSuccessResponseWithJson(w, r, roles, "successfully fetched user roles permission templates") } -// swagger:route POST /api/v1/users/role user createRole +// swagger:route GET /api/v1/user/role user getRole +// +// Get user role permission templates. +// +// Schemes: https +// +// Security: +// oauth +// +// Responses: +// 200: userBodyResponse +func getRole(w http.ResponseWriter, r *http.Request) { + var params = mux.Vars(r) + rid := params["role_id"] + if rid == "" { + logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("role is required"), "badrequest")) + return + } + role, err := logic.GetRole(rid) + if err != nil { + logic.ReturnErrorResponse(w, r, models.ErrorResponse{ + Code: http.StatusInternalServerError, + Message: err.Error(), + }) + return + } + logic.ReturnSuccessResponseWithJson(w, r, role, "successfully fetched user role permission templates") +} + +// swagger:route POST /api/v1/user/role user createRole // // Create user role permission template. // @@ -97,7 +266,7 @@ func createRole(w http.ResponseWriter, r *http.Request) { logic.ReturnSuccessResponseWithJson(w, r, userRole, "created user role") } -// swagger:route PUT /api/v1/users/role user updateRole +// swagger:route PUT /api/v1/user/role user updateRole // // Update user role permission template. // @@ -125,7 +294,7 @@ func updateRole(w http.ResponseWriter, r *http.Request) { logic.ReturnSuccessResponseWithJson(w, r, userRole, "updated user role") } -// swagger:route DELETE /api/v1/users/role user deleteRole +// swagger:route DELETE /api/v1/user/role user deleteRole // // Delete user role permission template. // @@ -137,7 +306,6 @@ func updateRole(w http.ResponseWriter, r *http.Request) { // Responses: // 200: userBodyResponse func deleteRole(w http.ResponseWriter, r *http.Request) { - var userRole models.UserRolePermissionTemplate var params = mux.Vars(r) rid := params["role_id"] if rid == "" { @@ -149,7 +317,7 @@ func deleteRole(w http.ResponseWriter, r *http.Request) { logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } - logic.ReturnSuccessResponseWithJson(w, r, userRole, "created user role") + logic.ReturnSuccessResponseWithJson(w, r, nil, "created user role") } // swagger:route POST /api/users/adm/authenticate authenticate authenticateUser diff --git a/logic/user_mgmt.go b/logic/user_mgmt.go index 6edacc0ed..c8eaa17a5 100644 --- a/logic/user_mgmt.go +++ b/logic/user_mgmt.go @@ -120,12 +120,93 @@ func DeleteRole(rid models.UserRole) error { } for _, user := range users { if user.GroupID != "" { - // TODO - get permission template of the group + ug, err := GetUserGroup(user.GroupID) + if err == nil && ug.PermissionTemplate.ID == rid { + err = errors.New("role cannot be deleted as active user groups are using this role") + return err + } continue } if user.PermissionTemplate.ID == rid { - errors.New("active roles cannot be deleted.switch existing users to a new role before deleting") + err = errors.New("active roles cannot be deleted.switch existing users to a new role before deleting") + return err } } return database.DeleteRecord(database.USER_PERMISSIONS_TABLE_NAME, rid.String()) } + +// CreateUserGroup - creates new user group +func CreateUserGroup(g models.UserGroup) error { + // check if role already exists + _, err := database.FetchRecord(database.USER_GROUPS_TABLE_NAME, g.ID) + if err == nil { + return errors.New("group already exists") + } + d, err := json.Marshal(g) + if err != nil { + return err + } + return database.Insert(g.ID, string(d), database.USER_GROUPS_TABLE_NAME) +} + +// GetUserGroup - fetches user group +func GetUserGroup(gid string) (models.UserGroup, error) { + // check if role already exists + d, err := database.FetchRecord(database.USER_GROUPS_TABLE_NAME, gid) + if err == nil { + return models.UserGroup{}, err + } + var ug models.UserGroup + err = json.Unmarshal([]byte(d), &ug) + if err != nil { + return ug, err + } + return ug, nil +} + +// ListUserGroups - lists user groups +func ListUserGroups() ([]models.UserGroup, error) { + data, err := database.FetchRecords(database.USER_GROUPS_TABLE_NAME) + if err != nil { + return []models.UserGroup{}, err + } + userGroups := []models.UserGroup{} + for _, dataI := range data { + userGroup := models.UserGroup{} + err := json.Unmarshal([]byte(dataI), &userGroup) + if err != nil { + continue + } + userGroups = append(userGroups, userGroup) + } + return userGroups, nil +} + +// UpdateUserGroup - updates new user group +func UpdateUserGroup(g models.UserGroup) error { + // check if group exists + _, err := database.FetchRecord(database.USER_GROUPS_TABLE_NAME, g.ID) + if err != nil { + return err + } + d, err := json.Marshal(g) + if err != nil { + return err + } + return database.Insert(g.ID, string(d), database.USER_GROUPS_TABLE_NAME) +} + +// DeleteUserGroup - deletes user group +func DeleteUserGroup(gid string) error { + users, err := GetUsersDB() + if err != nil { + return err + } + for _, user := range users { + if user.GroupID == gid { + err = errors.New("role cannot be deleted as active user groups are using this role") + return err + } + } + return database.DeleteRecord(database.USER_GROUPS_TABLE_NAME, gid) +} diff --git a/models/user_groups.go b/models/user_groups.go deleted file mode 100644 index 2f2215daf..000000000 --- a/models/user_groups.go +++ /dev/null @@ -1,7 +0,0 @@ -package models - -type UserGroup struct { - ID string `json:"id"` - PermissionTemplate UserRolePermissionTemplate `json:"role_permission_template"` - MetaData string `json:"meta_data"` -} diff --git a/models/user_mgmt.go b/models/user_mgmt.go index d99de0559..d68fed982 100644 --- a/models/user_mgmt.go +++ b/models/user_mgmt.go @@ -59,6 +59,12 @@ type UserRolePermissionTemplate struct { DashBoardAcls DashboardAccessControls `json:"dashboard_access_controls"` } +type UserGroup struct { + ID string `json:"id"` + PermissionTemplate UserRolePermissionTemplate `json:"role_permission_template"` + MetaData string `json:"meta_data"` +} + // User struct - struct for Users type User struct { UserName string `json:"username" bson:"username" validate:"min=3,max=40,in_charset|email"` From 1cf6387f763836fd7851ba1f7a508548b93fed46 Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Mon, 27 May 2024 01:39:15 +0530 Subject: [PATCH 009/139] additional api checks --- controllers/user.go | 1 + logic/user_mgmt.go | 16 +++++++++++++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/controllers/user.go b/controllers/user.go index 558ebe5e1..ec98c3995 100644 --- a/controllers/user.go +++ b/controllers/user.go @@ -50,6 +50,7 @@ func userHandlers(r *mux.Router) { r.HandleFunc("/api/v1/user/group", logic.SecurityCheck(true, http.HandlerFunc(createUserGroup))).Methods(http.MethodPost) r.HandleFunc("/api/v1/user/group", logic.SecurityCheck(true, http.HandlerFunc(updateUserGroup))).Methods(http.MethodPut) r.HandleFunc("/api/v1/user/group", logic.SecurityCheck(true, http.HandlerFunc(deleteUserGroup))).Methods(http.MethodDelete) + } // swagger:route GET /api/v1/user/groups user listUserGroups diff --git a/logic/user_mgmt.go b/logic/user_mgmt.go index c8eaa17a5..e2bd58e90 100644 --- a/logic/user_mgmt.go +++ b/logic/user_mgmt.go @@ -73,6 +73,9 @@ func ListRoles() ([]models.UserRolePermissionTemplate, error) { // CreateRole - inserts new role into DB func CreateRole(r models.UserRolePermissionTemplate) error { // check if role already exists + if r.ID.String() == "" { + return errors.New("role id cannot be empty") + } _, err := database.FetchRecord(database.USER_PERMISSIONS_TABLE_NAME, r.ID.String()) if err == nil { return errors.New("role already exists") @@ -101,6 +104,9 @@ func GetRole(roleID string) (models.UserRolePermissionTemplate, error) { // UpdateRole - updates role template func UpdateRole(r models.UserRolePermissionTemplate) error { + if r.ID.String() == "" { + return errors.New("role id cannot be empty") + } _, err := database.FetchRecord(database.USER_PERMISSIONS_TABLE_NAME, r.ID.String()) if err != nil { return err @@ -114,6 +120,9 @@ func UpdateRole(r models.UserRolePermissionTemplate) error { // DeleteRole - deletes user role func DeleteRole(rid models.UserRole) error { + if rid.String() == "" { + return errors.New("role id cannot be empty") + } users, err := GetUsersDB() if err != nil { return err @@ -138,6 +147,9 @@ func DeleteRole(rid models.UserRole) error { // CreateUserGroup - creates new user group func CreateUserGroup(g models.UserGroup) error { // check if role already exists + if g.ID == "" { + return errors.New("group id cannot be empty") + } _, err := database.FetchRecord(database.USER_GROUPS_TABLE_NAME, g.ID) if err == nil { return errors.New("group already exists") @@ -151,7 +163,6 @@ func CreateUserGroup(g models.UserGroup) error { // GetUserGroup - fetches user group func GetUserGroup(gid string) (models.UserGroup, error) { - // check if role already exists d, err := database.FetchRecord(database.USER_GROUPS_TABLE_NAME, gid) if err == nil { return models.UserGroup{}, err @@ -185,6 +196,9 @@ func ListUserGroups() ([]models.UserGroup, error) { // UpdateUserGroup - updates new user group func UpdateUserGroup(g models.UserGroup) error { // check if group exists + if g.ID == "" { + return errors.New("group id cannot be empty") + } _, err := database.FetchRecord(database.USER_GROUPS_TABLE_NAME, g.ID) if err != nil { return err From 5eb87dc3270be52f2ba1f93cf240e427fa98b1e0 Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Mon, 27 May 2024 13:41:56 +0530 Subject: [PATCH 010/139] add additional scopes --- models/user_mgmt.go | 1 + pro/auth/google.go | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/models/user_mgmt.go b/models/user_mgmt.go index d68fed982..f71760aff 100644 --- a/models/user_mgmt.go +++ b/models/user_mgmt.go @@ -18,6 +18,7 @@ const ( EgressGwRsrcID RsrcID = "all_egress" NetworkRsrcID RsrcID = "all_network" EnrollmentKeysRsrcID RsrcID = "all_enrollment_key" + UserRsrcID RsrcID = "all_user" ) // Pre-Defined User Roles diff --git a/pro/auth/google.go b/pro/auth/google.go index 64d503e73..f6ed4e088 100644 --- a/pro/auth/google.go +++ b/pro/auth/google.go @@ -34,7 +34,7 @@ func initGoogle(redirectURL string, clientID string, clientSecret string) { RedirectURL: redirectURL, ClientID: clientID, ClientSecret: clientSecret, - Scopes: []string{"https://www.googleapis.com/auth/userinfo.email"}, + Scopes: []string{"https://www.googleapis.com/auth/userinfo.email", "https://www.googleapis.com/auth/admin.directory.group.readonly"}, Endpoint: google.Endpoint, } } From 68758c8f761795e11885b13a38885cf232f66f32 Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Mon, 27 May 2024 13:48:36 +0530 Subject: [PATCH 011/139] add additional scopes url --- pro/auth/google.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pro/auth/google.go b/pro/auth/google.go index f6ed4e088..b5dace800 100644 --- a/pro/auth/google.go +++ b/pro/auth/google.go @@ -34,7 +34,7 @@ func initGoogle(redirectURL string, clientID string, clientSecret string) { RedirectURL: redirectURL, ClientID: clientID, ClientSecret: clientSecret, - Scopes: []string{"https://www.googleapis.com/auth/userinfo.email", "https://www.googleapis.com/auth/admin.directory.group.readonly"}, + Scopes: []string{"https://www.googleapis.com/auth/userinfo.email", "https://www.googleapis.com/auth/groups"}, Endpoint: google.Endpoint, } } From 55601d4d80da5a1917b89b7e08dd4a6eda9cd8a8 Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Mon, 27 May 2024 14:07:52 +0530 Subject: [PATCH 012/139] add additional scopes url --- pro/auth/google.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pro/auth/google.go b/pro/auth/google.go index b5dace800..9ba403f36 100644 --- a/pro/auth/google.go +++ b/pro/auth/google.go @@ -34,7 +34,7 @@ func initGoogle(redirectURL string, clientID string, clientSecret string) { RedirectURL: redirectURL, ClientID: clientID, ClientSecret: clientSecret, - Scopes: []string{"https://www.googleapis.com/auth/userinfo.email", "https://www.googleapis.com/auth/groups"}, + Scopes: []string{"https://www.googleapis.com/auth/userinfo.email", "https://apps-apis.google.com/a/feeds/groups"}, Endpoint: google.Endpoint, } } From 3adb0a3e32fe99446309e50f88bc80960c623167 Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Mon, 27 May 2024 14:50:55 +0530 Subject: [PATCH 013/139] rm additional scopes url --- pro/auth/google.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pro/auth/google.go b/pro/auth/google.go index 9ba403f36..64d503e73 100644 --- a/pro/auth/google.go +++ b/pro/auth/google.go @@ -34,7 +34,7 @@ func initGoogle(redirectURL string, clientID string, clientSecret string) { RedirectURL: redirectURL, ClientID: clientID, ClientSecret: clientSecret, - Scopes: []string{"https://www.googleapis.com/auth/userinfo.email", "https://apps-apis.google.com/a/feeds/groups"}, + Scopes: []string{"https://www.googleapis.com/auth/userinfo.email"}, Endpoint: google.Endpoint, } } From e73502414588de82beb8bbfc43100f3f70299e1a Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Tue, 28 May 2024 15:28:07 +0530 Subject: [PATCH 014/139] setup middlleware permission checks --- controllers/controller.go | 1 - logic/security.go | 78 +++++++++++++++++++++++++++++++++++++++ models/structs.go | 5 +++ models/user_mgmt.go | 45 ++++++++++++++-------- 4 files changed, 112 insertions(+), 17 deletions(-) diff --git a/controllers/controller.go b/controllers/controller.go index 4ce41e47c..4a401bcf0 100644 --- a/controllers/controller.go +++ b/controllers/controller.go @@ -40,7 +40,6 @@ func HandleRESTRequests(wg *sync.WaitGroup, ctx context.Context) { defer wg.Done() r := mux.NewRouter() - // Currently allowed dev origin is all. Should change in prod // should consider analyzing the allowed methods further headersOk := handlers.AllowedHeaders([]string{"Access-Control-Allow-Origin", "X-Requested-With", "Content-Type", "authorization", "From-Ui"}) diff --git a/logic/security.go b/logic/security.go index 37be1f441..9f8acd833 100644 --- a/logic/security.go +++ b/logic/security.go @@ -1,6 +1,8 @@ package logic import ( + "errors" + "fmt" "net/http" "strings" @@ -17,6 +19,82 @@ const ( Unauthorized_Err = models.Error(Unauthorized_Msg) ) +func networkPermissionsCheck(user models.User, r *http.Request) error { + // get info from header to determine the target rsrc + targetRsrc := r.Header.Get("TARGET_RSRC") + targetRsrcID := r.Header.Get("TARGET_RSRC_ID") + netID := r.Header.Get("NET_ID") + if targetRsrc == "" || targetRsrcID == "" { + return errors.New("target rsrc or rsrc id is missing") + } + if r.Method == "" { + r.Method = http.MethodGet + } + // check if user has scope for target resource + // TODO - differentitate between global scope and network scope apis + networkPermissionScope, ok := user.PermissionTemplate.DashBoardAcls.NetworkLevelAccess[models.NetworkID(netID)] + if !ok { + return errors.New("access denied") + } + if networkPermissionScope.FullAccess { + return nil + } + rsrcPermissionScope, ok := networkPermissionScope.NetworkRsrcPermissionsScope[models.RsrcType(targetRsrc)] + if !ok { + return fmt.Errorf("access denied to %s rsrc", targetRsrc) + } + if allRsrcsTypePermissionScope, ok := rsrcPermissionScope[models.RsrcID(fmt.Sprintf("all_%s", targetRsrc))]; ok { + return checkPermissionScopeWithReqMethod(allRsrcsTypePermissionScope, r.Method) + + } + if scope, ok := rsrcPermissionScope[models.RsrcID(targetRsrcID)]; ok { + return checkPermissionScopeWithReqMethod(scope, r.Method) + } + return errors.New("access denied") +} + +func globalPermissionsCheck(user models.User, r *http.Request) error { + targetRsrc := r.Header.Get("TARGET_RSRC") + targetRsrcID := r.Header.Get("TARGET_RSRC_ID") + if targetRsrc == "" || targetRsrcID == "" { + return errors.New("target rsrc or rsrc id is missing") + } + if r.Method == "" { + r.Method = http.MethodGet + } + if user.PermissionTemplate.DashBoardAcls.FullAccess { + return nil + } + rsrcPermissionScope, ok := user.PermissionTemplate.DashBoardAcls.GlobalLevelAccess[models.RsrcType(targetRsrc)] + if !ok { + return fmt.Errorf("access denied to %s rsrc", targetRsrc) + } + if allRsrcsTypePermissionScope, ok := rsrcPermissionScope[models.RsrcID(fmt.Sprintf("all_%s", targetRsrc))]; ok { + return checkPermissionScopeWithReqMethod(allRsrcsTypePermissionScope, r.Method) + + } + if scope, ok := rsrcPermissionScope[models.RsrcID(targetRsrcID)]; ok { + return checkPermissionScopeWithReqMethod(scope, r.Method) + } + return errors.New("access denied") +} + +func checkPermissionScopeWithReqMethod(scope models.RsrcPermissionScope, reqmethod string) error { + if reqmethod == http.MethodGet && scope.Read { + return nil + } + if (reqmethod == http.MethodPatch || reqmethod == http.MethodPut) && scope.Update { + return nil + } + if reqmethod == http.MethodDelete && scope.Delete { + return nil + } + if reqmethod == http.MethodPost && scope.Create { + return nil + } + return errors.New("operation not permitted") +} + // SecurityCheck - Check if user has appropriate permissions func SecurityCheck(reqAdmin bool, next http.Handler) http.HandlerFunc { diff --git a/models/structs.go b/models/structs.go index 38b09499a..745bff29b 100644 --- a/models/structs.go +++ b/models/structs.go @@ -345,3 +345,8 @@ const ( type GetClientConfReqDto struct { PreferredIp string `json:"preferred_ip"` } + +type RsrcURLInfo struct { + Method string + Path string +} diff --git a/models/user_mgmt.go b/models/user_mgmt.go index f71760aff..821502bf1 100644 --- a/models/user_mgmt.go +++ b/models/user_mgmt.go @@ -7,18 +7,31 @@ import ( ) type NetworkID string +type RsrcType string type RsrcID string type UserRole string const ( - HostRsrcID RsrcID = "all_host" - RelayRsrcID RsrcID = "all_relay" - RemoteAccessGwRsrcID RsrcID = "all_remote_access_gw" - InetGwRsrcID RsrcID = "all_inet_gw" - EgressGwRsrcID RsrcID = "all_egress" - NetworkRsrcID RsrcID = "all_network" - EnrollmentKeysRsrcID RsrcID = "all_enrollment_key" - UserRsrcID RsrcID = "all_user" + HostRsrc RsrcType = "host" + RelayRsrc RsrcType = "relay" + RemoteAccessGwRsrc RsrcType = "remote_access_gw" + InetGwRsrc RsrcType = "inet_gw" + EgressGwRsrc RsrcType = "egress" + NetworkRsrc RsrcType = "networks" + EnrollmentKeysRsrc RsrcType = "enrollment_key" + UserRsrc RsrcType = "user" + AclRsrc RsrcType = "acl" +) + +const ( + AllHostRsrcID RsrcID = "all_host" + AllRelayRsrcID RsrcID = "all_relay" + AllRemoteAccessGwRsrcID RsrcID = "all_remote_access_gw" + AllInetGwRsrcID RsrcID = "all_inet_gw" + AllEgressGwRsrcID RsrcID = "all_egress" + AllNetworkRsrcID RsrcID = "all_network" + AllEnrollmentKeysRsrcID RsrcID = "all_enrollment_key" + AllUserRsrcID RsrcID = "all_user" ) // Pre-Defined User Roles @@ -34,7 +47,7 @@ func (r UserRole) String() string { return string(r) } -type RsrcPermissions struct { +type RsrcPermissionScope struct { Create bool `json:"create"` Read bool `json:"read"` Update bool `json:"update"` @@ -42,16 +55,16 @@ type RsrcPermissions struct { } type NetworkAccessControls struct { - NetworkID string `json:"network_id"` - FullAccess bool `json:"full_access"` - NetworkRsrcPermissionsList map[RsrcID]RsrcPermissions `json:"network_permissions_list"` + NetworkID string `json:"network_id"` + FullAccess bool `json:"full_access"` + NetworkRsrcPermissionsScope map[RsrcType]map[RsrcID]RsrcPermissionScope `json:"network_permissions_list"` } type DashboardAccessControls struct { - FullAccess bool `json:"full_access"` - DenyDashboardAccess bool `json:"deny_dashboard_access"` - NetworkLevelAccess map[NetworkID]NetworkAccessControls `json:"network_access_controls"` - GlobalLevelAccess map[RsrcID]RsrcPermissions `json:"global_level_access"` + FullAccess bool `json:"full_access"` + DenyDashboardAccess bool `json:"deny_dashboard_access"` + NetworkLevelAccess map[NetworkID]NetworkAccessControls `json:"network_access_controls"` + GlobalLevelAccess map[RsrcType]map[RsrcID]RsrcPermissionScope `json:"global_level_access"` } type UserRolePermissionTemplate struct { From 401764d466deb4fc753bdb7f399034cd7a16a8d0 Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Tue, 28 May 2024 16:07:21 +0530 Subject: [PATCH 015/139] integrate permission check into middleware --- logic/security.go | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/logic/security.go b/logic/security.go index 9f8acd833..e08d019e3 100644 --- a/logic/security.go +++ b/logic/security.go @@ -19,7 +19,7 @@ const ( Unauthorized_Err = models.Error(Unauthorized_Msg) ) -func networkPermissionsCheck(user models.User, r *http.Request) error { +func networkPermissionsCheck(username string, r *http.Request) error { // get info from header to determine the target rsrc targetRsrc := r.Header.Get("TARGET_RSRC") targetRsrcID := r.Header.Get("TARGET_RSRC_ID") @@ -27,6 +27,10 @@ func networkPermissionsCheck(user models.User, r *http.Request) error { if targetRsrc == "" || targetRsrcID == "" { return errors.New("target rsrc or rsrc id is missing") } + user, err := GetUser(username) + if err != nil { + return err + } if r.Method == "" { r.Method = http.MethodGet } @@ -53,12 +57,16 @@ func networkPermissionsCheck(user models.User, r *http.Request) error { return errors.New("access denied") } -func globalPermissionsCheck(user models.User, r *http.Request) error { +func globalPermissionsCheck(username string, r *http.Request) error { targetRsrc := r.Header.Get("TARGET_RSRC") targetRsrcID := r.Header.Get("TARGET_RSRC_ID") if targetRsrc == "" || targetRsrcID == "" { return errors.New("target rsrc or rsrc id is missing") } + user, err := GetUser(username) + if err != nil { + return err + } if r.Method == "" { r.Method = http.MethodGet } @@ -101,6 +109,7 @@ func SecurityCheck(reqAdmin bool, next http.Handler) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { r.Header.Set("ismaster", "no") bearerToken := r.Header.Get("Authorization") + isGlobalAccesss := r.Header.Get("IS_GLOBAL_ACCESS") == "yes" username, err := UserPermissions(reqAdmin, bearerToken) if err != nil { ReturnErrorResponse(w, r, FormatError(err, err.Error())) @@ -109,6 +118,12 @@ func SecurityCheck(reqAdmin bool, next http.Handler) http.HandlerFunc { // detect masteradmin if username == MasterUser { r.Header.Set("ismaster", "yes") + } else { + if isGlobalAccesss { + globalPermissionsCheck(username, r) + } else { + networkPermissionsCheck(username, r) + } } r.Header.Set("user", username) next.ServeHTTP(w, r) From 89bbc467d907095d41fb8e9c0230728dd42f9b9b Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Tue, 28 May 2024 16:17:05 +0530 Subject: [PATCH 016/139] integrate permission check into middleware --- logic/security.go | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/logic/security.go b/logic/security.go index e08d019e3..2c8418360 100644 --- a/logic/security.go +++ b/logic/security.go @@ -20,6 +20,13 @@ const ( ) func networkPermissionsCheck(username string, r *http.Request) error { + user, err := GetUser(username) + if err != nil { + return err + } + if user.PermissionTemplate.ID == models.SuperAdminRole { + return nil + } // get info from header to determine the target rsrc targetRsrc := r.Header.Get("TARGET_RSRC") targetRsrcID := r.Header.Get("TARGET_RSRC_ID") @@ -27,10 +34,6 @@ func networkPermissionsCheck(username string, r *http.Request) error { if targetRsrc == "" || targetRsrcID == "" { return errors.New("target rsrc or rsrc id is missing") } - user, err := GetUser(username) - if err != nil { - return err - } if r.Method == "" { r.Method = http.MethodGet } @@ -58,15 +61,18 @@ func networkPermissionsCheck(username string, r *http.Request) error { } func globalPermissionsCheck(username string, r *http.Request) error { + user, err := GetUser(username) + if err != nil { + return err + } + if user.PermissionTemplate.ID == models.SuperAdminRole { + return nil + } targetRsrc := r.Header.Get("TARGET_RSRC") targetRsrcID := r.Header.Get("TARGET_RSRC_ID") if targetRsrc == "" || targetRsrcID == "" { return errors.New("target rsrc or rsrc id is missing") } - user, err := GetUser(username) - if err != nil { - return err - } if r.Method == "" { r.Method = http.MethodGet } From 91a23160d0bb1bf8b48ed1dc958be9396b09442b Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Tue, 28 May 2024 18:23:08 +0530 Subject: [PATCH 017/139] check for headers for subjects --- logic/security.go | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/logic/security.go b/logic/security.go index 2c8418360..6ad83340c 100644 --- a/logic/security.go +++ b/logic/security.go @@ -24,15 +24,18 @@ func networkPermissionsCheck(username string, r *http.Request) error { if err != nil { return err } - if user.PermissionTemplate.ID == models.SuperAdminRole { + if user.PermissionTemplate.DashBoardAcls.FullAccess { return nil } // get info from header to determine the target rsrc targetRsrc := r.Header.Get("TARGET_RSRC") targetRsrcID := r.Header.Get("TARGET_RSRC_ID") netID := r.Header.Get("NET_ID") - if targetRsrc == "" || targetRsrcID == "" { - return errors.New("target rsrc or rsrc id is missing") + if targetRsrc == "" { + return errors.New("target rsrc is missing") + } + if netID == "" { + return errors.New("network id is missing") } if r.Method == "" { r.Method = http.MethodGet @@ -54,6 +57,9 @@ func networkPermissionsCheck(username string, r *http.Request) error { return checkPermissionScopeWithReqMethod(allRsrcsTypePermissionScope, r.Method) } + if targetRsrcID == "" { + return errors.New("target rsrc is missing") + } if scope, ok := rsrcPermissionScope[models.RsrcID(targetRsrcID)]; ok { return checkPermissionScopeWithReqMethod(scope, r.Method) } @@ -65,13 +71,13 @@ func globalPermissionsCheck(username string, r *http.Request) error { if err != nil { return err } - if user.PermissionTemplate.ID == models.SuperAdminRole { + if user.PermissionTemplate.DashBoardAcls.FullAccess { return nil } targetRsrc := r.Header.Get("TARGET_RSRC") targetRsrcID := r.Header.Get("TARGET_RSRC_ID") - if targetRsrc == "" || targetRsrcID == "" { - return errors.New("target rsrc or rsrc id is missing") + if targetRsrc == "" { + return errors.New("target rsrc is missing") } if r.Method == "" { r.Method = http.MethodGet @@ -87,6 +93,9 @@ func globalPermissionsCheck(username string, r *http.Request) error { return checkPermissionScopeWithReqMethod(allRsrcsTypePermissionScope, r.Method) } + if targetRsrcID == "" { + return errors.New("target rsrc id is missing") + } if scope, ok := rsrcPermissionScope[models.RsrcID(targetRsrcID)]; ok { return checkPermissionScopeWithReqMethod(scope, r.Method) } From 78fc3ab508f522b5eae932d65eb949580ddbe018 Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Thu, 6 Jun 2024 00:34:28 +0530 Subject: [PATCH 018/139] refactor user role models --- logic/security.go | 30 ++++++++++------ logic/user_mgmt.go | 87 +++++++++++++++++++++++++++------------------ models/user_mgmt.go | 75 +++++++++++++++++++------------------- 3 files changed, 110 insertions(+), 82 deletions(-) diff --git a/logic/security.go b/logic/security.go index 6ad83340c..55d178b49 100644 --- a/logic/security.go +++ b/logic/security.go @@ -19,14 +19,21 @@ const ( Unauthorized_Err = models.Error(Unauthorized_Msg) ) +func GetSubjectsFromURL(URL string) (rsrcType models.RsrcType, rsrcID models.RsrcID) { + urlSplit := strings.Split(URL, "/") + rsrcType = models.RsrcType(urlSplit[1]) + if len(urlSplit) > 1 { + rsrcID = models.RsrcID(urlSplit[2]) + } + return +} + func networkPermissionsCheck(username string, r *http.Request) error { + // at this point global checks should be completed user, err := GetUser(username) if err != nil { return err } - if user.PermissionTemplate.DashBoardAcls.FullAccess { - return nil - } // get info from header to determine the target rsrc targetRsrc := r.Header.Get("TARGET_RSRC") targetRsrcID := r.Header.Get("TARGET_RSRC_ID") @@ -42,14 +49,14 @@ func networkPermissionsCheck(username string, r *http.Request) error { } // check if user has scope for target resource // TODO - differentitate between global scope and network scope apis - networkPermissionScope, ok := user.PermissionTemplate.DashBoardAcls.NetworkLevelAccess[models.NetworkID(netID)] - if !ok { + networkPermissionScope, err := GetRole(user.NetworkRoles[models.NetworkID(netID)].String()) + if err != nil { return errors.New("access denied") } if networkPermissionScope.FullAccess { return nil } - rsrcPermissionScope, ok := networkPermissionScope.NetworkRsrcPermissionsScope[models.RsrcType(targetRsrc)] + rsrcPermissionScope, ok := networkPermissionScope.NetworkLevelAccess[models.RsrcType(targetRsrc)] if !ok { return fmt.Errorf("access denied to %s rsrc", targetRsrc) } @@ -71,7 +78,11 @@ func globalPermissionsCheck(username string, r *http.Request) error { if err != nil { return err } - if user.PermissionTemplate.DashBoardAcls.FullAccess { + userRole, err := GetRole(user.PlatformRoleID.String()) + if err != nil { + return errors.New("access denied") + } + if userRole.FullAccess { return nil } targetRsrc := r.Header.Get("TARGET_RSRC") @@ -82,10 +93,7 @@ func globalPermissionsCheck(username string, r *http.Request) error { if r.Method == "" { r.Method = http.MethodGet } - if user.PermissionTemplate.DashBoardAcls.FullAccess { - return nil - } - rsrcPermissionScope, ok := user.PermissionTemplate.DashBoardAcls.GlobalLevelAccess[models.RsrcType(targetRsrc)] + rsrcPermissionScope, ok := userRole.GlobalLevelAccess[models.RsrcType(targetRsrc)] if !ok { return fmt.Errorf("access denied to %s rsrc", targetRsrc) } diff --git a/logic/user_mgmt.go b/logic/user_mgmt.go index e2bd58e90..a34577589 100644 --- a/logic/user_mgmt.go +++ b/logic/user_mgmt.go @@ -3,6 +3,7 @@ package logic import ( "encoding/json" "errors" + "fmt" "github.com/gravitl/netmaker/database" "github.com/gravitl/netmaker/models" @@ -10,35 +11,30 @@ import ( // Pre-Define Permission Templates for default Roles var SuperAdminPermissionTemplate = models.UserRolePermissionTemplate{ - ID: models.SuperAdminRole, - Default: true, - DashBoardAcls: models.DashboardAccessControls{ - FullAccess: true, - }, + ID: models.SuperAdminRole, + Default: true, + FullAccess: true, } var AdminPermissionTemplate = models.UserRolePermissionTemplate{ - ID: models.AdminRole, - Default: true, - DashBoardAcls: models.DashboardAccessControls{ - FullAccess: true, - }, + ID: models.AdminRole, + Default: true, + FullAccess: true, } var NetworkAdminPermissionTemplate = models.UserRolePermissionTemplate{ - ID: models.NetworkAdmin, - Default: true, - DashBoardAcls: models.DashboardAccessControls{ - NetworkLevelAccess: make(map[models.NetworkID]models.NetworkAccessControls), - }, + ID: models.NetworkAdmin, + Default: true, + IsNetworkRole: true, + FullAccess: true, + NetworkLevelAccess: make(map[models.RsrcType]map[models.RsrcID]models.RsrcPermissionScope), } var NetworkUserPermissionTemplate = models.UserRolePermissionTemplate{ - ID: models.NetworkUser, - Default: true, - DashBoardAcls: models.DashboardAccessControls{ - DenyDashboardAccess: true, - NetworkLevelAccess: make(map[models.NetworkID]models.NetworkAccessControls), - }, + ID: models.NetworkUser, + Default: true, + FullAccess: false, + DenyDashboardAccess: true, + NetworkLevelAccess: make(map[models.RsrcType]map[models.RsrcID]models.RsrcPermissionScope), } func UserRolesInit() { @@ -128,18 +124,24 @@ func DeleteRole(rid models.UserRole) error { return err } for _, user := range users { - if user.GroupID != "" { - ug, err := GetUserGroup(user.GroupID) - if err == nil && ug.PermissionTemplate.ID == rid { - err = errors.New("role cannot be deleted as active user groups are using this role") - return err - } - continue - } - if user.PermissionTemplate.ID == rid { + // for groupID := range user.UserGroups { + // ug, err := GetUserGroup(groupID.String()) + // if err == nil && ug.N.ID == rid { + // err = errors.New("role cannot be deleted as active user groups are using this role") + // return err + // } + // continue + // } + if user.PlatformRoleID == rid { err = errors.New("active roles cannot be deleted.switch existing users to a new role before deleting") return err } + for _, networkRole := range user.NetworkRoles { + if networkRole == rid { + err = errors.New("active roles cannot be deleted.switch existing users to a new role before deleting") + return err + } + } } return database.DeleteRecord(database.USER_PERMISSIONS_TABLE_NAME, rid.String()) } @@ -217,10 +219,27 @@ func DeleteUserGroup(gid string) error { return err } for _, user := range users { - if user.GroupID == gid { - err = errors.New("role cannot be deleted as active user groups are using this role") - return err - } + fmt.Println("TODO: ", user) + // if user.GroupID == gid { + // err = errors.New("role cannot be deleted as active user groups are using this role") + // return err + // } } return database.DeleteRecord(database.USER_GROUPS_TABLE_NAME, gid) } + +func HasNetworkRsrcScope(permissionTemplate models.UserRolePermissionTemplate, netid string, rsrcType models.RsrcType, rsrcID models.RsrcID, op string) bool { + if permissionTemplate.FullAccess { + return true + } + + rsrcScope, ok := permissionTemplate.NetworkLevelAccess[rsrcType] + if !ok { + return false + } + _, ok = rsrcScope[rsrcID] + if !ok { + return false + } + return true +} diff --git a/models/user_mgmt.go b/models/user_mgmt.go index 821502bf1..bed8fd39f 100644 --- a/models/user_mgmt.go +++ b/models/user_mgmt.go @@ -10,16 +10,17 @@ type NetworkID string type RsrcType string type RsrcID string type UserRole string +type UserGroupID string const ( - HostRsrc RsrcType = "host" - RelayRsrc RsrcType = "relay" + HostRsrc RsrcType = "hosts" + RelayRsrc RsrcType = "relays" RemoteAccessGwRsrc RsrcType = "remote_access_gw" InetGwRsrc RsrcType = "inet_gw" EgressGwRsrc RsrcType = "egress" NetworkRsrc RsrcType = "networks" EnrollmentKeysRsrc RsrcType = "enrollment_key" - UserRsrc RsrcType = "user" + UserRsrc RsrcType = "users" AclRsrc RsrcType = "acl" ) @@ -47,57 +48,57 @@ func (r UserRole) String() string { return string(r) } -type RsrcPermissionScope struct { - Create bool `json:"create"` - Read bool `json:"read"` - Update bool `json:"update"` - Delete bool `json:"delete"` +func (g UserGroupID) String() string { + return string(g) } -type NetworkAccessControls struct { - NetworkID string `json:"network_id"` - FullAccess bool `json:"full_access"` - NetworkRsrcPermissionsScope map[RsrcType]map[RsrcID]RsrcPermissionScope `json:"network_permissions_list"` +type RsrcPermissionScope struct { + Create bool `json:"create"` + Read bool `json:"read"` + Update bool `json:"update"` + Delete bool `json:"delete"` + VPNAccess bool `json:"vpn_access"` } -type DashboardAccessControls struct { - FullAccess bool `json:"full_access"` +type UserRolePermissionTemplate struct { + ID UserRole `json:"id"` + Default bool `json:"default"` DenyDashboardAccess bool `json:"deny_dashboard_access"` - NetworkLevelAccess map[NetworkID]NetworkAccessControls `json:"network_access_controls"` + FullAccess bool `json:"full_access"` + IsNetworkRole bool `json:"network_role"` + NetworkLevelAccess map[RsrcType]map[RsrcID]RsrcPermissionScope `json:"network_level_access"` GlobalLevelAccess map[RsrcType]map[RsrcID]RsrcPermissionScope `json:"global_level_access"` } -type UserRolePermissionTemplate struct { - ID UserRole `json:"id"` - Default bool `json:"default"` - DashBoardAcls DashboardAccessControls `json:"dashboard_access_controls"` -} - type UserGroup struct { - ID string `json:"id"` - PermissionTemplate UserRolePermissionTemplate `json:"role_permission_template"` - MetaData string `json:"meta_data"` + ID string `json:"id"` + NetworkRoles map[NetworkID]UserRole `json:"network_roles"` + MetaData string `json:"meta_data"` } // User struct - struct for Users type User struct { - UserName string `json:"username" bson:"username" validate:"min=3,max=40,in_charset|email"` - Password string `json:"password" bson:"password" validate:"required,min=5"` - IsAdmin bool `json:"isadmin" bson:"isadmin"` - IsSuperAdmin bool `json:"issuperadmin"` - RemoteGwIDs map[string]struct{} `json:"remote_gw_ids"` - GroupID string `json:"group_id"` - PermissionTemplate UserRolePermissionTemplate `json:"role_permission_template"` - LastLoginTime time.Time `json:"last_login_time"` + UserName string `json:"username" bson:"username" validate:"min=3,max=40,in_charset|email"` + Password string `json:"password" bson:"password" validate:"required,min=5"` + IsAdmin bool `json:"isadmin" bson:"isadmin"` + IsSuperAdmin bool `json:"issuperadmin"` + RemoteGwIDs map[string]struct{} `json:"remote_gw_ids"` + UserGroups map[UserGroupID]struct{} `json:"user_groups"` + PlatformRoleID UserRole `json:"platform_role_id"` + NetworkRoles map[NetworkID]UserRole `json:"network_roles"` + LastLoginTime time.Time `json:"last_login_time"` } // ReturnUser - return user struct type ReturnUser struct { - UserName string `json:"username"` - IsAdmin bool `json:"isadmin"` - IsSuperAdmin bool `json:"issuperadmin"` - RemoteGwIDs map[string]struct{} `json:"remote_gw_ids"` - LastLoginTime time.Time `json:"last_login_time"` + UserName string `json:"username"` + IsAdmin bool `json:"isadmin"` + IsSuperAdmin bool `json:"issuperadmin"` + RemoteGwIDs map[string]struct{} `json:"remote_gw_ids"` + UserGroups map[UserGroupID]struct{} `json:"user_groups"` + PlatformRoleID string `json:"platform_role_id"` + NetworkRoles map[NetworkID]UserRole `json:"network_roles"` + LastLoginTime time.Time `json:"last_login_time"` } // UserAuthParams - user auth params struct From bb44816800d1135d57fae0b4453df3ed42f544b2 Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Thu, 6 Jun 2024 00:43:00 +0530 Subject: [PATCH 019/139] refactor user groups models --- controllers/user.go | 4 ++-- logic/user_mgmt.go | 43 +++++++++++++++++++++---------------------- models/user_mgmt.go | 18 +++++++++--------- 3 files changed, 32 insertions(+), 33 deletions(-) diff --git a/controllers/user.go b/controllers/user.go index ec98c3995..583d1d359 100644 --- a/controllers/user.go +++ b/controllers/user.go @@ -94,7 +94,7 @@ func getUserGroup(w http.ResponseWriter, r *http.Request) { logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("group id is required"), "badrequest")) return } - group, err := logic.GetUserGroup(gid) + group, err := logic.GetUserGroup(models.UserGroupID(gid)) if err != nil { logic.ReturnErrorResponse(w, r, models.ErrorResponse{ Code: http.StatusInternalServerError, @@ -179,7 +179,7 @@ func deleteUserGroup(w http.ResponseWriter, r *http.Request) { logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("role is required"), "badrequest")) return } - err := logic.DeleteUserGroup(gid) + err := logic.DeleteUserGroup(models.UserGroupID(gid)) if err != nil { logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return diff --git a/logic/user_mgmt.go b/logic/user_mgmt.go index a34577589..31cfddfaf 100644 --- a/logic/user_mgmt.go +++ b/logic/user_mgmt.go @@ -3,7 +3,6 @@ package logic import ( "encoding/json" "errors" - "fmt" "github.com/gravitl/netmaker/database" "github.com/gravitl/netmaker/models" @@ -124,14 +123,18 @@ func DeleteRole(rid models.UserRole) error { return err } for _, user := range users { - // for groupID := range user.UserGroups { - // ug, err := GetUserGroup(groupID.String()) - // if err == nil && ug.N.ID == rid { - // err = errors.New("role cannot be deleted as active user groups are using this role") - // return err - // } - // continue - // } + if user.UserGroup != "" { + ug, err := GetUserGroup(user.UserGroup) + if err == nil { + for _, networkRole := range ug.NetworkRoles { + if networkRole == rid { + err = errors.New("role cannot be deleted as active user groups are using this role") + return err + } + } + } + } + if user.PlatformRoleID == rid { err = errors.New("active roles cannot be deleted.switch existing users to a new role before deleting") return err @@ -164,8 +167,8 @@ func CreateUserGroup(g models.UserGroup) error { } // GetUserGroup - fetches user group -func GetUserGroup(gid string) (models.UserGroup, error) { - d, err := database.FetchRecord(database.USER_GROUPS_TABLE_NAME, gid) +func GetUserGroup(gid models.UserGroupID) (models.UserGroup, error) { + d, err := database.FetchRecord(database.USER_GROUPS_TABLE_NAME, gid.String()) if err == nil { return models.UserGroup{}, err } @@ -213,19 +216,18 @@ func UpdateUserGroup(g models.UserGroup) error { } // DeleteUserGroup - deletes user group -func DeleteUserGroup(gid string) error { +func DeleteUserGroup(gid models.UserGroupID) error { users, err := GetUsersDB() if err != nil { return err } for _, user := range users { - fmt.Println("TODO: ", user) - // if user.GroupID == gid { - // err = errors.New("role cannot be deleted as active user groups are using this role") - // return err - // } + if user.UserGroup == gid { + err = errors.New("role cannot be deleted as active user groups are using this role") + return err + } } - return database.DeleteRecord(database.USER_GROUPS_TABLE_NAME, gid) + return database.DeleteRecord(database.USER_GROUPS_TABLE_NAME, gid.String()) } func HasNetworkRsrcScope(permissionTemplate models.UserRolePermissionTemplate, netid string, rsrcType models.RsrcType, rsrcID models.RsrcID, op string) bool { @@ -238,8 +240,5 @@ func HasNetworkRsrcScope(permissionTemplate models.UserRolePermissionTemplate, n return false } _, ok = rsrcScope[rsrcID] - if !ok { - return false - } - return true + return ok } diff --git a/models/user_mgmt.go b/models/user_mgmt.go index bed8fd39f..853627b67 100644 --- a/models/user_mgmt.go +++ b/models/user_mgmt.go @@ -78,15 +78,15 @@ type UserGroup struct { // User struct - struct for Users type User struct { - UserName string `json:"username" bson:"username" validate:"min=3,max=40,in_charset|email"` - Password string `json:"password" bson:"password" validate:"required,min=5"` - IsAdmin bool `json:"isadmin" bson:"isadmin"` - IsSuperAdmin bool `json:"issuperadmin"` - RemoteGwIDs map[string]struct{} `json:"remote_gw_ids"` - UserGroups map[UserGroupID]struct{} `json:"user_groups"` - PlatformRoleID UserRole `json:"platform_role_id"` - NetworkRoles map[NetworkID]UserRole `json:"network_roles"` - LastLoginTime time.Time `json:"last_login_time"` + UserName string `json:"username" bson:"username" validate:"min=3,max=40,in_charset|email"` + Password string `json:"password" bson:"password" validate:"required,min=5"` + IsAdmin bool `json:"isadmin" bson:"isadmin"` + IsSuperAdmin bool `json:"issuperadmin"` + RemoteGwIDs map[string]struct{} `json:"remote_gw_ids"` + UserGroup UserGroupID `json:"user_groups"` + PlatformRoleID UserRole `json:"platform_role_id"` + NetworkRoles map[NetworkID]UserRole `json:"network_roles"` + LastLoginTime time.Time `json:"last_login_time"` } // ReturnUser - return user struct From 607a2d98eeda5c7eaf28c24bf1ba5c6e139cc78d Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Mon, 10 Jun 2024 18:28:21 +0530 Subject: [PATCH 020/139] add new user to pending user via RAC login --- pro/auth/headless_callback.go | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/pro/auth/headless_callback.go b/pro/auth/headless_callback.go index 838f97130..19cf0593d 100644 --- a/pro/auth/headless_callback.go +++ b/pro/auth/headless_callback.go @@ -6,6 +6,7 @@ import ( "net/http" "github.com/gravitl/netmaker/auth" + "github.com/gravitl/netmaker/database" "github.com/gravitl/netmaker/logger" "github.com/gravitl/netmaker/logic" "github.com/gravitl/netmaker/logic/pro/netcache" @@ -58,10 +59,20 @@ func HandleHeadlessSSOCallback(w http.ResponseWriter, r *http.Request) { } user, err := logic.GetUser(userClaims.getUserName()) if err != nil { - response := returnErrTemplate("", "user not found", state, reqKeyIf) - w.WriteHeader(http.StatusForbidden) - w.Write(response) - return + if database.IsEmptyRecord(err) { // user must not exist, so try to make one + err = logic.InsertPendingUser(&models.User{ + UserName: userClaims.getUserName(), + }) + if err != nil { + handleSomethingWentWrong(w) + return + } + handleFirstTimeOauthUserSignUp(w) + return + } else { + handleSomethingWentWrong(w) + return + } } newPass, fetchErr := auth.FetchPassValue("") if fetchErr != nil { From d226deaf4da4885e77b3c74af6dc83b939402ab4 Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Mon, 10 Jun 2024 19:02:51 +0530 Subject: [PATCH 021/139] untracked --- controllers/middleware.go | 63 +++++++++++++++++++ models/user_mgmt.go | 126 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 189 insertions(+) create mode 100644 controllers/middleware.go create mode 100644 models/user_mgmt.go diff --git a/controllers/middleware.go b/controllers/middleware.go new file mode 100644 index 000000000..4a547b354 --- /dev/null +++ b/controllers/middleware.go @@ -0,0 +1,63 @@ +package controller + +import ( + "net/http" + "strings" + + "github.com/gorilla/mux" + "github.com/gravitl/netmaker/models" +) + +func userMiddleWare(handler http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + var params = mux.Vars(r) + r.Header.Set("NET_ID", params["network"]) + if strings.Contains(r.URL.Path, "host") || strings.Contains(r.URL.Path, "node") { + r.Header.Set("TARGET_RSRC", models.HostRsrc.String()) + r.Header.Set("RSRC_TYPE", models.HostRsrc.String()) + } + if strings.Contains(r.URL.Path, "dns") { + r.Header.Set("RSRC_TYPE", models.DnsRsrc.String()) + r.Header.Set("TARGET_RSRC", models.DnsRsrc.String()) + } + if strings.Contains(r.URL.Path, "users") { + r.Header.Set("RSRC_TYPE", models.UserRsrc.String()) + r.Header.Set("TARGET_RSRC", models.UserRsrc.String()) + } + if strings.Contains(r.URL.Path, "ingress") { + r.Header.Set("TARGET_RSRC", models.RemoteAccessGwRsrc.String()) + } + if strings.Contains(r.URL.Path, "gateway") { + r.Header.Set("TARGET_RSRC", models.EgressGwRsrc.String()) + } + if strings.Contains(r.URL.Path, "networks") { + r.Header.Set("TARGET_RSRC", models.NetworkRsrc.String()) + r.Header.Set("RSRC_TYPE", models.NetworkRsrc.String()) + } + if strings.Contains(r.URL.Path, "extclients") { + r.Header.Set("TARGET_RSRC", models.ExtClientsRsrc.String()) + r.Header.Set("RSRC_TYPE", models.ExtClientsRsrc.String()) + } + if nodeID, ok := params["nodeid"]; ok { + r.Header.Set("TARGET_RSRC_ID", nodeID) + } + if hostID, ok := params["hostid"]; ok { + r.Header.Set("TARGET_RSRC_ID", hostID) + } + if clientID, ok := params["clientid"]; ok { + r.Header.Set("TARGET_RSRC_ID", clientID) + } + if netID, ok := params["networkname"]; ok { + r.Header.Set("TARGET_RSRC_ID", netID) + } + if userID, ok := params["username"]; ok { + r.Header.Set("TARGET_RSRC_ID", userID) + } + if r.Header.Get("TARGET_RSRC_ID") == "" { + r.Header.Set("IS_GLOBAL_ACCESS", "yes") + } + // pro + + handler.ServeHTTP(w, r) + }) +} diff --git a/models/user_mgmt.go b/models/user_mgmt.go new file mode 100644 index 000000000..02646a335 --- /dev/null +++ b/models/user_mgmt.go @@ -0,0 +1,126 @@ +package models + +import ( + "time" + + jwt "github.com/golang-jwt/jwt/v4" +) + +type NetworkID string +type RsrcType string +type RsrcID string +type UserRole string +type UserGroupID string + +func (r RsrcType) String() string { + return string(r) +} + +const ( + HostRsrc RsrcType = "hosts" + RelayRsrc RsrcType = "relays" + RemoteAccessGwRsrc RsrcType = "remote_access_gw" + ExtClientsRsrc RsrcType = "extclients" + InetGwRsrc RsrcType = "inet_gw" + EgressGwRsrc RsrcType = "egress" + NetworkRsrc RsrcType = "networks" + EnrollmentKeysRsrc RsrcType = "enrollment_key" + UserRsrc RsrcType = "users" + AclRsrc RsrcType = "acl" + DnsRsrc RsrcType = "dns" + FailOverRsrc RsrcType = "fail_over" +) + +const ( + AllHostRsrcID RsrcID = "all_host" + AllRelayRsrcID RsrcID = "all_relay" + AllRemoteAccessGwRsrcID RsrcID = "all_remote_access_gw" + AllExtClientsRsrc RsrcType = "all_extclients" + AllInetGwRsrcID RsrcID = "all_inet_gw" + AllEgressGwRsrcID RsrcID = "all_egress" + AllNetworkRsrcID RsrcID = "all_network" + AllEnrollmentKeysRsrcID RsrcID = "all_enrollment_key" + AllUserRsrcID RsrcID = "all_user" + AllDnsRsrcID RsrcID = "all_dns" + AllFailOverRsrc RsrcID = "all_fail_over" +) + +// Pre-Defined User Roles + +const ( + SuperAdminRole UserRole = "super_admin" + AdminRole UserRole = "admin" + NetworkAdmin UserRole = "network_admin" + NetworkUser UserRole = "network_user" +) + +func (r UserRole) String() string { + return string(r) +} + +func (g UserGroupID) String() string { + return string(g) +} + +type RsrcPermissionScope struct { + Create bool `json:"create"` + Read bool `json:"read"` + Update bool `json:"update"` + Delete bool `json:"delete"` + VPNAccess bool `json:"vpn_access"` +} + +type UserRolePermissionTemplate struct { + ID UserRole `json:"id"` + Default bool `json:"default"` + DenyDashboardAccess bool `json:"deny_dashboard_access"` + FullAccess bool `json:"full_access"` + IsNetworkRole bool `json:"network_role"` + NetworkLevelAccess map[RsrcType]map[RsrcID]RsrcPermissionScope `json:"network_level_access"` + GlobalLevelAccess map[RsrcType]map[RsrcID]RsrcPermissionScope `json:"global_level_access"` +} + +type UserGroup struct { + ID string `json:"id"` + NetworkRoles map[NetworkID]UserRole `json:"network_roles"` + MetaData string `json:"meta_data"` +} + +// User struct - struct for Users +type User struct { + UserName string `json:"username" bson:"username" validate:"min=3,max=40,in_charset|email"` + Password string `json:"password" bson:"password" validate:"required,min=5"` + IsAdmin bool `json:"isadmin" bson:"isadmin"` + IsSuperAdmin bool `json:"issuperadmin"` + RemoteGwIDs map[string]struct{} `json:"remote_gw_ids"` + UserGroup UserGroupID `json:"user_groups"` + PlatformRoleID UserRole `json:"platform_role_id"` + NetworkRoles map[NetworkID]UserRole `json:"network_roles"` + LastLoginTime time.Time `json:"last_login_time"` +} + +// ReturnUser - return user struct +type ReturnUser struct { + UserName string `json:"username"` + IsAdmin bool `json:"isadmin"` + IsSuperAdmin bool `json:"issuperadmin"` + RemoteGwIDs map[string]struct{} `json:"remote_gw_ids"` + UserGroups map[UserGroupID]struct{} `json:"user_groups"` + PlatformRoleID string `json:"platform_role_id"` + NetworkRoles map[NetworkID]UserRole `json:"network_roles"` + LastLoginTime time.Time `json:"last_login_time"` +} + +// UserAuthParams - user auth params struct +type UserAuthParams struct { + UserName string `json:"username"` + Password string `json:"password"` +} + +// UserClaims - user claims struct +type UserClaims struct { + IsAdmin bool + IsSuperAdmin bool + UserName string + jwt.RegisteredClaims +} From 9fb83fa89cc4a5c2d47d221a034e38b5c127a7c8 Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Mon, 17 Jun 2024 11:24:38 +0530 Subject: [PATCH 022/139] allow multiple groups for an user --- logic/user_mgmt.go | 10 ++++------ models/user_mgmt.go | 18 +++++++++--------- 2 files changed, 13 insertions(+), 15 deletions(-) diff --git a/logic/user_mgmt.go b/logic/user_mgmt.go index 31cfddfaf..de0b7ef75 100644 --- a/logic/user_mgmt.go +++ b/logic/user_mgmt.go @@ -123,8 +123,8 @@ func DeleteRole(rid models.UserRole) error { return err } for _, user := range users { - if user.UserGroup != "" { - ug, err := GetUserGroup(user.UserGroup) + for userG := range user.UserGroups { + ug, err := GetUserGroup(userG) if err == nil { for _, networkRole := range ug.NetworkRoles { if networkRole == rid { @@ -222,10 +222,8 @@ func DeleteUserGroup(gid models.UserGroupID) error { return err } for _, user := range users { - if user.UserGroup == gid { - err = errors.New("role cannot be deleted as active user groups are using this role") - return err - } + delete(user.UserGroups, gid) + UpsertUser(user) } return database.DeleteRecord(database.USER_GROUPS_TABLE_NAME, gid.String()) } diff --git a/models/user_mgmt.go b/models/user_mgmt.go index 45bad6dc5..46232573f 100644 --- a/models/user_mgmt.go +++ b/models/user_mgmt.go @@ -88,15 +88,15 @@ type UserGroup struct { // User struct - struct for Users type User struct { - UserName string `json:"username" bson:"username" validate:"min=3,max=40,in_charset|email"` - Password string `json:"password" bson:"password" validate:"required,min=5"` - IsAdmin bool `json:"isadmin" bson:"isadmin"` - IsSuperAdmin bool `json:"issuperadmin"` - RemoteGwIDs map[string]struct{} `json:"remote_gw_ids"` - UserGroup UserGroupID `json:"user_groups"` - PlatformRoleID UserRole `json:"platform_role_id"` - NetworkRoles map[NetworkID]UserRole `json:"network_roles"` - LastLoginTime time.Time `json:"last_login_time"` + UserName string `json:"username" bson:"username" validate:"min=3,max=40,in_charset|email"` + Password string `json:"password" bson:"password" validate:"required,min=5"` + IsAdmin bool `json:"isadmin" bson:"isadmin"` + IsSuperAdmin bool `json:"issuperadmin"` + RemoteGwIDs map[string]struct{} `json:"remote_gw_ids"` + UserGroups map[UserGroupID]struct{} `json:"user_groups"` + PlatformRoleID UserRole `json:"platform_role_id"` + NetworkRoles map[NetworkID]UserRole `json:"network_roles"` + LastLoginTime time.Time `json:"last_login_time"` } // ReturnUser - return user struct From fb1eae729ad292a4317c8a6059132f9240c0497a Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Mon, 17 Jun 2024 15:56:11 +0530 Subject: [PATCH 023/139] change json tag --- models/user_mgmt.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/models/user_mgmt.go b/models/user_mgmt.go index 46232573f..6f20e9a64 100644 --- a/models/user_mgmt.go +++ b/models/user_mgmt.go @@ -93,7 +93,7 @@ type User struct { IsAdmin bool `json:"isadmin" bson:"isadmin"` IsSuperAdmin bool `json:"issuperadmin"` RemoteGwIDs map[string]struct{} `json:"remote_gw_ids"` - UserGroups map[UserGroupID]struct{} `json:"user_groups"` + UserGroups map[UserGroupID]struct{} `json:"user_group_ids"` PlatformRoleID UserRole `json:"platform_role_id"` NetworkRoles map[NetworkID]UserRole `json:"network_roles"` LastLoginTime time.Time `json:"last_login_time"` @@ -105,7 +105,7 @@ type ReturnUser struct { IsAdmin bool `json:"isadmin"` IsSuperAdmin bool `json:"issuperadmin"` RemoteGwIDs map[string]struct{} `json:"remote_gw_ids"` - UserGroups map[UserGroupID]struct{} `json:"user_groups"` + UserGroups map[UserGroupID]struct{} `json:"user_group_ids"` PlatformRoleID string `json:"platform_role_id"` NetworkRoles map[NetworkID]UserRole `json:"network_roles"` LastLoginTime time.Time `json:"last_login_time"` From 16d438a3903b6051386f83de191fee16d850501d Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Wed, 19 Jun 2024 14:43:23 +0530 Subject: [PATCH 024/139] add debug headers --- logic/security.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/logic/security.go b/logic/security.go index 55d178b49..221232b7e 100644 --- a/logic/security.go +++ b/logic/security.go @@ -143,11 +143,15 @@ func SecurityCheck(reqAdmin bool, next http.Handler) http.HandlerFunc { r.Header.Set("ismaster", "yes") } else { if isGlobalAccesss { - globalPermissionsCheck(username, r) + err = globalPermissionsCheck(username, r) } else { - networkPermissionsCheck(username, r) + err = networkPermissionsCheck(username, r) } } + w.Header().Set("TARGET_RSRC", r.Header.Get("TARGET_RSRC")) + w.Header().Set("TARGET_RSRC_ID", r.Header.Get("TARGET_RSRC_ID")) + w.Header().Set("NET_ID", r.Header.Get("NET_ID")) + w.Header().Set("ACCESS_RESP", err.Error()) r.Header.Set("user", username) next.ServeHTTP(w, r) } From 7d052e64e2521059d9fda9633b90297304ae2636 Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Thu, 20 Jun 2024 07:28:33 +0530 Subject: [PATCH 025/139] refer network controls form roles, add debug headers --- controllers/middleware.go | 8 +++++--- logic/security.go | 5 +---- logic/user_mgmt.go | 10 ++++++++-- models/user_mgmt.go | 9 ++++----- 4 files changed, 18 insertions(+), 14 deletions(-) diff --git a/controllers/middleware.go b/controllers/middleware.go index 4a547b354..38337305e 100644 --- a/controllers/middleware.go +++ b/controllers/middleware.go @@ -12,7 +12,7 @@ func userMiddleWare(handler http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { var params = mux.Vars(r) r.Header.Set("NET_ID", params["network"]) - if strings.Contains(r.URL.Path, "host") || strings.Contains(r.URL.Path, "node") { + if strings.Contains(r.URL.Path, "hosts") || strings.Contains(r.URL.Path, "nodes") { r.Header.Set("TARGET_RSRC", models.HostRsrc.String()) r.Header.Set("RSRC_TYPE", models.HostRsrc.String()) } @@ -56,8 +56,10 @@ func userMiddleWare(handler http.Handler) http.Handler { if r.Header.Get("TARGET_RSRC_ID") == "" { r.Header.Set("IS_GLOBAL_ACCESS", "yes") } - // pro - + w.Header().Set("TARGET_RSRC", r.Header.Get("TARGET_RSRC")) + w.Header().Set("TARGET_RSRC_ID", r.Header.Get("TARGET_RSRC_ID")) + w.Header().Set("RSRC_TYPE", r.Header.Get("RSRC_TYPE")) + w.Header().Set("IS_GLOBAL_ACCESS", r.Header.Get("IS_GLOBAL_ACCESS")) handler.ServeHTTP(w, r) }) } diff --git a/logic/security.go b/logic/security.go index 221232b7e..8d2179e8a 100644 --- a/logic/security.go +++ b/logic/security.go @@ -148,10 +148,7 @@ func SecurityCheck(reqAdmin bool, next http.Handler) http.HandlerFunc { err = networkPermissionsCheck(username, r) } } - w.Header().Set("TARGET_RSRC", r.Header.Get("TARGET_RSRC")) - w.Header().Set("TARGET_RSRC_ID", r.Header.Get("TARGET_RSRC_ID")) - w.Header().Set("NET_ID", r.Header.Get("NET_ID")) - w.Header().Set("ACCESS_RESP", err.Error()) + w.Header().Set("ACCESS_PERM", err.Error()) r.Header.Set("user", username) next.ServeHTTP(w, r) } diff --git a/logic/user_mgmt.go b/logic/user_mgmt.go index de0b7ef75..82044db01 100644 --- a/logic/user_mgmt.go +++ b/logic/user_mgmt.go @@ -32,8 +32,14 @@ var NetworkUserPermissionTemplate = models.UserRolePermissionTemplate{ ID: models.NetworkUser, Default: true, FullAccess: false, - DenyDashboardAccess: true, - NetworkLevelAccess: make(map[models.RsrcType]map[models.RsrcID]models.RsrcPermissionScope), + DenyDashboardAccess: false, + NetworkLevelAccess: map[models.RsrcType]map[models.RsrcID]models.RsrcPermissionScope{ + models.RemoteAccessGwRsrc: { + models.AllRemoteAccessGwRsrcID: models.RsrcPermissionScope{ + Read: true, + }, + }, + }, } func UserRolesInit() { diff --git a/models/user_mgmt.go b/models/user_mgmt.go index 6f20e9a64..76f7be54a 100644 --- a/models/user_mgmt.go +++ b/models/user_mgmt.go @@ -63,11 +63,10 @@ func (g UserGroupID) String() string { } type RsrcPermissionScope struct { - Create bool `json:"create"` - Read bool `json:"read"` - Update bool `json:"update"` - Delete bool `json:"delete"` - VPNAccess bool `json:"vpn_access"` + Create bool `json:"create"` + Read bool `json:"read"` + Update bool `json:"update"` + Delete bool `json:"delete"` } type UserRolePermissionTemplate struct { From 3f2716ce37d203d3a11b16d600df16e720052801 Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Thu, 20 Jun 2024 08:24:25 +0530 Subject: [PATCH 026/139] refer network controls form roles, add debug headers --- controllers/middleware.go | 4 ---- logic/security.go | 4 ++++ 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/controllers/middleware.go b/controllers/middleware.go index 38337305e..6ea9bf8a7 100644 --- a/controllers/middleware.go +++ b/controllers/middleware.go @@ -56,10 +56,6 @@ func userMiddleWare(handler http.Handler) http.Handler { if r.Header.Get("TARGET_RSRC_ID") == "" { r.Header.Set("IS_GLOBAL_ACCESS", "yes") } - w.Header().Set("TARGET_RSRC", r.Header.Get("TARGET_RSRC")) - w.Header().Set("TARGET_RSRC_ID", r.Header.Get("TARGET_RSRC_ID")) - w.Header().Set("RSRC_TYPE", r.Header.Get("RSRC_TYPE")) - w.Header().Set("IS_GLOBAL_ACCESS", r.Header.Get("IS_GLOBAL_ACCESS")) handler.ServeHTTP(w, r) }) } diff --git a/logic/security.go b/logic/security.go index 8d2179e8a..80c667ec4 100644 --- a/logic/security.go +++ b/logic/security.go @@ -148,6 +148,10 @@ func SecurityCheck(reqAdmin bool, next http.Handler) http.HandlerFunc { err = networkPermissionsCheck(username, r) } } + w.Header().Set("TARGET_RSRC", r.Header.Get("TARGET_RSRC")) + w.Header().Set("TARGET_RSRC_ID", r.Header.Get("TARGET_RSRC_ID")) + w.Header().Set("RSRC_TYPE", r.Header.Get("RSRC_TYPE")) + w.Header().Set("IS_GLOBAL_ACCESS", r.Header.Get("IS_GLOBAL_ACCESS")) w.Header().Set("ACCESS_PERM", err.Error()) r.Header.Set("user", username) next.ServeHTTP(w, r) From 56fdd6d98e5be195e6a4d03a385009bc8935d343 Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Sat, 22 Jun 2024 17:32:38 +0530 Subject: [PATCH 027/139] replace auth checks, add network id to role model --- controllers/middleware.go | 13 ++++++++++++- controllers/node.go | 4 ++-- controllers/server.go | 4 ++-- controllers/user.go | 7 ++++++- logic/user_mgmt.go | 12 +++++++++++- models/user_mgmt.go | 7 ++++--- 6 files changed, 37 insertions(+), 10 deletions(-) diff --git a/controllers/middleware.go b/controllers/middleware.go index 6ea9bf8a7..e27f45686 100644 --- a/controllers/middleware.go +++ b/controllers/middleware.go @@ -34,10 +34,21 @@ func userMiddleWare(handler http.Handler) http.Handler { r.Header.Set("TARGET_RSRC", models.NetworkRsrc.String()) r.Header.Set("RSRC_TYPE", models.NetworkRsrc.String()) } + if strings.Contains(r.URL.Path, "acls") { + r.Header.Set("TARGET_RSRC", models.AclRsrc.String()) + r.Header.Set("RSRC_TYPE", models.NetworkRsrc.String()) + } if strings.Contains(r.URL.Path, "extclients") { r.Header.Set("TARGET_RSRC", models.ExtClientsRsrc.String()) r.Header.Set("RSRC_TYPE", models.ExtClientsRsrc.String()) } + if strings.Contains(r.URL.Path, "enrollment-keys") { + r.Header.Set("TARGET_RSRC", models.EnrollmentKeysRsrc.String()) + r.Header.Set("RSRC_TYPE", models.EnrollmentKeysRsrc.String()) + } + if keyID, ok := params["keyID"]; ok { + r.Header.Set("TARGET_RSRC_ID", keyID) + } if nodeID, ok := params["nodeid"]; ok { r.Header.Set("TARGET_RSRC_ID", nodeID) } @@ -53,7 +64,7 @@ func userMiddleWare(handler http.Handler) http.Handler { if userID, ok := params["username"]; ok { r.Header.Set("TARGET_RSRC_ID", userID) } - if r.Header.Get("TARGET_RSRC_ID") == "" { + if r.Header.Get("TARGET_RSRC_ID") == "" || r.Header.Get("TARGET_RSRC") == models.EnrollmentKeysRsrc.String() { r.Header.Set("IS_GLOBAL_ACCESS", "yes") } handler.ServeHTTP(w, r) diff --git a/controllers/node.go b/controllers/node.go index ed104b354..1b14943ec 100644 --- a/controllers/node.go +++ b/controllers/node.go @@ -21,8 +21,8 @@ var hostIDHeader = "host-id" func nodeHandlers(r *mux.Router) { - r.HandleFunc("/api/nodes", Authorize(false, false, "user", http.HandlerFunc(getAllNodes))).Methods(http.MethodGet) - r.HandleFunc("/api/nodes/{network}", Authorize(false, true, "network", http.HandlerFunc(getNetworkNodes))).Methods(http.MethodGet) + r.HandleFunc("/api/nodes", logic.SecurityCheck(true, http.HandlerFunc(createEnrollmentKey))).Methods(http.MethodGet) + r.HandleFunc("/api/nodes/{network}", logic.SecurityCheck(true, http.HandlerFunc(getNetworkNodes))).Methods(http.MethodGet) r.HandleFunc("/api/nodes/{network}/{nodeid}", Authorize(true, true, "node", http.HandlerFunc(getNode))).Methods(http.MethodGet) r.HandleFunc("/api/nodes/{network}/{nodeid}", logic.SecurityCheck(true, http.HandlerFunc(updateNode))).Methods(http.MethodPut) r.HandleFunc("/api/nodes/{network}/{nodeid}", Authorize(true, true, "node", http.HandlerFunc(deleteNode))).Methods(http.MethodDelete) diff --git a/controllers/server.go b/controllers/server.go index 6e96688c4..2efd6d65b 100644 --- a/controllers/server.go +++ b/controllers/server.go @@ -38,10 +38,10 @@ func serverHandlers(r *mux.Router) { ).Methods(http.MethodPost) r.HandleFunc("/api/server/getconfig", allowUsers(http.HandlerFunc(getConfig))). Methods(http.MethodGet) - r.HandleFunc("/api/server/getserverinfo", Authorize(true, false, "node", http.HandlerFunc(getServerInfo))). + r.HandleFunc("/api/server/getserverinfo", logic.SecurityCheck(true, http.HandlerFunc(getServerInfo))). Methods(http.MethodGet) r.HandleFunc("/api/server/status", getStatus).Methods(http.MethodGet) - r.HandleFunc("/api/server/usage", Authorize(true, false, "user", http.HandlerFunc(getUsage))). + r.HandleFunc("/api/server/usage", logic.SecurityCheck(false, http.HandlerFunc(getUsage))). Methods(http.MethodGet) } diff --git a/controllers/user.go b/controllers/user.go index 583d1d359..db9af948d 100644 --- a/controllers/user.go +++ b/controllers/user.go @@ -37,7 +37,7 @@ func userHandlers(r *mux.Router) { r.HandleFunc("/api/users_pending/user/{username}", logic.SecurityCheck(true, http.HandlerFunc(deletePendingUser))).Methods(http.MethodDelete) r.HandleFunc("/api/users_pending/user/{username}", logic.SecurityCheck(true, http.HandlerFunc(approvePendingUser))).Methods(http.MethodPost) - // User Role handlers + // User Role Handlers r.HandleFunc("/api/v1/user/roles", logic.SecurityCheck(true, http.HandlerFunc(listRoles))).Methods(http.MethodGet) r.HandleFunc("/api/v1/user/role", logic.SecurityCheck(true, http.HandlerFunc(getRole))).Methods(http.MethodGet) r.HandleFunc("/api/v1/user/role", logic.SecurityCheck(true, http.HandlerFunc(createRole))).Methods(http.MethodPost) @@ -259,6 +259,11 @@ func createRole(w http.ResponseWriter, r *http.Request) { logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) return } + if userRole.NetworkID == "" { + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "only network roles are allowed to be created")) + return + } + userRole.GlobalLevelAccess = make(map[models.RsrcType]map[models.RsrcID]models.RsrcPermissionScope) err = logic.CreateRole(userRole) if err != nil { logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) diff --git a/logic/user_mgmt.go b/logic/user_mgmt.go index 82044db01..16225e4c0 100644 --- a/logic/user_mgmt.go +++ b/logic/user_mgmt.go @@ -14,6 +14,7 @@ var SuperAdminPermissionTemplate = models.UserRolePermissionTemplate{ Default: true, FullAccess: true, } + var AdminPermissionTemplate = models.UserRolePermissionTemplate{ ID: models.AdminRole, Default: true, @@ -23,7 +24,7 @@ var AdminPermissionTemplate = models.UserRolePermissionTemplate{ var NetworkAdminPermissionTemplate = models.UserRolePermissionTemplate{ ID: models.NetworkAdmin, Default: true, - IsNetworkRole: true, + NetworkID: "netmaker", FullAccess: true, NetworkLevelAccess: make(map[models.RsrcType]map[models.RsrcID]models.RsrcPermissionScope), } @@ -32,6 +33,7 @@ var NetworkUserPermissionTemplate = models.UserRolePermissionTemplate{ ID: models.NetworkUser, Default: true, FullAccess: false, + NetworkID: "netmaker", DenyDashboardAccess: false, NetworkLevelAccess: map[models.RsrcType]map[models.RsrcID]models.RsrcPermissionScope{ models.RemoteAccessGwRsrc: { @@ -39,6 +41,14 @@ var NetworkUserPermissionTemplate = models.UserRolePermissionTemplate{ Read: true, }, }, + models.ExtClientsRsrc: { + models.AllExtClientsRsrcID: models.RsrcPermissionScope{ + Read: true, + Create: true, + Update: true, + Delete: true, + }, + }, }, } diff --git a/models/user_mgmt.go b/models/user_mgmt.go index 76f7be54a..f10d612a9 100644 --- a/models/user_mgmt.go +++ b/models/user_mgmt.go @@ -35,14 +35,15 @@ const ( AllHostRsrcID RsrcID = "all_host" AllRelayRsrcID RsrcID = "all_relay" AllRemoteAccessGwRsrcID RsrcID = "all_remote_access_gw" - AllExtClientsRsrc RsrcID = "all_extclients" + AllExtClientsRsrcID RsrcID = "all_extclients" AllInetGwRsrcID RsrcID = "all_inet_gw" AllEgressGwRsrcID RsrcID = "all_egress" AllNetworkRsrcID RsrcID = "all_network" AllEnrollmentKeysRsrcID RsrcID = "all_enrollment_key" AllUserRsrcID RsrcID = "all_user" AllDnsRsrcID RsrcID = "all_dns" - AllFailOverRsrc RsrcID = "all_fail_over" + AllFailOverRsrcID RsrcID = "all_fail_over" + AllAclsRsrcID RsrcID = "all_acls" ) // Pre-Defined User Roles @@ -74,7 +75,7 @@ type UserRolePermissionTemplate struct { Default bool `json:"default"` DenyDashboardAccess bool `json:"deny_dashboard_access"` FullAccess bool `json:"full_access"` - IsNetworkRole bool `json:"network_role"` + NetworkID string `json:"network_id"` NetworkLevelAccess map[RsrcType]map[RsrcID]RsrcPermissionScope `json:"network_level_access"` GlobalLevelAccess map[RsrcType]map[RsrcID]RsrcPermissionScope `json:"global_level_access"` } From 2a1d59b07b6f2bacebc5f72c6e50fbdd504e8fe4 Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Sun, 23 Jun 2024 08:38:46 +0530 Subject: [PATCH 028/139] nodes handler --- controllers/node.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/controllers/node.go b/controllers/node.go index 1b14943ec..bbafa6fda 100644 --- a/controllers/node.go +++ b/controllers/node.go @@ -21,7 +21,7 @@ var hostIDHeader = "host-id" func nodeHandlers(r *mux.Router) { - r.HandleFunc("/api/nodes", logic.SecurityCheck(true, http.HandlerFunc(createEnrollmentKey))).Methods(http.MethodGet) + r.HandleFunc("/api/nodes", logic.SecurityCheck(true, http.HandlerFunc(getAllNodes))).Methods(http.MethodGet) r.HandleFunc("/api/nodes/{network}", logic.SecurityCheck(true, http.HandlerFunc(getNetworkNodes))).Methods(http.MethodGet) r.HandleFunc("/api/nodes/{network}/{nodeid}", Authorize(true, true, "node", http.HandlerFunc(getNode))).Methods(http.MethodGet) r.HandleFunc("/api/nodes/{network}/{nodeid}", logic.SecurityCheck(true, http.HandlerFunc(updateNode))).Methods(http.MethodPut) From 54c540ef64515bc1592a10394447d66ccfda2960 Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Sun, 23 Jun 2024 09:10:35 +0530 Subject: [PATCH 029/139] migration funcs --- logic/user_mgmt.go | 6 ++++++ migrate/migrate.go | 19 +++++++++++++++++++ models/user_mgmt.go | 1 + 3 files changed, 26 insertions(+) diff --git a/logic/user_mgmt.go b/logic/user_mgmt.go index 16225e4c0..d5bb0fde7 100644 --- a/logic/user_mgmt.go +++ b/logic/user_mgmt.go @@ -21,6 +21,12 @@ var AdminPermissionTemplate = models.UserRolePermissionTemplate{ FullAccess: true, } +var ServiceUserPermissionTemplate = models.UserRolePermissionTemplate{ + ID: models.ServiceUser, + Default: true, + FullAccess: false, +} + var NetworkAdminPermissionTemplate = models.UserRolePermissionTemplate{ ID: models.NetworkAdmin, Default: true, diff --git a/migrate/migrate.go b/migrate/migrate.go index e1474b862..c23fdc33c 100644 --- a/migrate/migrate.go +++ b/migrate/migrate.go @@ -311,3 +311,22 @@ func MigrateEmqx() { } } + +func SyncUsers() { + users, err := logic.GetUsersDB() + if err == nil { + for _, user := range users { + if user.IsSuperAdmin { + user.PlatformRoleID = models.SuperAdminRole + logic.UpsertUser(user) + } else if user.IsAdmin { + user.PlatformRoleID = models.AdminRole + logic.UpsertUser(user) + } else { + user.PlatformRoleID = models.ServiceUser + logic.UpsertUser(user) + } + + } + } +} diff --git a/models/user_mgmt.go b/models/user_mgmt.go index f10d612a9..d304e690f 100644 --- a/models/user_mgmt.go +++ b/models/user_mgmt.go @@ -51,6 +51,7 @@ const ( const ( SuperAdminRole UserRole = "super_admin" AdminRole UserRole = "admin" + ServiceUser UserRole = "user" NetworkAdmin UserRole = "network_admin" NetworkUser UserRole = "network_user" ) From ccf86757e41b24eb08cc1c50b2e44cac29bab0a5 Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Sun, 23 Jun 2024 09:57:38 +0530 Subject: [PATCH 030/139] invoke sync users migration func --- migrate/migrate.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/migrate/migrate.go b/migrate/migrate.go index c23fdc33c..b7cda5fef 100644 --- a/migrate/migrate.go +++ b/migrate/migrate.go @@ -24,6 +24,7 @@ func Run() { updateHosts() updateNodes() updateAcls() + syncUsers() } @@ -312,7 +313,7 @@ func MigrateEmqx() { } -func SyncUsers() { +func syncUsers() { users, err := logic.GetUsersDB() if err == nil { for _, user := range users { From bff38b54be0fe2e040579db9afadc39e751b2f30 Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Sun, 23 Jun 2024 11:16:54 +0530 Subject: [PATCH 031/139] add debug logs --- logic/security.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/logic/security.go b/logic/security.go index 80c667ec4..00e646852 100644 --- a/logic/security.go +++ b/logic/security.go @@ -7,6 +7,7 @@ import ( "strings" "github.com/gorilla/mux" + "github.com/gravitl/netmaker/logger" "github.com/gravitl/netmaker/models" "github.com/gravitl/netmaker/servercfg" ) @@ -130,11 +131,13 @@ func checkPermissionScopeWithReqMethod(scope models.RsrcPermissionScope, reqmeth func SecurityCheck(reqAdmin bool, next http.Handler) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { + logger.Log(0, "SECURITY CHECK - 1") r.Header.Set("ismaster", "no") bearerToken := r.Header.Get("Authorization") isGlobalAccesss := r.Header.Get("IS_GLOBAL_ACCESS") == "yes" username, err := UserPermissions(reqAdmin, bearerToken) if err != nil { + logger.Log(0, "SECURITY CHECK - 2", err.Error()) ReturnErrorResponse(w, r, FormatError(err, err.Error())) return } From 5033aef702ff10761663f7effa5184ac4751f94a Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Sun, 23 Jun 2024 11:40:34 +0530 Subject: [PATCH 032/139] comment middleware --- controllers/controller.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/controllers/controller.go b/controllers/controller.go index 8316d301c..875151728 100644 --- a/controllers/controller.go +++ b/controllers/controller.go @@ -18,7 +18,7 @@ import ( // HttpMiddlewares - middleware functions for REST interactions var HttpMiddlewares = []mux.MiddlewareFunc{ - userMiddleWare, + //userMiddleWare, } // HttpHandlers - handler functions for REST interactions From bfef87ebd6a859134e0cb320a0d993068cad30b1 Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Sun, 23 Jun 2024 11:59:43 +0530 Subject: [PATCH 033/139] fix get all nodes api --- controllers/controller.go | 2 +- controllers/node.go | 20 ++++++-------------- 2 files changed, 7 insertions(+), 15 deletions(-) diff --git a/controllers/controller.go b/controllers/controller.go index 875151728..8316d301c 100644 --- a/controllers/controller.go +++ b/controllers/controller.go @@ -18,7 +18,7 @@ import ( // HttpMiddlewares - middleware functions for REST interactions var HttpMiddlewares = []mux.MiddlewareFunc{ - //userMiddleWare, + userMiddleWare, } // HttpHandlers - handler functions for REST interactions diff --git a/controllers/node.go b/controllers/node.go index bbafa6fda..3ff400dfd 100644 --- a/controllers/node.go +++ b/controllers/node.go @@ -302,23 +302,15 @@ func getNetworkNodes(w http.ResponseWriter, r *http.Request) { // // Not quite sure if this is necessary. Probably necessary based on front end but may want to review after iteration 1 if it's being used or not func getAllNodes(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/json") - user, err := logic.GetUser(r.Header.Get("user")) - if err != nil && r.Header.Get("ismasterkey") != "yes" { - logger.Log(0, r.Header.Get("user"), - "error fetching user info: ", err.Error()) + w.Header().Add("Content-Type", "application/json") + var nodes []models.Node + nodes, err := logic.GetAllNodes() + if err != nil { + logger.Log(0, "error fetching all nodes info: ", err.Error()) logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } - var nodes []models.Node - if user.IsAdmin || r.Header.Get("ismasterkey") == "yes" { - nodes, err = logic.GetAllNodes() - if err != nil { - logger.Log(0, "error fetching all nodes info: ", err.Error()) - logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) - return - } - } + // return all the nodes in JSON/API format apiNodes := logic.GetAllNodesAPI(nodes[:]) logger.Log(3, r.Header.Get("user"), "fetched all nodes they have access to") From 8af425b0bcd111fe56c2669bf903f0dba93343f1 Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Sun, 23 Jun 2024 12:09:39 +0530 Subject: [PATCH 034/139] add debug logs --- controllers/node.go | 3 ++- logic/security.go | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/controllers/node.go b/controllers/node.go index 3ff400dfd..09dc09bed 100644 --- a/controllers/node.go +++ b/controllers/node.go @@ -302,7 +302,8 @@ func getNetworkNodes(w http.ResponseWriter, r *http.Request) { // // Not quite sure if this is necessary. Probably necessary based on front end but may want to review after iteration 1 if it's being used or not func getAllNodes(w http.ResponseWriter, r *http.Request) { - w.Header().Add("Content-Type", "application/json") + w.Header().Set("Content-Type", "application/json") + logger.Log(0, "SECURITY CHECK - 5") var nodes []models.Node nodes, err := logic.GetAllNodes() if err != nil { diff --git a/logic/security.go b/logic/security.go index 00e646852..e0c4946b5 100644 --- a/logic/security.go +++ b/logic/security.go @@ -157,6 +157,7 @@ func SecurityCheck(reqAdmin bool, next http.Handler) http.HandlerFunc { w.Header().Set("IS_GLOBAL_ACCESS", r.Header.Get("IS_GLOBAL_ACCESS")) w.Header().Set("ACCESS_PERM", err.Error()) r.Header.Set("user", username) + logger.Log(0, "SECURITY CHECK - 3") next.ServeHTTP(w, r) } } From 31fc7efe5f9b932a715333bc4a2c9446e68812b1 Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Sun, 23 Jun 2024 12:17:44 +0530 Subject: [PATCH 035/139] fix middleware error nil check --- controllers/node.go | 1 - logic/security.go | 8 +++----- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/controllers/node.go b/controllers/node.go index 09dc09bed..c2f45429b 100644 --- a/controllers/node.go +++ b/controllers/node.go @@ -303,7 +303,6 @@ func getNetworkNodes(w http.ResponseWriter, r *http.Request) { // Not quite sure if this is necessary. Probably necessary based on front end but may want to review after iteration 1 if it's being used or not func getAllNodes(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") - logger.Log(0, "SECURITY CHECK - 5") var nodes []models.Node nodes, err := logic.GetAllNodes() if err != nil { diff --git a/logic/security.go b/logic/security.go index e0c4946b5..8a3c727a2 100644 --- a/logic/security.go +++ b/logic/security.go @@ -7,7 +7,6 @@ import ( "strings" "github.com/gorilla/mux" - "github.com/gravitl/netmaker/logger" "github.com/gravitl/netmaker/models" "github.com/gravitl/netmaker/servercfg" ) @@ -131,13 +130,11 @@ func checkPermissionScopeWithReqMethod(scope models.RsrcPermissionScope, reqmeth func SecurityCheck(reqAdmin bool, next http.Handler) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - logger.Log(0, "SECURITY CHECK - 1") r.Header.Set("ismaster", "no") bearerToken := r.Header.Get("Authorization") isGlobalAccesss := r.Header.Get("IS_GLOBAL_ACCESS") == "yes" username, err := UserPermissions(reqAdmin, bearerToken) if err != nil { - logger.Log(0, "SECURITY CHECK - 2", err.Error()) ReturnErrorResponse(w, r, FormatError(err, err.Error())) return } @@ -155,9 +152,10 @@ func SecurityCheck(reqAdmin bool, next http.Handler) http.HandlerFunc { w.Header().Set("TARGET_RSRC_ID", r.Header.Get("TARGET_RSRC_ID")) w.Header().Set("RSRC_TYPE", r.Header.Get("RSRC_TYPE")) w.Header().Set("IS_GLOBAL_ACCESS", r.Header.Get("IS_GLOBAL_ACCESS")) - w.Header().Set("ACCESS_PERM", err.Error()) + if err != nil { + w.Header().Set("ACCESS_PERM", err.Error()) + } r.Header.Set("user", username) - logger.Log(0, "SECURITY CHECK - 3") next.ServeHTTP(w, r) } } From aa9ea135648097b227b37e66c4644ee07cd433f2 Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Sun, 23 Jun 2024 16:26:27 +0530 Subject: [PATCH 036/139] add new func to get username from jwt --- logic/jwts.go | 26 ++++++++++++++++++++++++++ logic/security.go | 2 +- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/logic/jwts.go b/logic/jwts.go index a2b95049a..39a99afe7 100644 --- a/logic/jwts.go +++ b/logic/jwts.go @@ -87,6 +87,32 @@ func VerifyJWT(bearerToken string) (username string, issuperadmin, isadmin bool, return VerifyUserToken(token) } +func GetUserNameFromToken(tokenString string) (username string, err error) { + claims := &models.UserClaims{} + + if tokenString == servercfg.GetMasterKey() && servercfg.GetMasterKey() != "" { + return MasterUser, nil + } + + token, err := jwt.ParseWithClaims(tokenString, claims, func(token *jwt.Token) (interface{}, error) { + return jwtSecretKey, nil + }) + + if token != nil && token.Valid { + var user *models.User + // check that user exists + user, err = GetUser(claims.UserName) + if err != nil { + return "", err + } + if user.UserName != "" { + return user.UserName, nil + } + err = errors.New("user does not exist") + } + return "", err +} + // VerifyUserToken func will used to Verify the JWT Token while using APIS func VerifyUserToken(tokenString string) (username string, issuperadmin, isadmin bool, err error) { claims := &models.UserClaims{} diff --git a/logic/security.go b/logic/security.go index 8a3c727a2..cba95721f 100644 --- a/logic/security.go +++ b/logic/security.go @@ -133,7 +133,7 @@ func SecurityCheck(reqAdmin bool, next http.Handler) http.HandlerFunc { r.Header.Set("ismaster", "no") bearerToken := r.Header.Get("Authorization") isGlobalAccesss := r.Header.Get("IS_GLOBAL_ACCESS") == "yes" - username, err := UserPermissions(reqAdmin, bearerToken) + username, err := GetUserNameFromToken(bearerToken) if err != nil { ReturnErrorResponse(w, r, FormatError(err, err.Error())) return From d08c5b6995f6e70a1b27c029ed5161dc723298d6 Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Sun, 23 Jun 2024 17:36:38 +0530 Subject: [PATCH 037/139] fix jwt parsing --- logic/jwts.go | 9 ++++++++- logic/security.go | 1 + 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/logic/jwts.go b/logic/jwts.go index 39a99afe7..6656727ac 100644 --- a/logic/jwts.go +++ b/logic/jwts.go @@ -87,9 +87,16 @@ func VerifyJWT(bearerToken string) (username string, issuperadmin, isadmin bool, return VerifyUserToken(token) } -func GetUserNameFromToken(tokenString string) (username string, err error) { +func GetUserNameFromToken(authtoken string) (username string, err error) { claims := &models.UserClaims{} + var tokenSplit = strings.Split(authtoken, " ") + var tokenString = "" + if len(tokenSplit) < 2 { + return "", Unauthorized_Err + } else { + tokenString = tokenSplit[1] + } if tokenString == servercfg.GetMasterKey() && servercfg.GetMasterKey() != "" { return MasterUser, nil } diff --git a/logic/security.go b/logic/security.go index cba95721f..99fd3e6e7 100644 --- a/logic/security.go +++ b/logic/security.go @@ -154,6 +154,7 @@ func SecurityCheck(reqAdmin bool, next http.Handler) http.HandlerFunc { w.Header().Set("IS_GLOBAL_ACCESS", r.Header.Get("IS_GLOBAL_ACCESS")) if err != nil { w.Header().Set("ACCESS_PERM", err.Error()) + } r.Header.Set("user", username) next.ServeHTTP(w, r) From 7d9589f2a52b38512c4c71648146b61d06521523 Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Sun, 23 Jun 2024 17:55:30 +0530 Subject: [PATCH 038/139] abort on error --- logic/security.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/logic/security.go b/logic/security.go index 99fd3e6e7..ce8aebfbd 100644 --- a/logic/security.go +++ b/logic/security.go @@ -154,7 +154,8 @@ func SecurityCheck(reqAdmin bool, next http.Handler) http.HandlerFunc { w.Header().Set("IS_GLOBAL_ACCESS", r.Header.Get("IS_GLOBAL_ACCESS")) if err != nil { w.Header().Set("ACCESS_PERM", err.Error()) - + ReturnErrorResponse(w, r, FormatError(err, "forbidden")) + return } r.Header.Set("user", username) next.ServeHTTP(w, r) From e326c0fd49a0c15f5e78f3342918084ee60a1d16 Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Mon, 24 Jun 2024 09:51:06 +0530 Subject: [PATCH 039/139] allow multiple network roles --- controllers/node.go | 39 ++++++++++++++++++++++++++++++++++- controllers/user.go | 2 +- logic/security.go | 50 +++++++++++++++++++++++++++------------------ logic/user_mgmt.go | 20 ++++++++++-------- migrate/migrate.go | 3 +++ models/user_mgmt.go | 31 ++++++++++++++++------------ 6 files changed, 102 insertions(+), 43 deletions(-) diff --git a/controllers/node.go b/controllers/node.go index c2f45429b..06c7d711a 100644 --- a/controllers/node.go +++ b/controllers/node.go @@ -280,7 +280,44 @@ func getNetworkNodes(w http.ResponseWriter, r *http.Request) { logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } - + username := r.Header.Get("user") + user, err := logic.GetUser(username) + if err != nil { + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) + return + } + networkRoles := user.NetworkRoles[models.NetworkID(networkName)] + for networkRoleID := range networkRoles { + userPermTemplate, err := logic.GetRole(networkRoleID) + if err != nil { + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) + return + } + if !userPermTemplate.FullAccess { + filteredNodes := []models.Node{} + if rsrcPerms, ok := userPermTemplate.NetworkLevelAccess[models.RemoteAccessGwRsrc]; ok { + if _, ok := rsrcPerms[models.AllRemoteAccessGwRsrcID]; ok { + for _, node := range nodes { + if node.IsIngressGateway { + filteredNodes = append(filteredNodes, node) + } + } + } else { + for gwID, scope := range rsrcPerms { + if scope.Read { + gwNode, err := logic.GetNodeByID(gwID.String()) + if err == nil && gwNode.IsIngressGateway { + filteredNodes = append(filteredNodes, gwNode) + } + } + } + } + } + nodes = filteredNodes + } else { + break + } + } // returns all the nodes in JSON/API format apiNodes := logic.GetAllNodesAPI(nodes[:]) logger.Log(2, r.Header.Get("user"), "fetched nodes on network", networkName) diff --git a/controllers/user.go b/controllers/user.go index db9af948d..0389828d0 100644 --- a/controllers/user.go +++ b/controllers/user.go @@ -228,7 +228,7 @@ func getRole(w http.ResponseWriter, r *http.Request) { logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("role is required"), "badrequest")) return } - role, err := logic.GetRole(rid) + role, err := logic.GetRole(models.UserRole(rid)) if err != nil { logic.ReturnErrorResponse(w, r, models.ErrorResponse{ Code: http.StatusInternalServerError, diff --git a/logic/security.go b/logic/security.go index ce8aebfbd..359b9112a 100644 --- a/logic/security.go +++ b/logic/security.go @@ -49,27 +49,37 @@ func networkPermissionsCheck(username string, r *http.Request) error { } // check if user has scope for target resource // TODO - differentitate between global scope and network scope apis - networkPermissionScope, err := GetRole(user.NetworkRoles[models.NetworkID(netID)].String()) - if err != nil { - return errors.New("access denied") - } - if networkPermissionScope.FullAccess { - return nil - } - rsrcPermissionScope, ok := networkPermissionScope.NetworkLevelAccess[models.RsrcType(targetRsrc)] - if !ok { - return fmt.Errorf("access denied to %s rsrc", targetRsrc) - } - if allRsrcsTypePermissionScope, ok := rsrcPermissionScope[models.RsrcID(fmt.Sprintf("all_%s", targetRsrc))]; ok { - return checkPermissionScopeWithReqMethod(allRsrcsTypePermissionScope, r.Method) + netRoles := user.NetworkRoles[models.NetworkID(netID)] + for netRoleID := range netRoles { + networkPermissionScope, err := GetRole(netRoleID) + if err != nil { + continue + } + if networkPermissionScope.FullAccess { + return nil + } + rsrcPermissionScope, ok := networkPermissionScope.NetworkLevelAccess[models.RsrcType(targetRsrc)] + if !ok { + continue + } + if allRsrcsTypePermissionScope, ok := rsrcPermissionScope[models.RsrcID(fmt.Sprintf("all_%s", targetRsrc))]; ok { + err = checkPermissionScopeWithReqMethod(allRsrcsTypePermissionScope, r.Method) + if err == nil { + return nil + } + } + if targetRsrcID == "" { + continue + } + if scope, ok := rsrcPermissionScope[models.RsrcID(targetRsrcID)]; ok { + err = checkPermissionScopeWithReqMethod(scope, r.Method) + if err == nil { + return nil + } + } } - if targetRsrcID == "" { - return errors.New("target rsrc is missing") - } - if scope, ok := rsrcPermissionScope[models.RsrcID(targetRsrcID)]; ok { - return checkPermissionScopeWithReqMethod(scope, r.Method) - } + return errors.New("access denied") } @@ -78,7 +88,7 @@ func globalPermissionsCheck(username string, r *http.Request) error { if err != nil { return err } - userRole, err := GetRole(user.PlatformRoleID.String()) + userRole, err := GetRole(user.PlatformRoleID) if err != nil { return errors.New("access denied") } diff --git a/logic/user_mgmt.go b/logic/user_mgmt.go index d5bb0fde7..23c6c3aae 100644 --- a/logic/user_mgmt.go +++ b/logic/user_mgmt.go @@ -30,7 +30,7 @@ var ServiceUserPermissionTemplate = models.UserRolePermissionTemplate{ var NetworkAdminPermissionTemplate = models.UserRolePermissionTemplate{ ID: models.NetworkAdmin, Default: true, - NetworkID: "netmaker", + NetworkID: "*", FullAccess: true, NetworkLevelAccess: make(map[models.RsrcType]map[models.RsrcID]models.RsrcPermissionScope), } @@ -39,7 +39,7 @@ var NetworkUserPermissionTemplate = models.UserRolePermissionTemplate{ ID: models.NetworkUser, Default: true, FullAccess: false, - NetworkID: "netmaker", + NetworkID: "*", DenyDashboardAccess: false, NetworkLevelAccess: map[models.RsrcType]map[models.RsrcID]models.RsrcPermissionScope{ models.RemoteAccessGwRsrc: { @@ -63,6 +63,8 @@ func UserRolesInit() { database.Insert(SuperAdminPermissionTemplate.ID.String(), string(d), database.USER_PERMISSIONS_TABLE_NAME) d, _ = json.Marshal(AdminPermissionTemplate) database.Insert(AdminPermissionTemplate.ID.String(), string(d), database.USER_PERMISSIONS_TABLE_NAME) + d, _ = json.Marshal(ServiceUserPermissionTemplate) + database.Insert(ServiceUserPermissionTemplate.ID.String(), string(d), database.USER_PERMISSIONS_TABLE_NAME) d, _ = json.Marshal(NetworkAdminPermissionTemplate) database.Insert(NetworkAdminPermissionTemplate.ID.String(), string(d), database.USER_PERMISSIONS_TABLE_NAME) d, _ = json.Marshal(NetworkUserPermissionTemplate) @@ -105,9 +107,9 @@ func CreateRole(r models.UserRolePermissionTemplate) error { } // GetRole - fetches role template by id -func GetRole(roleID string) (models.UserRolePermissionTemplate, error) { +func GetRole(roleID models.UserRole) (models.UserRolePermissionTemplate, error) { // check if role already exists - data, err := database.FetchRecord(database.USER_PERMISSIONS_TABLE_NAME, roleID) + data, err := database.FetchRecord(database.USER_PERMISSIONS_TABLE_NAME, roleID.String()) if err != nil { return models.UserRolePermissionTemplate{}, errors.New("role already exists") } @@ -161,10 +163,12 @@ func DeleteRole(rid models.UserRole) error { err = errors.New("active roles cannot be deleted.switch existing users to a new role before deleting") return err } - for _, networkRole := range user.NetworkRoles { - if networkRole == rid { - err = errors.New("active roles cannot be deleted.switch existing users to a new role before deleting") - return err + for _, networkRoles := range user.NetworkRoles { + for networkRole := range networkRoles { + if networkRole == rid { + err = errors.New("active roles cannot be deleted.switch existing users to a new role before deleting") + return err + } } } } diff --git a/migrate/migrate.go b/migrate/migrate.go index b7cda5fef..a635b8193 100644 --- a/migrate/migrate.go +++ b/migrate/migrate.go @@ -327,7 +327,10 @@ func syncUsers() { user.PlatformRoleID = models.ServiceUser logic.UpsertUser(user) } + if len(user.RemoteGwIDs) > 0 { + // define user roles for network + } } } } diff --git a/models/user_mgmt.go b/models/user_mgmt.go index d304e690f..649770123 100644 --- a/models/user_mgmt.go +++ b/models/user_mgmt.go @@ -16,6 +16,10 @@ func (r RsrcType) String() string { return string(r) } +func (rid RsrcID) String() string { + return string(rid) +} + const ( HostRsrc RsrcType = "hosts" RelayRsrc RsrcType = "relays" @@ -65,10 +69,11 @@ func (g UserGroupID) String() string { } type RsrcPermissionScope struct { - Create bool `json:"create"` - Read bool `json:"read"` - Update bool `json:"update"` - Delete bool `json:"delete"` + Create bool `json:"create"` + Read bool `json:"read"` + Update bool `json:"update"` + Delete bool `json:"delete"` + VPNaccess bool `json:"vpn_access"` } type UserRolePermissionTemplate struct { @@ -89,15 +94,15 @@ type UserGroup struct { // User struct - struct for Users type User struct { - UserName string `json:"username" bson:"username" validate:"min=3,max=40,in_charset|email"` - Password string `json:"password" bson:"password" validate:"required,min=5"` - IsAdmin bool `json:"isadmin" bson:"isadmin"` - IsSuperAdmin bool `json:"issuperadmin"` - RemoteGwIDs map[string]struct{} `json:"remote_gw_ids"` - UserGroups map[UserGroupID]struct{} `json:"user_group_ids"` - PlatformRoleID UserRole `json:"platform_role_id"` - NetworkRoles map[NetworkID]UserRole `json:"network_roles"` - LastLoginTime time.Time `json:"last_login_time"` + UserName string `json:"username" bson:"username" validate:"min=3,max=40,in_charset|email"` + Password string `json:"password" bson:"password" validate:"required,min=5"` + IsAdmin bool `json:"isadmin" bson:"isadmin"` + IsSuperAdmin bool `json:"issuperadmin"` + RemoteGwIDs map[string]struct{} `json:"remote_gw_ids"` + UserGroups map[UserGroupID]struct{} `json:"user_group_ids"` + PlatformRoleID UserRole `json:"platform_role_id"` + NetworkRoles map[NetworkID]map[UserRole]struct{} `json:"network_roles"` + LastLoginTime time.Time `json:"last_login_time"` } // ReturnUser - return user struct From 49c2e60744106cda97bab2ee3c17ca5386033c03 Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Tue, 25 Jun 2024 00:09:24 +0530 Subject: [PATCH 040/139] allow multiple network roles --- controllers/user.go | 14 ++++++++++ logic/security.go | 64 +++++++++++++++++++++++++++++---------------- logic/user_mgmt.go | 37 +++++++++++++++----------- models/user_mgmt.go | 7 ++--- 4 files changed, 82 insertions(+), 40 deletions(-) diff --git a/controllers/user.go b/controllers/user.go index 0389828d0..5d8e30442 100644 --- a/controllers/user.go +++ b/controllers/user.go @@ -642,6 +642,20 @@ func createUser(w http.ResponseWriter, r *http.Request) { logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) return } + uniqueGroupsPlatformRole := make(map[models.UserRole]struct{}) + for groupID := range user.UserGroups { + userG, err := logic.GetUserGroup(groupID) + if err != nil { + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) + return + } + uniqueGroupsPlatformRole[userG.PlatformRole] = struct{}{} + } + if len(uniqueGroupsPlatformRole) > 1 { + err = errors.New("only groups with same platform role can be assigned to an user") + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) + return + } if !caller.IsSuperAdmin && user.IsAdmin { err = errors.New("only superadmin can create admin users") slog.Error("error creating new user: ", "user", user.UserName, "error", err) diff --git a/logic/security.go b/logic/security.go index 359b9112a..29d9134d4 100644 --- a/logic/security.go +++ b/logic/security.go @@ -51,35 +51,55 @@ func networkPermissionsCheck(username string, r *http.Request) error { // TODO - differentitate between global scope and network scope apis netRoles := user.NetworkRoles[models.NetworkID(netID)] for netRoleID := range netRoles { - networkPermissionScope, err := GetRole(netRoleID) - if err != nil { - continue - } - if networkPermissionScope.FullAccess { + err = checkNetworkAccessPermissions(netRoleID, r.Method, targetRsrc, targetRsrcID) + if err == nil { return nil } - rsrcPermissionScope, ok := networkPermissionScope.NetworkLevelAccess[models.RsrcType(targetRsrc)] - if !ok { - continue - } - if allRsrcsTypePermissionScope, ok := rsrcPermissionScope[models.RsrcID(fmt.Sprintf("all_%s", targetRsrc))]; ok { - err = checkPermissionScopeWithReqMethod(allRsrcsTypePermissionScope, r.Method) - if err == nil { - return nil + } + for groupID := range user.UserGroups { + userG, err := GetUserGroup(groupID) + if err == nil { + netRoles := userG.NetworkRoles[models.NetworkID(netID)] + for netRoleID := range netRoles { + err = checkNetworkAccessPermissions(netRoleID, r.Method, targetRsrc, targetRsrcID) + if err == nil { + return nil + } } - } - if targetRsrcID == "" { - continue + } + + return errors.New("access denied") +} + +func checkNetworkAccessPermissions(netRoleID models.UserRole, reqScope, targetRsrc, targetRsrcID string) error { + networkPermissionScope, err := GetRole(netRoleID) + if err != nil { + return err + } + if networkPermissionScope.FullAccess { + return nil + } + rsrcPermissionScope, ok := networkPermissionScope.NetworkLevelAccess[models.RsrcType(targetRsrc)] + if !ok { + return errors.New("access denied") + } + if allRsrcsTypePermissionScope, ok := rsrcPermissionScope[models.RsrcID(fmt.Sprintf("all_%s", targetRsrc))]; ok { + err = checkPermissionScopeWithReqMethod(allRsrcsTypePermissionScope, reqScope) + if err == nil { + return nil } - if scope, ok := rsrcPermissionScope[models.RsrcID(targetRsrcID)]; ok { - err = checkPermissionScopeWithReqMethod(scope, r.Method) - if err == nil { - return nil - } + + } + if targetRsrcID == "" { + return errors.New("target rsrc id is empty") + } + if scope, ok := rsrcPermissionScope[models.RsrcID(targetRsrcID)]; ok { + err = checkPermissionScopeWithReqMethod(scope, reqScope) + if err == nil { + return nil } } - return errors.New("access denied") } diff --git a/logic/user_mgmt.go b/logic/user_mgmt.go index 23c6c3aae..1999afd0a 100644 --- a/logic/user_mgmt.go +++ b/logic/user_mgmt.go @@ -30,7 +30,7 @@ var ServiceUserPermissionTemplate = models.UserRolePermissionTemplate{ var NetworkAdminPermissionTemplate = models.UserRolePermissionTemplate{ ID: models.NetworkAdmin, Default: true, - NetworkID: "*", + NetworkID: "netmaker", FullAccess: true, NetworkLevelAccess: make(map[models.RsrcType]map[models.RsrcID]models.RsrcPermissionScope), } @@ -39,7 +39,7 @@ var NetworkUserPermissionTemplate = models.UserRolePermissionTemplate{ ID: models.NetworkUser, Default: true, FullAccess: false, - NetworkID: "*", + NetworkID: "netmaker", DenyDashboardAccess: false, NetworkLevelAccess: map[models.RsrcType]map[models.RsrcID]models.RsrcPermissionScope{ models.RemoteAccessGwRsrc: { @@ -49,10 +49,11 @@ var NetworkUserPermissionTemplate = models.UserRolePermissionTemplate{ }, models.ExtClientsRsrc: { models.AllExtClientsRsrcID: models.RsrcPermissionScope{ - Read: true, - Create: true, - Update: true, - Delete: true, + Read: true, + Create: true, + Update: true, + Delete: true, + VPNaccess: true, }, }, }, @@ -146,16 +147,23 @@ func DeleteRole(rid models.UserRole) error { if err != nil { return err } + role, err := GetRole(rid) + if err != nil { + return err + } for _, user := range users { for userG := range user.UserGroups { ug, err := GetUserGroup(userG) if err == nil { - for _, networkRole := range ug.NetworkRoles { - if networkRole == rid { - err = errors.New("role cannot be deleted as active user groups are using this role") - return err + if role.NetworkID != "" { + for _, networkRoles := range ug.NetworkRoles { + if _, ok := networkRoles[rid]; ok { + err = errors.New("role cannot be deleted as active user groups are using this role") + return err + } } } + } } @@ -164,12 +172,11 @@ func DeleteRole(rid models.UserRole) error { return err } for _, networkRoles := range user.NetworkRoles { - for networkRole := range networkRoles { - if networkRole == rid { - err = errors.New("active roles cannot be deleted.switch existing users to a new role before deleting") - return err - } + if _, ok := networkRoles[rid]; ok { + err = errors.New("active roles cannot be deleted.switch existing users to a new role before deleting") + return err } + } } return database.DeleteRecord(database.USER_PERMISSIONS_TABLE_NAME, rid.String()) diff --git a/models/user_mgmt.go b/models/user_mgmt.go index 649770123..4f630de49 100644 --- a/models/user_mgmt.go +++ b/models/user_mgmt.go @@ -87,9 +87,10 @@ type UserRolePermissionTemplate struct { } type UserGroup struct { - ID string `json:"id"` - NetworkRoles map[NetworkID]UserRole `json:"network_roles"` - MetaData string `json:"meta_data"` + ID string `json:"id"` + PlatformRole UserRole `json:"platform_role"` + NetworkRoles map[NetworkID]map[UserRole]struct{} `json:"network_roles"` + MetaData string `json:"meta_data"` } // User struct - struct for Users From b385b50a16b5beed7db21bd6075a75bb9e23292a Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Tue, 25 Jun 2024 13:37:29 +0530 Subject: [PATCH 041/139] add migration func --- controllers/user.go | 9 ++++++-- migrate/migrate.go | 53 ++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 59 insertions(+), 3 deletions(-) diff --git a/controllers/user.go b/controllers/user.go index 5d8e30442..30cc2d2f8 100644 --- a/controllers/user.go +++ b/controllers/user.go @@ -369,8 +369,13 @@ func authenticateUser(response http.ResponseWriter, request *http.Request) { logic.ReturnErrorResponse(response, request, logic.FormatError(err, "unauthorized")) return } - if !(user.IsAdmin || user.IsSuperAdmin) { - logic.ReturnErrorResponse(response, request, logic.FormatError(errors.New("only admins can access dashboard"), "unauthorized")) + role, err := logic.GetRole(user.PlatformRoleID) + if err != nil { + logic.ReturnErrorResponse(response, request, logic.FormatError(errors.New("access denied to dashboard"), "unauthorized")) + return + } + if role.DenyDashboardAccess { + logic.ReturnErrorResponse(response, request, logic.FormatError(errors.New("access denied to dashboard"), "unauthorized")) return } } diff --git a/migrate/migrate.go b/migrate/migrate.go index a635b8193..a2593aa19 100644 --- a/migrate/migrate.go +++ b/migrate/migrate.go @@ -314,6 +314,35 @@ func MigrateEmqx() { } func syncUsers() { + // create default network user roles for existing networks + networks, _ := logic.GetNetworks() + nodes, err := logic.GetAllNodes() + if err == nil { + for _, netI := range networks { + networkNodes := logic.GetNetworkNodesMemory(nodes, netI.NetID) + for _, networkNodeI := range networkNodes { + if networkNodeI.IsIngressGateway { + h, err := logic.GetHost(networkNodeI.HostID.String()) + if err == nil { + logic.CreateRole(models.UserRolePermissionTemplate{ + ID: models.UserRole(fmt.Sprintf("net-%s-user-gw-%s", netI.NetID, h.Name)), + DenyDashboardAccess: true, + NetworkID: netI.NetID, + NetworkLevelAccess: map[models.RsrcType]map[models.RsrcID]models.RsrcPermissionScope{ + models.RemoteAccessGwRsrc: { + models.RsrcID(networkNodeI.ID.String()): models.RsrcPermissionScope{ + VPNaccess: true, + }, + }, + }, + }) + } + + } + } + } + } + users, err := logic.GetUsersDB() if err == nil { for _, user := range users { @@ -329,7 +358,29 @@ func syncUsers() { } if len(user.RemoteGwIDs) > 0 { // define user roles for network - + // assign relevant network role to user + for remoteGwID := range user.RemoteGwIDs { + gwNode, err := logic.GetNodeByID(remoteGwID) + if err != nil { + continue + } + h, err := logic.GetHost(gwNode.HostID.String()) + if err != nil { + continue + } + r, err := logic.GetRole(models.UserRole(fmt.Sprintf("net-%s-user-gw-%s", gwNode.Network, h.Name))) + if err != nil { + continue + } + if netRoles, ok := user.NetworkRoles[models.NetworkID(gwNode.Network)]; ok { + netRoles[r.ID] = struct{}{} + } else { + user.NetworkRoles[models.NetworkID(gwNode.Network)] = map[models.UserRole]struct{}{ + r.ID: {}, + } + } + } + logic.UpsertUser(user) } } } From 2be6e8588cac36f68dd291b43abdc86c06323c31 Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Tue, 25 Jun 2024 14:08:35 +0530 Subject: [PATCH 042/139] return err if jwt parsing fails --- logic/jwts.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/logic/jwts.go b/logic/jwts.go index 6656727ac..f3838ec16 100644 --- a/logic/jwts.go +++ b/logic/jwts.go @@ -104,6 +104,9 @@ func GetUserNameFromToken(authtoken string) (username string, err error) { token, err := jwt.ParseWithClaims(tokenString, claims, func(token *jwt.Token) (interface{}, error) { return jwtSecretKey, nil }) + if err != nil { + return "", Unauthorized_Err + } if token != nil && token.Valid { var user *models.User @@ -116,6 +119,8 @@ func GetUserNameFromToken(authtoken string) (username string, err error) { return user.UserName, nil } err = errors.New("user does not exist") + } else { + err = Unauthorized_Err } return "", err } From 9082b2fe9169f774c9ca24ca14bfdce93be48767 Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Tue, 25 Jun 2024 14:20:49 +0530 Subject: [PATCH 043/139] set global check to true when accessing user apis --- controllers/middleware.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/controllers/middleware.go b/controllers/middleware.go index e27f45686..b1f9d2e2d 100644 --- a/controllers/middleware.go +++ b/controllers/middleware.go @@ -64,7 +64,9 @@ func userMiddleWare(handler http.Handler) http.Handler { if userID, ok := params["username"]; ok { r.Header.Set("TARGET_RSRC_ID", userID) } - if r.Header.Get("TARGET_RSRC_ID") == "" || r.Header.Get("TARGET_RSRC") == models.EnrollmentKeysRsrc.String() { + if r.Header.Get("TARGET_RSRC_ID") == "" || + r.Header.Get("TARGET_RSRC") == models.EnrollmentKeysRsrc.String() || + r.Header.Get("TARGET_RSRC") == models.UserRsrc.String() { r.Header.Set("IS_GLOBAL_ACCESS", "yes") } handler.ServeHTTP(w, r) From 26d82595842e9daf9950df9d0c33df8478fd52e3 Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Tue, 25 Jun 2024 15:53:08 +0530 Subject: [PATCH 044/139] set netid for acls api calls --- controllers/middleware.go | 1 + 1 file changed, 1 insertion(+) diff --git a/controllers/middleware.go b/controllers/middleware.go index b1f9d2e2d..d7b708730 100644 --- a/controllers/middleware.go +++ b/controllers/middleware.go @@ -60,6 +60,7 @@ func userMiddleWare(handler http.Handler) http.Handler { } if netID, ok := params["networkname"]; ok { r.Header.Set("TARGET_RSRC_ID", netID) + r.Header.Set("NET_ID", params["networkname"]) } if userID, ok := params["username"]; ok { r.Header.Set("TARGET_RSRC_ID", userID) From ca581aac5c17890bddf624a15d2501d3fa445202 Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Tue, 25 Jun 2024 18:21:15 +0530 Subject: [PATCH 045/139] set netid for acls api calls --- controllers/middleware.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/controllers/middleware.go b/controllers/middleware.go index d7b708730..1b4a536ba 100644 --- a/controllers/middleware.go +++ b/controllers/middleware.go @@ -59,9 +59,12 @@ func userMiddleWare(handler http.Handler) http.Handler { r.Header.Set("TARGET_RSRC_ID", clientID) } if netID, ok := params["networkname"]; ok { - r.Header.Set("TARGET_RSRC_ID", netID) + if !strings.Contains(r.URL.Path, "acls") { + r.Header.Set("TARGET_RSRC_ID", netID) + } r.Header.Set("NET_ID", params["networkname"]) } + if userID, ok := params["username"]; ok { r.Header.Set("TARGET_RSRC_ID", userID) } From f1e88678172905989a0d337d389947b4b3e89db4 Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Thu, 27 Jun 2024 20:21:21 +0530 Subject: [PATCH 046/139] update role and groups routes --- controllers/user.go | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/controllers/user.go b/controllers/user.go index 30cc2d2f8..d2fa93e17 100644 --- a/controllers/user.go +++ b/controllers/user.go @@ -38,18 +38,18 @@ func userHandlers(r *mux.Router) { r.HandleFunc("/api/users_pending/user/{username}", logic.SecurityCheck(true, http.HandlerFunc(approvePendingUser))).Methods(http.MethodPost) // User Role Handlers - r.HandleFunc("/api/v1/user/roles", logic.SecurityCheck(true, http.HandlerFunc(listRoles))).Methods(http.MethodGet) - r.HandleFunc("/api/v1/user/role", logic.SecurityCheck(true, http.HandlerFunc(getRole))).Methods(http.MethodGet) - r.HandleFunc("/api/v1/user/role", logic.SecurityCheck(true, http.HandlerFunc(createRole))).Methods(http.MethodPost) - r.HandleFunc("/api/v1/user/role", logic.SecurityCheck(true, http.HandlerFunc(updateRole))).Methods(http.MethodPut) - r.HandleFunc("/api/v1/user/role", logic.SecurityCheck(true, http.HandlerFunc(deleteRole))).Methods(http.MethodDelete) + r.HandleFunc("/api/v1/users/roles", logic.SecurityCheck(true, http.HandlerFunc(listRoles))).Methods(http.MethodGet) + r.HandleFunc("/api/v1/users/role", logic.SecurityCheck(true, http.HandlerFunc(getRole))).Methods(http.MethodGet) + r.HandleFunc("/api/v1/users/role", logic.SecurityCheck(true, http.HandlerFunc(createRole))).Methods(http.MethodPost) + r.HandleFunc("/api/v1/users/role", logic.SecurityCheck(true, http.HandlerFunc(updateRole))).Methods(http.MethodPut) + r.HandleFunc("/api/v1/users/role", logic.SecurityCheck(true, http.HandlerFunc(deleteRole))).Methods(http.MethodDelete) // User Group Handlers - r.HandleFunc("/api/v1/user/groups", logic.SecurityCheck(true, http.HandlerFunc(listUserGroups))).Methods(http.MethodGet) - r.HandleFunc("/api/v1/user/group", logic.SecurityCheck(true, http.HandlerFunc(getUserGroup))).Methods(http.MethodGet) - r.HandleFunc("/api/v1/user/group", logic.SecurityCheck(true, http.HandlerFunc(createUserGroup))).Methods(http.MethodPost) - r.HandleFunc("/api/v1/user/group", logic.SecurityCheck(true, http.HandlerFunc(updateUserGroup))).Methods(http.MethodPut) - r.HandleFunc("/api/v1/user/group", logic.SecurityCheck(true, http.HandlerFunc(deleteUserGroup))).Methods(http.MethodDelete) + r.HandleFunc("/api/v1/users/groups", logic.SecurityCheck(true, http.HandlerFunc(listUserGroups))).Methods(http.MethodGet) + r.HandleFunc("/api/v1/users/group", logic.SecurityCheck(true, http.HandlerFunc(getUserGroup))).Methods(http.MethodGet) + r.HandleFunc("/api/v1/users/group", logic.SecurityCheck(true, http.HandlerFunc(createUserGroup))).Methods(http.MethodPost) + r.HandleFunc("/api/v1/users/group", logic.SecurityCheck(true, http.HandlerFunc(updateUserGroup))).Methods(http.MethodPut) + r.HandleFunc("/api/v1/users/group", logic.SecurityCheck(true, http.HandlerFunc(deleteUserGroup))).Methods(http.MethodDelete) } From 306d844540769a3312bad44e0a013df06b319dbb Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Sat, 29 Jun 2024 00:06:36 +0530 Subject: [PATCH 047/139] add validation checks --- controllers/user.go | 12 ++++++++++-- logic/user_mgmt.go | 30 ++++++++++++++++++++++++++++++ models/user_mgmt.go | 4 ++-- 3 files changed, 42 insertions(+), 4 deletions(-) diff --git a/controllers/user.go b/controllers/user.go index d2fa93e17..88b79097b 100644 --- a/controllers/user.go +++ b/controllers/user.go @@ -259,10 +259,12 @@ func createRole(w http.ResponseWriter, r *http.Request) { logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) return } - if userRole.NetworkID == "" { - logic.ReturnErrorResponse(w, r, logic.FormatError(err, "only network roles are allowed to be created")) + err = logic.ValidateCreateRoleReq(userRole) + if err != nil { + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) return } + userRole.Default = false userRole.GlobalLevelAccess = make(map[models.RsrcType]map[models.RsrcID]models.RsrcPermissionScope) err = logic.CreateRole(userRole) if err != nil { @@ -292,6 +294,12 @@ func updateRole(w http.ResponseWriter, r *http.Request) { logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) return } + err = logic.ValidateUpdateRoleReq(userRole) + if err != nil { + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) + return + } + userRole.GlobalLevelAccess = make(map[models.RsrcType]map[models.RsrcID]models.RsrcPermissionScope) err = logic.UpdateRole(userRole) if err != nil { logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) diff --git a/logic/user_mgmt.go b/logic/user_mgmt.go index 1999afd0a..720326567 100644 --- a/logic/user_mgmt.go +++ b/logic/user_mgmt.go @@ -3,6 +3,7 @@ package logic import ( "encoding/json" "errors" + "fmt" "github.com/gravitl/netmaker/database" "github.com/gravitl/netmaker/models" @@ -90,6 +91,32 @@ func ListRoles() ([]models.UserRolePermissionTemplate, error) { return userRoles, nil } +func ValidateCreateRoleReq(userRole models.UserRolePermissionTemplate) error { + // check if role exists with this id + _, err := GetRole(userRole.ID) + if err == nil { + return fmt.Errorf("role with id `%s` exists already", userRole.ID.String()) + } + if userRole.NetworkID == "" { + return errors.New("only network roles are allowed to be created") + } + return nil +} + +func ValidateUpdateRoleReq(userRole models.UserRolePermissionTemplate) error { + roleInDB, err := GetRole(userRole.ID) + if err != nil { + return err + } + if roleInDB.NetworkID != userRole.NetworkID { + return errors.New("network id mismatch") + } + if roleInDB.Default { + return errors.New("cannot update default role") + } + return nil +} + // CreateRole - inserts new role into DB func CreateRole(r models.UserRolePermissionTemplate) error { // check if role already exists @@ -151,6 +178,9 @@ func DeleteRole(rid models.UserRole) error { if err != nil { return err } + if role.Default { + return errors.New("cannot delete default role") + } for _, user := range users { for userG := range user.UserGroups { ug, err := GetUserGroup(userG) diff --git a/models/user_mgmt.go b/models/user_mgmt.go index 4f630de49..1008dfb04 100644 --- a/models/user_mgmt.go +++ b/models/user_mgmt.go @@ -99,7 +99,7 @@ type User struct { Password string `json:"password" bson:"password" validate:"required,min=5"` IsAdmin bool `json:"isadmin" bson:"isadmin"` IsSuperAdmin bool `json:"issuperadmin"` - RemoteGwIDs map[string]struct{} `json:"remote_gw_ids"` + RemoteGwIDs map[string]struct{} `json:"remote_gw_ids"` // deprecated UserGroups map[UserGroupID]struct{} `json:"user_group_ids"` PlatformRoleID UserRole `json:"platform_role_id"` NetworkRoles map[NetworkID]map[UserRole]struct{} `json:"network_roles"` @@ -111,7 +111,7 @@ type ReturnUser struct { UserName string `json:"username"` IsAdmin bool `json:"isadmin"` IsSuperAdmin bool `json:"issuperadmin"` - RemoteGwIDs map[string]struct{} `json:"remote_gw_ids"` + RemoteGwIDs map[string]struct{} `json:"remote_gw_ids"` // deprecated UserGroups map[UserGroupID]struct{} `json:"user_group_ids"` PlatformRoleID string `json:"platform_role_id"` NetworkRoles map[NetworkID]UserRole `json:"network_roles"` From 0fc9a181fdf566be0086ade52320454d0788060e Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Tue, 2 Jul 2024 12:42:11 +0530 Subject: [PATCH 048/139] add invite flow apis and magic links --- controllers/user.go | 202 ++++++++++++++++++++++++++++++++++- database/database.go | 2 + go.mod | 2 + go.sum | 4 + logic/notification.go | 48 +++++++++ logic/user_mgmt.go | 8 +- logic/users.go | 47 ++++++++ models/user_mgmt.go | 14 ++- scripts/netmaker.default.env | 9 ++ servercfg/serverconf.go | 17 ++- 10 files changed, 346 insertions(+), 7 deletions(-) create mode 100644 logic/notification.go diff --git a/controllers/user.go b/controllers/user.go index 88b79097b..a59dbea45 100644 --- a/controllers/user.go +++ b/controllers/user.go @@ -51,6 +51,14 @@ func userHandlers(r *mux.Router) { r.HandleFunc("/api/v1/users/group", logic.SecurityCheck(true, http.HandlerFunc(updateUserGroup))).Methods(http.MethodPut) r.HandleFunc("/api/v1/users/group", logic.SecurityCheck(true, http.HandlerFunc(deleteUserGroup))).Methods(http.MethodDelete) + // User Invite Handlers + r.HandleFunc("/api/v1/users/invite", userInviteVerify).Methods(http.MethodGet) + r.HandleFunc("/api/v1/users/invite-signup", userInviteSignUp).Methods(http.MethodPost) + r.HandleFunc("/api/v1/users/invite", logic.SecurityCheck(true, http.HandlerFunc(inviteUsers))).Methods(http.MethodPost) + r.HandleFunc("/api/v1/users/invites", logic.SecurityCheck(true, http.HandlerFunc(listUserInvites))).Methods(http.MethodGet) + r.HandleFunc("/api/v1/users/invite", logic.SecurityCheck(true, http.HandlerFunc(deleteUserInvite))).Methods(http.MethodDelete) + r.HandleFunc("/api/v1/users/invites", logic.SecurityCheck(true, http.HandlerFunc(deleteAllUserInvites))).Methods(http.MethodDelete) + } // swagger:route GET /api/v1/user/groups user listUserGroups @@ -1024,7 +1032,6 @@ func deletePendingUser(w http.ResponseWriter, r *http.Request) { // 200: userBodyResponse func deleteAllPendingUsers(w http.ResponseWriter, r *http.Request) { // set header. - w.Header().Set("Content-Type", "application/json") err := database.DeleteAllRecords(database.PENDING_USERS_TABLE_NAME) if err != nil { logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("failed to delete all pending users "+err.Error()), "internal")) @@ -1032,3 +1039,196 @@ func deleteAllPendingUsers(w http.ResponseWriter, r *http.Request) { } logic.ReturnSuccessResponse(w, r, "cleared all pending users") } + +// swagger:route POST /api/v1/users/invite-signup user userInviteSignUp +// +// user signup via invite. +// +// Schemes: https +// +// Responses: +// 200: ReturnSuccessResponse +func userInviteSignUp(w http.ResponseWriter, r *http.Request) { + var params = mux.Vars(r) + email := params["email"] + code := params["code"] + in, err := logic.GetUserInvite(email) + if err != nil { + logger.Log(0, "failed to fetch users: ", err.Error()) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) + return + } + if code != in.InviteCode { + logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("invalid invite code"), "badrequest")) + return + } + // check if user already exists + _, err = logic.GetUser(email) + if err == nil { + logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("user already exists"), "badrequest")) + return + } + var user models.User + err = json.NewDecoder(r.Body).Decode(&user) + if err != nil { + logger.Log(0, user.UserName, "error decoding request body: ", + err.Error()) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) + return + } + if user.UserName != email { + logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("username not matching with invite"), "badrequest")) + return + } + if user.Password == "" { + logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("password cannot be empty"), "badrequest")) + return + } + for _, inviteGroupID := range in.Groups { + userG, err := logic.GetUserGroup(inviteGroupID) + if err != nil { + logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("error fetching group id "+inviteGroupID.String()), "badrequest")) + return + } + user.PlatformRoleID = userG.PlatformRole + user.UserGroups[inviteGroupID] = struct{}{} + } + user.NetworkRoles = make(map[models.NetworkID]map[models.UserRole]struct{}) + user.IsSuperAdmin = false + err = logic.CreateUser(&user) + if err != nil { + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) + return + } + // delete invite + logic.DeleteUserInvite(email) + logic.DeletePendingUser(email) + logic.ReturnSuccessResponse(w, r, "created user successfully "+email) +} + +// swagger:route GET /api/v1/users/invite user userInviteVerify +// +// verfies user invite. +// +// Schemes: https +// +// Responses: +// 200: ReturnSuccessResponse +func userInviteVerify(w http.ResponseWriter, r *http.Request) { + var params = mux.Vars(r) + email := params["email"] + code := params["code"] + err := logic.ValidateAndApproveUserInvite(email, code) + if err != nil { + logger.Log(0, "failed to fetch users: ", err.Error()) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) + return + } + logic.ReturnSuccessResponse(w, r, "invite is valid") +} + +// swagger:route POST /api/v1/users/invite user inviteUsers +// +// invite users. +// +// Schemes: https +// +// Security: +// oauth +// +// Responses: +// 200: userBodyResponse +func inviteUsers(w http.ResponseWriter, r *http.Request) { + var inviteReq models.InviteUsersReq + err := json.NewDecoder(r.Body).Decode(&inviteReq) + if err != nil { + slog.Error("error decoding request body", "error", + err.Error()) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) + return + } + for _, inviteeEmail := range inviteReq.UserEmails { + // check if user with email exists, then ignore + _, err := logic.GetUser(inviteeEmail) + if err == nil { + // user exists already, so ignore + continue + } + invite := models.UserInvite{ + Email: inviteeEmail, + Groups: inviteReq.Groups, + InviteCode: logic.RandomString(8), + } + err = logic.InsertUserInvite(invite) + if err != nil { + slog.Error("failed to insert invite for user", "email", invite.Email, "error", err) + } + // notify user with magic link + go logic.SendInviteEmail(invite) + } + +} + +// swagger:route GET /api/v1/users/invites user listUserInvites +// +// lists all pending invited users. +// +// Schemes: https +// +// Security: +// oauth +// +// Responses: +// 200: ReturnSuccessResponseWithJson +func listUserInvites(w http.ResponseWriter, r *http.Request) { + usersInvites, err := logic.ListUserInvites() + if err != nil { + logger.Log(0, "failed to fetch users: ", err.Error()) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) + return + } + logic.ReturnSuccessResponseWithJson(w, r, usersInvites, "fetched pending user invites") +} + +// swagger:route DELETE /api/v1/users/invite user deleteUserInvite +// +// delete pending invite. +// +// Schemes: https +// +// Security: +// oauth +// +// Responses: +// 200: ReturnSuccessResponse +func deleteUserInvite(w http.ResponseWriter, r *http.Request) { + var params = mux.Vars(r) + username := params["invitee_email"] + err := logic.DeleteUserInvite(username) + if err != nil { + logger.Log(0, "failed to delete user invite: ", username, err.Error()) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) + return + } + logic.ReturnSuccessResponse(w, r, "deleted user invite") +} + +// swagger:route DELETE /api/v1/users/invites user deleteAllUserInvites +// +// deletes all pending invites. +// +// Schemes: https +// +// Security: +// oauth +// +// Responses: +// 200: ReturnSuccessResponse +func deleteAllUserInvites(w http.ResponseWriter, r *http.Request) { + err := database.DeleteAllRecords(database.USER_INVITES_TABLE_NAME) + if err != nil { + logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("failed to delete all pending user invites "+err.Error()), "internal")) + return + } + logic.ReturnSuccessResponse(w, r, "cleared all pending user invites") +} diff --git a/database/database.go b/database/database.go index b29aaec11..213263b9f 100644 --- a/database/database.go +++ b/database/database.go @@ -65,6 +65,8 @@ const ( HOST_ACTIONS_TABLE_NAME = "hostactions" // PENDING_USERS_TABLE_NAME - table name for pending users PENDING_USERS_TABLE_NAME = "pending_users" + // USER_INVITES - table for user invites + USER_INVITES_TABLE_NAME = "user_invites" // == ERROR CONSTS == // NO_RECORD - no singular result found NO_RECORD = "no result found" diff --git a/go.mod b/go.mod index 83774b4b1..d17d2e031 100644 --- a/go.mod +++ b/go.mod @@ -42,6 +42,7 @@ require ( github.com/matryer/is v1.4.1 github.com/olekukonko/tablewriter v0.0.5 github.com/spf13/cobra v1.8.1 + gopkg.in/mail.v2 v2.3.1 ) require ( @@ -50,6 +51,7 @@ require ( github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/rivo/uniseg v0.2.0 // indirect github.com/spf13/pflag v1.0.5 // indirect + gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect ) require ( diff --git a/go.sum b/go.sum index 1a869dd8c..7f7c970e0 100644 --- a/go.sum +++ b/go.sum @@ -140,8 +140,12 @@ golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.zx2c4.com/wireguard/wgctrl v0.0.0-20221104135756-97bc4ad4a1cb h1:9aqVcYEDHmSNb0uOWukxV5lHV09WqiSiCuhEgWNETLY= golang.zx2c4.com/wireguard/wgctrl v0.0.0-20221104135756-97bc4ad4a1cb/go.mod h1:mQqgjkW8GQQcJQsbBvK890TKqUK1DfKWkuBGbOkuMHQ= +gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk= +gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/mail.v2 v2.3.1 h1:WYFn/oANrAGP2C0dcV6/pbkPzv8yGzqTjPmTeO7qoXk= +gopkg.in/mail.v2 v2.3.1/go.mod h1:htwXN1Qh09vZJ1NVKxQqHPBaCBbzKhp5GzuJEA4VJWw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/logic/notification.go b/logic/notification.go new file mode 100644 index 000000000..9abed58c9 --- /dev/null +++ b/logic/notification.go @@ -0,0 +1,48 @@ +package logic + +import ( + "crypto/tls" + "fmt" + + gomail "gopkg.in/mail.v2" + + "github.com/gravitl/netmaker/models" + "github.com/gravitl/netmaker/servercfg" +) + +var ( + smtpHost = servercfg.GetSmtpHost() + smtpPort = servercfg.GetSmtpPort() + senderEmail = servercfg.GetSenderEmail() + senderPassword = servercfg.GetSenderEmailPassWord() +) + +func SendInviteEmail(invite models.UserInvite) error { + m := gomail.NewMessage() + + // Set E-Mail sender + m.SetHeader("From", senderEmail) + + // Set E-Mail receivers + m.SetHeader("To", invite.Email) + + // Set E-Mail subject + m.SetHeader("Subject", "Netmaker Invite") + + // Set E-Mail body. You can set plain text or html with text/html + m.SetBody("text/html", "Click Here to Signup! "+fmt.Sprintf("https://api.%s/invitesignup?code=%v", servercfg.GetServer(), invite.InviteCode)) + + // Settings for SMTP server + d := gomail.NewDialer(smtpHost, smtpPort, senderEmail, senderPassword) + + // This is only needed when SSL/TLS certificate is not valid on server. + // In production this should be set to false. + d.TLSConfig = &tls.Config{InsecureSkipVerify: true} + + // Now send E-Mail + if err := d.DialAndSend(m); err != nil { + return err + } + + return nil +} diff --git a/logic/user_mgmt.go b/logic/user_mgmt.go index 720326567..da990101a 100644 --- a/logic/user_mgmt.go +++ b/logic/user_mgmt.go @@ -218,7 +218,7 @@ func CreateUserGroup(g models.UserGroup) error { if g.ID == "" { return errors.New("group id cannot be empty") } - _, err := database.FetchRecord(database.USER_GROUPS_TABLE_NAME, g.ID) + _, err := database.FetchRecord(database.USER_GROUPS_TABLE_NAME, g.ID.String()) if err == nil { return errors.New("group already exists") } @@ -226,7 +226,7 @@ func CreateUserGroup(g models.UserGroup) error { if err != nil { return err } - return database.Insert(g.ID, string(d), database.USER_GROUPS_TABLE_NAME) + return database.Insert(g.ID.String(), string(d), database.USER_GROUPS_TABLE_NAME) } // GetUserGroup - fetches user group @@ -267,7 +267,7 @@ func UpdateUserGroup(g models.UserGroup) error { if g.ID == "" { return errors.New("group id cannot be empty") } - _, err := database.FetchRecord(database.USER_GROUPS_TABLE_NAME, g.ID) + _, err := database.FetchRecord(database.USER_GROUPS_TABLE_NAME, g.ID.String()) if err != nil { return err } @@ -275,7 +275,7 @@ func UpdateUserGroup(g models.UserGroup) error { if err != nil { return err } - return database.Insert(g.ID, string(d), database.USER_GROUPS_TABLE_NAME) + return database.Insert(g.ID.String(), string(d), database.USER_GROUPS_TABLE_NAME) } // DeleteUserGroup - deletes user group diff --git a/logic/users.go b/logic/users.go index 987556e23..fc8517501 100644 --- a/logic/users.go +++ b/logic/users.go @@ -119,3 +119,50 @@ func ListPendingUsers() ([]models.ReturnUser, error) { } return pendingUsers, nil } + +func InsertUserInvite(invite models.UserInvite) error { + data, err := json.Marshal(invite) + if err != nil { + return err + } + return database.Insert(invite.Email, string(data), database.USER_INVITES_TABLE_NAME) +} + +func GetUserInvite(email string) (in models.UserInvite, err error) { + d, err := database.FetchRecord(database.USER_INVITES_TABLE_NAME, email) + if err != nil { + return + } + err = json.Unmarshal([]byte(d), &in) + return +} + +func ListUserInvites() ([]models.UserInvite, error) { + invites := []models.UserInvite{} + records, err := database.FetchRecords(database.USER_INVITES_TABLE_NAME) + if err != nil && !database.IsEmptyRecord(err) { + return invites, err + } + for _, record := range records { + in := models.UserInvite{} + err = json.Unmarshal([]byte(record), &in) + if err == nil { + invites = append(invites, in) + } + } + return invites, nil +} + +func DeleteUserInvite(email string) error { + return database.DeleteRecord(database.USER_INVITES_TABLE_NAME, email) +} +func ValidateAndApproveUserInvite(email, code string) error { + in, err := GetUserInvite(email) + if err != nil { + return err + } + if code != in.InviteCode { + return errors.New("invalid code") + } + return nil +} diff --git a/models/user_mgmt.go b/models/user_mgmt.go index 1008dfb04..33c29c3a4 100644 --- a/models/user_mgmt.go +++ b/models/user_mgmt.go @@ -87,7 +87,7 @@ type UserRolePermissionTemplate struct { } type UserGroup struct { - ID string `json:"id"` + ID UserGroupID `json:"id"` PlatformRole UserRole `json:"platform_role"` NetworkRoles map[NetworkID]map[UserRole]struct{} `json:"network_roles"` MetaData string `json:"meta_data"` @@ -131,3 +131,15 @@ type UserClaims struct { UserName string jwt.RegisteredClaims } + +type InviteUsersReq struct { + UserEmails []string `json:"user_emails"` + Groups []UserGroupID +} + +// UserInvite - model for user invite +type UserInvite struct { + Email string `json:"email"` + Groups []UserGroupID `json:"groups"` + InviteCode string `json:"invite_code"` +} diff --git a/scripts/netmaker.default.env b/scripts/netmaker.default.env index 4ccb71c2f..e83e06389 100644 --- a/scripts/netmaker.default.env +++ b/scripts/netmaker.default.env @@ -75,3 +75,12 @@ RAC_AUTO_DISABLE=false CACHING_ENABLED=true # if turned on netclient checks if peers are reachable over private/LAN address, and choose that as peer endpoint ENDPOINT_DETECTION=true +# config for sending emails +# mail server host +SMTP_HOST=smtp.gmail.com +# mail server port +SMTP_PORT=587 +# sender email +SENDER_EMAIL= +# sender email password +SENDER_PASSWORD= \ No newline at end of file diff --git a/servercfg/serverconf.go b/servercfg/serverconf.go index 56e00e340..b471ad034 100644 --- a/servercfg/serverconf.go +++ b/servercfg/serverconf.go @@ -241,6 +241,21 @@ func GetPublicBrokerEndpoint() string { } } +func GetSmtpHost() string { + return os.Getenv("SMTP_HOST") +} + +func GetSmtpPort() int { + port, _ := strconv.Atoi(os.Getenv("SMTP_PORT")) + return port +} +func GetSenderEmail() string { + return os.Getenv("SENDER_EMAIL") +} +func GetSenderEmailPassWord() string { + return os.Getenv("SENDER_PASSWORD") +} + // GetOwnerEmail - gets the owner email (saas) func GetOwnerEmail() string { return os.Getenv("SAAS_OWNER_EMAIL") @@ -471,7 +486,7 @@ func GetPublicIP() (string, error) { break } } - if err == nil && endpoint == "" { + if endpoint == "" { err = errors.New("public address not found") } return endpoint, err From 12acada4a131c5306b16b142b1c3074d3b7da2f8 Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Tue, 2 Jul 2024 16:14:22 +0530 Subject: [PATCH 049/139] add invited user via oauth signup automatically --- pro/auth/azure-ad.go | 40 ++++++++++++++++++++++++++++++++-------- pro/auth/github.go | 39 +++++++++++++++++++++++++++++++-------- pro/auth/google.go | 40 ++++++++++++++++++++++++++++++++-------- pro/auth/oidc.go | 39 +++++++++++++++++++++++++++++++-------- 4 files changed, 126 insertions(+), 32 deletions(-) diff --git a/pro/auth/azure-ad.go b/pro/auth/azure-ad.go index 62ecedc22..c53b98e5f 100644 --- a/pro/auth/azure-ad.go +++ b/pro/auth/azure-ad.go @@ -71,23 +71,47 @@ func handleAzureCallback(w http.ResponseWriter, r *http.Request) { handleOauthUserNotAllowedToSignUp(w) return } + var inviteExists bool + // check if invite exists for User + _, err = logic.GetUserInvite(content.UserPrincipalName) + if err == nil { + inviteExists = true + } // check if user approval is already pending - if logic.IsPendingUser(content.UserPrincipalName) { + if !inviteExists && logic.IsPendingUser(content.UserPrincipalName) { handleOauthUserSignUpApprovalPending(w) return } + _, err = logic.GetUser(content.UserPrincipalName) if err != nil { if database.IsEmptyRecord(err) { // user must not exist, so try to make one - err = logic.InsertPendingUser(&models.User{ - UserName: content.UserPrincipalName, - }) - if err != nil { - handleSomethingWentWrong(w) + if inviteExists { + // create user + var newPass, fetchErr = auth.FetchPassValue("") + if fetchErr != nil { + logic.ReturnErrorResponse(w, r, logic.FormatError(fetchErr, "internal")) + return + } + if err = logic.CreateUser(&models.User{ + UserName: content.UserPrincipalName, + Password: newPass, + }); err != nil { + handleSomethingWentWrong(w) + return + } + logic.DeletePendingUser(content.UserPrincipalName) + } else { + err = logic.InsertPendingUser(&models.User{ + UserName: content.UserPrincipalName, + }) + if err != nil { + handleSomethingWentWrong(w) + return + } + handleFirstTimeOauthUserSignUp(w) return } - handleFirstTimeOauthUserSignUp(w) - return } else { handleSomethingWentWrong(w) return diff --git a/pro/auth/github.go b/pro/auth/github.go index 67223de40..a8434b1e1 100644 --- a/pro/auth/github.go +++ b/pro/auth/github.go @@ -71,23 +71,46 @@ func handleGithubCallback(w http.ResponseWriter, r *http.Request) { handleOauthUserNotAllowedToSignUp(w) return } + var inviteExists bool + // check if invite exists for User + _, err = logic.GetUserInvite(content.Login) + if err == nil { + inviteExists = true + } // check if user approval is already pending - if logic.IsPendingUser(content.Login) { + if !inviteExists && logic.IsPendingUser(content.Login) { handleOauthUserSignUpApprovalPending(w) return } _, err = logic.GetUser(content.Login) if err != nil { if database.IsEmptyRecord(err) { // user must not exist, so try to make one - err = logic.InsertPendingUser(&models.User{ - UserName: content.Login, - }) - if err != nil { - handleSomethingWentWrong(w) + if inviteExists { + // create user + var newPass, fetchErr = auth.FetchPassValue("") + if fetchErr != nil { + logic.ReturnErrorResponse(w, r, logic.FormatError(fetchErr, "internal")) + return + } + if err = logic.CreateUser(&models.User{ + UserName: content.Login, + Password: newPass, + }); err != nil { + handleSomethingWentWrong(w) + return + } + logic.DeletePendingUser(content.Login) + } else { + err = logic.InsertPendingUser(&models.User{ + UserName: content.Login, + }) + if err != nil { + handleSomethingWentWrong(w) + return + } + handleFirstTimeOauthUserSignUp(w) return } - handleFirstTimeOauthUserSignUp(w) - return } else { handleSomethingWentWrong(w) return diff --git a/pro/auth/google.go b/pro/auth/google.go index 64d503e73..3d5cfe4b7 100644 --- a/pro/auth/google.go +++ b/pro/auth/google.go @@ -73,23 +73,47 @@ func handleGoogleCallback(w http.ResponseWriter, r *http.Request) { handleOauthUserNotAllowedToSignUp(w) return } + var inviteExists bool + // check if invite exists for User + _, err = logic.GetUserInvite(content.Email) + if err == nil { + inviteExists = true + } // check if user approval is already pending - if logic.IsPendingUser(content.Email) { + if !inviteExists && logic.IsPendingUser(content.Email) { handleOauthUserSignUpApprovalPending(w) return } _, err = logic.GetUser(content.Email) if err != nil { if database.IsEmptyRecord(err) { // user must not exist, so try to make one - err = logic.InsertPendingUser(&models.User{ - UserName: content.Email, - }) - if err != nil { - handleSomethingWentWrong(w) + if inviteExists { + // create user + var newPass, fetchErr = auth.FetchPassValue("") + if fetchErr != nil { + logic.ReturnErrorResponse(w, r, logic.FormatError(fetchErr, "internal")) + return + } + if err = logic.CreateUser(&models.User{ + UserName: content.Email, + Password: newPass, + }); err != nil { + handleSomethingWentWrong(w) + return + } + logic.DeletePendingUser(content.Email) + } else { + err = logic.InsertPendingUser(&models.User{ + UserName: content.Email, + }) + if err != nil { + handleSomethingWentWrong(w) + return + } + handleFirstTimeOauthUserSignUp(w) return } - handleFirstTimeOauthUserSignUp(w) - return + } else { handleSomethingWentWrong(w) return diff --git a/pro/auth/oidc.go b/pro/auth/oidc.go index a2bf5739a..6c0dd498f 100644 --- a/pro/auth/oidc.go +++ b/pro/auth/oidc.go @@ -84,23 +84,46 @@ func handleOIDCCallback(w http.ResponseWriter, r *http.Request) { handleOauthUserNotAllowedToSignUp(w) return } + var inviteExists bool + // check if invite exists for User + _, err = logic.GetUserInvite(content.Login) + if err == nil { + inviteExists = true + } // check if user approval is already pending - if logic.IsPendingUser(content.Email) { + if !inviteExists && logic.IsPendingUser(content.Email) { handleOauthUserSignUpApprovalPending(w) return } _, err = logic.GetUser(content.Email) if err != nil { if database.IsEmptyRecord(err) { // user must not exist, so try to make one - err = logic.InsertPendingUser(&models.User{ - UserName: content.Email, - }) - if err != nil { - handleSomethingWentWrong(w) + if inviteExists { + // create user + var newPass, fetchErr = auth.FetchPassValue("") + if fetchErr != nil { + logic.ReturnErrorResponse(w, r, logic.FormatError(fetchErr, "internal")) + return + } + if err = logic.CreateUser(&models.User{ + UserName: content.Email, + Password: newPass, + }); err != nil { + handleSomethingWentWrong(w) + return + } + logic.DeletePendingUser(content.Email) + } else { + err = logic.InsertPendingUser(&models.User{ + UserName: content.Email, + }) + if err != nil { + handleSomethingWentWrong(w) + return + } + handleFirstTimeOauthUserSignUp(w) return } - handleFirstTimeOauthUserSignUp(w) - return } else { handleSomethingWentWrong(w) return From b486787deb15588afbace9592cc9b93fa9f9d79b Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Thu, 4 Jul 2024 08:51:27 +0530 Subject: [PATCH 050/139] create invited user on oauth signup, with groups in the invite --- logic/users.go | 2 ++ models/user_mgmt.go | 16 ++++++++-------- pro/auth/azure-ad.go | 17 ++++++++++++++--- pro/auth/github.go | 18 +++++++++++++++--- pro/auth/google.go | 17 ++++++++++++++--- pro/auth/oidc.go | 17 ++++++++++++++--- 6 files changed, 67 insertions(+), 20 deletions(-) diff --git a/logic/users.go b/logic/users.go index fc8517501..881878a94 100644 --- a/logic/users.go +++ b/logic/users.go @@ -128,6 +128,8 @@ func InsertUserInvite(invite models.UserInvite) error { return database.Insert(invite.Email, string(data), database.USER_INVITES_TABLE_NAME) } +func ImportGroupsFromInvite() {} + func GetUserInvite(email string) (in models.UserInvite, err error) { d, err := database.FetchRecord(database.USER_INVITES_TABLE_NAME, email) if err != nil { diff --git a/models/user_mgmt.go b/models/user_mgmt.go index 33c29c3a4..3904f7ccc 100644 --- a/models/user_mgmt.go +++ b/models/user_mgmt.go @@ -108,14 +108,14 @@ type User struct { // ReturnUser - return user struct type ReturnUser struct { - UserName string `json:"username"` - IsAdmin bool `json:"isadmin"` - IsSuperAdmin bool `json:"issuperadmin"` - RemoteGwIDs map[string]struct{} `json:"remote_gw_ids"` // deprecated - UserGroups map[UserGroupID]struct{} `json:"user_group_ids"` - PlatformRoleID string `json:"platform_role_id"` - NetworkRoles map[NetworkID]UserRole `json:"network_roles"` - LastLoginTime time.Time `json:"last_login_time"` + UserName string `json:"username"` + IsAdmin bool `json:"isadmin"` + IsSuperAdmin bool `json:"issuperadmin"` + RemoteGwIDs map[string]struct{} `json:"remote_gw_ids"` // deprecated + UserGroups map[UserGroupID]struct{} `json:"user_group_ids"` + PlatformRoleID UserRole `json:"platform_role_id"` + NetworkRoles map[NetworkID]map[UserRole]struct{} `json:"network_roles"` + LastLoginTime time.Time `json:"last_login_time"` } // UserAuthParams - user auth params struct diff --git a/pro/auth/azure-ad.go b/pro/auth/azure-ad.go index c53b98e5f..5a1abb557 100644 --- a/pro/auth/azure-ad.go +++ b/pro/auth/azure-ad.go @@ -3,6 +3,7 @@ package auth import ( "context" "encoding/json" + "errors" "fmt" "io" "net/http" @@ -73,7 +74,7 @@ func handleAzureCallback(w http.ResponseWriter, r *http.Request) { } var inviteExists bool // check if invite exists for User - _, err = logic.GetUserInvite(content.UserPrincipalName) + in, err := logic.GetUserInvite(content.UserPrincipalName) if err == nil { inviteExists = true } @@ -93,10 +94,20 @@ func handleAzureCallback(w http.ResponseWriter, r *http.Request) { logic.ReturnErrorResponse(w, r, logic.FormatError(fetchErr, "internal")) return } - if err = logic.CreateUser(&models.User{ + user := &models.User{ UserName: content.UserPrincipalName, Password: newPass, - }); err != nil { + } + for _, inviteGroupID := range in.Groups { + userG, err := logic.GetUserGroup(inviteGroupID) + if err != nil { + logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("error fetching group id "+inviteGroupID.String()), "badrequest")) + return + } + user.PlatformRoleID = userG.PlatformRole + user.UserGroups[inviteGroupID] = struct{}{} + } + if err = logic.CreateUser(user); err != nil { handleSomethingWentWrong(w) return } diff --git a/pro/auth/github.go b/pro/auth/github.go index a8434b1e1..ee6db2997 100644 --- a/pro/auth/github.go +++ b/pro/auth/github.go @@ -3,6 +3,7 @@ package auth import ( "context" "encoding/json" + "errors" "fmt" "io" "net/http" @@ -73,7 +74,7 @@ func handleGithubCallback(w http.ResponseWriter, r *http.Request) { } var inviteExists bool // check if invite exists for User - _, err = logic.GetUserInvite(content.Login) + in, err := logic.GetUserInvite(content.Login) if err == nil { inviteExists = true } @@ -92,10 +93,21 @@ func handleGithubCallback(w http.ResponseWriter, r *http.Request) { logic.ReturnErrorResponse(w, r, logic.FormatError(fetchErr, "internal")) return } - if err = logic.CreateUser(&models.User{ + user := &models.User{ UserName: content.Login, Password: newPass, - }); err != nil { + } + + for _, inviteGroupID := range in.Groups { + userG, err := logic.GetUserGroup(inviteGroupID) + if err != nil { + logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("error fetching group id "+inviteGroupID.String()), "badrequest")) + return + } + user.PlatformRoleID = userG.PlatformRole + user.UserGroups[inviteGroupID] = struct{}{} + } + if err = logic.CreateUser(user); err != nil { handleSomethingWentWrong(w) return } diff --git a/pro/auth/google.go b/pro/auth/google.go index 3d5cfe4b7..cc84539df 100644 --- a/pro/auth/google.go +++ b/pro/auth/google.go @@ -3,6 +3,7 @@ package auth import ( "context" "encoding/json" + "errors" "fmt" "io" "net/http" @@ -75,7 +76,7 @@ func handleGoogleCallback(w http.ResponseWriter, r *http.Request) { } var inviteExists bool // check if invite exists for User - _, err = logic.GetUserInvite(content.Email) + in, err := logic.GetUserInvite(content.Email) if err == nil { inviteExists = true } @@ -94,10 +95,20 @@ func handleGoogleCallback(w http.ResponseWriter, r *http.Request) { logic.ReturnErrorResponse(w, r, logic.FormatError(fetchErr, "internal")) return } - if err = logic.CreateUser(&models.User{ + user := &models.User{ UserName: content.Email, Password: newPass, - }); err != nil { + } + for _, inviteGroupID := range in.Groups { + userG, err := logic.GetUserGroup(inviteGroupID) + if err != nil { + logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("error fetching group id "+inviteGroupID.String()), "badrequest")) + return + } + user.PlatformRoleID = userG.PlatformRole + user.UserGroups[inviteGroupID] = struct{}{} + } + if err = logic.CreateUser(user); err != nil { handleSomethingWentWrong(w) return } diff --git a/pro/auth/oidc.go b/pro/auth/oidc.go index 6c0dd498f..e0c42d164 100644 --- a/pro/auth/oidc.go +++ b/pro/auth/oidc.go @@ -2,6 +2,7 @@ package auth import ( "context" + "errors" "fmt" "net/http" "strings" @@ -86,7 +87,7 @@ func handleOIDCCallback(w http.ResponseWriter, r *http.Request) { } var inviteExists bool // check if invite exists for User - _, err = logic.GetUserInvite(content.Login) + in, err := logic.GetUserInvite(content.Login) if err == nil { inviteExists = true } @@ -105,10 +106,20 @@ func handleOIDCCallback(w http.ResponseWriter, r *http.Request) { logic.ReturnErrorResponse(w, r, logic.FormatError(fetchErr, "internal")) return } - if err = logic.CreateUser(&models.User{ + user := &models.User{ UserName: content.Email, Password: newPass, - }); err != nil { + } + for _, inviteGroupID := range in.Groups { + userG, err := logic.GetUserGroup(inviteGroupID) + if err != nil { + logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("error fetching group id "+inviteGroupID.String()), "badrequest")) + return + } + user.PlatformRoleID = userG.PlatformRole + user.UserGroups[inviteGroupID] = struct{}{} + } + if err = logic.CreateUser(user); err != nil { handleSomethingWentWrong(w) return } From b75c0e89ee9773d3560ebe8566195b9999cacd9e Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Thu, 4 Jul 2024 08:57:01 +0530 Subject: [PATCH 051/139] add group validation for user invite --- controllers/user.go | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/controllers/user.go b/controllers/user.go index a59dbea45..2f35b07d3 100644 --- a/controllers/user.go +++ b/controllers/user.go @@ -700,6 +700,8 @@ func createUser(w http.ResponseWriter, r *http.Request) { logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) return } + logic.DeleteUserInvite(user.UserName) + logic.DeletePendingUser(user.UserName) slog.Info("user was created", "username", user.UserName) json.NewEncoder(w).Encode(logic.ToReturnUser(user)) } @@ -1147,6 +1149,21 @@ func inviteUsers(w http.ResponseWriter, r *http.Request) { logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) return } + //validate Req + uniqueGroupsPlatformRole := make(map[models.UserRole]struct{}) + for _, groupID := range inviteReq.Groups { + userG, err := logic.GetUserGroup(groupID) + if err != nil { + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) + return + } + uniqueGroupsPlatformRole[userG.PlatformRole] = struct{}{} + } + if len(uniqueGroupsPlatformRole) > 1 { + err = errors.New("only groups with same platform role can be assigned to an user") + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) + return + } for _, inviteeEmail := range inviteReq.UserEmails { // check if user with email exists, then ignore _, err := logic.GetUser(inviteeEmail) From adec4c491a589e4b0e060485ecf6d2ec6ffa8372 Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Thu, 4 Jul 2024 11:00:53 +0530 Subject: [PATCH 052/139] update create user handler with new role mgmt --- controllers/user.go | 28 ++++++++++++++++++++++------ logic/auth.go | 1 + 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/controllers/user.go b/controllers/user.go index 2f35b07d3..cb0aae798 100644 --- a/controllers/user.go +++ b/controllers/user.go @@ -652,7 +652,12 @@ func createUser(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") caller, err := logic.GetUser(r.Header.Get("user")) if err != nil { - logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) + return + } + callerUserRole, err := logic.GetRole(caller.PlatformRoleID) + if err != nil { + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) return } var user models.User @@ -677,23 +682,34 @@ func createUser(w http.ResponseWriter, r *http.Request) { logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) return } - if !caller.IsSuperAdmin && user.IsAdmin { - err = errors.New("only superadmin can create admin users") + userRole, err := logic.GetRole(user.PlatformRoleID) + if err != nil { + err = errors.New("error fetching role " + user.PlatformRoleID.String() + " " + err.Error()) slog.Error("error creating new user: ", "user", user.UserName, "error", err) - logic.ReturnErrorResponse(w, r, logic.FormatError(err, "forbidden")) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) return } - if user.IsSuperAdmin { + if userRole.ID == models.SuperAdminRole { err = errors.New("additional superadmins cannot be created") slog.Error("error creating new user: ", "user", user.UserName, "error", err) logic.ReturnErrorResponse(w, r, logic.FormatError(err, "forbidden")) return } + + if callerUserRole.ID != models.SuperAdminRole && user.IsAdmin { + err = errors.New("only superadmin can create admin users") + slog.Error("error creating new user: ", "user", user.UserName, "error", err) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "forbidden")) + return + } + if !servercfg.IsPro && !user.IsAdmin { logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("non-admins users can only be created on Pro version"), "forbidden")) return } - + if userRole.ID == models.AdminRole { + user.IsAdmin = true + } err = logic.CreateUser(&user) if err != nil { slog.Error("error creating new user: ", "user", user.UserName, "error", err.Error()) diff --git a/logic/auth.go b/logic/auth.go index e2a660db7..2b18fba10 100644 --- a/logic/auth.go +++ b/logic/auth.go @@ -146,6 +146,7 @@ func CreateSuperAdmin(u *models.User) error { if hassuperadmin { return errors.New("superadmin user already exists") } + u.PlatformRoleID = models.SuperAdminRole u.IsSuperAdmin = true u.IsAdmin = false return CreateUser(u) From e9e0a9136db72582fe5b68d01b3548779d1d5453 Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Fri, 5 Jul 2024 17:22:11 +0530 Subject: [PATCH 053/139] add validation checks --- controllers/user.go | 11 +++++++++++ logic/user_mgmt.go | 31 +++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/controllers/user.go b/controllers/user.go index cb0aae798..cd06bc511 100644 --- a/controllers/user.go +++ b/controllers/user.go @@ -133,6 +133,11 @@ func createUserGroup(w http.ResponseWriter, r *http.Request) { logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) return } + err = logic.ValidateCreateGroupReq(userGroup) + if err != nil { + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) + return + } err = logic.CreateUserGroup(userGroup) if err != nil { logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) @@ -161,6 +166,11 @@ func updateUserGroup(w http.ResponseWriter, r *http.Request) { logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) return } + err = logic.ValidateUpdateGroupReq(userGroup) + if err != nil { + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) + return + } err = logic.UpdateUserGroup(userGroup) if err != nil { logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) @@ -1180,6 +1190,7 @@ func inviteUsers(w http.ResponseWriter, r *http.Request) { logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) return } + for _, inviteeEmail := range inviteReq.UserEmails { // check if user with email exists, then ignore _, err := logic.GetUser(inviteeEmail) diff --git a/logic/user_mgmt.go b/logic/user_mgmt.go index da990101a..3f5bbe5ad 100644 --- a/logic/user_mgmt.go +++ b/logic/user_mgmt.go @@ -212,6 +212,37 @@ func DeleteRole(rid models.UserRole) error { return database.DeleteRecord(database.USER_PERMISSIONS_TABLE_NAME, rid.String()) } +func ValidateCreateGroupReq(g models.UserGroup) error { + // check platform role is valid + _, err := GetRole(g.PlatformRole) + if err != nil { + err = fmt.Errorf("invalid platform role") + return err + } + // check if network roles are valid + + return nil +} +func ValidateUpdateGroupReq(g models.UserGroup) error { + // check platform role is valid + _, err := GetRole(g.PlatformRole) + if err != nil { + err = fmt.Errorf("invalid platform role") + return err + } + for networkID := range g.NetworkRoles { + userRolesMap := g.NetworkRoles[networkID] + for roleID := range userRolesMap { + _, err := GetRole(roleID) + if err != nil { + err = fmt.Errorf("invalid network role") + return err + } + } + } + return nil +} + // CreateUserGroup - creates new user group func CreateUserGroup(g models.UserGroup) error { // check if role already exists From 0dd6a237d377dd7b88383d66daee555b1912740b Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Sat, 6 Jul 2024 19:55:41 +0530 Subject: [PATCH 054/139] create user invites tables --- database/database.go | 1 + 1 file changed, 1 insertion(+) diff --git a/database/database.go b/database/database.go index 213263b9f..f8508b3f0 100644 --- a/database/database.go +++ b/database/database.go @@ -151,6 +151,7 @@ func createTables() { CreateTable(HOST_ACTIONS_TABLE_NAME) CreateTable(PENDING_USERS_TABLE_NAME) CreateTable(USER_PERMISSIONS_TABLE_NAME) + CreateTable(USER_INVITES_TABLE_NAME) } func CreateTable(tableName string) error { From ab1abb5e9229bd8f0d9a87966070cb7f588c9757 Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Sat, 6 Jul 2024 19:58:06 +0530 Subject: [PATCH 055/139] add error logging for email invite --- controllers/user.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/controllers/user.go b/controllers/user.go index cd06bc511..93b933d58 100644 --- a/controllers/user.go +++ b/controllers/user.go @@ -1208,7 +1208,12 @@ func inviteUsers(w http.ResponseWriter, r *http.Request) { slog.Error("failed to insert invite for user", "email", invite.Email, "error", err) } // notify user with magic link - go logic.SendInviteEmail(invite) + go func(invite models.UserInvite) { + err = logic.SendInviteEmail(invite) + if err != nil { + slog.Error("failed to send email invite", "user", invite.Email, "error", err) + } + }(invite) } } From d34396544e00a236b56905aeba38ab630df69bed Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Sat, 6 Jul 2024 22:49:02 +0530 Subject: [PATCH 056/139] fix invite singup url --- logic/notification.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/logic/notification.go b/logic/notification.go index 9abed58c9..6497353b2 100644 --- a/logic/notification.go +++ b/logic/notification.go @@ -30,7 +30,8 @@ func SendInviteEmail(invite models.UserInvite) error { m.SetHeader("Subject", "Netmaker Invite") // Set E-Mail body. You can set plain text or html with text/html - m.SetBody("text/html", "Click Here to Signup! "+fmt.Sprintf("https://api.%s/invitesignup?code=%v", servercfg.GetServer(), invite.InviteCode)) + m.SetBody("text/html", "Click Here to Signup! "+fmt.Sprintf("https://api.%s/api/v1/users/invite?email=%s&code=%v", + servercfg.GetServer(), invite.Email, invite.InviteCode)) // Settings for SMTP server d := gomail.NewDialer(smtpHost, smtpPort, senderEmail, senderPassword) From 6abeab5b679e7c0aa5552a8c91a0fa18fc3c9fd6 Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Sat, 6 Jul 2024 23:12:55 +0530 Subject: [PATCH 057/139] debug log --- controllers/user.go | 1 + 1 file changed, 1 insertion(+) diff --git a/controllers/user.go b/controllers/user.go index 93b933d58..b93475c84 100644 --- a/controllers/user.go +++ b/controllers/user.go @@ -1146,6 +1146,7 @@ func userInviteVerify(w http.ResponseWriter, r *http.Request) { var params = mux.Vars(r) email := params["email"] code := params["code"] + logger.Log(0, "EMAIL", email, "CODE", code) err := logic.ValidateAndApproveUserInvite(email, code) if err != nil { logger.Log(0, "failed to fetch users: ", err.Error()) From 4dd7e14c4ee2568bb0b7d5dfb6a96f7f53e89730 Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Sat, 6 Jul 2024 23:25:11 +0530 Subject: [PATCH 058/139] get query params from url --- controllers/user.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/controllers/user.go b/controllers/user.go index b93475c84..1c137a2fe 100644 --- a/controllers/user.go +++ b/controllers/user.go @@ -1143,9 +1143,8 @@ func userInviteSignUp(w http.ResponseWriter, r *http.Request) { // Responses: // 200: ReturnSuccessResponse func userInviteVerify(w http.ResponseWriter, r *http.Request) { - var params = mux.Vars(r) - email := params["email"] - code := params["code"] + email := r.URL.Query().Get("email") + code := r.URL.Query().Get("code") logger.Log(0, "EMAIL", email, "CODE", code) err := logic.ValidateAndApproveUserInvite(email, code) if err != nil { From 4d81d8ec1fb557c0da988415fbf0c9147ab87bee Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Sat, 6 Jul 2024 23:43:41 +0530 Subject: [PATCH 059/139] get query params from url --- controllers/user.go | 6 ++++-- logic/notification.go | 7 ++++++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/controllers/user.go b/controllers/user.go index 1c137a2fe..2a5c0b7b8 100644 --- a/controllers/user.go +++ b/controllers/user.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "net/http" + "net/url" "github.com/gorilla/mux" "github.com/gorilla/websocket" @@ -1143,8 +1144,9 @@ func userInviteSignUp(w http.ResponseWriter, r *http.Request) { // Responses: // 200: ReturnSuccessResponse func userInviteVerify(w http.ResponseWriter, r *http.Request) { - email := r.URL.Query().Get("email") - code := r.URL.Query().Get("code") + params, _ := url.ParseQuery(r.URL.String()) + email := params.Get("email") + code := params.Get("code") logger.Log(0, "EMAIL", email, "CODE", code) err := logic.ValidateAndApproveUserInvite(email, code) if err != nil { diff --git a/logic/notification.go b/logic/notification.go index 6497353b2..521c68478 100644 --- a/logic/notification.go +++ b/logic/notification.go @@ -3,6 +3,7 @@ package logic import ( "crypto/tls" "fmt" + "net/url" gomail "gopkg.in/mail.v2" @@ -30,8 +31,12 @@ func SendInviteEmail(invite models.UserInvite) error { m.SetHeader("Subject", "Netmaker Invite") // Set E-Mail body. You can set plain text or html with text/html - m.SetBody("text/html", "Click Here to Signup! "+fmt.Sprintf("https://api.%s/api/v1/users/invite?email=%s&code=%v", + u, err := url.Parse(fmt.Sprintf("https://api.%s/api/v1/users/invite?email=%s&code=%s", servercfg.GetServer(), invite.Email, invite.InviteCode)) + if err != nil { + return err + } + m.SetBody("text/html", "Click Here to Signup! "+u.String()) // Settings for SMTP server d := gomail.NewDialer(smtpHost, smtpPort, senderEmail, senderPassword) From faae8429f314b642aad6c5498423e1c837d82c73 Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Sun, 7 Jul 2024 00:18:13 +0530 Subject: [PATCH 060/139] add query escape --- controllers/user.go | 5 ++--- logic/notification.go | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/controllers/user.go b/controllers/user.go index 2a5c0b7b8..43309fc1b 100644 --- a/controllers/user.go +++ b/controllers/user.go @@ -1144,9 +1144,8 @@ func userInviteSignUp(w http.ResponseWriter, r *http.Request) { // Responses: // 200: ReturnSuccessResponse func userInviteVerify(w http.ResponseWriter, r *http.Request) { - params, _ := url.ParseQuery(r.URL.String()) - email := params.Get("email") - code := params.Get("code") + email, _ := url.QueryUnescape(r.URL.Query().Get("email")) + code, _ := url.QueryUnescape(r.URL.Query().Get("code")) logger.Log(0, "EMAIL", email, "CODE", code) err := logic.ValidateAndApproveUserInvite(email, code) if err != nil { diff --git a/logic/notification.go b/logic/notification.go index 521c68478..95da36cf6 100644 --- a/logic/notification.go +++ b/logic/notification.go @@ -32,7 +32,7 @@ func SendInviteEmail(invite models.UserInvite) error { // Set E-Mail body. You can set plain text or html with text/html u, err := url.Parse(fmt.Sprintf("https://api.%s/api/v1/users/invite?email=%s&code=%s", - servercfg.GetServer(), invite.Email, invite.InviteCode)) + servercfg.GetServer(), url.QueryEscape(invite.Email), url.QueryEscape(invite.InviteCode))) if err != nil { return err } From 018db7618f0bb1548e4402a5c8644d612fb0dd90 Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Sun, 7 Jul 2024 00:26:04 +0530 Subject: [PATCH 061/139] debug log --- logic/users.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/logic/users.go b/logic/users.go index 881878a94..8f4cfb782 100644 --- a/logic/users.go +++ b/logic/users.go @@ -3,9 +3,11 @@ package logic import ( "encoding/json" "errors" + "fmt" "sort" "github.com/gravitl/netmaker/database" + "github.com/gravitl/netmaker/logger" "github.com/gravitl/netmaker/models" ) @@ -163,6 +165,7 @@ func ValidateAndApproveUserInvite(email, code string) error { if err != nil { return err } + logger.Log(0, "CODE: ", code, fmt.Sprintf("%d", len(code)), " DB-CODE: ", in.InviteCode, fmt.Sprintf("%d", len(in.InviteCode))) if code != in.InviteCode { return errors.New("invalid code") } From 1d2e59157b3195d8e6fa7165f6b7f5dc71bc7336 Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Sun, 7 Jul 2024 00:30:50 +0530 Subject: [PATCH 062/139] debug log --- logic/users.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/logic/users.go b/logic/users.go index 8f4cfb782..881878a94 100644 --- a/logic/users.go +++ b/logic/users.go @@ -3,11 +3,9 @@ package logic import ( "encoding/json" "errors" - "fmt" "sort" "github.com/gravitl/netmaker/database" - "github.com/gravitl/netmaker/logger" "github.com/gravitl/netmaker/models" ) @@ -165,7 +163,6 @@ func ValidateAndApproveUserInvite(email, code string) error { if err != nil { return err } - logger.Log(0, "CODE: ", code, fmt.Sprintf("%d", len(code)), " DB-CODE: ", in.InviteCode, fmt.Sprintf("%d", len(in.InviteCode))) if code != in.InviteCode { return errors.New("invalid code") } From 4996122090bdf8cc5d5b6560885fccb863666276 Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Sun, 7 Jul 2024 10:30:19 +0530 Subject: [PATCH 063/139] fix user signup via invite api --- controllers/user.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/controllers/user.go b/controllers/user.go index 43309fc1b..a68ea83fa 100644 --- a/controllers/user.go +++ b/controllers/user.go @@ -1078,9 +1078,8 @@ func deleteAllPendingUsers(w http.ResponseWriter, r *http.Request) { // Responses: // 200: ReturnSuccessResponse func userInviteSignUp(w http.ResponseWriter, r *http.Request) { - var params = mux.Vars(r) - email := params["email"] - code := params["code"] + email, _ := url.QueryUnescape(r.URL.Query().Get("email")) + code, _ := url.QueryUnescape(r.URL.Query().Get("code")) in, err := logic.GetUserInvite(email) if err != nil { logger.Log(0, "failed to fetch users: ", err.Error()) From 34bcff2b1d68ab6f32e8ae36b939fdeb42682eff Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Sun, 7 Jul 2024 10:49:01 +0530 Subject: [PATCH 064/139] set admin field for backward compatbility --- controllers/user.go | 4 ++++ pro/auth/azure-ad.go | 3 +++ pro/auth/github.go | 3 +++ pro/auth/google.go | 3 +++ pro/auth/oidc.go | 3 +++ 5 files changed, 16 insertions(+) diff --git a/controllers/user.go b/controllers/user.go index a68ea83fa..ec4c9b7be 100644 --- a/controllers/user.go +++ b/controllers/user.go @@ -1112,6 +1112,7 @@ func userInviteSignUp(w http.ResponseWriter, r *http.Request) { logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("password cannot be empty"), "badrequest")) return } + for _, inviteGroupID := range in.Groups { userG, err := logic.GetUserGroup(inviteGroupID) if err != nil { @@ -1121,6 +1122,9 @@ func userInviteSignUp(w http.ResponseWriter, r *http.Request) { user.PlatformRoleID = userG.PlatformRole user.UserGroups[inviteGroupID] = struct{}{} } + if user.PlatformRoleID == models.AdminRole { + user.IsAdmin = true + } user.NetworkRoles = make(map[models.NetworkID]map[models.UserRole]struct{}) user.IsSuperAdmin = false err = logic.CreateUser(&user) diff --git a/pro/auth/azure-ad.go b/pro/auth/azure-ad.go index 5a1abb557..64c704b93 100644 --- a/pro/auth/azure-ad.go +++ b/pro/auth/azure-ad.go @@ -107,6 +107,9 @@ func handleAzureCallback(w http.ResponseWriter, r *http.Request) { user.PlatformRoleID = userG.PlatformRole user.UserGroups[inviteGroupID] = struct{}{} } + if user.PlatformRoleID == models.AdminRole { + user.IsAdmin = true + } if err = logic.CreateUser(user); err != nil { handleSomethingWentWrong(w) return diff --git a/pro/auth/github.go b/pro/auth/github.go index ee6db2997..21abd7ea5 100644 --- a/pro/auth/github.go +++ b/pro/auth/github.go @@ -107,6 +107,9 @@ func handleGithubCallback(w http.ResponseWriter, r *http.Request) { user.PlatformRoleID = userG.PlatformRole user.UserGroups[inviteGroupID] = struct{}{} } + if user.PlatformRoleID == models.AdminRole { + user.IsAdmin = true + } if err = logic.CreateUser(user); err != nil { handleSomethingWentWrong(w) return diff --git a/pro/auth/google.go b/pro/auth/google.go index cc84539df..cf127d20c 100644 --- a/pro/auth/google.go +++ b/pro/auth/google.go @@ -108,6 +108,9 @@ func handleGoogleCallback(w http.ResponseWriter, r *http.Request) { user.PlatformRoleID = userG.PlatformRole user.UserGroups[inviteGroupID] = struct{}{} } + if user.PlatformRoleID == models.AdminRole { + user.IsAdmin = true + } if err = logic.CreateUser(user); err != nil { handleSomethingWentWrong(w) return diff --git a/pro/auth/oidc.go b/pro/auth/oidc.go index e0c42d164..a41b027e9 100644 --- a/pro/auth/oidc.go +++ b/pro/auth/oidc.go @@ -119,6 +119,9 @@ func handleOIDCCallback(w http.ResponseWriter, r *http.Request) { user.PlatformRoleID = userG.PlatformRole user.UserGroups[inviteGroupID] = struct{}{} } + if user.PlatformRoleID == models.AdminRole { + user.IsAdmin = true + } if err = logic.CreateUser(user); err != nil { handleSomethingWentWrong(w) return From 78da9fa9010ba568d6d6c84ef856e559571d01e6 Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Sun, 7 Jul 2024 13:19:46 +0530 Subject: [PATCH 065/139] use new role id for user apis --- controllers/user.go | 30 +++++++++++++++++++++--------- logic/auth.go | 9 +++++++-- 2 files changed, 28 insertions(+), 11 deletions(-) diff --git a/controllers/user.go b/controllers/user.go index ec4c9b7be..f07ddb597 100644 --- a/controllers/user.go +++ b/controllers/user.go @@ -786,22 +786,22 @@ func updateUser(w http.ResponseWriter, r *http.Request) { } if !ismaster && !selfUpdate { - if caller.IsAdmin && user.IsSuperAdmin { + if caller.PlatformRoleID == models.AdminRole && user.PlatformRoleID == models.SuperAdminRole { slog.Error("non-superadmin user", "caller", caller.UserName, "attempted to update superadmin user", username) logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("cannot update superadmin user"), "forbidden")) return } - if !caller.IsAdmin && !caller.IsSuperAdmin { + if caller.PlatformRoleID != models.AdminRole && caller.PlatformRoleID != models.SuperAdminRole { slog.Error("operation not allowed", "caller", caller.UserName, "attempted to update user", username) logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("cannot update superadmin user"), "forbidden")) return } - if caller.IsAdmin && user.IsAdmin { + if caller.PlatformRoleID == models.AdminRole && user.PlatformRoleID == models.AdminRole { slog.Error("admin user cannot update another admin", "caller", caller.UserName, "attempted to update admin user", username) logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("admin user cannot update another admin"), "forbidden")) return } - if caller.IsAdmin && userchange.IsAdmin { + if caller.PlatformRoleID == models.AdminRole && userchange.PlatformRoleID == models.AdminRole { err = errors.New("admin user cannot update role of an another user to admin") slog.Error("failed to update user", "caller", caller.UserName, "attempted to update user", username, "error", err) logic.ReturnErrorResponse(w, r, logic.FormatError(err, "forbidden")) @@ -810,7 +810,7 @@ func updateUser(w http.ResponseWriter, r *http.Request) { } if !ismaster && selfUpdate { - if user.IsAdmin != userchange.IsAdmin || user.IsSuperAdmin != userchange.IsSuperAdmin { + if user.PlatformRoleID != userchange.PlatformRoleID { slog.Error("user cannot change his own role", "caller", caller.UserName, "attempted to update user role", username) logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("user not allowed to self assign role"), "forbidden")) return @@ -818,7 +818,7 @@ func updateUser(w http.ResponseWriter, r *http.Request) { } } if ismaster { - if !user.IsSuperAdmin && userchange.IsSuperAdmin { + if user.PlatformRoleID != models.SuperAdminRole && userchange.PlatformRoleID == models.SuperAdminRole { slog.Error("operation not allowed", "caller", logic.MasterUser, "attempted to update user role to superadmin", username) logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("attempted to update user role to superadmin"), "forbidden")) return @@ -863,6 +863,12 @@ func deleteUser(w http.ResponseWriter, r *http.Request) { if err != nil { logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) } + callerUserRole, err := logic.GetRole(caller.PlatformRoleID) + if err != nil { + slog.Error("failed to get role ", "role", callerUserRole.ID, "error", err) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) + return + } username := params["username"] user, err := logic.GetUser(username) if err != nil { @@ -871,14 +877,20 @@ func deleteUser(w http.ResponseWriter, r *http.Request) { logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } - if user.IsSuperAdmin { + userRole, err := logic.GetRole(user.PlatformRoleID) + if err != nil { + slog.Error("failed to get role ", "role", userRole.ID, "error", err) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) + return + } + if userRole.ID == models.SuperAdminRole { slog.Error( "failed to delete user: ", "user", username, "error", "superadmin cannot be deleted") logic.ReturnErrorResponse(w, r, logic.FormatError(fmt.Errorf("superadmin cannot be deleted"), "internal")) return } - if !caller.IsSuperAdmin { - if caller.IsAdmin && user.IsAdmin { + if callerUserRole.ID != models.SuperAdminRole { + if callerUserRole.ID == models.AdminRole && userRole.ID == models.AdminRole { slog.Error( "failed to delete user: ", "user", username, "error", "admin cannot delete another admin user, including oneself") logic.ReturnErrorResponse(w, r, logic.FormatError(fmt.Errorf("admin cannot delete another admin user, including oneself"), "internal")) diff --git a/logic/auth.go b/logic/auth.go index 2b18fba10..8ec207aa0 100644 --- a/logic/auth.go +++ b/logic/auth.go @@ -235,7 +235,7 @@ func UpdateUser(userchange, user *models.User) (*models.User, error) { user.Password = userchange.Password } - user.IsAdmin = userchange.IsAdmin + user.PlatformRoleID = userchange.PlatformRoleID err := ValidateUser(user) if err != nil { @@ -259,12 +259,17 @@ func UpdateUser(userchange, user *models.User) (*models.User, error) { // ValidateUser - validates a user model func ValidateUser(user *models.User) error { + // check if role is valid + _, err := GetRole(user.PlatformRoleID) + if err != nil { + return err + } v := validator.New() _ = v.RegisterValidation("in_charset", func(fl validator.FieldLevel) bool { isgood := user.NameInCharSet() return isgood }) - err := v.Struct(user) + err = v.Struct(user) if err != nil { for _, e := range err.(validator.ValidationErrors) { From db3deba181ebfd6ba8bd7e7873b5f8d7f29ea627 Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Sun, 7 Jul 2024 13:53:22 +0530 Subject: [PATCH 066/139] deprecate use of old admin fields --- auth/auth.go | 52 ++--------------------------------- auth/host_session.go | 38 ++++++++++++------------- controllers/ext_client.go | 2 +- controllers/user.go | 23 ++++------------ logic/auth.go | 6 ++-- logic/gateway.go | 2 +- logic/jwts.go | 10 +++---- logic/users.go | 10 ++++--- migrate/migrate.go | 6 ++-- models/user_mgmt.go | 5 ++-- pro/auth/azure-ad.go | 10 ++++--- pro/auth/github.go | 10 ++++--- pro/auth/google.go | 10 ++++--- pro/auth/oidc.go | 10 ++++--- pro/auth/register_callback.go | 3 +- pro/controllers/users.go | 6 ++-- 16 files changed, 75 insertions(+), 128 deletions(-) diff --git a/auth/auth.go b/auth/auth.go index d6ce8b4cd..0f89fdc19 100644 --- a/auth/auth.go +++ b/auth/auth.go @@ -3,13 +3,11 @@ package auth import ( "encoding/base64" "encoding/json" - "fmt" "github.com/gravitl/netmaker/logger" "github.com/gravitl/netmaker/logic" "github.com/gravitl/netmaker/models" "golang.org/x/crypto/bcrypt" - "golang.org/x/exp/slog" "golang.org/x/oauth2" ) @@ -55,55 +53,11 @@ func FetchPassValue(newValue string) (string, error) { return string(b64CurrentValue), nil } -// == private == - -func addUser(email string) error { - var hasSuperAdmin, err = logic.HasSuperAdmin() - if err != nil { - slog.Error("error checking for existence of admin user during OAuth login for", "email", email, "error", err) - return err - } // generate random password to adapt to current model - var newPass, fetchErr = FetchPassValue("") - if fetchErr != nil { - slog.Error("failed to get password", "error", fetchErr.Error()) - return fetchErr - } - var newUser = models.User{ - UserName: email, - Password: newPass, - } - if !hasSuperAdmin { // must be first attempt, create a superadmin - logger.Log(0, "creating superadmin") - if err = logic.CreateSuperAdmin(&newUser); err != nil { - slog.Error("error creating super admin from user", "email", email, "error", err) - } else { - slog.Info("superadmin created from user", "email", email) - } - } else { // otherwise add to db as admin..? - // TODO: add ability to add users with preemptive permissions - newUser.IsAdmin = false - if err = logic.CreateUser(&newUser); err != nil { - logger.Log(0, "error creating user,", email, "; user not added", "error", err.Error()) - } else { - logger.Log(0, "user created from ", email) - } - } - return nil -} - -func isUserIsAllowed(username, network string, shouldAddUser bool) (*models.User, error) { +func isUserIsAllowed(username, network string) (*models.User, error) { user, err := logic.GetUser(username) - if err != nil && shouldAddUser { // user must not exist, so try to make one - if err = addUser(username); err != nil { - logger.Log(0, "failed to add user", username, "during a node SSO network join on network", network) - // response := returnErrTemplate(user.UserName, "failed to add user", state, reqKeyIf) - // w.WriteHeader(http.StatusInternalServerError) - // w.Write(response) - return nil, fmt.Errorf("failed to add user to system") - } - logger.Log(0, "user", username, "was added during a node SSO network join on network", network) - user, _ = logic.GetUser(username) + if err != nil { // user must not exist, so try to make one + return &models.User{}, err } return user, nil diff --git a/auth/host_session.go b/auth/host_session.go index 0113351a3..3ef4d137f 100644 --- a/auth/host_session.go +++ b/auth/host_session.go @@ -85,24 +85,24 @@ func SessionHandler(conn *websocket.Conn) { return } req.Pass = req.Host.ID.String() - user, err := logic.GetUser(req.User) - if err != nil { - logger.Log(0, "failed to get user", req.User, "from database") - err = conn.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, "")) - if err != nil { - logger.Log(0, "error during message writing:", err.Error()) - } - return - } - if !user.IsAdmin && !user.IsSuperAdmin { - logger.Log(0, "user", req.User, "is neither an admin or superadmin. denying registeration") - conn.WriteMessage(messageType, []byte("cannot register with a non-admin or non-superadmin")) - err = conn.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, "")) - if err != nil { - logger.Log(0, "error during message writing:", err.Error()) - } - return - } + // user, err := logic.GetUser(req.User) + // if err != nil { + // logger.Log(0, "failed to get user", req.User, "from database") + // err = conn.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, "")) + // if err != nil { + // logger.Log(0, "error during message writing:", err.Error()) + // } + // return + // } + // if !user.IsAdmin && !user.IsSuperAdmin { + // logger.Log(0, "user", req.User, "is neither an admin or superadmin. denying registeration") + // conn.WriteMessage(messageType, []byte("cannot register with a non-admin or non-superadmin")) + // err = conn.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, "")) + // if err != nil { + // logger.Log(0, "error during message writing:", err.Error()) + // } + // return + // } if err = netcache.Set(stateStr, req); err != nil { // give the user's host access in the DB logger.Log(0, "machine failed to complete join on network,", registerMessage.Network, "-", err.Error()) @@ -201,7 +201,7 @@ func SessionHandler(conn *websocket.Conn) { for _, newNet := range currentNetworks { if !logic.StringSliceContains(hostNets, newNet) { if len(result.User) > 0 { - _, err := isUserIsAllowed(result.User, newNet, false) + _, err := isUserIsAllowed(result.User, newNet) if err != nil { logger.Log(0, "unauthorized user", result.User, "attempted to register to network", newNet) handleHostRegErr(conn, err) diff --git a/controllers/ext_client.go b/controllers/ext_client.go index eb5308bba..0f937aa24 100644 --- a/controllers/ext_client.go +++ b/controllers/ext_client.go @@ -403,7 +403,7 @@ func createExtClient(w http.ResponseWriter, r *http.Request) { return } userName = caller.UserName - if _, ok := caller.RemoteGwIDs[nodeid]; (!caller.IsAdmin && !caller.IsSuperAdmin) && !ok { + if _, ok := caller.RemoteGwIDs[nodeid]; (caller.PlatformRoleID != models.AdminRole && caller.PlatformRoleID != models.SuperAdminRole) && !ok { err = errors.New("permission denied") slog.Error("failed to create extclient", "error", err) logic.ReturnErrorResponse(w, r, logic.FormatError(err, "forbidden")) diff --git a/controllers/user.go b/controllers/user.go index f07ddb597..20f740b51 100644 --- a/controllers/user.go +++ b/controllers/user.go @@ -619,7 +619,7 @@ func transferSuperAdmin(w http.ResponseWriter, r *http.Request) { logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) return } - if !u.IsAdmin { + if u.PlatformRoleID != models.AdminRole { logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("only admins can be promoted to superadmin role"), "forbidden")) return } @@ -628,16 +628,14 @@ func transferSuperAdmin(w http.ResponseWriter, r *http.Request) { return } - u.IsSuperAdmin = true - u.IsAdmin = false + u.PlatformRoleID = models.SuperAdminRole err = logic.UpsertUser(*u) if err != nil { slog.Error("error updating user to superadmin: ", "user", u.UserName, "error", err.Error()) logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } - caller.IsSuperAdmin = false - caller.IsAdmin = true + caller.PlatformRoleID = models.AdminRole err = logic.UpsertUser(*caller) if err != nil { slog.Error("error demoting user to admin: ", "user", caller.UserName, "error", err.Error()) @@ -666,11 +664,6 @@ func createUser(w http.ResponseWriter, r *http.Request) { logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) return } - callerUserRole, err := logic.GetRole(caller.PlatformRoleID) - if err != nil { - logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) - return - } var user models.User err = json.NewDecoder(r.Body).Decode(&user) if err != nil { @@ -707,20 +700,17 @@ func createUser(w http.ResponseWriter, r *http.Request) { return } - if callerUserRole.ID != models.SuperAdminRole && user.IsAdmin { + if caller.PlatformRoleID != models.SuperAdminRole && user.PlatformRoleID == models.AdminRole { err = errors.New("only superadmin can create admin users") slog.Error("error creating new user: ", "user", user.UserName, "error", err) logic.ReturnErrorResponse(w, r, logic.FormatError(err, "forbidden")) return } - if !servercfg.IsPro && !user.IsAdmin { + if !servercfg.IsPro && user.PlatformRoleID != models.AdminRole { logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("non-admins users can only be created on Pro version"), "forbidden")) return } - if userRole.ID == models.AdminRole { - user.IsAdmin = true - } err = logic.CreateUser(&user) if err != nil { slog.Error("error creating new user: ", "user", user.UserName, "error", err.Error()) @@ -1134,9 +1124,6 @@ func userInviteSignUp(w http.ResponseWriter, r *http.Request) { user.PlatformRoleID = userG.PlatformRole user.UserGroups[inviteGroupID] = struct{}{} } - if user.PlatformRoleID == models.AdminRole { - user.IsAdmin = true - } user.NetworkRoles = make(map[models.NetworkID]map[models.UserRole]struct{}) user.IsSuperAdmin = false err = logic.CreateUser(&user) diff --git a/logic/auth.go b/logic/auth.go index 8ec207aa0..34d04f01e 100644 --- a/logic/auth.go +++ b/logic/auth.go @@ -114,7 +114,7 @@ func CreateUser(user *models.User) error { // set password to encrypted password user.Password = string(hash) - tokenString, _ := CreateUserJWT(user.UserName, user.IsSuperAdmin, user.IsAdmin) + tokenString, _ := CreateUserJWT(user.UserName, user.PlatformRoleID) if tokenString == "" { logger.Log(0, "failed to generate token", err.Error()) return err @@ -147,8 +147,6 @@ func CreateSuperAdmin(u *models.User) error { return errors.New("superadmin user already exists") } u.PlatformRoleID = models.SuperAdminRole - u.IsSuperAdmin = true - u.IsAdmin = false return CreateUser(u) } @@ -177,7 +175,7 @@ func VerifyAuthRequest(authRequest models.UserAuthParams) (string, error) { } // Create a new JWT for the node - tokenString, err := CreateUserJWT(authRequest.UserName, result.IsSuperAdmin, result.IsAdmin) + tokenString, err := CreateUserJWT(authRequest.UserName, result.PlatformRoleID) if err != nil { slog.Error("error creating jwt", "error", err) return "", err diff --git a/logic/gateway.go b/logic/gateway.go index 154a2a8d8..7b8489ed0 100644 --- a/logic/gateway.go +++ b/logic/gateway.go @@ -264,7 +264,7 @@ func IsUserAllowedAccessToExtClient(username string, client models.ExtClient) bo if err != nil { return false } - if !user.IsAdmin && !user.IsSuperAdmin { + if user.PlatformRoleID != models.AdminRole && user.PlatformRoleID != models.SuperAdminRole { if user.UserName != client.OwnerID { return false } diff --git a/logic/jwts.go b/logic/jwts.go index f3838ec16..f43979f54 100644 --- a/logic/jwts.go +++ b/logic/jwts.go @@ -53,12 +53,11 @@ func CreateJWT(uuid string, macAddress string, network string) (response string, } // CreateUserJWT - creates a user jwt token -func CreateUserJWT(username string, issuperadmin, isadmin bool) (response string, err error) { +func CreateUserJWT(username string, role models.UserRole) (response string, err error) { expirationTime := time.Now().Add(servercfg.GetServerConfig().JwtValidityDuration) claims := &models.UserClaims{ - UserName: username, - IsSuperAdmin: issuperadmin, - IsAdmin: isadmin, + UserName: username, + Role: role, RegisteredClaims: jwt.RegisteredClaims{ Issuer: "Netmaker", Subject: fmt.Sprintf("user|%s", username), @@ -145,7 +144,8 @@ func VerifyUserToken(tokenString string) (username string, issuperadmin, isadmin return "", false, false, err } if user.UserName != "" { - return user.UserName, user.IsSuperAdmin, user.IsAdmin, nil + return user.UserName, user.PlatformRoleID == models.SuperAdminRole, + user.PlatformRoleID == models.AdminRole, nil } err = errors.New("user does not exist") } diff --git a/logic/users.go b/logic/users.go index 881878a94..051624e92 100644 --- a/logic/users.go +++ b/logic/users.go @@ -41,10 +41,12 @@ func GetReturnUser(username string) (models.ReturnUser, error) { // ToReturnUser - gets a user as a return user func ToReturnUser(user models.User) models.ReturnUser { return models.ReturnUser{ - UserName: user.UserName, - IsSuperAdmin: user.IsSuperAdmin, - IsAdmin: user.IsAdmin, - RemoteGwIDs: user.RemoteGwIDs, + UserName: user.UserName, + PlatformRoleID: user.PlatformRoleID, + UserGroups: user.UserGroups, + NetworkRoles: user.NetworkRoles, + RemoteGwIDs: user.RemoteGwIDs, + LastLoginTime: user.LastLoginTime, } } diff --git a/migrate/migrate.go b/migrate/migrate.go index a2593aa19..b154c66a8 100644 --- a/migrate/migrate.go +++ b/migrate/migrate.go @@ -44,8 +44,7 @@ func assignSuperAdmin() { if err != nil { log.Fatal("error getting user", "user", owner, "error", err.Error()) } - user.IsSuperAdmin = true - user.IsAdmin = false + user.PlatformRoleID = models.SuperAdminRole err = logic.UpsertUser(*user) if err != nil { log.Fatal( @@ -65,8 +64,7 @@ func assignSuperAdmin() { slog.Error("error getting user", "user", u.UserName, "error", err.Error()) continue } - user.IsSuperAdmin = true - user.IsAdmin = false + user.PlatformRoleID = models.SuperAdminRole err = logic.UpsertUser(*user) if err != nil { slog.Error( diff --git a/models/user_mgmt.go b/models/user_mgmt.go index 3904f7ccc..7a6878d90 100644 --- a/models/user_mgmt.go +++ b/models/user_mgmt.go @@ -126,9 +126,8 @@ type UserAuthParams struct { // UserClaims - user claims struct type UserClaims struct { - IsAdmin bool - IsSuperAdmin bool - UserName string + Role UserRole + UserName string jwt.RegisteredClaims } diff --git a/pro/auth/azure-ad.go b/pro/auth/azure-ad.go index 64c704b93..97d918d55 100644 --- a/pro/auth/azure-ad.go +++ b/pro/auth/azure-ad.go @@ -107,9 +107,6 @@ func handleAzureCallback(w http.ResponseWriter, r *http.Request) { user.PlatformRoleID = userG.PlatformRole user.UserGroups[inviteGroupID] = struct{}{} } - if user.PlatformRoleID == models.AdminRole { - user.IsAdmin = true - } if err = logic.CreateUser(user); err != nil { handleSomethingWentWrong(w) return @@ -136,7 +133,12 @@ func handleAzureCallback(w http.ResponseWriter, r *http.Request) { handleOauthUserNotFound(w) return } - if !(user.IsSuperAdmin || user.IsAdmin) { + userRole, err := logic.GetRole(user.PlatformRoleID) + if err != nil { + handleSomethingWentWrong(w) + return + } + if userRole.DenyDashboardAccess { handleOauthUserNotAllowed(w) return } diff --git a/pro/auth/github.go b/pro/auth/github.go index 21abd7ea5..6cc2e6661 100644 --- a/pro/auth/github.go +++ b/pro/auth/github.go @@ -107,9 +107,6 @@ func handleGithubCallback(w http.ResponseWriter, r *http.Request) { user.PlatformRoleID = userG.PlatformRole user.UserGroups[inviteGroupID] = struct{}{} } - if user.PlatformRoleID == models.AdminRole { - user.IsAdmin = true - } if err = logic.CreateUser(user); err != nil { handleSomethingWentWrong(w) return @@ -136,7 +133,12 @@ func handleGithubCallback(w http.ResponseWriter, r *http.Request) { handleOauthUserNotFound(w) return } - if !(user.IsSuperAdmin || user.IsAdmin) { + userRole, err := logic.GetRole(user.PlatformRoleID) + if err != nil { + handleSomethingWentWrong(w) + return + } + if userRole.DenyDashboardAccess { handleOauthUserNotAllowed(w) return } diff --git a/pro/auth/google.go b/pro/auth/google.go index cf127d20c..5d90a5bca 100644 --- a/pro/auth/google.go +++ b/pro/auth/google.go @@ -108,9 +108,6 @@ func handleGoogleCallback(w http.ResponseWriter, r *http.Request) { user.PlatformRoleID = userG.PlatformRole user.UserGroups[inviteGroupID] = struct{}{} } - if user.PlatformRoleID == models.AdminRole { - user.IsAdmin = true - } if err = logic.CreateUser(user); err != nil { handleSomethingWentWrong(w) return @@ -139,7 +136,12 @@ func handleGoogleCallback(w http.ResponseWriter, r *http.Request) { handleOauthUserNotFound(w) return } - if !(user.IsSuperAdmin || user.IsAdmin) { + userRole, err := logic.GetRole(user.PlatformRoleID) + if err != nil { + handleSomethingWentWrong(w) + return + } + if userRole.DenyDashboardAccess { handleOauthUserNotAllowed(w) return } diff --git a/pro/auth/oidc.go b/pro/auth/oidc.go index a41b027e9..452a15c8d 100644 --- a/pro/auth/oidc.go +++ b/pro/auth/oidc.go @@ -119,9 +119,6 @@ func handleOIDCCallback(w http.ResponseWriter, r *http.Request) { user.PlatformRoleID = userG.PlatformRole user.UserGroups[inviteGroupID] = struct{}{} } - if user.PlatformRoleID == models.AdminRole { - user.IsAdmin = true - } if err = logic.CreateUser(user); err != nil { handleSomethingWentWrong(w) return @@ -148,7 +145,12 @@ func handleOIDCCallback(w http.ResponseWriter, r *http.Request) { handleOauthUserNotFound(w) return } - if !(user.IsSuperAdmin || user.IsAdmin) { + userRole, err := logic.GetRole(user.PlatformRoleID) + if err != nil { + handleSomethingWentWrong(w) + return + } + if userRole.DenyDashboardAccess { handleOauthUserNotAllowed(w) return } diff --git a/pro/auth/register_callback.go b/pro/auth/register_callback.go index c7edd958c..a2085c401 100644 --- a/pro/auth/register_callback.go +++ b/pro/auth/register_callback.go @@ -10,6 +10,7 @@ import ( "github.com/gravitl/netmaker/logger" "github.com/gravitl/netmaker/logic" "github.com/gravitl/netmaker/logic/pro/netcache" + "github.com/gravitl/netmaker/models" ) var ( @@ -73,7 +74,7 @@ func HandleHostSSOCallback(w http.ResponseWriter, r *http.Request) { handleOauthUserNotFound(w) return } - if !user.IsAdmin && !user.IsSuperAdmin { + if user.PlatformRoleID != models.AdminRole && user.PlatformRoleID != models.SuperAdminRole { response := returnErrTemplate(userClaims.getUserName(), "only admin users can register using SSO", state, reqKeyIf) w.WriteHeader(http.StatusForbidden) w.Write(response) diff --git a/pro/controllers/users.go b/pro/controllers/users.go index 1d6d20930..c303729c6 100644 --- a/pro/controllers/users.go +++ b/pro/controllers/users.go @@ -55,7 +55,7 @@ func attachUserToRemoteAccessGw(w http.ResponseWriter, r *http.Request) { logic.ReturnErrorResponse(w, r, logic.FormatError(fmt.Errorf("failed to fetch user %s, error: %v", username, err), "badrequest")) return } - if user.IsAdmin || user.IsSuperAdmin { + if user.PlatformRoleID == models.AdminRole || user.PlatformRoleID == models.SuperAdminRole { logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("superadmins/admins have access to all gateways"), "badrequest")) return } @@ -219,7 +219,7 @@ func getUserRemoteAccessGws(w http.ResponseWriter, r *http.Request) { slog.Error("failed to get node network", "error", err) } - if _, ok := user.RemoteGwIDs[node.ID.String()]; (!user.IsAdmin && !user.IsSuperAdmin) && ok { + if _, ok := user.RemoteGwIDs[node.ID.String()]; (user.PlatformRoleID != models.AdminRole && user.PlatformRoleID != models.SuperAdminRole) && ok { gws := userGws[node.Network] extClient.AllowedIPs = logic.GetExtclientAllowedIPs(extClient) gws = append(gws, models.UserRemoteGws{ @@ -260,7 +260,7 @@ func getUserRemoteAccessGws(w http.ResponseWriter, r *http.Request) { } // add remaining gw nodes to resp - if !user.IsAdmin && !user.IsSuperAdmin { + if user.PlatformRoleID != models.AdminRole && user.PlatformRoleID != models.SuperAdminRole { for gwID := range user.RemoteGwIDs { node, err := logic.GetNodeByID(gwID) if err != nil { From 7bc30f12133aa319ef63ce3203a7abb1b754f4fd Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Mon, 8 Jul 2024 14:24:17 +0530 Subject: [PATCH 067/139] deprecate usage of old user fields --- controllers/network_test.go | 6 +++--- controllers/user.go | 3 +-- controllers/user_test.go | 26 +++++++++++++------------- functions/helpers_test.go | 6 +++--- logic/auth.go | 2 +- migrate/migrate.go | 5 ++++- 6 files changed, 25 insertions(+), 23 deletions(-) diff --git a/controllers/network_test.go b/controllers/network_test.go index 8678f4089..51a0477be 100644 --- a/controllers/network_test.go +++ b/controllers/network_test.go @@ -26,9 +26,9 @@ func TestMain(m *testing.M) { database.InitializeDatabase() defer database.CloseDB() logic.CreateSuperAdmin(&models.User{ - UserName: "admin", - Password: "password", - IsAdmin: true, + UserName: "admin", + Password: "password", + PlatformRoleID: models.SuperAdminRole, }) peerUpdate := make(chan *models.Node) go logic.ManageZombies(context.Background(), peerUpdate) diff --git a/controllers/user.go b/controllers/user.go index 20f740b51..0cec458bc 100644 --- a/controllers/user.go +++ b/controllers/user.go @@ -607,7 +607,7 @@ func transferSuperAdmin(w http.ResponseWriter, r *http.Request) { if err != nil { logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) } - if !caller.IsSuperAdmin { + if caller.PlatformRoleID != models.SuperAdminRole { logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("only superadmin can assign the superadmin role to another user"), "forbidden")) return } @@ -1125,7 +1125,6 @@ func userInviteSignUp(w http.ResponseWriter, r *http.Request) { user.UserGroups[inviteGroupID] = struct{}{} } user.NetworkRoles = make(map[models.NetworkID]map[models.UserRole]struct{}) - user.IsSuperAdmin = false err = logic.CreateUser(&user) if err != nil { logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) diff --git a/controllers/user_test.go b/controllers/user_test.go index c99b8c475..89a8497e9 100644 --- a/controllers/user_test.go +++ b/controllers/user_test.go @@ -66,7 +66,7 @@ func prepareUserRequest(t *testing.T, userForBody models.User, userNameForParam func haveOnlyOneUser(t *testing.T, user models.User) { deleteAllUsers(t) var err error - if user.IsSuperAdmin { + if user.PlatformRoleID == models.SuperAdminRole { err = logic.CreateSuperAdmin(&user) } else { err = logic.CreateUser(&user) @@ -104,7 +104,7 @@ func TestHasSuperAdmin(t *testing.T) { assert.False(t, found) }) t.Run("superadmin user", func(t *testing.T) { - var user = models.User{UserName: "superadmin", Password: "password", IsSuperAdmin: true} + var user = models.User{UserName: "superadmin", Password: "password", PlatformRoleID: models.SuperAdminRole} err := logic.CreateUser(&user) assert.Nil(t, err) found, err := logic.HasSuperAdmin() @@ -112,7 +112,7 @@ func TestHasSuperAdmin(t *testing.T) { assert.True(t, found) }) t.Run("multiple superadmins", func(t *testing.T) { - var user = models.User{UserName: "superadmin1", Password: "password", IsSuperAdmin: true} + var user = models.User{UserName: "superadmin1", Password: "password", PlatformRoleID: models.SuperAdminRole} err := logic.CreateUser(&user) assert.Nil(t, err) found, err := logic.HasSuperAdmin() @@ -123,7 +123,7 @@ func TestHasSuperAdmin(t *testing.T) { func TestCreateUser(t *testing.T) { deleteAllUsers(t) - user := models.User{UserName: "admin", Password: "password", IsAdmin: true} + user := models.User{UserName: "admin", Password: "password", PlatformRoleID: models.AdminRole} t.Run("NoUser", func(t *testing.T) { err := logic.CreateUser(&user) assert.Nil(t, err) @@ -160,7 +160,7 @@ func TestDeleteUser(t *testing.T) { assert.False(t, deleted) }) t.Run("Existing User", func(t *testing.T) { - user := models.User{UserName: "admin", Password: "password", IsAdmin: true} + user := models.User{UserName: "admin", Password: "password", PlatformRoleID: models.AdminRole} if err := logic.CreateUser(&user); err != nil { t.Fatal(err) } @@ -220,7 +220,7 @@ func TestValidateUser(t *testing.T) { func TestGetUser(t *testing.T) { deleteAllUsers(t) - user := models.User{UserName: "admin", Password: "password", IsAdmin: true} + user := models.User{UserName: "admin", Password: "password", PlatformRoleID: models.AdminRole} t.Run("NonExistantUser", func(t *testing.T) { admin, err := logic.GetUser("admin") @@ -240,8 +240,8 @@ func TestGetUser(t *testing.T) { func TestGetUsers(t *testing.T) { deleteAllUsers(t) - adminUser := models.User{UserName: "admin", Password: "password", IsAdmin: true} - user := models.User{UserName: "admin", Password: "password", IsAdmin: false} + adminUser := models.User{UserName: "admin", Password: "password", PlatformRoleID: models.AdminRole} + user := models.User{UserName: "admin", Password: "password", PlatformRoleID: models.AdminRole} t.Run("NonExistantUser", func(t *testing.T) { admin, err := logic.GetUsers() @@ -268,7 +268,7 @@ func TestGetUsers(t *testing.T) { assert.Equal(t, true, u.IsAdmin) } else { assert.Equal(t, user.UserName, u.UserName) - assert.Equal(t, user.IsAdmin, u.IsAdmin) + assert.Equal(t, user.PlatformRoleID, u.PlatformRoleID) } } }) @@ -277,8 +277,8 @@ func TestGetUsers(t *testing.T) { func TestUpdateUser(t *testing.T) { deleteAllUsers(t) - user := models.User{UserName: "admin", Password: "password", IsAdmin: true} - newuser := models.User{UserName: "hello", Password: "world", IsAdmin: true} + user := models.User{UserName: "admin", Password: "password", PlatformRoleID: models.AdminRole} + newuser := models.User{UserName: "hello", Password: "world", PlatformRoleID: models.AdminRole} t.Run("NonExistantUser", func(t *testing.T) { admin, err := logic.UpdateUser(&newuser, &user) assert.EqualError(t, err, "could not find any records") @@ -321,7 +321,7 @@ func TestUpdateUser(t *testing.T) { func TestVerifyAuthRequest(t *testing.T) { deleteAllUsers(t) - user := models.User{UserName: "admin", Password: "password", IsSuperAdmin: false, IsAdmin: true} + user := models.User{UserName: "admin", Password: "password", PlatformRoleID: models.AdminRole} var authRequest models.UserAuthParams t.Run("EmptyUserName", func(t *testing.T) { authRequest.UserName = "" @@ -345,7 +345,7 @@ func TestVerifyAuthRequest(t *testing.T) { assert.EqualError(t, err, "incorrect credentials") }) t.Run("Non-Admin", func(t *testing.T) { - user.IsAdmin = false + user.PlatformRoleID = models.ServiceUser user.Password = "somepass" user.UserName = "nonadmin" if err := logic.CreateUser(&user); err != nil { diff --git a/functions/helpers_test.go b/functions/helpers_test.go index 4dc3d5297..ecc343306 100644 --- a/functions/helpers_test.go +++ b/functions/helpers_test.go @@ -26,9 +26,9 @@ func TestMain(m *testing.M) { database.InitializeDatabase() defer database.CloseDB() logic.CreateSuperAdmin(&models.User{ - UserName: "superadmin", - Password: "password", - IsSuperAdmin: true, + UserName: "superadmin", + Password: "password", + PlatformRoleID: models.SuperAdminRole, }) peerUpdate := make(chan *models.Node) go logic.ManageZombies(context.Background(), peerUpdate) diff --git a/logic/auth.go b/logic/auth.go index 34d04f01e..7ee2b6dbc 100644 --- a/logic/auth.go +++ b/logic/auth.go @@ -37,7 +37,7 @@ func HasSuperAdmin() (bool, error) { if err != nil { continue } - if user.IsSuperAdmin { + if user.PlatformRoleID == models.SuperAdminRole { return true, nil } } diff --git a/migrate/migrate.go b/migrate/migrate.go index b154c66a8..58b27a1d5 100644 --- a/migrate/migrate.go +++ b/migrate/migrate.go @@ -20,11 +20,11 @@ import ( // Run - runs all migrations func Run() { updateEnrollmentKeys() + syncUsers() assignSuperAdmin() updateHosts() updateNodes() updateAcls() - syncUsers() } @@ -344,6 +344,9 @@ func syncUsers() { users, err := logic.GetUsersDB() if err == nil { for _, user := range users { + if user.PlatformRoleID.String() != "" { + continue + } if user.IsSuperAdmin { user.PlatformRoleID = models.SuperAdminRole logic.UpsertUser(user) From 7b0906ac6280ba934bc67240cb32275fea06e89e Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Mon, 8 Jul 2024 17:54:44 +0530 Subject: [PATCH 068/139] add user role as service user if empty --- controllers/user.go | 3 +++ logic/user_mgmt.go | 2 +- pro/auth/azure-ad.go | 3 +++ pro/auth/github.go | 3 +++ pro/auth/google.go | 3 +++ pro/auth/oidc.go | 3 +++ 6 files changed, 16 insertions(+), 1 deletion(-) diff --git a/controllers/user.go b/controllers/user.go index 0cec458bc..c41d74008 100644 --- a/controllers/user.go +++ b/controllers/user.go @@ -1124,6 +1124,9 @@ func userInviteSignUp(w http.ResponseWriter, r *http.Request) { user.PlatformRoleID = userG.PlatformRole user.UserGroups[inviteGroupID] = struct{}{} } + if user.PlatformRoleID == "" { + user.PlatformRoleID = models.ServiceUser + } user.NetworkRoles = make(map[models.NetworkID]map[models.UserRole]struct{}) err = logic.CreateUser(&user) if err != nil { diff --git a/logic/user_mgmt.go b/logic/user_mgmt.go index 3f5bbe5ad..0e9634975 100644 --- a/logic/user_mgmt.go +++ b/logic/user_mgmt.go @@ -139,7 +139,7 @@ func GetRole(roleID models.UserRole) (models.UserRolePermissionTemplate, error) // check if role already exists data, err := database.FetchRecord(database.USER_PERMISSIONS_TABLE_NAME, roleID.String()) if err != nil { - return models.UserRolePermissionTemplate{}, errors.New("role already exists") + return models.UserRolePermissionTemplate{}, err } ur := models.UserRolePermissionTemplate{} err = json.Unmarshal([]byte(data), &ur) diff --git a/pro/auth/azure-ad.go b/pro/auth/azure-ad.go index 97d918d55..a52c78166 100644 --- a/pro/auth/azure-ad.go +++ b/pro/auth/azure-ad.go @@ -107,6 +107,9 @@ func handleAzureCallback(w http.ResponseWriter, r *http.Request) { user.PlatformRoleID = userG.PlatformRole user.UserGroups[inviteGroupID] = struct{}{} } + if user.PlatformRoleID == "" { + user.PlatformRoleID = models.ServiceUser + } if err = logic.CreateUser(user); err != nil { handleSomethingWentWrong(w) return diff --git a/pro/auth/github.go b/pro/auth/github.go index 6cc2e6661..a4a5b5b96 100644 --- a/pro/auth/github.go +++ b/pro/auth/github.go @@ -107,6 +107,9 @@ func handleGithubCallback(w http.ResponseWriter, r *http.Request) { user.PlatformRoleID = userG.PlatformRole user.UserGroups[inviteGroupID] = struct{}{} } + if user.PlatformRoleID == "" { + user.PlatformRoleID = models.ServiceUser + } if err = logic.CreateUser(user); err != nil { handleSomethingWentWrong(w) return diff --git a/pro/auth/google.go b/pro/auth/google.go index 5d90a5bca..985c25111 100644 --- a/pro/auth/google.go +++ b/pro/auth/google.go @@ -108,6 +108,9 @@ func handleGoogleCallback(w http.ResponseWriter, r *http.Request) { user.PlatformRoleID = userG.PlatformRole user.UserGroups[inviteGroupID] = struct{}{} } + if user.PlatformRoleID == "" { + user.PlatformRoleID = models.ServiceUser + } if err = logic.CreateUser(user); err != nil { handleSomethingWentWrong(w) return diff --git a/pro/auth/oidc.go b/pro/auth/oidc.go index 452a15c8d..5ce076bbd 100644 --- a/pro/auth/oidc.go +++ b/pro/auth/oidc.go @@ -119,6 +119,9 @@ func handleOIDCCallback(w http.ResponseWriter, r *http.Request) { user.PlatformRoleID = userG.PlatformRole user.UserGroups[inviteGroupID] = struct{}{} } + if user.PlatformRoleID == "" { + user.PlatformRoleID = models.ServiceUser + } if err = logic.CreateUser(user); err != nil { handleSomethingWentWrong(w) return From 3fe59a108249eb71ac7b24dde2849116d7b6a999 Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Tue, 9 Jul 2024 12:32:05 +0530 Subject: [PATCH 069/139] setup email sender --- controllers/user.go | 17 +- logic/notification.go => email/email.go | 37 +- email/invite.go | 27 ++ email/utils.go | 567 ++++++++++++++++++++++++ 4 files changed, 632 insertions(+), 16 deletions(-) rename logic/notification.go => email/email.go (63%) create mode 100644 email/invite.go create mode 100644 email/utils.go diff --git a/controllers/user.go b/controllers/user.go index c41d74008..cab5031ef 100644 --- a/controllers/user.go +++ b/controllers/user.go @@ -11,6 +11,7 @@ import ( "github.com/gorilla/websocket" "github.com/gravitl/netmaker/auth" "github.com/gravitl/netmaker/database" + "github.com/gravitl/netmaker/email" "github.com/gravitl/netmaker/logger" "github.com/gravitl/netmaker/logic" "github.com/gravitl/netmaker/models" @@ -1214,7 +1215,21 @@ func inviteUsers(w http.ResponseWriter, r *http.Request) { } // notify user with magic link go func(invite models.UserInvite) { - err = logic.SendInviteEmail(invite) + // Set E-Mail body. You can set plain text or html with text/html + u, err := url.Parse(fmt.Sprintf("https://api.%s/api/v1/users/invite?email=%s&code=%s", + servercfg.GetServer(), url.QueryEscape(invite.Email), url.QueryEscape(invite.InviteCode))) + if err != nil { + slog.Error("failed to parse to invite url", "error", err) + return + } + e := email.UserInvitedMail{ + BodyBuilder: &email.EmailBodyBuilderWithH1HeadlineAndImage{}, + InviteURL: u.String(), + } + n := email.Notification{ + RecipientMail: invite.Email, + } + err = email.Send(n.NewEmailSender(e)) if err != nil { slog.Error("failed to send email invite", "user", invite.Email, "error", err) } diff --git a/logic/notification.go b/email/email.go similarity index 63% rename from logic/notification.go rename to email/email.go index 95da36cf6..a2e16f0ed 100644 --- a/logic/notification.go +++ b/email/email.go @@ -1,13 +1,10 @@ -package logic +package email import ( "crypto/tls" - "fmt" - "net/url" gomail "gopkg.in/mail.v2" - "github.com/gravitl/netmaker/models" "github.com/gravitl/netmaker/servercfg" ) @@ -18,25 +15,35 @@ var ( senderPassword = servercfg.GetSenderEmailPassWord() ) -func SendInviteEmail(invite models.UserInvite) error { +type Email interface { + GetBody(info Notification) string + GetSubject(info Notification) string +} + +// Notification - struct for notification details +type Notification struct { + RecipientMail string + RecipientName string + ProductName string +} + +func (n Notification) NewEmailSender(e Email) *gomail.Message { m := gomail.NewMessage() // Set E-Mail sender m.SetHeader("From", senderEmail) // Set E-Mail receivers - m.SetHeader("To", invite.Email) - + m.SetHeader("To", n.RecipientMail) // Set E-Mail subject - m.SetHeader("Subject", "Netmaker Invite") - + m.SetHeader("Subject", e.GetSubject(n)) // Set E-Mail body. You can set plain text or html with text/html - u, err := url.Parse(fmt.Sprintf("https://api.%s/api/v1/users/invite?email=%s&code=%s", - servercfg.GetServer(), url.QueryEscape(invite.Email), url.QueryEscape(invite.InviteCode))) - if err != nil { - return err - } - m.SetBody("text/html", "Click Here to Signup! "+u.String()) + m.SetBody("text/html", e.GetBody(n)) + + return m +} + +func Send(m *gomail.Message) error { // Settings for SMTP server d := gomail.NewDialer(smtpHost, smtpPort, senderEmail, senderPassword) diff --git a/email/invite.go b/email/invite.go new file mode 100644 index 000000000..c7e3ca425 --- /dev/null +++ b/email/invite.go @@ -0,0 +1,27 @@ +package email + +import ( + "fmt" +) + +// UserInvitedMail - mail for users that are invited to a tenant +type UserInvitedMail struct { + BodyBuilder EmailBodyBuilder + InviteURL string +} + +// GetSubject - gets the subject of the email +func (UserInvitedMail) GetSubject(info Notification) string { + return "Netmaker SaaS: Invitation Pending Acceptance" +} + +// GetBody - gets the body of the email +func (invite UserInvitedMail) GetBody(info Notification) string { + + return invite.BodyBuilder. + WithHeadline("Join Netmaker from this invite!"). + WithParagraph("Hello from Netmaker,"). + WithParagraph("You have been invited to join Netmaker."). + WithParagraph(fmt.Sprintf("Join Using This Invite Link Netmaker", invite.InviteURL)). + Build() +} diff --git a/email/utils.go b/email/utils.go new file mode 100644 index 000000000..69a58fe01 --- /dev/null +++ b/email/utils.go @@ -0,0 +1,567 @@ +package email + +import "strings" + +// mail related images hosted on github +var ( + nLogoTeal = "https://raw.githubusercontent.com/gravitl/netmaker/netmaker_logos/img/logos/N_Teal.png" + netmakerLogoTeal = "https://raw.githubusercontent.com/gravitl/netmaker/netmaker_logos/img/logos/netmaker-logo-2.png" + netmakerMeshLogo = "https://raw.githubusercontent.com/gravitl/netmaker/netmaker_logos/img/logos/netmaker-mesh.png" + linkedinIcon = "https://raw.githubusercontent.com/gravitl/netmaker/netmaker_logos/img/logos/linkedin2x.png" + discordIcon = "https://raw.githubusercontent.com/gravitl/netmaker/netmaker_logos/img/logos/discord-logo-png-7617.png" + githubIcon = "https://raw.githubusercontent.com/gravitl/netmaker/netmaker_logos/img/logos/Octocat.png" + mailIcon = "https://raw.githubusercontent.com/gravitl/netmaker/netmaker_logos/img/logos/icons8-mail-24.png" + addressIcon = "https://raw.githubusercontent.com/gravitl/netmaker/netmaker_logos/img/logos/icons8-address-16.png" + linkIcon = "https://raw.githubusercontent.com/gravitl/netmaker/netmaker_logos/img/logos/icons8-hyperlink-64.png" +) + +type EmailBodyBuilder interface { + WithHeadline(text string) EmailBodyBuilder + WithParagraph(text string) EmailBodyBuilder + WithSignature() EmailBodyBuilder + Build() string +} + +type EmailBodyBuilderWithH1HeadlineAndImage struct { + headline string + paragraphs []string + hasSignature bool +} + +func (b *EmailBodyBuilderWithH1HeadlineAndImage) WithHeadline(text string) EmailBodyBuilder { + b.headline = text + return b +} + +func (b *EmailBodyBuilderWithH1HeadlineAndImage) WithParagraph(text string) EmailBodyBuilder { + b.paragraphs = append(b.paragraphs, text) + return b +} + +func (b *EmailBodyBuilderWithH1HeadlineAndImage) WithSignature() EmailBodyBuilder { + b.hasSignature = true + return b +} + +func (b *EmailBodyBuilderWithH1HeadlineAndImage) Build() string { + // map paragraphs to styled paragraphs + styledParagraphsSlice := make([]string, len(b.paragraphs)) + for i, paragraph := range b.paragraphs { + styledParagraphsSlice[i] = styledParagraph(paragraph) + } + // join styled paragraphs + styledParagraphsString := strings.Join(styledParagraphsSlice, "") + + signature := "" + if b.hasSignature { + signature = styledSignature() + } + + return ` + + + + + + + + + + + + + + + + + + + + ` + signature + ` + ` +} + +func styledSignature() string { + return ` +
+ + + + + + +
+ + + + + + + + +
+ + + + + + + + + + + + +
+ + + + + + + + + + + + + +
Linkedin +
+
Discord +
+
Github +
+
+
+
+
+
+

Alex Feiszli

+

Co-Founder & CEO

+

Netmaker

+ + + + + + + + + + + + +
+ + + + + + + + + + + + + + + +
+ + + + + + +
+
alex@netmaker.io
+ + + + + + +
+
https://www.netmaker.io/
+ + + + + + +
+
1465 Sand Hill Rd.Suite 2014, Candler, NC 28715
+ + + + + + +
+
+
+
` +} + +func styledParagraph(text string) string { + return `

+ ` + text + ` +

` +} + +func GetMailSignature() string { + return styledSignature() +} From a797875c9b240ed88fc9bb5e1b3c0122c2633e45 Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Tue, 9 Jul 2024 13:37:35 +0530 Subject: [PATCH 070/139] delete invite after user singup --- email/invite.go | 2 +- pro/auth/azure-ad.go | 1 + pro/auth/github.go | 1 + pro/auth/google.go | 1 + pro/auth/oidc.go | 1 + 5 files changed, 5 insertions(+), 1 deletion(-) diff --git a/email/invite.go b/email/invite.go index c7e3ca425..c2b39bd2c 100644 --- a/email/invite.go +++ b/email/invite.go @@ -12,7 +12,7 @@ type UserInvitedMail struct { // GetSubject - gets the subject of the email func (UserInvitedMail) GetSubject(info Notification) string { - return "Netmaker SaaS: Invitation Pending Acceptance" + return "Netmaker: Pending Invitation" } // GetBody - gets the body of the email diff --git a/pro/auth/azure-ad.go b/pro/auth/azure-ad.go index a52c78166..d0a46851b 100644 --- a/pro/auth/azure-ad.go +++ b/pro/auth/azure-ad.go @@ -114,6 +114,7 @@ func handleAzureCallback(w http.ResponseWriter, r *http.Request) { handleSomethingWentWrong(w) return } + logic.DeleteUserInvite(user.UserName) logic.DeletePendingUser(content.UserPrincipalName) } else { err = logic.InsertPendingUser(&models.User{ diff --git a/pro/auth/github.go b/pro/auth/github.go index a4a5b5b96..193a321b2 100644 --- a/pro/auth/github.go +++ b/pro/auth/github.go @@ -114,6 +114,7 @@ func handleGithubCallback(w http.ResponseWriter, r *http.Request) { handleSomethingWentWrong(w) return } + logic.DeleteUserInvite(user.UserName) logic.DeletePendingUser(content.Login) } else { err = logic.InsertPendingUser(&models.User{ diff --git a/pro/auth/google.go b/pro/auth/google.go index 985c25111..359ff89f9 100644 --- a/pro/auth/google.go +++ b/pro/auth/google.go @@ -115,6 +115,7 @@ func handleGoogleCallback(w http.ResponseWriter, r *http.Request) { handleSomethingWentWrong(w) return } + logic.DeleteUserInvite(user.UserName) logic.DeletePendingUser(content.Email) } else { err = logic.InsertPendingUser(&models.User{ diff --git a/pro/auth/oidc.go b/pro/auth/oidc.go index 5ce076bbd..393e456c3 100644 --- a/pro/auth/oidc.go +++ b/pro/auth/oidc.go @@ -126,6 +126,7 @@ func handleOIDCCallback(w http.ResponseWriter, r *http.Request) { handleSomethingWentWrong(w) return } + logic.DeleteUserInvite(user.UserName) logic.DeletePendingUser(content.Email) } else { err = logic.InsertPendingUser(&models.User{ From 87b585266e2f735ac40c0b43605d5b3e46393ca5 Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Tue, 9 Jul 2024 13:56:55 +0530 Subject: [PATCH 071/139] add plaform user role --- logic/user_mgmt.go | 9 ++++++++- models/user_mgmt.go | 3 ++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/logic/user_mgmt.go b/logic/user_mgmt.go index 0e9634975..fac41ff52 100644 --- a/logic/user_mgmt.go +++ b/logic/user_mgmt.go @@ -23,7 +23,14 @@ var AdminPermissionTemplate = models.UserRolePermissionTemplate{ } var ServiceUserPermissionTemplate = models.UserRolePermissionTemplate{ - ID: models.ServiceUser, + ID: models.ServiceUser, + Default: true, + FullAccess: false, + DenyDashboardAccess: true, +} + +var PlatformUserUserPermissionTemplate = models.UserRolePermissionTemplate{ + ID: models.PlatformUser, Default: true, FullAccess: false, } diff --git a/models/user_mgmt.go b/models/user_mgmt.go index 7a6878d90..f1a06601d 100644 --- a/models/user_mgmt.go +++ b/models/user_mgmt.go @@ -55,7 +55,8 @@ const ( const ( SuperAdminRole UserRole = "super_admin" AdminRole UserRole = "admin" - ServiceUser UserRole = "user" + ServiceUser UserRole = "service_user" + PlatformUser UserRole = "platform_user" NetworkAdmin UserRole = "network_admin" NetworkUser UserRole = "network_user" ) From 377c73f5cab8b65213ca2c2bdeac4858f4d6f417 Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Wed, 10 Jul 2024 00:38:06 +0530 Subject: [PATCH 072/139] redirect on invite verification link --- controllers/user.go | 3 +-- logic/auth.go | 7 ++++++- logic/user_mgmt.go | 14 ++++++++++++++ migrate/migrate.go | 11 ++++++++--- models/user_mgmt.go | 15 +++++++++++++++ 5 files changed, 44 insertions(+), 6 deletions(-) diff --git a/controllers/user.go b/controllers/user.go index cab5031ef..522e351f9 100644 --- a/controllers/user.go +++ b/controllers/user.go @@ -1151,14 +1151,13 @@ func userInviteSignUp(w http.ResponseWriter, r *http.Request) { func userInviteVerify(w http.ResponseWriter, r *http.Request) { email, _ := url.QueryUnescape(r.URL.Query().Get("email")) code, _ := url.QueryUnescape(r.URL.Query().Get("code")) - logger.Log(0, "EMAIL", email, "CODE", code) err := logic.ValidateAndApproveUserInvite(email, code) if err != nil { logger.Log(0, "failed to fetch users: ", err.Error()) logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } - logic.ReturnSuccessResponse(w, r, "invite is valid") + http.Redirect(w, r, url.QueryEscape(fmt.Sprintf("%s/invite-signup?email=%s&code=%s", servercfg.GetFrontendURL(), email, code)), http.StatusPermanentRedirect) } // swagger:route POST /api/v1/users/invite user inviteUsers diff --git a/logic/auth.go b/logic/auth.go index 7ee2b6dbc..981371ef6 100644 --- a/logic/auth.go +++ b/logic/auth.go @@ -113,7 +113,12 @@ func CreateUser(user *models.User) error { } // set password to encrypted password user.Password = string(hash) - + if len(user.NetworkRoles) == 0 { + user.NetworkRoles = make(map[models.NetworkID]map[models.UserRole]struct{}) + } + if len(user.UserGroups) == 0 { + user.UserGroups = make(map[models.UserGroupID]struct{}) + } tokenString, _ := CreateUserJWT(user.UserName, user.PlatformRoleID) if tokenString == "" { logger.Log(0, "failed to generate token", err.Error()) diff --git a/logic/user_mgmt.go b/logic/user_mgmt.go index fac41ff52..027de9ef8 100644 --- a/logic/user_mgmt.go +++ b/logic/user_mgmt.go @@ -104,6 +104,13 @@ func ValidateCreateRoleReq(userRole models.UserRolePermissionTemplate) error { if err == nil { return fmt.Errorf("role with id `%s` exists already", userRole.ID.String()) } + if len(userRole.NetworkLevelAccess) > 0 { + for rsrcType := range userRole.NetworkLevelAccess { + if _, ok := models.RsrcTypeMap[rsrcType]; !ok { + return errors.New("invalid rsrc type " + rsrcType.String()) + } + } + } if userRole.NetworkID == "" { return errors.New("only network roles are allowed to be created") } @@ -121,6 +128,13 @@ func ValidateUpdateRoleReq(userRole models.UserRolePermissionTemplate) error { if roleInDB.Default { return errors.New("cannot update default role") } + if len(userRole.NetworkLevelAccess) > 0 { + for rsrcType := range userRole.NetworkLevelAccess { + if _, ok := models.RsrcTypeMap[rsrcType]; !ok { + return errors.New("invalid rsrc type " + rsrcType.String()) + } + } + } return nil } diff --git a/migrate/migrate.go b/migrate/migrate.go index 58b27a1d5..ab4fbad52 100644 --- a/migrate/migrate.go +++ b/migrate/migrate.go @@ -347,16 +347,21 @@ func syncUsers() { if user.PlatformRoleID.String() != "" { continue } + if len(user.NetworkRoles) == 0 { + user.NetworkRoles = make(map[models.NetworkID]map[models.UserRole]struct{}) + } + if len(user.UserGroups) == 0 { + user.UserGroups = make(map[models.UserGroupID]struct{}) + } if user.IsSuperAdmin { user.PlatformRoleID = models.SuperAdminRole - logic.UpsertUser(user) + } else if user.IsAdmin { user.PlatformRoleID = models.AdminRole - logic.UpsertUser(user) } else { user.PlatformRoleID = models.ServiceUser - logic.UpsertUser(user) } + logic.UpsertUser(user) if len(user.RemoteGwIDs) > 0 { // define user roles for network // assign relevant network role to user diff --git a/models/user_mgmt.go b/models/user_mgmt.go index f1a06601d..d566a26bf 100644 --- a/models/user_mgmt.go +++ b/models/user_mgmt.go @@ -20,6 +20,21 @@ func (rid RsrcID) String() string { return string(rid) } +var RsrcTypeMap = map[RsrcType]struct{}{ + HostRsrc: {}, + RelayRsrc: {}, + RemoteAccessGwRsrc: {}, + ExtClientsRsrc: {}, + InetGwRsrc: {}, + EgressGwRsrc: {}, + NetworkRsrc: {}, + EnrollmentKeysRsrc: {}, + UserRsrc: {}, + AclRsrc: {}, + DnsRsrc: {}, + FailOverRsrc: {}, +} + const ( HostRsrc RsrcType = "hosts" RelayRsrc RsrcType = "relays" From f18822b2b3dee8f88462f202868c886bcc32e01a Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Wed, 10 Jul 2024 00:53:01 +0530 Subject: [PATCH 073/139] fix invite redirect --- controllers/user.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/controllers/user.go b/controllers/user.go index 522e351f9..b155b316f 100644 --- a/controllers/user.go +++ b/controllers/user.go @@ -1157,7 +1157,7 @@ func userInviteVerify(w http.ResponseWriter, r *http.Request) { logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } - http.Redirect(w, r, url.QueryEscape(fmt.Sprintf("%s/invite-signup?email=%s&code=%s", servercfg.GetFrontendURL(), email, code)), http.StatusPermanentRedirect) + http.Redirect(w, r, fmt.Sprintf("%s/login?email=%s&code=%s", servercfg.GetFrontendURL(), email, code), http.StatusPermanentRedirect) } // swagger:route POST /api/v1/users/invite user inviteUsers From 3bcf4a8f5a5903fdc9dd2171b20017af2c57e692 Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Wed, 10 Jul 2024 08:25:11 +0530 Subject: [PATCH 074/139] temporary redirect --- controllers/user.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/controllers/user.go b/controllers/user.go index b155b316f..986372c11 100644 --- a/controllers/user.go +++ b/controllers/user.go @@ -1157,7 +1157,7 @@ func userInviteVerify(w http.ResponseWriter, r *http.Request) { logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } - http.Redirect(w, r, fmt.Sprintf("%s/login?email=%s&code=%s", servercfg.GetFrontendURL(), email, code), http.StatusPermanentRedirect) + http.Redirect(w, r, fmt.Sprintf("%s/login?email=%s&code=%s", servercfg.GetFrontendURL(), email, code), http.StatusTemporaryRedirect) } // swagger:route POST /api/v1/users/invite user inviteUsers From 64601600d21b7ca5e64b873c70c837c5da92785e Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Wed, 10 Jul 2024 10:30:27 +0530 Subject: [PATCH 075/139] fix invite redirect --- controllers/user.go | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/controllers/user.go b/controllers/user.go index 986372c11..20dc0abe6 100644 --- a/controllers/user.go +++ b/controllers/user.go @@ -4,6 +4,7 @@ import ( "encoding/json" "errors" "fmt" + "log" "net/http" "net/url" @@ -1157,7 +1158,17 @@ func userInviteVerify(w http.ResponseWriter, r *http.Request) { logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } - http.Redirect(w, r, fmt.Sprintf("%s/login?email=%s&code=%s", servercfg.GetFrontendURL(), email, code), http.StatusTemporaryRedirect) + queryParams := url.Values{} + queryParams.Add("email", email) + queryParams.Add("code", code) + u := fmt.Sprintf("%s/login?%s", servercfg.GetFrontendURL(), queryParams.Encode()) + + // and redirect to the URL you built. + req, err := http.NewRequest(http.MethodGet, u, nil) + if err != nil { + log.Fatalf("Failed to create request object: %v\n", err) + } + http.Redirect(w, req, u, http.StatusPermanentRedirect) } // swagger:route POST /api/v1/users/invite user inviteUsers From 442819d95dce9bebbd0dc186c8dbb14b1e1bc48d Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Thu, 11 Jul 2024 10:45:11 +0530 Subject: [PATCH 076/139] point invite link to frontend --- controllers/user.go | 17 +++-------------- logic/jwts.go | 3 +++ 2 files changed, 6 insertions(+), 14 deletions(-) diff --git a/controllers/user.go b/controllers/user.go index 20dc0abe6..6c72e3afc 100644 --- a/controllers/user.go +++ b/controllers/user.go @@ -4,7 +4,6 @@ import ( "encoding/json" "errors" "fmt" - "log" "net/http" "net/url" @@ -1158,17 +1157,7 @@ func userInviteVerify(w http.ResponseWriter, r *http.Request) { logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } - queryParams := url.Values{} - queryParams.Add("email", email) - queryParams.Add("code", code) - u := fmt.Sprintf("%s/login?%s", servercfg.GetFrontendURL(), queryParams.Encode()) - - // and redirect to the URL you built. - req, err := http.NewRequest(http.MethodGet, u, nil) - if err != nil { - log.Fatalf("Failed to create request object: %v\n", err) - } - http.Redirect(w, req, u, http.StatusPermanentRedirect) + logic.ReturnSuccessResponse(w, r, "invite is valid") } // swagger:route POST /api/v1/users/invite user inviteUsers @@ -1226,8 +1215,8 @@ func inviteUsers(w http.ResponseWriter, r *http.Request) { // notify user with magic link go func(invite models.UserInvite) { // Set E-Mail body. You can set plain text or html with text/html - u, err := url.Parse(fmt.Sprintf("https://api.%s/api/v1/users/invite?email=%s&code=%s", - servercfg.GetServer(), url.QueryEscape(invite.Email), url.QueryEscape(invite.InviteCode))) + u, err := url.Parse(fmt.Sprintf("%s/invite?email=%s&code=%s", + servercfg.GetFrontendURL(), url.QueryEscape(invite.Email), url.QueryEscape(invite.InviteCode))) if err != nil { slog.Error("failed to parse to invite url", "error", err) return diff --git a/logic/jwts.go b/logic/jwts.go index f43979f54..61aca21e7 100644 --- a/logic/jwts.go +++ b/logic/jwts.go @@ -117,6 +117,9 @@ func GetUserNameFromToken(authtoken string) (username string, err error) { if user.UserName != "" { return user.UserName, nil } + if user.PlatformRoleID != claims.Role { + return "", Unauthorized_Err + } err = errors.New("user does not exist") } else { err = Unauthorized_Err From 7e1af54787e26aa6a2a1a351ab7476a08fb8e7ea Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Tue, 16 Jul 2024 09:27:41 +0530 Subject: [PATCH 077/139] fix query params lookup --- controllers/user.go | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/controllers/user.go b/controllers/user.go index 6c72e3afc..92ff0953b 100644 --- a/controllers/user.go +++ b/controllers/user.go @@ -98,8 +98,8 @@ func listUserGroups(w http.ResponseWriter, r *http.Request) { // Responses: // 200: userBodyResponse func getUserGroup(w http.ResponseWriter, r *http.Request) { - var params = mux.Vars(r) - gid := params["group_id"] + + gid, _ := url.QueryUnescape(r.URL.Query().Get("group_id")) if gid == "" { logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("group id is required"), "badrequest")) return @@ -193,8 +193,8 @@ func updateUserGroup(w http.ResponseWriter, r *http.Request) { // Responses: // 200: userBodyResponse func deleteUserGroup(w http.ResponseWriter, r *http.Request) { - var params = mux.Vars(r) - gid := params["group_id"] + + gid, _ := url.QueryUnescape(r.URL.Query().Get("group_id")) if gid == "" { logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("role is required"), "badrequest")) return @@ -242,8 +242,7 @@ func listRoles(w http.ResponseWriter, r *http.Request) { // Responses: // 200: userBodyResponse func getRole(w http.ResponseWriter, r *http.Request) { - var params = mux.Vars(r) - rid := params["role_id"] + rid, _ := url.QueryUnescape(r.URL.Query().Get("role_id")) if rid == "" { logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("role is required"), "badrequest")) return @@ -340,8 +339,8 @@ func updateRole(w http.ResponseWriter, r *http.Request) { // Responses: // 200: userBodyResponse func deleteRole(w http.ResponseWriter, r *http.Request) { - var params = mux.Vars(r) - rid := params["role_id"] + + rid, _ := url.QueryUnescape(r.URL.Query().Get("role_id")) if rid == "" { logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("role is required"), "badrequest")) return From cbfd43e0c59db81d14e54bac838d874e5f1ebc18 Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Tue, 16 Jul 2024 12:27:11 +0530 Subject: [PATCH 078/139] add resend support, configure email interface types --- config/config.go | 5 +++ controllers/user.go | 3 +- email/email.go | 64 ++++++++++++++---------------------- email/resend.go | 55 +++++++++++++++++++++++++++++++ email/smtp.go | 42 +++++++++++++++++++++++ go.mod | 1 + go.sum | 2 ++ scripts/netmaker.default.env | 8 +++-- servercfg/serverconf.go | 50 ++++++++++++++++++++++++---- 9 files changed, 181 insertions(+), 49 deletions(-) create mode 100644 email/resend.go create mode 100644 email/smtp.go diff --git a/config/config.go b/config/config.go index 522372eff..ffc17af4d 100644 --- a/config/config.go +++ b/config/config.go @@ -94,6 +94,11 @@ type ServerConfig struct { CacheEnabled string `yaml:"caching_enabled"` EndpointDetection bool `json:"endpoint_detection"` AllowedEmailDomains string `yaml:"allowed_email_domains"` + EmailSenderAddr string `json:"email_sender_addr"` + EmailSenderAuth string `json:"email_sender_auth"` + EmailSenderType string `json:"email_sender_type"` + SmtpHost string `json:"smtp_host"` + SmtpPort int `json:"smtp_port"` } // SQLConfig - Generic SQL Config diff --git a/controllers/user.go b/controllers/user.go index 92ff0953b..5c03b2059 100644 --- a/controllers/user.go +++ b/controllers/user.go @@ -1,6 +1,7 @@ package controller import ( + "context" "encoding/json" "errors" "fmt" @@ -1227,7 +1228,7 @@ func inviteUsers(w http.ResponseWriter, r *http.Request) { n := email.Notification{ RecipientMail: invite.Email, } - err = email.Send(n.NewEmailSender(e)) + err = email.GetClient().SendEmail(context.Background(), n, e) if err != nil { slog.Error("failed to send email invite", "user", invite.Email, "error", err) } diff --git a/email/email.go b/email/email.go index a2e16f0ed..c8eb2017c 100644 --- a/email/email.go +++ b/email/email.go @@ -1,21 +1,25 @@ package email import ( - "crypto/tls" - - gomail "gopkg.in/mail.v2" + "context" "github.com/gravitl/netmaker/servercfg" ) -var ( - smtpHost = servercfg.GetSmtpHost() - smtpPort = servercfg.GetSmtpPort() - senderEmail = servercfg.GetSenderEmail() - senderPassword = servercfg.GetSenderEmailPassWord() +type EmailSenderType string + +const ( + Smtp EmailSenderType = "smtp" + Resend EmailSenderType = "resend" ) -type Email interface { +// EmailSender - an interface for sending emails based on notifications and mail templates +type EmailSender interface { + // SendEmail - sends an email based on a context, notification and mail template + SendEmail(ctx context.Context, notification Notification, email Mail) error +} + +type Mail interface { GetBody(info Notification) string GetSubject(info Notification) string } @@ -27,35 +31,17 @@ type Notification struct { ProductName string } -func (n Notification) NewEmailSender(e Email) *gomail.Message { - m := gomail.NewMessage() - - // Set E-Mail sender - m.SetHeader("From", senderEmail) - - // Set E-Mail receivers - m.SetHeader("To", n.RecipientMail) - // Set E-Mail subject - m.SetHeader("Subject", e.GetSubject(n)) - // Set E-Mail body. You can set plain text or html with text/html - m.SetBody("text/html", e.GetBody(n)) - - return m -} - -func Send(m *gomail.Message) error { - - // Settings for SMTP server - d := gomail.NewDialer(smtpHost, smtpPort, senderEmail, senderPassword) - - // This is only needed when SSL/TLS certificate is not valid on server. - // In production this should be set to false. - d.TLSConfig = &tls.Config{InsecureSkipVerify: true} - - // Now send E-Mail - if err := d.DialAndSend(m); err != nil { - return err +func GetClient() (e EmailSender) { + switch EmailSenderType(servercfg.EmailSenderType()) { + case Smtp: + e = &SmtpSender{ + SmtpHost: servercfg.GetSmtpHost(), + SmtpPort: servercfg.GetSmtpPort(), + SenderEmail: servercfg.GetSenderEmail(), + SenderPass: servercfg.GetEmaiSenderAuth(), + } + case Resend: + e = NewResendEmailSenderFromConfig() } - - return nil + return } diff --git a/email/resend.go b/email/resend.go new file mode 100644 index 000000000..1125bc7e5 --- /dev/null +++ b/email/resend.go @@ -0,0 +1,55 @@ +package email + +import ( + "context" + "fmt" + + "github.com/gravitl/netmaker/servercfg" + "github.com/resendlabs/resend-go" +) + +// ResendEmailSender - implementation of EmailSender using Resend (https://resend.com) +type ResendEmailSender struct { + client ResendClient + from string +} + +// ResendClient - dependency interface for resend client +type ResendClient interface { + Send(*resend.SendEmailRequest) (resend.SendEmailResponse, error) +} + +// NewResendEmailSender - constructs a ResendEmailSender +func NewResendEmailSender(client ResendClient, from string) ResendEmailSender { + return ResendEmailSender{client: client, from: from} +} + +// NewResendEmailSender - constructs a ResendEmailSender from config +// TODO let main.go handle this and use dependency injection instead of calling this function +func NewResendEmailSenderFromConfig() ResendEmailSender { + key, from := servercfg.GetEmaiSenderAuth(), servercfg.GetSenderEmail() + resender := resend.NewClient(key) + return NewResendEmailSender(resender.Emails, from) +} + +// SendEmail - sends an email using resend-go (https://github.com/resendlabs/resend-go) +func (es ResendEmailSender) SendEmail(ctx context.Context, notification Notification, email Mail) error { + var ( + from = es.from + to = notification.RecipientMail + subject = email.GetSubject(notification) + body = email.GetBody(notification) + ) + params := resend.SendEmailRequest{ + From: from, + To: []string{to}, + Subject: subject, + Html: body, + } + _, err := es.client.Send(¶ms) + if err != nil { + return fmt.Errorf("failed sending mail via resend: %w", err) + } + + return nil +} diff --git a/email/smtp.go b/email/smtp.go new file mode 100644 index 000000000..89965ca5e --- /dev/null +++ b/email/smtp.go @@ -0,0 +1,42 @@ +package email + +import ( + "context" + "crypto/tls" + + gomail "gopkg.in/mail.v2" +) + +type SmtpSender struct { + SmtpHost string + SmtpPort int + SenderEmail string + SenderPass string +} + +func (s *SmtpSender) SendEmail(ctx context.Context, n Notification, e Mail) error { + m := gomail.NewMessage() + + // Set E-Mail sender + m.SetHeader("From", s.SenderEmail) + + // Set E-Mail receivers + m.SetHeader("To", n.RecipientMail) + // Set E-Mail subject + m.SetHeader("Subject", e.GetSubject(n)) + // Set E-Mail body. You can set plain text or html with text/html + m.SetBody("text/html", e.GetBody(n)) + // Settings for SMTP server + d := gomail.NewDialer(s.SmtpHost, s.SmtpPort, s.SenderEmail, s.SenderPass) + + // This is only needed when SSL/TLS certificate is not valid on server. + // In production this should be set to false. + d.TLSConfig = &tls.Config{InsecureSkipVerify: true} + + // Now send E-Mail + if err := d.DialAndSend(m); err != nil { + return err + } + + return nil +} diff --git a/go.mod b/go.mod index d17d2e031..ce2a935e4 100644 --- a/go.mod +++ b/go.mod @@ -41,6 +41,7 @@ require ( github.com/guumaster/tablewriter v0.0.10 github.com/matryer/is v1.4.1 github.com/olekukonko/tablewriter v0.0.5 + github.com/resendlabs/resend-go v1.7.0 github.com/spf13/cobra v1.8.1 gopkg.in/mail.v2 v2.3.1 ) diff --git a/go.sum b/go.sum index 7f7c970e0..6d9e2fc7e 100644 --- a/go.sum +++ b/go.sum @@ -63,6 +63,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posthog/posthog-go v0.0.0-20211028072449-93c17c49e2b0 h1:Y2hUrkfuM0on62KZOci/VLijlkdF/yeWU262BQgvcjE= github.com/posthog/posthog-go v0.0.0-20211028072449-93c17c49e2b0/go.mod h1:oa2sAs9tGai3VldabTV0eWejt/O4/OOD7azP8GaikqU= +github.com/resendlabs/resend-go v1.7.0 h1:DycOqSXtw2q7aB+Nt9DDJUDtaYcrNPGn1t5RFposas0= +github.com/resendlabs/resend-go v1.7.0/go.mod h1:yip1STH7Bqfm4fD0So5HgyNbt5taG5Cplc4xXxETyLI= github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= diff --git a/scripts/netmaker.default.env b/scripts/netmaker.default.env index e83e06389..876c2a4b2 100644 --- a/scripts/netmaker.default.env +++ b/scripts/netmaker.default.env @@ -81,6 +81,8 @@ SMTP_HOST=smtp.gmail.com # mail server port SMTP_PORT=587 # sender email -SENDER_EMAIL= -# sender email password -SENDER_PASSWORD= \ No newline at end of file +EMAIL_SENDER_ADDR= +# sender email auth +EMAIL_SENDER_AUTH= +# mail sender type (smtp or resend) +EMAIL_SENDER_TYPE=smtp \ No newline at end of file diff --git a/servercfg/serverconf.go b/servercfg/serverconf.go index b471ad034..ec7308d7c 100644 --- a/servercfg/serverconf.go +++ b/servercfg/serverconf.go @@ -242,18 +242,56 @@ func GetPublicBrokerEndpoint() string { } func GetSmtpHost() string { - return os.Getenv("SMTP_HOST") + v := "" + if fromEnv := os.Getenv("SMTP_HOST"); fromEnv != "" { + v = fromEnv + } else if fromCfg := config.Config.Server.SmtpHost; fromCfg != "" { + v = fromCfg + } + return v } func GetSmtpPort() int { - port, _ := strconv.Atoi(os.Getenv("SMTP_PORT")) - return port + v := 587 + if fromEnv := os.Getenv("SMTP_PORT"); fromEnv != "" { + port, err := strconv.Atoi(fromEnv) + if err == nil { + v = port + } + } else if fromCfg := config.Config.Server.SmtpPort; fromCfg != 0 { + v = fromCfg + } + return v } + func GetSenderEmail() string { - return os.Getenv("SENDER_EMAIL") + v := "" + if fromEnv := os.Getenv("EMAIL_SENDER_ADDR"); fromEnv != "" { + v = fromEnv + } else if fromCfg := config.Config.Server.EmailSenderAddr; fromCfg != "" { + v = fromCfg + } + return v } -func GetSenderEmailPassWord() string { - return os.Getenv("SENDER_PASSWORD") + +func GetEmaiSenderAuth() string { + v := "" + if fromEnv := os.Getenv("EMAIL_SENDER_AUTH"); fromEnv != "" { + v = fromEnv + } else if fromCfg := config.Config.Server.EmailSenderAddr; fromCfg != "" { + v = fromCfg + } + return v +} + +func EmailSenderType() string { + s := "" + if fromEnv := os.Getenv("EMAIL_SENDER_TYPE"); fromEnv != "" { + s = fromEnv + } else if fromCfg := config.Config.Server.EmailSenderType; fromCfg != "" { + s = fromCfg + } + return s } // GetOwnerEmail - gets the owner email (saas) From c0311d1b98fb52f3d4546bbc590069b92310237e Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Wed, 17 Jul 2024 15:17:07 +0530 Subject: [PATCH 079/139] fix groups and user creation --- controllers/user.go | 1 + logic/user_mgmt.go | 6 +++--- models/user_mgmt.go | 6 +++--- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/controllers/user.go b/controllers/user.go index 5c03b2059..2a7fd5b0d 100644 --- a/controllers/user.go +++ b/controllers/user.go @@ -681,6 +681,7 @@ func createUser(w http.ResponseWriter, r *http.Request) { return } uniqueGroupsPlatformRole[userG.PlatformRole] = struct{}{} + user.PlatformRoleID = userG.PlatformRole } if len(uniqueGroupsPlatformRole) > 1 { err = errors.New("only groups with same platform role can be assigned to an user") diff --git a/logic/user_mgmt.go b/logic/user_mgmt.go index 027de9ef8..1ce094455 100644 --- a/logic/user_mgmt.go +++ b/logic/user_mgmt.go @@ -83,7 +83,7 @@ func UserRolesInit() { // ListRoles - lists user roles permission templates func ListRoles() ([]models.UserRolePermissionTemplate, error) { data, err := database.FetchRecords(database.USER_PERMISSIONS_TABLE_NAME) - if err != nil { + if err != nil && !database.IsEmptyRecord(err) { return []models.UserRolePermissionTemplate{}, err } userRoles := []models.UserRolePermissionTemplate{} @@ -284,7 +284,7 @@ func CreateUserGroup(g models.UserGroup) error { // GetUserGroup - fetches user group func GetUserGroup(gid models.UserGroupID) (models.UserGroup, error) { d, err := database.FetchRecord(database.USER_GROUPS_TABLE_NAME, gid.String()) - if err == nil { + if err != nil { return models.UserGroup{}, err } var ug models.UserGroup @@ -298,7 +298,7 @@ func GetUserGroup(gid models.UserGroupID) (models.UserGroup, error) { // ListUserGroups - lists user groups func ListUserGroups() ([]models.UserGroup, error) { data, err := database.FetchRecords(database.USER_GROUPS_TABLE_NAME) - if err != nil { + if err != nil && !database.IsEmptyRecord(err) { return []models.UserGroup{}, err } userGroups := []models.UserGroup{} diff --git a/models/user_mgmt.go b/models/user_mgmt.go index d566a26bf..58afe85a1 100644 --- a/models/user_mgmt.go +++ b/models/user_mgmt.go @@ -113,9 +113,9 @@ type UserGroup struct { type User struct { UserName string `json:"username" bson:"username" validate:"min=3,max=40,in_charset|email"` Password string `json:"password" bson:"password" validate:"required,min=5"` - IsAdmin bool `json:"isadmin" bson:"isadmin"` - IsSuperAdmin bool `json:"issuperadmin"` - RemoteGwIDs map[string]struct{} `json:"remote_gw_ids"` // deprecated + IsAdmin bool `json:"isadmin" bson:"isadmin"` // deprecated + IsSuperAdmin bool `json:"issuperadmin"` // deprecated + RemoteGwIDs map[string]struct{} `json:"remote_gw_ids"` // deprecated UserGroups map[UserGroupID]struct{} `json:"user_group_ids"` PlatformRoleID UserRole `json:"platform_role_id"` NetworkRoles map[NetworkID]map[UserRole]struct{} `json:"network_roles"` From 3371c822d027510327f931f48486a5a9b1e6dba4 Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Thu, 18 Jul 2024 20:55:22 +0530 Subject: [PATCH 080/139] validate user groups, add check for metrics api in middleware --- controllers/middleware.go | 8 ++++++-- logic/security.go | 18 +++++++++++++++++- logic/user_mgmt.go | 25 ++++++++++++++++++++++--- models/user_mgmt.go | 2 ++ 4 files changed, 47 insertions(+), 6 deletions(-) diff --git a/controllers/middleware.go b/controllers/middleware.go index 1b4a536ba..44d971321 100644 --- a/controllers/middleware.go +++ b/controllers/middleware.go @@ -11,6 +11,7 @@ import ( func userMiddleWare(handler http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { var params = mux.Vars(r) + r.Header.Set("IS_GLOBAL_ACCESS", "no") r.Header.Set("NET_ID", params["network"]) if strings.Contains(r.URL.Path, "hosts") || strings.Contains(r.URL.Path, "nodes") { r.Header.Set("TARGET_RSRC", models.HostRsrc.String()) @@ -46,6 +47,9 @@ func userMiddleWare(handler http.Handler) http.Handler { r.Header.Set("TARGET_RSRC", models.EnrollmentKeysRsrc.String()) r.Header.Set("RSRC_TYPE", models.EnrollmentKeysRsrc.String()) } + if strings.Contains(r.URL.Path, "metrics") { + r.Header.Set("RSRC_TYPE", models.MetricRsrc.String()) + } if keyID, ok := params["keyID"]; ok { r.Header.Set("TARGET_RSRC_ID", keyID) } @@ -68,9 +72,9 @@ func userMiddleWare(handler http.Handler) http.Handler { if userID, ok := params["username"]; ok { r.Header.Set("TARGET_RSRC_ID", userID) } - if r.Header.Get("TARGET_RSRC_ID") == "" || + if r.Header.Get("NET_ID") == "" && (r.Header.Get("TARGET_RSRC_ID") == "" || r.Header.Get("TARGET_RSRC") == models.EnrollmentKeysRsrc.String() || - r.Header.Get("TARGET_RSRC") == models.UserRsrc.String() { + r.Header.Get("TARGET_RSRC") == models.UserRsrc.String()) { r.Header.Set("IS_GLOBAL_ACCESS", "yes") } handler.ServeHTTP(w, r) diff --git a/logic/security.go b/logic/security.go index 29d9134d4..e67f62f7b 100644 --- a/logic/security.go +++ b/logic/security.go @@ -34,6 +34,13 @@ func networkPermissionsCheck(username string, r *http.Request) error { if err != nil { return err } + userRole, err := GetRole(user.PlatformRoleID) + if err != nil { + return errors.New("access denied") + } + if userRole.FullAccess { + return nil + } // get info from header to determine the target rsrc targetRsrc := r.Header.Get("TARGET_RSRC") targetRsrcID := r.Header.Get("TARGET_RSRC_ID") @@ -47,6 +54,9 @@ func networkPermissionsCheck(username string, r *http.Request) error { if r.Method == "" { r.Method = http.MethodGet } + if targetRsrc == models.MetricRsrc.String() { + return nil + } // check if user has scope for target resource // TODO - differentitate between global scope and network scope apis netRoles := user.NetworkRoles[models.NetworkID(netID)] @@ -123,6 +133,12 @@ func globalPermissionsCheck(username string, r *http.Request) error { if r.Method == "" { r.Method = http.MethodGet } + if targetRsrc == models.MetricRsrc.String() { + return nil + } + if targetRsrc == models.UserRsrc.String() && username == targetRsrcID && (r.Method != http.MethodDelete) { + return nil + } rsrcPermissionScope, ok := userRole.GlobalLevelAccess[models.RsrcType(targetRsrc)] if !ok { return fmt.Errorf("access denied to %s rsrc", targetRsrc) @@ -161,8 +177,8 @@ func SecurityCheck(reqAdmin bool, next http.Handler) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { r.Header.Set("ismaster", "no") - bearerToken := r.Header.Get("Authorization") isGlobalAccesss := r.Header.Get("IS_GLOBAL_ACCESS") == "yes" + bearerToken := r.Header.Get("Authorization") username, err := GetUserNameFromToken(bearerToken) if err != nil { ReturnErrorResponse(w, r, FormatError(err, err.Error())) diff --git a/logic/user_mgmt.go b/logic/user_mgmt.go index 1ce094455..b4530254c 100644 --- a/logic/user_mgmt.go +++ b/logic/user_mgmt.go @@ -235,22 +235,38 @@ func DeleteRole(rid models.UserRole) error { func ValidateCreateGroupReq(g models.UserGroup) error { // check platform role is valid - _, err := GetRole(g.PlatformRole) + role, err := GetRole(g.PlatformRole) if err != nil { err = fmt.Errorf("invalid platform role") return err } + if role.NetworkID != "" { + return errors.New("network role cannot be used as platform role") + } // check if network roles are valid - + for _, roleMap := range g.NetworkRoles { + for roleID := range roleMap { + role, err := GetRole(roleID) + if err != nil { + return fmt.Errorf("invalid network role %s", roleID) + } + if role.NetworkID == "" { + return errors.New("platform role cannot be used as network role") + } + } + } return nil } func ValidateUpdateGroupReq(g models.UserGroup) error { // check platform role is valid - _, err := GetRole(g.PlatformRole) + role, err := GetRole(g.PlatformRole) if err != nil { err = fmt.Errorf("invalid platform role") return err } + if role.NetworkID != "" { + return errors.New("network role cannot be used as platform role") + } for networkID := range g.NetworkRoles { userRolesMap := g.NetworkRoles[networkID] for roleID := range userRolesMap { @@ -259,6 +275,9 @@ func ValidateUpdateGroupReq(g models.UserGroup) error { err = fmt.Errorf("invalid network role") return err } + if role.NetworkID == "" { + return errors.New("platform role cannot be used as network role") + } } } return nil diff --git a/models/user_mgmt.go b/models/user_mgmt.go index 58afe85a1..70c23ca41 100644 --- a/models/user_mgmt.go +++ b/models/user_mgmt.go @@ -48,6 +48,7 @@ const ( AclRsrc RsrcType = "acl" DnsRsrc RsrcType = "dns" FailOverRsrc RsrcType = "fail_over" + MetricRsrc RsrcType = "metrics" ) const ( @@ -90,6 +91,7 @@ type RsrcPermissionScope struct { Update bool `json:"update"` Delete bool `json:"delete"` VPNaccess bool `json:"vpn_access"` + SelfOnly bool `json:"self_only"` } type UserRolePermissionTemplate struct { From b9fb2e7e80c40e11d819ef50d44731afa8357632 Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Fri, 19 Jul 2024 08:36:58 +0530 Subject: [PATCH 081/139] add invite url to invite model --- controllers/user.go | 16 +++++++++------- models/user_mgmt.go | 1 + 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/controllers/user.go b/controllers/user.go index 2a7fd5b0d..0a6945360 100644 --- a/controllers/user.go +++ b/controllers/user.go @@ -1209,6 +1209,13 @@ func inviteUsers(w http.ResponseWriter, r *http.Request) { Groups: inviteReq.Groups, InviteCode: logic.RandomString(8), } + u, err := url.Parse(fmt.Sprintf("%s/invite?email=%s&code=%s", + servercfg.GetFrontendURL(), url.QueryEscape(invite.Email), url.QueryEscape(invite.InviteCode))) + if err != nil { + slog.Error("failed to parse to invite url", "error", err) + return + } + invite.InviteURL = u.String() err = logic.InsertUserInvite(invite) if err != nil { slog.Error("failed to insert invite for user", "email", invite.Email, "error", err) @@ -1216,15 +1223,10 @@ func inviteUsers(w http.ResponseWriter, r *http.Request) { // notify user with magic link go func(invite models.UserInvite) { // Set E-Mail body. You can set plain text or html with text/html - u, err := url.Parse(fmt.Sprintf("%s/invite?email=%s&code=%s", - servercfg.GetFrontendURL(), url.QueryEscape(invite.Email), url.QueryEscape(invite.InviteCode))) - if err != nil { - slog.Error("failed to parse to invite url", "error", err) - return - } + e := email.UserInvitedMail{ BodyBuilder: &email.EmailBodyBuilderWithH1HeadlineAndImage{}, - InviteURL: u.String(), + InviteURL: invite.InviteURL, } n := email.Notification{ RecipientMail: invite.Email, diff --git a/models/user_mgmt.go b/models/user_mgmt.go index 70c23ca41..b68f90deb 100644 --- a/models/user_mgmt.go +++ b/models/user_mgmt.go @@ -159,4 +159,5 @@ type UserInvite struct { Email string `json:"email"` Groups []UserGroupID `json:"groups"` InviteCode string `json:"invite_code"` + InviteURL string `json:"invite_url"` } From 9abc892c5a344b3cffe5ce3a4cda3e0a45697134 Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Mon, 22 Jul 2024 00:00:26 +0530 Subject: [PATCH 082/139] migrate rac apis to new user mgmt --- controllers/ext_client.go | 36 +--------- logic/auth.go | 3 +- logic/gateway.go | 6 +- logic/security.go | 23 ++++++- logic/user_mgmt.go | 78 ++++++++++++++++++++++ pro/controllers/users.go | 134 +++++++++++++++++++++++++++++++++++++- 6 files changed, 236 insertions(+), 44 deletions(-) diff --git a/controllers/ext_client.go b/controllers/ext_client.go index 697e42f2e..e043dfb16 100644 --- a/controllers/ext_client.go +++ b/controllers/ext_client.go @@ -133,14 +133,6 @@ func getExtClient(w http.ResponseWriter, r *http.Request) { logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } - if !logic.IsUserAllowedAccessToExtClient(r.Header.Get("user"), client) { - // check if user has access to extclient - slog.Error("failed to get extclient", "network", network, "clientID", - clientid, "error", errors.New("access is denied")) - logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("access is denied"), "forbidden")) - return - - } w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(client) @@ -171,12 +163,6 @@ func getExtClientConf(w http.ResponseWriter, r *http.Request) { logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } - if !logic.IsUserAllowedAccessToExtClient(r.Header.Get("user"), client) { - slog.Error("failed to get extclient", "network", networkid, "clientID", - clientid, "error", errors.New("access is denied")) - logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("access is denied"), "forbidden")) - return - } gwnode, err := logic.GetNodeByID(client.IngressGatewayID) if err != nil { @@ -414,12 +400,6 @@ func createExtClient(w http.ResponseWriter, r *http.Request) { return } userName = caller.UserName - if _, ok := caller.RemoteGwIDs[nodeid]; (caller.PlatformRoleID != models.AdminRole && caller.PlatformRoleID != models.SuperAdminRole) && !ok { - err = errors.New("permission denied") - slog.Error("failed to create extclient", "error", err) - logic.ReturnErrorResponse(w, r, logic.FormatError(err, "forbidden")) - return - } // check if user has a config already for remote access client extclients, err := logic.GetNetworkExtClients(node.Network) if err != nil { @@ -515,21 +495,12 @@ func updateExtClient(w http.ResponseWriter, r *http.Request) { return } clientid := params["clientid"] - network := params["network"] oldExtClient, err := logic.GetExtClientByName(clientid) if err != nil { slog.Error("failed to retrieve extclient", "user", r.Header.Get("user"), "id", clientid, "error", err) logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } - if !logic.IsUserAllowedAccessToExtClient(r.Header.Get("user"), oldExtClient) { - // check if user has access to extclient - slog.Error("failed to get extclient", "network", network, "clientID", - clientid, "error", errors.New("access is denied")) - logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("access is denied"), "forbidden")) - return - - } if oldExtClient.ClientID == update.ClientID { if err := validateCustomExtClient(&update, false); err != nil { logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) @@ -637,12 +608,7 @@ func deleteExtClient(w http.ResponseWriter, r *http.Request) { logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } - if !logic.IsUserAllowedAccessToExtClient(r.Header.Get("user"), extclient) { - slog.Error("user not allowed to delete", "network", network, "clientID", - clientid, "error", errors.New("access is denied")) - logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("access is denied"), "forbidden")) - return - } + ingressnode, err := logic.GetNodeByID(extclient.IngressGatewayID) if err != nil { logger.Log(0, r.Header.Get("user"), diff --git a/logic/auth.go b/logic/auth.go index 981371ef6..127d4442a 100644 --- a/logic/auth.go +++ b/logic/auth.go @@ -239,7 +239,8 @@ func UpdateUser(userchange, user *models.User) (*models.User, error) { user.Password = userchange.Password } user.PlatformRoleID = userchange.PlatformRoleID - + user.UserGroups = userchange.UserGroups + user.NetworkRoles = userchange.NetworkRoles err := ValidateUser(user) if err != nil { return &models.User{}, err diff --git a/logic/gateway.go b/logic/gateway.go index 326a29a03..98fdac167 100644 --- a/logic/gateway.go +++ b/logic/gateway.go @@ -264,10 +264,8 @@ func IsUserAllowedAccessToExtClient(username string, client models.ExtClient) bo if err != nil { return false } - if user.PlatformRoleID != models.AdminRole && user.PlatformRoleID != models.SuperAdminRole { - if user.UserName != client.OwnerID { - return false - } + if user.UserName != client.OwnerID { + return false } return true } diff --git a/logic/security.go b/logic/security.go index e67f62f7b..ae1b88d65 100644 --- a/logic/security.go +++ b/logic/security.go @@ -7,6 +7,7 @@ import ( "strings" "github.com/gorilla/mux" + "github.com/gravitl/netmaker/logger" "github.com/gravitl/netmaker/models" "github.com/gravitl/netmaker/servercfg" ) @@ -34,6 +35,7 @@ func networkPermissionsCheck(username string, r *http.Request) error { if err != nil { return err } + logger.Log(0, "NET MIDDL----> 1") userRole, err := GetRole(user.PlatformRoleID) if err != nil { return errors.New("access denied") @@ -41,6 +43,7 @@ func networkPermissionsCheck(username string, r *http.Request) error { if userRole.FullAccess { return nil } + logger.Log(0, "NET MIDDL----> 2") // get info from header to determine the target rsrc targetRsrc := r.Header.Get("TARGET_RSRC") targetRsrcID := r.Header.Get("TARGET_RSRC_ID") @@ -61,7 +64,7 @@ func networkPermissionsCheck(username string, r *http.Request) error { // TODO - differentitate between global scope and network scope apis netRoles := user.NetworkRoles[models.NetworkID(netID)] for netRoleID := range netRoles { - err = checkNetworkAccessPermissions(netRoleID, r.Method, targetRsrc, targetRsrcID) + err = checkNetworkAccessPermissions(netRoleID, username, r.Method, targetRsrc, targetRsrcID) if err == nil { return nil } @@ -71,7 +74,7 @@ func networkPermissionsCheck(username string, r *http.Request) error { if err == nil { netRoles := userG.NetworkRoles[models.NetworkID(netID)] for netRoleID := range netRoles { - err = checkNetworkAccessPermissions(netRoleID, r.Method, targetRsrc, targetRsrcID) + err = checkNetworkAccessPermissions(netRoleID, username, r.Method, targetRsrc, targetRsrcID) if err == nil { return nil } @@ -82,11 +85,12 @@ func networkPermissionsCheck(username string, r *http.Request) error { return errors.New("access denied") } -func checkNetworkAccessPermissions(netRoleID models.UserRole, reqScope, targetRsrc, targetRsrcID string) error { +func checkNetworkAccessPermissions(netRoleID models.UserRole, username, reqScope, targetRsrc, targetRsrcID string) error { networkPermissionScope, err := GetRole(netRoleID) if err != nil { return err } + logger.Log(0, "NET MIDDL----> 3", string(netRoleID)) if networkPermissionScope.FullAccess { return nil } @@ -94,13 +98,25 @@ func checkNetworkAccessPermissions(netRoleID models.UserRole, reqScope, targetRs if !ok { return errors.New("access denied") } + logger.Log(0, "NET MIDDL----> 4", string(netRoleID)) if allRsrcsTypePermissionScope, ok := rsrcPermissionScope[models.RsrcID(fmt.Sprintf("all_%s", targetRsrc))]; ok { + // handle extclient apis here + if models.RsrcType(targetRsrc) == models.ExtClientsRsrc && allRsrcsTypePermissionScope.SelfOnly && targetRsrcID != "" { + extclient, err := GetExtClient(targetRsrcID, networkPermissionScope.NetworkID) + if err != nil { + return err + } + if !IsUserAllowedAccessToExtClient(username, extclient) { + return errors.New("access denied") + } + } err = checkPermissionScopeWithReqMethod(allRsrcsTypePermissionScope, reqScope) if err == nil { return nil } } + logger.Log(0, "NET MIDDL----> 5", string(netRoleID)) if targetRsrcID == "" { return errors.New("target rsrc id is empty") } @@ -110,6 +126,7 @@ func checkNetworkAccessPermissions(netRoleID models.UserRole, reqScope, targetRs return nil } } + logger.Log(0, "NET MIDDL----> 6", string(netRoleID)) return errors.New("access denied") } diff --git a/logic/user_mgmt.go b/logic/user_mgmt.go index b4530254c..bcad902e3 100644 --- a/logic/user_mgmt.go +++ b/logic/user_mgmt.go @@ -374,3 +374,81 @@ func HasNetworkRsrcScope(permissionTemplate models.UserRolePermissionTemplate, n _, ok = rsrcScope[rsrcID] return ok } +func GetUserRAGNodes(user models.User) (gws map[string]models.Node) { + gws = make(map[string]models.Node) + userGwAccessScope := GetUserNetworkRolesWithRemoteVPNAccess(user) + _, allNetAccess := userGwAccessScope["*"] + nodes, err := GetAllNodes() + if err != nil { + return + } + for _, node := range nodes { + if node.IsIngressGateway && !node.PendingDelete { + if allNetAccess { + gws[node.ID.String()] = node + } else { + gwRsrcMap := userGwAccessScope[models.NetworkID(node.Network)] + scope, ok := gwRsrcMap[models.AllRemoteAccessGwRsrcID] + if !ok { + if _, ok := gwRsrcMap[models.RsrcID(node.ID.String())]; !ok { + continue + } + } + if scope.VPNaccess { + gws[node.ID.String()] = node + } + + } + } + } + return +} + +// GetUserNetworkRoles - get user network roles +func GetUserNetworkRolesWithRemoteVPNAccess(user models.User) (gwAccess map[models.NetworkID]map[models.RsrcID]models.RsrcPermissionScope) { + gwAccess = make(map[models.NetworkID]map[models.RsrcID]models.RsrcPermissionScope) + platformRole, err := GetRole(user.PlatformRoleID) + if err != nil { + return + } + if platformRole.FullAccess { + gwAccess[models.NetworkID("*")] = make(map[models.RsrcID]models.RsrcPermissionScope) + return + } + for netID, roleMap := range user.NetworkRoles { + for roleID := range roleMap { + role, err := GetRole(roleID) + if err == nil { + if role.FullAccess { + gwAccess[netID] = map[models.RsrcID]models.RsrcPermissionScope{ + models.AllRemoteAccessGwRsrcID: { + Create: true, + Read: true, + VPNaccess: true, + Delete: true, + }, + } + break + } + if rsrcsMap, ok := role.NetworkLevelAccess[models.RemoteAccessGwRsrc]; ok { + if permissions, ok := rsrcsMap[models.AllRemoteAccessGwRsrcID]; ok && permissions.VPNaccess { + if len(gwAccess[netID]) == 0 { + gwAccess[netID] = make(map[models.RsrcID]models.RsrcPermissionScope) + } + gwAccess[netID][models.AllRemoteAccessGwRsrcID] = permissions + break + } else { + for gwID, scope := range rsrcsMap { + if scope.VPNaccess { + gwAccess[netID][gwID] = scope + } + } + } + + } + + } + } + } + return +} diff --git a/pro/controllers/users.go b/pro/controllers/users.go index c303729c6..d1fbb8590 100644 --- a/pro/controllers/users.go +++ b/pro/controllers/users.go @@ -19,7 +19,7 @@ import ( func UserHandlers(r *mux.Router) { r.HandleFunc("/api/users/{username}/remote_access_gw/{remote_access_gateway_id}", logic.SecurityCheck(true, http.HandlerFunc(attachUserToRemoteAccessGw))).Methods(http.MethodPost) r.HandleFunc("/api/users/{username}/remote_access_gw/{remote_access_gateway_id}", logic.SecurityCheck(true, http.HandlerFunc(removeUserFromRemoteAccessGW))).Methods(http.MethodDelete) - r.HandleFunc("/api/users/{username}/remote_access_gw", logic.SecurityCheck(false, logic.ContinueIfUserMatch(http.HandlerFunc(getUserRemoteAccessGws)))).Methods(http.MethodGet) + r.HandleFunc("/api/users/{username}/remote_access_gw", logic.SecurityCheck(false, logic.ContinueIfUserMatch(http.HandlerFunc(getUserRemoteAccessGwsV1)))).Methods(http.MethodGet) r.HandleFunc("/api/users/ingress/{ingress_id}", logic.SecurityCheck(true, http.HandlerFunc(ingressGatewayUsers))).Methods(http.MethodGet) r.HandleFunc("/api/oauth/login", auth.HandleAuthLogin).Methods(http.MethodGet) r.HandleFunc("/api/oauth/callback", auth.HandleAuthCallback).Methods(http.MethodGet) @@ -145,6 +145,138 @@ func removeUserFromRemoteAccessGW(w http.ResponseWriter, r *http.Request) { json.NewEncoder(w).Encode(logic.ToReturnUser(*user)) } +func getUserRemoteAccessGwsV1(w http.ResponseWriter, r *http.Request) { + // set header. + w.Header().Set("Content-Type", "application/json") + + var params = mux.Vars(r) + username := params["username"] + if username == "" { + logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("required params username"), "badrequest")) + return + } + user, err := logic.GetUser(username) + if err != nil { + logger.Log(0, username, "failed to fetch user: ", err.Error()) + logic.ReturnErrorResponse(w, r, logic.FormatError(fmt.Errorf("failed to fetch user %s, error: %v", username, err), "badrequest")) + return + } + remoteAccessClientID := r.URL.Query().Get("remote_access_clientid") + var req models.UserRemoteGwsReq + if remoteAccessClientID == "" { + err := json.NewDecoder(r.Body).Decode(&req) + if err != nil { + slog.Error("error decoding request body: ", "error", err) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) + return + } + } + reqFromMobile := r.URL.Query().Get("from_mobile") == "true" + if req.RemoteAccessClientID == "" && remoteAccessClientID == "" { + logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("remote access client id cannot be empty"), "badrequest")) + return + } + if req.RemoteAccessClientID == "" { + req.RemoteAccessClientID = remoteAccessClientID + } + userGws := make(map[string][]models.UserRemoteGws) + + allextClients, err := logic.GetAllExtClients() + if err != nil { + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) + return + } + userGwNodes := logic.GetUserRAGNodes(*user) + logger.Log(0, fmt.Sprintf("1. User Gw Nodes: %+v", userGwNodes)) + for _, extClient := range allextClients { + node, ok := userGwNodes[extClient.IngressGatewayID] + if !ok { + continue + } + if extClient.RemoteAccessClientID == req.RemoteAccessClientID && extClient.OwnerID == username { + + host, err := logic.GetHost(node.HostID.String()) + if err != nil { + continue + } + network, err := logic.GetNetwork(node.Network) + if err != nil { + slog.Error("failed to get node network", "error", err) + } + + gws := userGws[node.Network] + extClient.AllowedIPs = logic.GetExtclientAllowedIPs(extClient) + gws = append(gws, models.UserRemoteGws{ + GwID: node.ID.String(), + GWName: host.Name, + Network: node.Network, + GwClient: extClient, + Connected: true, + IsInternetGateway: node.IsInternetGateway, + GwPeerPublicKey: host.PublicKey.String(), + GwListenPort: logic.GetPeerListenPort(host), + Metadata: node.Metadata, + AllowedEndpoints: getAllowedRagEndpoints(&node, host), + NetworkAddresses: []string{network.AddressRange, network.AddressRange6}, + }) + userGws[node.Network] = gws + delete(userGwNodes, node.ID.String()) + } + } + logger.Log(0, fmt.Sprintf("2. User Gw Nodes: %+v", userGwNodes)) + // add remaining gw nodes to resp + for gwID := range userGwNodes { + logger.Log(0, "RAG ---> 1") + node, err := logic.GetNodeByID(gwID) + if err != nil { + continue + } + if !node.IsIngressGateway { + continue + } + if node.PendingDelete { + continue + } + logger.Log(0, "RAG ---> 2") + host, err := logic.GetHost(node.HostID.String()) + if err != nil { + continue + } + network, err := logic.GetNetwork(node.Network) + if err != nil { + slog.Error("failed to get node network", "error", err) + } + logger.Log(0, "RAG ---> 3") + gws := userGws[node.Network] + + gws = append(gws, models.UserRemoteGws{ + GwID: node.ID.String(), + GWName: host.Name, + Network: node.Network, + IsInternetGateway: node.IsInternetGateway, + GwPeerPublicKey: host.PublicKey.String(), + GwListenPort: logic.GetPeerListenPort(host), + Metadata: node.Metadata, + AllowedEndpoints: getAllowedRagEndpoints(&node, host), + NetworkAddresses: []string{network.AddressRange, network.AddressRange6}, + }) + userGws[node.Network] = gws + } + + if reqFromMobile { + // send resp in array format + userGwsArr := []models.UserRemoteGws{} + for _, userGwI := range userGws { + userGwsArr = append(userGwsArr, userGwI...) + } + logic.ReturnSuccessResponseWithJson(w, r, userGwsArr, "fetched gateways for user"+username) + return + } + slog.Debug("returned user gws", "user", username, "gws", userGws) + w.WriteHeader(http.StatusOK) + json.NewEncoder(w).Encode(userGws) +} + // swagger:route GET "/api/users/{username}/remote_access_gw" nodes getUserRemoteAccessGws // // Get an individual node. From 76dda15a310c22f253f720d3c87104c6e5fcdd1a Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Tue, 23 Jul 2024 08:48:53 +0530 Subject: [PATCH 083/139] handle network nodes --- controllers/middleware.go | 1 + controllers/node.go | 42 ++++++++++++++++++++++++++++----------- logic/security.go | 12 +++++++++++ 3 files changed, 43 insertions(+), 12 deletions(-) diff --git a/controllers/middleware.go b/controllers/middleware.go index 44d971321..c66c97b1b 100644 --- a/controllers/middleware.go +++ b/controllers/middleware.go @@ -49,6 +49,7 @@ func userMiddleWare(handler http.Handler) http.Handler { } if strings.Contains(r.URL.Path, "metrics") { r.Header.Set("RSRC_TYPE", models.MetricRsrc.String()) + r.Header.Set("TARGET_RSRC", models.MetricRsrc.String()) } if keyID, ok := params["keyID"]; ok { r.Header.Set("TARGET_RSRC_ID", keyID) diff --git a/controllers/node.go b/controllers/node.go index 41d80849b..ad0199e4c 100644 --- a/controllers/node.go +++ b/controllers/node.go @@ -286,24 +286,40 @@ func getNetworkNodes(w http.ResponseWriter, r *http.Request) { logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } - networkRoles := user.NetworkRoles[models.NetworkID(networkName)] - for networkRoleID := range networkRoles { - userPermTemplate, err := logic.GetRole(networkRoleID) - if err != nil { - logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) - return - } - if !userPermTemplate.FullAccess { - filteredNodes := []models.Node{} + userPlatformRole, err := logic.GetRole(user.PlatformRoleID) + if err != nil { + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) + return + } + filteredNodes := []models.Node{} + if !userPlatformRole.FullAccess { + nodesMap := make(map[string]struct{}) + networkRoles := user.NetworkRoles[models.NetworkID(networkName)] + for networkRoleID := range networkRoles { + userPermTemplate, err := logic.GetRole(networkRoleID) + if err != nil { + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) + return + } + if userPermTemplate.FullAccess { + break + } if rsrcPerms, ok := userPermTemplate.NetworkLevelAccess[models.RemoteAccessGwRsrc]; ok { if _, ok := rsrcPerms[models.AllRemoteAccessGwRsrcID]; ok { for _, node := range nodes { + if _, ok := nodesMap[node.ID.String()]; ok { + continue + } if node.IsIngressGateway { + nodesMap[node.ID.String()] = struct{}{} filteredNodes = append(filteredNodes, node) } } } else { for gwID, scope := range rsrcPerms { + if _, ok := nodesMap[gwID.String()]; ok { + continue + } if scope.Read { gwNode, err := logic.GetNodeByID(gwID.String()) if err == nil && gwNode.IsIngressGateway { @@ -313,11 +329,13 @@ func getNetworkNodes(w http.ResponseWriter, r *http.Request) { } } } - nodes = filteredNodes - } else { - break + } } + if len(filteredNodes) > 0 { + nodes = filteredNodes + } + // returns all the nodes in JSON/API format apiNodes := logic.GetAllNodesAPI(nodes[:]) logger.Log(2, r.Header.Get("user"), "fetched nodes on network", networkName) diff --git a/logic/security.go b/logic/security.go index ae1b88d65..8a13d1fd7 100644 --- a/logic/security.go +++ b/logic/security.go @@ -60,6 +60,7 @@ func networkPermissionsCheck(username string, r *http.Request) error { if targetRsrc == models.MetricRsrc.String() { return nil } + // check if user has scope for target resource // TODO - differentitate between global scope and network scope apis netRoles := user.NetworkRoles[models.NetworkID(netID)] @@ -95,6 +96,9 @@ func checkNetworkAccessPermissions(netRoleID models.UserRole, username, reqScope return nil } rsrcPermissionScope, ok := networkPermissionScope.NetworkLevelAccess[models.RsrcType(targetRsrc)] + if targetRsrc == models.HostRsrc.String() && !ok { + rsrcPermissionScope, ok = networkPermissionScope.NetworkLevelAccess[models.RemoteAccessGwRsrc] + } if !ok { return errors.New("access denied") } @@ -116,6 +120,14 @@ func checkNetworkAccessPermissions(netRoleID models.UserRole, username, reqScope } } + if targetRsrc == models.HostRsrc.String() { + if allRsrcsTypePermissionScope, ok := rsrcPermissionScope[models.RsrcID(fmt.Sprintf("all_%s", models.RemoteAccessGwRsrc))]; ok { + err = checkPermissionScopeWithReqMethod(allRsrcsTypePermissionScope, reqScope) + if err == nil { + return nil + } + } + } logger.Log(0, "NET MIDDL----> 5", string(netRoleID)) if targetRsrcID == "" { return errors.New("target rsrc id is empty") From cb940ac68466156e63c39172c5e85f8fc9600638 Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Tue, 23 Jul 2024 09:11:02 +0530 Subject: [PATCH 084/139] add platform user to default role --- logic/user_mgmt.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/logic/user_mgmt.go b/logic/user_mgmt.go index bcad902e3..9efd6a9b2 100644 --- a/logic/user_mgmt.go +++ b/logic/user_mgmt.go @@ -74,6 +74,8 @@ func UserRolesInit() { database.Insert(AdminPermissionTemplate.ID.String(), string(d), database.USER_PERMISSIONS_TABLE_NAME) d, _ = json.Marshal(ServiceUserPermissionTemplate) database.Insert(ServiceUserPermissionTemplate.ID.String(), string(d), database.USER_PERMISSIONS_TABLE_NAME) + d, _ = json.Marshal(PlatformUserUserPermissionTemplate) + database.Insert(PlatformUserUserPermissionTemplate.ID.String(), string(d), database.USER_PERMISSIONS_TABLE_NAME) d, _ = json.Marshal(NetworkAdminPermissionTemplate) database.Insert(NetworkAdminPermissionTemplate.ID.String(), string(d), database.USER_PERMISSIONS_TABLE_NAME) d, _ = json.Marshal(NetworkUserPermissionTemplate) From d617f19cf2ec3471a822d5b3fe76a1e8c07d7a03 Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Tue, 23 Jul 2024 10:16:20 +0530 Subject: [PATCH 085/139] fix user role migration --- logic/user_mgmt.go | 1 + migrate/migrate.go | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/logic/user_mgmt.go b/logic/user_mgmt.go index 9efd6a9b2..3f582bec6 100644 --- a/logic/user_mgmt.go +++ b/logic/user_mgmt.go @@ -62,6 +62,7 @@ var NetworkUserPermissionTemplate = models.UserRolePermissionTemplate{ Update: true, Delete: true, VPNaccess: true, + SelfOnly: true, }, }, }, diff --git a/migrate/migrate.go b/migrate/migrate.go index ab4fbad52..37c608516 100644 --- a/migrate/migrate.go +++ b/migrate/migrate.go @@ -323,13 +323,13 @@ func syncUsers() { h, err := logic.GetHost(networkNodeI.HostID.String()) if err == nil { logic.CreateRole(models.UserRolePermissionTemplate{ - ID: models.UserRole(fmt.Sprintf("net-%s-user-gw-%s", netI.NetID, h.Name)), - DenyDashboardAccess: true, - NetworkID: netI.NetID, + ID: models.UserRole(fmt.Sprintf("net-%s-rag-%s", netI.NetID, h.Name)), + NetworkID: netI.NetID, NetworkLevelAccess: map[models.RsrcType]map[models.RsrcID]models.RsrcPermissionScope{ models.RemoteAccessGwRsrc: { models.RsrcID(networkNodeI.ID.String()): models.RsrcPermissionScope{ VPNaccess: true, + SelfOnly: true, }, }, }, @@ -374,7 +374,7 @@ func syncUsers() { if err != nil { continue } - r, err := logic.GetRole(models.UserRole(fmt.Sprintf("net-%s-user-gw-%s", gwNode.Network, h.Name))) + r, err := logic.GetRole(models.UserRole(fmt.Sprintf("net-%s-rag-%s", gwNode.Network, h.Name))) if err != nil { continue } From 560edcde446cd564beff7482b02e14ab0b2120a5 Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Tue, 23 Jul 2024 15:46:37 +0530 Subject: [PATCH 086/139] add default on rag creation and cleanup after deletion --- controllers/node.go | 40 +++++++++++++++++++++++++++++++++++++--- migrate/migrate.go | 9 +++++++++ 2 files changed, 46 insertions(+), 3 deletions(-) diff --git a/controllers/node.go b/controllers/node.go index ad0199e4c..134bd045c 100644 --- a/controllers/node.go +++ b/controllers/node.go @@ -565,6 +565,33 @@ func createIngressGateway(w http.ResponseWriter, r *http.Request) { logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } + host, err := logic.GetHost(node.HostID.String()) + if err != nil { + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) + return + } + // create network role for this gateway + logic.CreateRole(models.UserRolePermissionTemplate{ + ID: models.UserRole(fmt.Sprintf("net-%s-rag-%s", node.Network, host.Name)), + NetworkID: node.Network, + NetworkLevelAccess: map[models.RsrcType]map[models.RsrcID]models.RsrcPermissionScope{ + models.RemoteAccessGwRsrc: { + models.RsrcID(node.ID.String()): models.RsrcPermissionScope{ + Read: true, + }, + }, + models.ExtClientsRsrc: { + models.AllExtClientsRsrcID: models.RsrcPermissionScope{ + Read: true, + Create: true, + Update: true, + Delete: true, + VPNaccess: true, + SelfOnly: true, + }, + }, + }, + }) apiNode := node.ConvertToAPINode() logger.Log(1, r.Header.Get("user"), "created ingress gateway on node", nodeid, "on network", netid) @@ -606,14 +633,20 @@ func deleteIngressGateway(w http.ResponseWriter, r *http.Request) { logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } - + host, err := logic.GetHost(node.HostID.String()) + if err != nil { + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) + return + } if servercfg.IsPro { go func() { users, err := logic.GetUsersDB() if err == nil { for _, user := range users { - if _, ok := user.RemoteGwIDs[nodeid]; ok { - delete(user.RemoteGwIDs, nodeid) + // delete role from user + if netRoles, ok := user.NetworkRoles[models.NetworkID(node.Network)]; ok { + delete(netRoles, models.UserRole(fmt.Sprintf("net-%s-rag-%s", node.Network, host.Name))) + user.NetworkRoles[models.NetworkID(node.Network)] = netRoles err = logic.UpsertUser(user) if err != nil { slog.Error("failed to get user", "user", user.UserName, "error", err) @@ -623,6 +656,7 @@ func deleteIngressGateway(w http.ResponseWriter, r *http.Request) { } else { slog.Error("failed to get users", "error", err) } + logic.DeleteRole(models.UserRole(fmt.Sprintf("net-%s-rag-%s", node.Network, host.Name))) }() } diff --git a/migrate/migrate.go b/migrate/migrate.go index 37c608516..b72f38b41 100644 --- a/migrate/migrate.go +++ b/migrate/migrate.go @@ -328,6 +328,15 @@ func syncUsers() { NetworkLevelAccess: map[models.RsrcType]map[models.RsrcID]models.RsrcPermissionScope{ models.RemoteAccessGwRsrc: { models.RsrcID(networkNodeI.ID.String()): models.RsrcPermissionScope{ + Read: true, + }, + }, + models.ExtClientsRsrc: { + models.AllExtClientsRsrcID: models.RsrcPermissionScope{ + Read: true, + Create: true, + Update: true, + Delete: true, VPNaccess: true, SelfOnly: true, }, From f6ae4788a1ee5821cb4e63bd52bcce29c45535c4 Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Wed, 24 Jul 2024 16:08:59 +0530 Subject: [PATCH 087/139] fix rac apis --- controllers/middleware.go | 2 +- controllers/node.go | 20 ++++++++++---------- logic/security.go | 3 +++ logic/user_mgmt.go | 34 ++++++++++++++++++++++++++-------- migrate/migrate.go | 18 +++++++++--------- models/user_mgmt.go | 5 +++++ pro/controllers/users.go | 8 ++++++-- 7 files changed, 60 insertions(+), 30 deletions(-) diff --git a/controllers/middleware.go b/controllers/middleware.go index c66c97b1b..d4a3fff1a 100644 --- a/controllers/middleware.go +++ b/controllers/middleware.go @@ -54,7 +54,7 @@ func userMiddleWare(handler http.Handler) http.Handler { if keyID, ok := params["keyID"]; ok { r.Header.Set("TARGET_RSRC_ID", keyID) } - if nodeID, ok := params["nodeid"]; ok { + if nodeID, ok := params["nodeid"]; ok && r.Header.Get("TARGET_RSRC") != models.ExtClientsRsrc.String() { r.Header.Set("TARGET_RSRC_ID", nodeID) } if hostID, ok := params["hostid"]; ok { diff --git a/controllers/node.go b/controllers/node.go index 134bd045c..f8b37ccc8 100644 --- a/controllers/node.go +++ b/controllers/node.go @@ -572,22 +572,22 @@ func createIngressGateway(w http.ResponseWriter, r *http.Request) { } // create network role for this gateway logic.CreateRole(models.UserRolePermissionTemplate{ - ID: models.UserRole(fmt.Sprintf("net-%s-rag-%s", node.Network, host.Name)), + ID: models.GetRAGRoleName(node.Network, host.Name), NetworkID: node.Network, NetworkLevelAccess: map[models.RsrcType]map[models.RsrcID]models.RsrcPermissionScope{ models.RemoteAccessGwRsrc: { models.RsrcID(node.ID.String()): models.RsrcPermissionScope{ - Read: true, + Read: true, + VPNaccess: true, }, }, models.ExtClientsRsrc: { models.AllExtClientsRsrcID: models.RsrcPermissionScope{ - Read: true, - Create: true, - Update: true, - Delete: true, - VPNaccess: true, - SelfOnly: true, + Read: true, + Create: true, + Update: true, + Delete: true, + SelfOnly: true, }, }, }, @@ -645,7 +645,7 @@ func deleteIngressGateway(w http.ResponseWriter, r *http.Request) { for _, user := range users { // delete role from user if netRoles, ok := user.NetworkRoles[models.NetworkID(node.Network)]; ok { - delete(netRoles, models.UserRole(fmt.Sprintf("net-%s-rag-%s", node.Network, host.Name))) + delete(netRoles, models.GetRAGRoleName(node.Network, host.Name)) user.NetworkRoles[models.NetworkID(node.Network)] = netRoles err = logic.UpsertUser(user) if err != nil { @@ -656,7 +656,7 @@ func deleteIngressGateway(w http.ResponseWriter, r *http.Request) { } else { slog.Error("failed to get users", "error", err) } - logic.DeleteRole(models.UserRole(fmt.Sprintf("net-%s-rag-%s", node.Network, host.Name))) + logic.DeleteRole(models.GetRAGRoleName(node.Network, host.Name)) }() } diff --git a/logic/security.go b/logic/security.go index 8a13d1fd7..dbac624a6 100644 --- a/logic/security.go +++ b/logic/security.go @@ -206,10 +206,12 @@ func SecurityCheck(reqAdmin bool, next http.Handler) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { r.Header.Set("ismaster", "no") + logger.Log(0, "next", r.URL.String()) isGlobalAccesss := r.Header.Get("IS_GLOBAL_ACCESS") == "yes" bearerToken := r.Header.Get("Authorization") username, err := GetUserNameFromToken(bearerToken) if err != nil { + logger.Log(0, "next 1", r.URL.String(), err.Error()) ReturnErrorResponse(w, r, FormatError(err, err.Error())) return } @@ -276,6 +278,7 @@ func ContinueIfUserMatch(next http.Handler) http.HandlerFunc { var params = mux.Vars(r) var requestedUser = params["username"] if requestedUser != r.Header.Get("user") { + logger.Log(0, "next 2", r.URL.String(), errorResponse.Message) ReturnErrorResponse(w, r, errorResponse) return } diff --git a/logic/user_mgmt.go b/logic/user_mgmt.go index 3f582bec6..15ce44b80 100644 --- a/logic/user_mgmt.go +++ b/logic/user_mgmt.go @@ -6,6 +6,7 @@ import ( "fmt" "github.com/gravitl/netmaker/database" + "github.com/gravitl/netmaker/logger" "github.com/gravitl/netmaker/models" ) @@ -52,17 +53,17 @@ var NetworkUserPermissionTemplate = models.UserRolePermissionTemplate{ NetworkLevelAccess: map[models.RsrcType]map[models.RsrcID]models.RsrcPermissionScope{ models.RemoteAccessGwRsrc: { models.AllRemoteAccessGwRsrcID: models.RsrcPermissionScope{ - Read: true, + Read: true, + VPNaccess: true, }, }, models.ExtClientsRsrc: { models.AllExtClientsRsrcID: models.RsrcPermissionScope{ - Read: true, - Create: true, - Update: true, - Delete: true, - VPNaccess: true, - SelfOnly: true, + Read: true, + Create: true, + Update: true, + Delete: true, + SelfOnly: true, }, }, }, @@ -378,13 +379,16 @@ func HasNetworkRsrcScope(permissionTemplate models.UserRolePermissionTemplate, n return ok } func GetUserRAGNodes(user models.User) (gws map[string]models.Node) { + logger.Log(0, "------------> 7. getUserRemoteAccessGwsV1") gws = make(map[string]models.Node) userGwAccessScope := GetUserNetworkRolesWithRemoteVPNAccess(user) + logger.Log(0, fmt.Sprintf("User Gw Access Scope: %+v", userGwAccessScope)) _, allNetAccess := userGwAccessScope["*"] nodes, err := GetAllNodes() if err != nil { return } + logger.Log(0, "------------> 8. getUserRemoteAccessGwsV1") for _, node := range nodes { if node.IsIngressGateway && !node.PendingDelete { if allNetAccess { @@ -393,7 +397,7 @@ func GetUserRAGNodes(user models.User) (gws map[string]models.Node) { gwRsrcMap := userGwAccessScope[models.NetworkID(node.Network)] scope, ok := gwRsrcMap[models.AllRemoteAccessGwRsrcID] if !ok { - if _, ok := gwRsrcMap[models.RsrcID(node.ID.String())]; !ok { + if scope, ok = gwRsrcMap[models.RsrcID(node.ID.String())]; !ok { continue } } @@ -404,12 +408,14 @@ func GetUserRAGNodes(user models.User) (gws map[string]models.Node) { } } } + logger.Log(0, "------------> 9. getUserRemoteAccessGwsV1") return } // GetUserNetworkRoles - get user network roles func GetUserNetworkRolesWithRemoteVPNAccess(user models.User) (gwAccess map[models.NetworkID]map[models.RsrcID]models.RsrcPermissionScope) { gwAccess = make(map[models.NetworkID]map[models.RsrcID]models.RsrcPermissionScope) + logger.Log(0, "------------> 7.1 getUserRemoteAccessGwsV1") platformRole, err := GetRole(user.PlatformRoleID) if err != nil { return @@ -418,6 +424,7 @@ func GetUserNetworkRolesWithRemoteVPNAccess(user models.User) (gwAccess map[mode gwAccess[models.NetworkID("*")] = make(map[models.RsrcID]models.RsrcPermissionScope) return } + logger.Log(0, "------------> 7.2 getUserRemoteAccessGwsV1") for netID, roleMap := range user.NetworkRoles { for roleID := range roleMap { role, err := GetRole(roleID) @@ -427,9 +434,16 @@ func GetUserNetworkRolesWithRemoteVPNAccess(user models.User) (gwAccess map[mode models.AllRemoteAccessGwRsrcID: { Create: true, Read: true, + Update: true, VPNaccess: true, Delete: true, }, + models.AllExtClientsRsrcID: { + Create: true, + Read: true, + Update: true, + Delete: true, + }, } break } @@ -443,6 +457,9 @@ func GetUserNetworkRolesWithRemoteVPNAccess(user models.User) (gwAccess map[mode } else { for gwID, scope := range rsrcsMap { if scope.VPNaccess { + if len(gwAccess[netID]) == 0 { + gwAccess[netID] = make(map[models.RsrcID]models.RsrcPermissionScope) + } gwAccess[netID][gwID] = scope } } @@ -453,5 +470,6 @@ func GetUserNetworkRolesWithRemoteVPNAccess(user models.User) (gwAccess map[mode } } } + logger.Log(0, "------------> 7.3 getUserRemoteAccessGwsV1") return } diff --git a/migrate/migrate.go b/migrate/migrate.go index b72f38b41..a4d4600f5 100644 --- a/migrate/migrate.go +++ b/migrate/migrate.go @@ -323,22 +323,22 @@ func syncUsers() { h, err := logic.GetHost(networkNodeI.HostID.String()) if err == nil { logic.CreateRole(models.UserRolePermissionTemplate{ - ID: models.UserRole(fmt.Sprintf("net-%s-rag-%s", netI.NetID, h.Name)), + ID: models.GetRAGRoleName(networkNodeI.Network, h.Name), NetworkID: netI.NetID, NetworkLevelAccess: map[models.RsrcType]map[models.RsrcID]models.RsrcPermissionScope{ models.RemoteAccessGwRsrc: { models.RsrcID(networkNodeI.ID.String()): models.RsrcPermissionScope{ - Read: true, + Read: true, + VPNaccess: true, }, }, models.ExtClientsRsrc: { models.AllExtClientsRsrcID: models.RsrcPermissionScope{ - Read: true, - Create: true, - Update: true, - Delete: true, - VPNaccess: true, - SelfOnly: true, + Read: true, + Create: true, + Update: true, + Delete: true, + SelfOnly: true, }, }, }, @@ -383,7 +383,7 @@ func syncUsers() { if err != nil { continue } - r, err := logic.GetRole(models.UserRole(fmt.Sprintf("net-%s-rag-%s", gwNode.Network, h.Name))) + r, err := logic.GetRole(models.GetRAGRoleName(gwNode.Network, h.Name)) if err != nil { continue } diff --git a/models/user_mgmt.go b/models/user_mgmt.go index b68f90deb..7d61ad26f 100644 --- a/models/user_mgmt.go +++ b/models/user_mgmt.go @@ -1,6 +1,7 @@ package models import ( + "fmt" "time" jwt "github.com/golang-jwt/jwt/v4" @@ -20,6 +21,10 @@ func (rid RsrcID) String() string { return string(rid) } +func GetRAGRoleName(netID, hostName string) UserRole { + return UserRole(fmt.Sprintf("netID-%s-rag-%s", netID, hostName)) +} + var RsrcTypeMap = map[RsrcType]struct{}{ HostRsrc: {}, RelayRsrc: {}, diff --git a/pro/controllers/users.go b/pro/controllers/users.go index d1fbb8590..d21a1446c 100644 --- a/pro/controllers/users.go +++ b/pro/controllers/users.go @@ -148,19 +148,21 @@ func removeUserFromRemoteAccessGW(w http.ResponseWriter, r *http.Request) { func getUserRemoteAccessGwsV1(w http.ResponseWriter, r *http.Request) { // set header. w.Header().Set("Content-Type", "application/json") - + logger.Log(0, "------------> 1. getUserRemoteAccessGwsV1") var params = mux.Vars(r) username := params["username"] if username == "" { logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("required params username"), "badrequest")) return } + logger.Log(0, "------------> 2. getUserRemoteAccessGwsV1") user, err := logic.GetUser(username) if err != nil { logger.Log(0, username, "failed to fetch user: ", err.Error()) logic.ReturnErrorResponse(w, r, logic.FormatError(fmt.Errorf("failed to fetch user %s, error: %v", username, err), "badrequest")) return } + logger.Log(0, "------------> 3. getUserRemoteAccessGwsV1") remoteAccessClientID := r.URL.Query().Get("remote_access_clientid") var req models.UserRemoteGwsReq if remoteAccessClientID == "" { @@ -171,6 +173,7 @@ func getUserRemoteAccessGwsV1(w http.ResponseWriter, r *http.Request) { return } } + logger.Log(0, "------------> 4. getUserRemoteAccessGwsV1") reqFromMobile := r.URL.Query().Get("from_mobile") == "true" if req.RemoteAccessClientID == "" && remoteAccessClientID == "" { logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("remote access client id cannot be empty"), "badrequest")) @@ -180,12 +183,13 @@ func getUserRemoteAccessGwsV1(w http.ResponseWriter, r *http.Request) { req.RemoteAccessClientID = remoteAccessClientID } userGws := make(map[string][]models.UserRemoteGws) - + logger.Log(0, "------------> 5. getUserRemoteAccessGwsV1") allextClients, err := logic.GetAllExtClients() if err != nil { logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } + logger.Log(0, "------------> 6. getUserRemoteAccessGwsV1") userGwNodes := logic.GetUserRAGNodes(*user) logger.Log(0, fmt.Sprintf("1. User Gw Nodes: %+v", userGwNodes)) for _, extClient := range allextClients { From 076fd6302e7b06e6b5a532f6d717a7de374a741f Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Thu, 25 Jul 2024 11:17:19 +0530 Subject: [PATCH 088/139] change to invite code param --- controllers/user.go | 6 +++--- email/email.go | 30 ++++++++++++++++++------------ 2 files changed, 21 insertions(+), 15 deletions(-) diff --git a/controllers/user.go b/controllers/user.go index 0a6945360..46de0ac21 100644 --- a/controllers/user.go +++ b/controllers/user.go @@ -1083,7 +1083,7 @@ func deleteAllPendingUsers(w http.ResponseWriter, r *http.Request) { // 200: ReturnSuccessResponse func userInviteSignUp(w http.ResponseWriter, r *http.Request) { email, _ := url.QueryUnescape(r.URL.Query().Get("email")) - code, _ := url.QueryUnescape(r.URL.Query().Get("code")) + code, _ := url.QueryUnescape(r.URL.Query().Get("invite_code")) in, err := logic.GetUserInvite(email) if err != nil { logger.Log(0, "failed to fetch users: ", err.Error()) @@ -1151,7 +1151,7 @@ func userInviteSignUp(w http.ResponseWriter, r *http.Request) { // 200: ReturnSuccessResponse func userInviteVerify(w http.ResponseWriter, r *http.Request) { email, _ := url.QueryUnescape(r.URL.Query().Get("email")) - code, _ := url.QueryUnescape(r.URL.Query().Get("code")) + code, _ := url.QueryUnescape(r.URL.Query().Get("invite_code")) err := logic.ValidateAndApproveUserInvite(email, code) if err != nil { logger.Log(0, "failed to fetch users: ", err.Error()) @@ -1209,7 +1209,7 @@ func inviteUsers(w http.ResponseWriter, r *http.Request) { Groups: inviteReq.Groups, InviteCode: logic.RandomString(8), } - u, err := url.Parse(fmt.Sprintf("%s/invite?email=%s&code=%s", + u, err := url.Parse(fmt.Sprintf("%s/invite?email=%s&invite_code=%s", servercfg.GetFrontendURL(), url.QueryEscape(invite.Email), url.QueryEscape(invite.InviteCode))) if err != nil { slog.Error("failed to parse to invite url", "error", err) diff --git a/email/email.go b/email/email.go index c8eb2017c..650548420 100644 --- a/email/email.go +++ b/email/email.go @@ -8,11 +8,28 @@ import ( type EmailSenderType string +var client EmailSender + const ( Smtp EmailSenderType = "smtp" Resend EmailSenderType = "resend" ) +func init() { + switch EmailSenderType(servercfg.EmailSenderType()) { + case Smtp: + client = &SmtpSender{ + SmtpHost: servercfg.GetSmtpHost(), + SmtpPort: servercfg.GetSmtpPort(), + SenderEmail: servercfg.GetSenderEmail(), + SenderPass: servercfg.GetEmaiSenderAuth(), + } + case Resend: + client = NewResendEmailSenderFromConfig() + } + client = GetClient() +} + // EmailSender - an interface for sending emails based on notifications and mail templates type EmailSender interface { // SendEmail - sends an email based on a context, notification and mail template @@ -32,16 +49,5 @@ type Notification struct { } func GetClient() (e EmailSender) { - switch EmailSenderType(servercfg.EmailSenderType()) { - case Smtp: - e = &SmtpSender{ - SmtpHost: servercfg.GetSmtpHost(), - SmtpPort: servercfg.GetSmtpPort(), - SenderEmail: servercfg.GetSenderEmail(), - SenderPass: servercfg.GetEmaiSenderAuth(), - } - case Resend: - e = NewResendEmailSenderFromConfig() - } - return + return client } From 16874d49921bab851d7a9e054e43dbaac126b2c0 Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Thu, 25 Jul 2024 23:41:50 +0530 Subject: [PATCH 089/139] filter nodes and hosts based on user network access --- controllers/hosts.go | 43 ++++++++++++++-- controllers/middleware.go | 2 + controllers/network.go | 3 +- controllers/node.go | 12 +++++ controllers/user.go | 9 ++-- logic/auth.go | 4 +- logic/nodes.go | 52 +++++++++++++++++++ logic/security.go | 4 ++ logic/user_mgmt.go | 103 +++++++++++++++++++++++++------------- pro/auth/google.go | 13 ++++- 10 files changed, 197 insertions(+), 48 deletions(-) diff --git a/controllers/hosts.go b/controllers/hosts.go index faf56411d..be44730ba 100644 --- a/controllers/hosts.go +++ b/controllers/hosts.go @@ -64,12 +64,49 @@ func upgradeHost(w http.ResponseWriter, r *http.Request) { // Responses: // 200: apiHostSliceResponse func getHosts(w http.ResponseWriter, r *http.Request) { - currentHosts, err := logic.GetAllHosts() + + currentHosts := []models.Host{} + username := r.Header.Get("user") + user, err := logic.GetUser(username) + if err != nil { + return + } + userPlatformRole, err := logic.GetRole(user.PlatformRoleID) if err != nil { - logger.Log(0, r.Header.Get("user"), "failed to fetch hosts: ", err.Error()) - logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } + + if !userPlatformRole.FullAccess { + nodes, err := logic.GetAllNodes() + if err != nil { + logger.Log(0, "error fetching all nodes info: ", err.Error()) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) + return + } + filteredNodes := logic.GetFilteredNodesByUserAccess(*user, nodes) + if len(filteredNodes) > 0 { + currentHostsMap, err := logic.GetHostsMap() + if err != nil { + logger.Log(0, r.Header.Get("user"), "failed to fetch hosts: ", err.Error()) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) + return + } + for _, node := range filteredNodes { + if host, ok := currentHostsMap[node.HostID.String()]; ok { + currentHosts = append(currentHosts, host) + } + } + + } + } else { + currentHosts, err = logic.GetAllHosts() + if err != nil { + logger.Log(0, r.Header.Get("user"), "failed to fetch hosts: ", err.Error()) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) + return + } + } + apiHosts := logic.GetAllHostsAPI(currentHosts[:]) logger.Log(2, r.Header.Get("user"), "fetched all hosts") logic.SortApiHosts(apiHosts[:]) diff --git a/controllers/middleware.go b/controllers/middleware.go index d4a3fff1a..4ccd9eb3c 100644 --- a/controllers/middleware.go +++ b/controllers/middleware.go @@ -5,6 +5,7 @@ import ( "strings" "github.com/gorilla/mux" + "github.com/gravitl/netmaker/logger" "github.com/gravitl/netmaker/models" ) @@ -78,6 +79,7 @@ func userMiddleWare(handler http.Handler) http.Handler { r.Header.Get("TARGET_RSRC") == models.UserRsrc.String()) { r.Header.Set("IS_GLOBAL_ACCESS", "yes") } + logger.Log(0, "URL ------> ", r.URL.String()) handler.ServeHTTP(w, r) }) } diff --git a/controllers/network.go b/controllers/network.go index 4bd6606d4..4fd471f6f 100644 --- a/controllers/network.go +++ b/controllers/network.go @@ -392,7 +392,7 @@ func deleteNetwork(w http.ResponseWriter, r *http.Request) { logic.ReturnErrorResponse(w, r, logic.FormatError(err, errtype)) return } - + go logic.DeleteNetworkRoles(network) logger.Log(1, r.Header.Get("user"), "deleted network", network) w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode("success") @@ -467,6 +467,7 @@ func createNetwork(w http.ResponseWriter, r *http.Request) { logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) return } + logic.CreateDefaultNetworkRoles(network.NetID) go func() { defaultHosts := logic.GetDefaultHosts() for i := range defaultHosts { diff --git a/controllers/node.go b/controllers/node.go index f8b37ccc8..391843425 100644 --- a/controllers/node.go +++ b/controllers/node.go @@ -365,6 +365,18 @@ func getAllNodes(w http.ResponseWriter, r *http.Request) { logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } + username := r.Header.Get("user") + user, err := logic.GetUser(username) + if err != nil { + return + } + userPlatformRole, err := logic.GetRole(user.PlatformRoleID) + if err != nil { + return + } + if !userPlatformRole.FullAccess { + nodes = logic.GetFilteredNodesByUserAccess(*user, nodes) + } // return all the nodes in JSON/API format apiNodes := logic.GetAllNodesAPI(nodes[:]) diff --git a/controllers/user.go b/controllers/user.go index 46de0ac21..19d388e83 100644 --- a/controllers/user.go +++ b/controllers/user.go @@ -1124,6 +1124,7 @@ func userInviteSignUp(w http.ResponseWriter, r *http.Request) { return } user.PlatformRoleID = userG.PlatformRole + user.UserGroups = make(map[models.UserGroupID]struct{}) user.UserGroups[inviteGroupID] = struct{}{} } if user.PlatformRoleID == "" { @@ -1138,6 +1139,7 @@ func userInviteSignUp(w http.ResponseWriter, r *http.Request) { // delete invite logic.DeleteUserInvite(email) logic.DeletePendingUser(email) + w.Header().Set("Access-Control-Allow-Origin", "*") logic.ReturnSuccessResponse(w, r, "created user successfully "+email) } @@ -1273,11 +1275,10 @@ func listUserInvites(w http.ResponseWriter, r *http.Request) { // Responses: // 200: ReturnSuccessResponse func deleteUserInvite(w http.ResponseWriter, r *http.Request) { - var params = mux.Vars(r) - username := params["invitee_email"] - err := logic.DeleteUserInvite(username) + email, _ := url.QueryUnescape(r.URL.Query().Get("invitee_email")) + err := logic.DeleteUserInvite(email) if err != nil { - logger.Log(0, "failed to delete user invite: ", username, err.Error()) + logger.Log(0, "failed to delete user invite: ", email, err.Error()) logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } diff --git a/logic/auth.go b/logic/auth.go index 127d4442a..2a07e56a3 100644 --- a/logic/auth.go +++ b/logic/auth.go @@ -119,8 +119,8 @@ func CreateUser(user *models.User) error { if len(user.UserGroups) == 0 { user.UserGroups = make(map[models.UserGroupID]struct{}) } - tokenString, _ := CreateUserJWT(user.UserName, user.PlatformRoleID) - if tokenString == "" { + _, err = CreateUserJWT(user.UserName, user.PlatformRoleID) + if err != nil { logger.Log(0, "failed to generate token", err.Error()) return err } diff --git a/logic/nodes.go b/logic/nodes.go index 62f49557c..e3a74d3bc 100644 --- a/logic/nodes.go +++ b/logic/nodes.go @@ -674,3 +674,55 @@ func GetAllFailOvers() ([]models.Node, error) { } return igs, nil } + +func GetFilteredNodesByUserAccess(user models.User, nodes []models.Node) (filteredNodes []models.Node) { + + nodesMap := make(map[string]struct{}) + allNetworkRoles := []models.UserRole{} + for _, netRoles := range user.NetworkRoles { + for netRoleI := range netRoles { + allNetworkRoles = append(allNetworkRoles, netRoleI) + } + } + for _, networkRoleID := range allNetworkRoles { + userPermTemplate, err := GetRole(networkRoleID) + if err != nil { + return + } + networkNodes := GetNetworkNodesMemory(nodes, userPermTemplate.NetworkID) + if userPermTemplate.FullAccess { + for _, node := range networkNodes { + nodesMap[node.ID.String()] = struct{}{} + } + filteredNodes = append(filteredNodes, networkNodes...) + continue + } + if rsrcPerms, ok := userPermTemplate.NetworkLevelAccess[models.RemoteAccessGwRsrc]; ok { + if _, ok := rsrcPerms[models.AllRemoteAccessGwRsrcID]; ok { + for _, node := range networkNodes { + if _, ok := nodesMap[node.ID.String()]; ok { + continue + } + if node.IsIngressGateway { + nodesMap[node.ID.String()] = struct{}{} + filteredNodes = append(filteredNodes, node) + } + } + } else { + for gwID, scope := range rsrcPerms { + if _, ok := nodesMap[gwID.String()]; ok { + continue + } + if scope.Read { + gwNode, err := GetNodeByID(gwID.String()) + if err == nil && gwNode.IsIngressGateway { + filteredNodes = append(filteredNodes, gwNode) + } + } + } + } + } + + } + return +} diff --git a/logic/security.go b/logic/security.go index dbac624a6..d3ee6a30e 100644 --- a/logic/security.go +++ b/logic/security.go @@ -165,6 +165,9 @@ func globalPermissionsCheck(username string, r *http.Request) error { if targetRsrc == models.MetricRsrc.String() { return nil } + if targetRsrc == models.HostRsrc.String() && r.Method == http.MethodGet && targetRsrcID == "" { + return nil + } if targetRsrc == models.UserRsrc.String() && username == targetRsrcID && (r.Method != http.MethodDelete) { return nil } @@ -229,6 +232,7 @@ func SecurityCheck(reqAdmin bool, next http.Handler) http.HandlerFunc { w.Header().Set("TARGET_RSRC_ID", r.Header.Get("TARGET_RSRC_ID")) w.Header().Set("RSRC_TYPE", r.Header.Get("RSRC_TYPE")) w.Header().Set("IS_GLOBAL_ACCESS", r.Header.Get("IS_GLOBAL_ACCESS")) + w.Header().Set("Access-Control-Allow-Origin", "*") if err != nil { w.Header().Set("ACCESS_PERM", err.Error()) ReturnErrorResponse(w, r, FormatError(err, "forbidden")) diff --git a/logic/user_mgmt.go b/logic/user_mgmt.go index 15ce44b80..70d93a6da 100644 --- a/logic/user_mgmt.go +++ b/logic/user_mgmt.go @@ -36,39 +36,6 @@ var PlatformUserUserPermissionTemplate = models.UserRolePermissionTemplate{ FullAccess: false, } -var NetworkAdminPermissionTemplate = models.UserRolePermissionTemplate{ - ID: models.NetworkAdmin, - Default: true, - NetworkID: "netmaker", - FullAccess: true, - NetworkLevelAccess: make(map[models.RsrcType]map[models.RsrcID]models.RsrcPermissionScope), -} - -var NetworkUserPermissionTemplate = models.UserRolePermissionTemplate{ - ID: models.NetworkUser, - Default: true, - FullAccess: false, - NetworkID: "netmaker", - DenyDashboardAccess: false, - NetworkLevelAccess: map[models.RsrcType]map[models.RsrcID]models.RsrcPermissionScope{ - models.RemoteAccessGwRsrc: { - models.AllRemoteAccessGwRsrcID: models.RsrcPermissionScope{ - Read: true, - VPNaccess: true, - }, - }, - models.ExtClientsRsrc: { - models.AllExtClientsRsrcID: models.RsrcPermissionScope{ - Read: true, - Create: true, - Update: true, - Delete: true, - SelfOnly: true, - }, - }, - }, -} - func UserRolesInit() { d, _ := json.Marshal(SuperAdminPermissionTemplate) database.Insert(SuperAdminPermissionTemplate.ID.String(), string(d), database.USER_PERMISSIONS_TABLE_NAME) @@ -78,12 +45,76 @@ func UserRolesInit() { database.Insert(ServiceUserPermissionTemplate.ID.String(), string(d), database.USER_PERMISSIONS_TABLE_NAME) d, _ = json.Marshal(PlatformUserUserPermissionTemplate) database.Insert(PlatformUserUserPermissionTemplate.ID.String(), string(d), database.USER_PERMISSIONS_TABLE_NAME) - d, _ = json.Marshal(NetworkAdminPermissionTemplate) + +} + +func CreateDefaultNetworkRoles(netID string) { + var NetworkAdminPermissionTemplate = models.UserRolePermissionTemplate{ + ID: models.UserRole(fmt.Sprintf("%s_%s", netID, models.NetworkAdmin)), + Default: false, + NetworkID: netID, + FullAccess: true, + NetworkLevelAccess: make(map[models.RsrcType]map[models.RsrcID]models.RsrcPermissionScope), + } + + var NetworkUserPermissionTemplate = models.UserRolePermissionTemplate{ + ID: models.UserRole(fmt.Sprintf("%s_%s", netID, models.NetworkUser)), + Default: false, + FullAccess: false, + NetworkID: netID, + DenyDashboardAccess: false, + NetworkLevelAccess: map[models.RsrcType]map[models.RsrcID]models.RsrcPermissionScope{ + models.RemoteAccessGwRsrc: { + models.AllRemoteAccessGwRsrcID: models.RsrcPermissionScope{ + Read: true, + VPNaccess: true, + }, + }, + models.ExtClientsRsrc: { + models.AllExtClientsRsrcID: models.RsrcPermissionScope{ + Read: true, + Create: true, + Update: true, + Delete: true, + SelfOnly: true, + }, + }, + }, + } + d, _ := json.Marshal(NetworkAdminPermissionTemplate) database.Insert(NetworkAdminPermissionTemplate.ID.String(), string(d), database.USER_PERMISSIONS_TABLE_NAME) d, _ = json.Marshal(NetworkUserPermissionTemplate) database.Insert(NetworkUserPermissionTemplate.ID.String(), string(d), database.USER_PERMISSIONS_TABLE_NAME) } +func DeleteNetworkRoles(netID string) { + users, err := GetUsersDB() + if err != nil { + return + } + for _, user := range users { + if _, ok := user.NetworkRoles[models.NetworkID(netID)]; ok { + delete(user.NetworkRoles, models.NetworkID(netID)) + UpsertUser(user) + } + + } + userGs, _ := ListUserGroups() + for _, userGI := range userGs { + if _, ok := userGI.NetworkRoles[models.NetworkID(netID)]; ok { + delete(userGI.NetworkRoles, models.NetworkID(netID)) + UpdateUserGroup(userGI) + } + } + + roles, _ := ListRoles() + for _, role := range roles { + if role.NetworkID == netID { + DeleteRole(role.ID) + } + } +} + // ListRoles - lists user roles permission templates func ListRoles() ([]models.UserRolePermissionTemplate, error) { data, err := database.FetchRecords(database.USER_PERMISSIONS_TABLE_NAME) @@ -274,12 +305,12 @@ func ValidateUpdateGroupReq(g models.UserGroup) error { for networkID := range g.NetworkRoles { userRolesMap := g.NetworkRoles[networkID] for roleID := range userRolesMap { - _, err := GetRole(roleID) + netRole, err := GetRole(roleID) if err != nil { err = fmt.Errorf("invalid network role") return err } - if role.NetworkID == "" { + if netRole.NetworkID == "" { return errors.New("platform role cannot be used as network role") } } diff --git a/pro/auth/google.go b/pro/auth/google.go index 359ff89f9..295653506 100644 --- a/pro/auth/google.go +++ b/pro/auth/google.go @@ -46,7 +46,7 @@ func handleGoogleLogin(w http.ResponseWriter, r *http.Request) { handleOauthNotConfigured(w) return } - + logger.Log(0, "Setting OAuth State ", oauth_state_string) if err := logic.SetState(oauth_state_string); err != nil { handleOauthNotConfigured(w) return @@ -59,7 +59,7 @@ func handleGoogleLogin(w http.ResponseWriter, r *http.Request) { func handleGoogleCallback(w http.ResponseWriter, r *http.Request) { var rState, rCode = getStateAndCode(r) - + logger.Log(0, "Fetched OAuth State ", rState) var content, err = getGoogleUserInfo(rState, rCode) if err != nil { logger.Log(1, "error when getting user info from google:", err.Error()) @@ -70,26 +70,31 @@ func handleGoogleCallback(w http.ResponseWriter, r *http.Request) { handleOauthNotConfigured(w) return } + logger.Log(0, "CALLBACK ----> 1") if !isEmailAllowed(content.Email) { handleOauthUserNotAllowedToSignUp(w) return } + logger.Log(0, "CALLBACK ----> 2") var inviteExists bool // check if invite exists for User in, err := logic.GetUserInvite(content.Email) if err == nil { inviteExists = true } + logger.Log(0, fmt.Sprintf("CALLBACK ----> 3 %v", inviteExists)) // check if user approval is already pending if !inviteExists && logic.IsPendingUser(content.Email) { handleOauthUserSignUpApprovalPending(w) return } + logger.Log(0, "CALLBACK ----> 4") _, err = logic.GetUser(content.Email) if err != nil { if database.IsEmptyRecord(err) { // user must not exist, so try to make one if inviteExists { // create user + logger.Log(0, "CALLBACK ----> 4.0") var newPass, fetchErr = auth.FetchPassValue("") if fetchErr != nil { logic.ReturnErrorResponse(w, r, logic.FormatError(fetchErr, "internal")) @@ -99,6 +104,7 @@ func handleGoogleCallback(w http.ResponseWriter, r *http.Request) { UserName: content.Email, Password: newPass, } + logger.Log(0, "CALLBACK ----> 4.1") for _, inviteGroupID := range in.Groups { userG, err := logic.GetUserGroup(inviteGroupID) if err != nil { @@ -106,8 +112,10 @@ func handleGoogleCallback(w http.ResponseWriter, r *http.Request) { return } user.PlatformRoleID = userG.PlatformRole + user.UserGroups = make(map[models.UserGroupID]struct{}) user.UserGroups[inviteGroupID] = struct{}{} } + logger.Log(0, "CALLBACK ----> 5") if user.PlatformRoleID == "" { user.PlatformRoleID = models.ServiceUser } @@ -134,6 +142,7 @@ func handleGoogleCallback(w http.ResponseWriter, r *http.Request) { return } } + logger.Log(0, "CALLBACK ----> 6") user, err := logic.GetUser(content.Email) if err != nil { logger.Log(0, "error fetching user: ", err.Error()) From c2134abaefe36dd773c77a408aad94bba1afd33b Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Fri, 26 Jul 2024 00:06:11 +0530 Subject: [PATCH 090/139] extend create user group req to accomodate users --- controllers/user.go | 21 ++++++++++++++++----- models/user_mgmt.go | 5 +++++ 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/controllers/user.go b/controllers/user.go index 19d388e83..e536038d6 100644 --- a/controllers/user.go +++ b/controllers/user.go @@ -128,25 +128,36 @@ func getUserGroup(w http.ResponseWriter, r *http.Request) { // Responses: // 200: userBodyResponse func createUserGroup(w http.ResponseWriter, r *http.Request) { - var userGroup models.UserGroup - err := json.NewDecoder(r.Body).Decode(&userGroup) + var userGroupReq models.CreateGroupReq + err := json.NewDecoder(r.Body).Decode(&userGroupReq.Group) if err != nil { slog.Error("error decoding request body", "error", err.Error()) logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) return } - err = logic.ValidateCreateGroupReq(userGroup) + err = logic.ValidateCreateGroupReq(userGroupReq.Group) if err != nil { logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) return } - err = logic.CreateUserGroup(userGroup) + err = logic.CreateUserGroup(userGroupReq.Group) if err != nil { logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } - logic.ReturnSuccessResponseWithJson(w, r, userGroup, "created user group") + for _, userID := range userGroupReq.Members { + user, err := logic.GetUser(userID) + if err != nil { + continue + } + if len(user.UserGroups) == 0 { + user.UserGroups = make(map[models.UserGroupID]struct{}) + } + user.UserGroups[userGroupReq.Group.ID] = struct{}{} + logic.UpsertUser(*user) + } + logic.ReturnSuccessResponseWithJson(w, r, userGroupReq.Group, "created user group") } // swagger:route PUT /api/v1/user/group user updateUserGroup diff --git a/models/user_mgmt.go b/models/user_mgmt.go index 7d61ad26f..1d8981de1 100644 --- a/models/user_mgmt.go +++ b/models/user_mgmt.go @@ -109,6 +109,11 @@ type UserRolePermissionTemplate struct { GlobalLevelAccess map[RsrcType]map[RsrcID]RsrcPermissionScope `json:"global_level_access"` } +type CreateGroupReq struct { + Group UserGroup `json:"user_group"` + Members []string `json:"members"` +} + type UserGroup struct { ID UserGroupID `json:"id"` PlatformRole UserRole `json:"platform_role"` From 80d9dd6357f6feff2645a37914f4b19b77e335d4 Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Fri, 26 Jul 2024 15:22:14 +0530 Subject: [PATCH 091/139] filter network based on user access --- controllers/middleware.go | 3 +++ controllers/network.go | 40 ++++++++++++++++++++++++++++++++++++++ controllers/user.go | 41 +++++++++++++++++++++++++++++++++++++-- logic/nodes.go | 24 +++++++++++++++++++---- logic/security.go | 2 +- models/user_mgmt.go | 5 +++++ 6 files changed, 108 insertions(+), 7 deletions(-) diff --git a/controllers/middleware.go b/controllers/middleware.go index 4ccd9eb3c..8576d9cc9 100644 --- a/controllers/middleware.go +++ b/controllers/middleware.go @@ -13,6 +13,9 @@ func userMiddleWare(handler http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { var params = mux.Vars(r) r.Header.Set("IS_GLOBAL_ACCESS", "no") + r.Header.Set("TARGET_RSRC", "") + r.Header.Set("RSRC_TYPE", "") + r.Header.Set("TARGET_RSRC_ID", "") r.Header.Set("NET_ID", params["network"]) if strings.Contains(r.URL.Path, "hosts") || strings.Contains(r.URL.Path, "nodes") { r.Header.Set("TARGET_RSRC", models.HostRsrc.String()) diff --git a/controllers/network.go b/controllers/network.go index 4fd471f6f..41c0cf0d3 100644 --- a/controllers/network.go +++ b/controllers/network.go @@ -54,6 +54,46 @@ func getNetworks(w http.ResponseWriter, r *http.Request) { logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } + username := r.Header.Get("user") + user, err := logic.GetUser(username) + if err != nil { + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) + return + } + platformRole, err := logic.GetRole(user.PlatformRoleID) + if err != nil { + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) + return + } + if !platformRole.FullAccess { + allNetworkRoles := make(map[models.NetworkID]struct{}) + if len(user.NetworkRoles) > 0 { + for netID := range user.NetworkRoles { + allNetworkRoles[netID] = struct{}{} + + } + } + if len(user.UserGroups) > 0 { + for userGID := range user.UserGroups { + userG, err := logic.GetUserGroup(userGID) + if err == nil { + if len(userG.NetworkRoles) > 0 { + for netID := range userG.NetworkRoles { + allNetworkRoles[netID] = struct{}{} + + } + } + } + } + } + filteredNetworks := []models.Network{} + for _, networkI := range allnetworks { + if _, ok := allNetworkRoles[models.NetworkID(networkI.NetID)]; ok { + filteredNetworks = append(filteredNetworks, networkI) + } + } + allnetworks = filteredNetworks + } logger.Log(2, r.Header.Get("user"), "fetched networks.") logic.SortNetworks(allnetworks[:]) diff --git a/controllers/user.go b/controllers/user.go index e536038d6..266608eff 100644 --- a/controllers/user.go +++ b/controllers/user.go @@ -34,6 +34,7 @@ func userHandlers(r *mux.Router) { r.HandleFunc("/api/users/{username}", logic.SecurityCheck(true, checkFreeTierLimits(limitChoiceUsers, http.HandlerFunc(createUser)))).Methods(http.MethodPost) r.HandleFunc("/api/users/{username}", logic.SecurityCheck(true, http.HandlerFunc(deleteUser))).Methods(http.MethodDelete) r.HandleFunc("/api/users/{username}", logic.SecurityCheck(false, logic.ContinueIfUserMatch(http.HandlerFunc(getUser)))).Methods(http.MethodGet) + //r.HandleFunc("/api/v1/users/{username}", logic.SecurityCheck(false, logic.ContinueIfUserMatch(http.HandlerFunc(getUserV1)))).Methods(http.MethodGet) r.HandleFunc("/api/users", logic.SecurityCheck(true, http.HandlerFunc(getUsers))).Methods(http.MethodGet) r.HandleFunc("/api/users_pending", logic.SecurityCheck(true, http.HandlerFunc(getPendingUsers))).Methods(http.MethodGet) r.HandleFunc("/api/users_pending", logic.SecurityCheck(true, http.HandlerFunc(deleteAllPendingUsers))).Methods(http.MethodDelete) @@ -42,7 +43,7 @@ func userHandlers(r *mux.Router) { // User Role Handlers r.HandleFunc("/api/v1/users/roles", logic.SecurityCheck(true, http.HandlerFunc(listRoles))).Methods(http.MethodGet) - r.HandleFunc("/api/v1/users/role", logic.SecurityCheck(true, http.HandlerFunc(getRole))).Methods(http.MethodGet) + r.HandleFunc("/api/v1/users/role", getRole).Methods(http.MethodGet) r.HandleFunc("/api/v1/users/role", logic.SecurityCheck(true, http.HandlerFunc(createRole))).Methods(http.MethodPost) r.HandleFunc("/api/v1/users/role", logic.SecurityCheck(true, http.HandlerFunc(updateRole))).Methods(http.MethodPut) r.HandleFunc("/api/v1/users/role", logic.SecurityCheck(true, http.HandlerFunc(deleteRole))).Methods(http.MethodDelete) @@ -129,7 +130,7 @@ func getUserGroup(w http.ResponseWriter, r *http.Request) { // 200: userBodyResponse func createUserGroup(w http.ResponseWriter, r *http.Request) { var userGroupReq models.CreateGroupReq - err := json.NewDecoder(r.Body).Decode(&userGroupReq.Group) + err := json.NewDecoder(r.Body).Decode(&userGroupReq) if err != nil { slog.Error("error decoding request body", "error", err.Error()) @@ -536,6 +537,42 @@ func getUser(w http.ResponseWriter, r *http.Request) { json.NewEncoder(w).Encode(user) } +// swagger:route GET /api/v1/users/{username} user getUser +// +// Get an individual user with role info. +// +// Schemes: https +// +// Security: +// oauth +// +// Responses: +// 200: userBodyResponse +func getUserV1(w http.ResponseWriter, r *http.Request) { + // set header. + w.Header().Set("Content-Type", "application/json") + + var params = mux.Vars(r) + usernameFetched := params["username"] + user, err := logic.GetReturnUser(usernameFetched) + if err != nil { + logger.Log(0, usernameFetched, "failed to fetch user: ", err.Error()) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) + return + } + userRoleTemplate, err := logic.GetRole(user.PlatformRoleID) + if err != nil { + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) + return + } + resp := models.ReturnUserWithRolesAndGroups{ + ReturnUser: user, + PlatformRole: userRoleTemplate, + } + logger.Log(2, r.Header.Get("user"), "fetched user", usernameFetched) + logic.ReturnSuccessResponseWithJson(w, r, resp, "fetched user with role info") +} + // swagger:route GET /api/users user getUsers // // Get all users. diff --git a/logic/nodes.go b/logic/nodes.go index e3a74d3bc..05ba3989c 100644 --- a/logic/nodes.go +++ b/logic/nodes.go @@ -679,15 +679,31 @@ func GetFilteredNodesByUserAccess(user models.User, nodes []models.Node) (filter nodesMap := make(map[string]struct{}) allNetworkRoles := []models.UserRole{} - for _, netRoles := range user.NetworkRoles { - for netRoleI := range netRoles { - allNetworkRoles = append(allNetworkRoles, netRoleI) + if len(user.NetworkRoles) > 0 { + for _, netRoles := range user.NetworkRoles { + for netRoleI := range netRoles { + allNetworkRoles = append(allNetworkRoles, netRoleI) + } + } + } + if len(user.UserGroups) > 0 { + for userGID := range user.UserGroups { + userG, err := GetUserGroup(userGID) + if err == nil { + if len(userG.NetworkRoles) > 0 { + for _, netRoles := range userG.NetworkRoles { + for netRoleI := range netRoles { + allNetworkRoles = append(allNetworkRoles, netRoleI) + } + } + } + } } } for _, networkRoleID := range allNetworkRoles { userPermTemplate, err := GetRole(networkRoleID) if err != nil { - return + continue } networkNodes := GetNetworkNodesMemory(nodes, userPermTemplate.NetworkID) if userPermTemplate.FullAccess { diff --git a/logic/security.go b/logic/security.go index d3ee6a30e..9600f298d 100644 --- a/logic/security.go +++ b/logic/security.go @@ -165,7 +165,7 @@ func globalPermissionsCheck(username string, r *http.Request) error { if targetRsrc == models.MetricRsrc.String() { return nil } - if targetRsrc == models.HostRsrc.String() && r.Method == http.MethodGet && targetRsrcID == "" { + if (targetRsrc == models.HostRsrc.String() || targetRsrc == models.NetworkRsrc.String()) && r.Method == http.MethodGet && targetRsrcID == "" { return nil } if targetRsrc == models.UserRsrc.String() && username == targetRsrcID && (r.Method != http.MethodDelete) { diff --git a/models/user_mgmt.go b/models/user_mgmt.go index 1d8981de1..0c3f391e7 100644 --- a/models/user_mgmt.go +++ b/models/user_mgmt.go @@ -134,6 +134,11 @@ type User struct { LastLoginTime time.Time `json:"last_login_time"` } +type ReturnUserWithRolesAndGroups struct { + ReturnUser + PlatformRole UserRolePermissionTemplate +} + // ReturnUser - return user struct type ReturnUser struct { UserName string `json:"username"` From 9d92fe52c1bffe5409c2e77cbf75ea54107eb764 Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Sun, 28 Jul 2024 07:00:46 +0530 Subject: [PATCH 092/139] format oauth error --- pro/auth/error.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pro/auth/error.go b/pro/auth/error.go index 6a4605635..8d5d196b8 100644 --- a/pro/auth/error.go +++ b/pro/auth/error.go @@ -18,7 +18,7 @@ const oauthStateInvalid = ` const userNotAllowed = ` -

Only administrators can access the Dashboard. Please contact your administrator to elevate your account.

+

Your account does not have access to the dashboard. Please contact your administrator for more information about your account.

Non-Admins can access the netmaker networks using RemoteAccessClient.

From a7a431b36b19314641cceec2bf77270f9f324dd3 Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Mon, 29 Jul 2024 16:41:57 +0530 Subject: [PATCH 093/139] move user roles and groups --- controllers/user.go | 721 +-------------------------------- logic/nodes.go | 68 ---- logic/security.go | 191 +-------- logic/user_mgmt.go | 481 +--------------------- pro/auth/azure-ad.go | 3 +- pro/auth/github.go | 3 +- pro/auth/google.go | 3 +- pro/auth/oidc.go | 3 +- pro/controllers/users.go | 711 +++++++++++++++++++++++++++++++- {email => pro/email}/email.go | 0 {email => pro/email}/invite.go | 0 {email => pro/email}/resend.go | 0 {email => pro/email}/smtp.go | 0 {email => pro/email}/utils.go | 0 pro/initialize.go | 5 + pro/logic/security.go | 186 +++++++++ pro/logic/user_mgmt.go | 560 +++++++++++++++++++++++++ 17 files changed, 1489 insertions(+), 1446 deletions(-) rename {email => pro/email}/email.go (100%) rename {email => pro/email}/invite.go (100%) rename {email => pro/email}/resend.go (100%) rename {email => pro/email}/smtp.go (100%) rename {email => pro/email}/utils.go (100%) create mode 100644 pro/logic/security.go create mode 100644 pro/logic/user_mgmt.go diff --git a/controllers/user.go b/controllers/user.go index 266608eff..86c2172b8 100644 --- a/controllers/user.go +++ b/controllers/user.go @@ -1,7 +1,6 @@ package controller import ( - "context" "encoding/json" "errors" "fmt" @@ -11,8 +10,6 @@ import ( "github.com/gorilla/mux" "github.com/gorilla/websocket" "github.com/gravitl/netmaker/auth" - "github.com/gravitl/netmaker/database" - "github.com/gravitl/netmaker/email" "github.com/gravitl/netmaker/logger" "github.com/gravitl/netmaker/logic" "github.com/gravitl/netmaker/models" @@ -34,336 +31,9 @@ func userHandlers(r *mux.Router) { r.HandleFunc("/api/users/{username}", logic.SecurityCheck(true, checkFreeTierLimits(limitChoiceUsers, http.HandlerFunc(createUser)))).Methods(http.MethodPost) r.HandleFunc("/api/users/{username}", logic.SecurityCheck(true, http.HandlerFunc(deleteUser))).Methods(http.MethodDelete) r.HandleFunc("/api/users/{username}", logic.SecurityCheck(false, logic.ContinueIfUserMatch(http.HandlerFunc(getUser)))).Methods(http.MethodGet) - //r.HandleFunc("/api/v1/users/{username}", logic.SecurityCheck(false, logic.ContinueIfUserMatch(http.HandlerFunc(getUserV1)))).Methods(http.MethodGet) + r.HandleFunc("/api/v1/users", logic.SecurityCheck(false, logic.ContinueIfUserMatch(http.HandlerFunc(getUserV1)))).Methods(http.MethodGet) r.HandleFunc("/api/users", logic.SecurityCheck(true, http.HandlerFunc(getUsers))).Methods(http.MethodGet) - r.HandleFunc("/api/users_pending", logic.SecurityCheck(true, http.HandlerFunc(getPendingUsers))).Methods(http.MethodGet) - r.HandleFunc("/api/users_pending", logic.SecurityCheck(true, http.HandlerFunc(deleteAllPendingUsers))).Methods(http.MethodDelete) - r.HandleFunc("/api/users_pending/user/{username}", logic.SecurityCheck(true, http.HandlerFunc(deletePendingUser))).Methods(http.MethodDelete) - r.HandleFunc("/api/users_pending/user/{username}", logic.SecurityCheck(true, http.HandlerFunc(approvePendingUser))).Methods(http.MethodPost) - // User Role Handlers - r.HandleFunc("/api/v1/users/roles", logic.SecurityCheck(true, http.HandlerFunc(listRoles))).Methods(http.MethodGet) - r.HandleFunc("/api/v1/users/role", getRole).Methods(http.MethodGet) - r.HandleFunc("/api/v1/users/role", logic.SecurityCheck(true, http.HandlerFunc(createRole))).Methods(http.MethodPost) - r.HandleFunc("/api/v1/users/role", logic.SecurityCheck(true, http.HandlerFunc(updateRole))).Methods(http.MethodPut) - r.HandleFunc("/api/v1/users/role", logic.SecurityCheck(true, http.HandlerFunc(deleteRole))).Methods(http.MethodDelete) - - // User Group Handlers - r.HandleFunc("/api/v1/users/groups", logic.SecurityCheck(true, http.HandlerFunc(listUserGroups))).Methods(http.MethodGet) - r.HandleFunc("/api/v1/users/group", logic.SecurityCheck(true, http.HandlerFunc(getUserGroup))).Methods(http.MethodGet) - r.HandleFunc("/api/v1/users/group", logic.SecurityCheck(true, http.HandlerFunc(createUserGroup))).Methods(http.MethodPost) - r.HandleFunc("/api/v1/users/group", logic.SecurityCheck(true, http.HandlerFunc(updateUserGroup))).Methods(http.MethodPut) - r.HandleFunc("/api/v1/users/group", logic.SecurityCheck(true, http.HandlerFunc(deleteUserGroup))).Methods(http.MethodDelete) - - // User Invite Handlers - r.HandleFunc("/api/v1/users/invite", userInviteVerify).Methods(http.MethodGet) - r.HandleFunc("/api/v1/users/invite-signup", userInviteSignUp).Methods(http.MethodPost) - r.HandleFunc("/api/v1/users/invite", logic.SecurityCheck(true, http.HandlerFunc(inviteUsers))).Methods(http.MethodPost) - r.HandleFunc("/api/v1/users/invites", logic.SecurityCheck(true, http.HandlerFunc(listUserInvites))).Methods(http.MethodGet) - r.HandleFunc("/api/v1/users/invite", logic.SecurityCheck(true, http.HandlerFunc(deleteUserInvite))).Methods(http.MethodDelete) - r.HandleFunc("/api/v1/users/invites", logic.SecurityCheck(true, http.HandlerFunc(deleteAllUserInvites))).Methods(http.MethodDelete) - -} - -// swagger:route GET /api/v1/user/groups user listUserGroups -// -// Get all user groups. -// -// Schemes: https -// -// Security: -// oauth -// -// Responses: -// 200: userBodyResponse -func listUserGroups(w http.ResponseWriter, r *http.Request) { - groups, err := logic.ListUserGroups() - if err != nil { - logic.ReturnErrorResponse(w, r, models.ErrorResponse{ - Code: http.StatusInternalServerError, - Message: err.Error(), - }) - return - } - logic.ReturnSuccessResponseWithJson(w, r, groups, "successfully fetched user groups") -} - -// swagger:route GET /api/v1/user/group user getUserGroup -// -// Get user group. -// -// Schemes: https -// -// Security: -// oauth -// -// Responses: -// 200: userBodyResponse -func getUserGroup(w http.ResponseWriter, r *http.Request) { - - gid, _ := url.QueryUnescape(r.URL.Query().Get("group_id")) - if gid == "" { - logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("group id is required"), "badrequest")) - return - } - group, err := logic.GetUserGroup(models.UserGroupID(gid)) - if err != nil { - logic.ReturnErrorResponse(w, r, models.ErrorResponse{ - Code: http.StatusInternalServerError, - Message: err.Error(), - }) - return - } - logic.ReturnSuccessResponseWithJson(w, r, group, "successfully fetched user group") -} - -// swagger:route POST /api/v1/user/group user createUserGroup -// -// Create user groups. -// -// Schemes: https -// -// Security: -// oauth -// -// Responses: -// 200: userBodyResponse -func createUserGroup(w http.ResponseWriter, r *http.Request) { - var userGroupReq models.CreateGroupReq - err := json.NewDecoder(r.Body).Decode(&userGroupReq) - if err != nil { - slog.Error("error decoding request body", "error", - err.Error()) - logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) - return - } - err = logic.ValidateCreateGroupReq(userGroupReq.Group) - if err != nil { - logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) - return - } - err = logic.CreateUserGroup(userGroupReq.Group) - if err != nil { - logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) - return - } - for _, userID := range userGroupReq.Members { - user, err := logic.GetUser(userID) - if err != nil { - continue - } - if len(user.UserGroups) == 0 { - user.UserGroups = make(map[models.UserGroupID]struct{}) - } - user.UserGroups[userGroupReq.Group.ID] = struct{}{} - logic.UpsertUser(*user) - } - logic.ReturnSuccessResponseWithJson(w, r, userGroupReq.Group, "created user group") -} - -// swagger:route PUT /api/v1/user/group user updateUserGroup -// -// Update user group. -// -// Schemes: https -// -// Security: -// oauth -// -// Responses: -// 200: userBodyResponse -func updateUserGroup(w http.ResponseWriter, r *http.Request) { - var userGroup models.UserGroup - err := json.NewDecoder(r.Body).Decode(&userGroup) - if err != nil { - slog.Error("error decoding request body", "error", - err.Error()) - logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) - return - } - err = logic.ValidateUpdateGroupReq(userGroup) - if err != nil { - logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) - return - } - err = logic.UpdateUserGroup(userGroup) - if err != nil { - logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) - return - } - logic.ReturnSuccessResponseWithJson(w, r, userGroup, "updated user group") -} - -// swagger:route DELETE /api/v1/user/group user deleteUserGroup -// -// delete user group. -// -// Schemes: https -// -// Security: -// oauth -// -// Responses: -// 200: userBodyResponse -func deleteUserGroup(w http.ResponseWriter, r *http.Request) { - - gid, _ := url.QueryUnescape(r.URL.Query().Get("group_id")) - if gid == "" { - logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("role is required"), "badrequest")) - return - } - err := logic.DeleteUserGroup(models.UserGroupID(gid)) - if err != nil { - logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) - return - } - logic.ReturnSuccessResponseWithJson(w, r, nil, "deleted user group") -} - -// swagger:route GET /api/v1/user/roles user listRoles -// -// lists all user roles. -// -// Schemes: https -// -// Security: -// oauth -// -// Responses: -// 200: userBodyResponse -func listRoles(w http.ResponseWriter, r *http.Request) { - roles, err := logic.ListRoles() - if err != nil { - logic.ReturnErrorResponse(w, r, models.ErrorResponse{ - Code: http.StatusInternalServerError, - Message: err.Error(), - }) - return - } - logic.ReturnSuccessResponseWithJson(w, r, roles, "successfully fetched user roles permission templates") -} - -// swagger:route GET /api/v1/user/role user getRole -// -// Get user role permission templates. -// -// Schemes: https -// -// Security: -// oauth -// -// Responses: -// 200: userBodyResponse -func getRole(w http.ResponseWriter, r *http.Request) { - rid, _ := url.QueryUnescape(r.URL.Query().Get("role_id")) - if rid == "" { - logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("role is required"), "badrequest")) - return - } - role, err := logic.GetRole(models.UserRole(rid)) - if err != nil { - logic.ReturnErrorResponse(w, r, models.ErrorResponse{ - Code: http.StatusInternalServerError, - Message: err.Error(), - }) - return - } - logic.ReturnSuccessResponseWithJson(w, r, role, "successfully fetched user role permission templates") -} - -// swagger:route POST /api/v1/user/role user createRole -// -// Create user role permission template. -// -// Schemes: https -// -// Security: -// oauth -// -// Responses: -// 200: userBodyResponse -func createRole(w http.ResponseWriter, r *http.Request) { - var userRole models.UserRolePermissionTemplate - err := json.NewDecoder(r.Body).Decode(&userRole) - if err != nil { - slog.Error("error decoding request body", "error", - err.Error()) - logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) - return - } - err = logic.ValidateCreateRoleReq(userRole) - if err != nil { - logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) - return - } - userRole.Default = false - userRole.GlobalLevelAccess = make(map[models.RsrcType]map[models.RsrcID]models.RsrcPermissionScope) - err = logic.CreateRole(userRole) - if err != nil { - logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) - return - } - logic.ReturnSuccessResponseWithJson(w, r, userRole, "created user role") -} - -// swagger:route PUT /api/v1/user/role user updateRole -// -// Update user role permission template. -// -// Schemes: https -// -// Security: -// oauth -// -// Responses: -// 200: userBodyResponse -func updateRole(w http.ResponseWriter, r *http.Request) { - var userRole models.UserRolePermissionTemplate - err := json.NewDecoder(r.Body).Decode(&userRole) - if err != nil { - slog.Error("error decoding request body", "error", - err.Error()) - logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) - return - } - err = logic.ValidateUpdateRoleReq(userRole) - if err != nil { - logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) - return - } - userRole.GlobalLevelAccess = make(map[models.RsrcType]map[models.RsrcID]models.RsrcPermissionScope) - err = logic.UpdateRole(userRole) - if err != nil { - logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) - return - } - logic.ReturnSuccessResponseWithJson(w, r, userRole, "updated user role") -} - -// swagger:route DELETE /api/v1/user/role user deleteRole -// -// Delete user role permission template. -// -// Schemes: https -// -// Security: -// oauth -// -// Responses: -// 200: userBodyResponse -func deleteRole(w http.ResponseWriter, r *http.Request) { - - rid, _ := url.QueryUnescape(r.URL.Query().Get("role_id")) - if rid == "" { - logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("role is required"), "badrequest")) - return - } - err := logic.DeleteRole(models.UserRole(rid)) - if err != nil { - logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) - return - } - logic.ReturnSuccessResponseWithJson(w, r, nil, "created user role") } // swagger:route POST /api/users/adm/authenticate authenticate authenticateUser @@ -537,7 +207,7 @@ func getUser(w http.ResponseWriter, r *http.Request) { json.NewEncoder(w).Encode(user) } -// swagger:route GET /api/v1/users/{username} user getUser +// swagger:route GET /api/v1/users user getUserV1 // // Get an individual user with role info. // @@ -547,13 +217,15 @@ func getUser(w http.ResponseWriter, r *http.Request) { // oauth // // Responses: -// 200: userBodyResponse +// 200: ReturnUserWithRolesAndGroups func getUserV1(w http.ResponseWriter, r *http.Request) { // set header. w.Header().Set("Content-Type", "application/json") - - var params = mux.Vars(r) - usernameFetched := params["username"] + usernameFetched, _ := url.QueryUnescape(r.URL.Query().Get("username")) + if usernameFetched == "" { + logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("username is required"), "badrequest")) + return + } user, err := logic.GetReturnUser(usernameFetched) if err != nil { logger.Log(0, usernameFetched, "failed to fetch user: ", err.Error()) @@ -721,18 +393,7 @@ func createUser(w http.ResponseWriter, r *http.Request) { logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) return } - uniqueGroupsPlatformRole := make(map[models.UserRole]struct{}) - for groupID := range user.UserGroups { - userG, err := logic.GetUserGroup(groupID) - if err != nil { - logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) - return - } - uniqueGroupsPlatformRole[userG.PlatformRole] = struct{}{} - user.PlatformRoleID = userG.PlatformRole - } - if len(uniqueGroupsPlatformRole) > 1 { - err = errors.New("only groups with same platform role can be assigned to an user") + if err = logic.IsGroupsValid(user.UserGroups); err != nil { logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) return } @@ -988,367 +649,3 @@ func socketHandler(w http.ResponseWriter, r *http.Request) { // Start handling the session go auth.SessionHandler(conn) } - -// swagger:route GET /api/users_pending user getPendingUsers -// -// Get all pending users. -// -// Schemes: https -// -// Security: -// oauth -// -// Responses: -// 200: userBodyResponse -func getPendingUsers(w http.ResponseWriter, r *http.Request) { - // set header. - w.Header().Set("Content-Type", "application/json") - - users, err := logic.ListPendingUsers() - if err != nil { - logger.Log(0, "failed to fetch users: ", err.Error()) - logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) - return - } - - logic.SortUsers(users[:]) - logger.Log(2, r.Header.Get("user"), "fetched pending users") - json.NewEncoder(w).Encode(users) -} - -// swagger:route POST /api/users_pending/user/{username} user approvePendingUser -// -// approve pending user. -// -// Schemes: https -// -// Security: -// oauth -// -// Responses: -// 200: userBodyResponse -func approvePendingUser(w http.ResponseWriter, r *http.Request) { - // set header. - w.Header().Set("Content-Type", "application/json") - var params = mux.Vars(r) - username := params["username"] - users, err := logic.ListPendingUsers() - - if err != nil { - logger.Log(0, "failed to fetch users: ", err.Error()) - logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) - return - } - for _, user := range users { - if user.UserName == username { - var newPass, fetchErr = auth.FetchPassValue("") - if fetchErr != nil { - logic.ReturnErrorResponse(w, r, logic.FormatError(fetchErr, "internal")) - return - } - if err = logic.CreateUser(&models.User{ - UserName: user.UserName, - Password: newPass, - }); err != nil { - logic.ReturnErrorResponse(w, r, logic.FormatError(fmt.Errorf("failed to create user: %s", err), "internal")) - return - } - err = logic.DeletePendingUser(username) - if err != nil { - logic.ReturnErrorResponse(w, r, logic.FormatError(fmt.Errorf("failed to delete pending user: %s", err), "internal")) - return - } - break - } - } - logic.ReturnSuccessResponse(w, r, "approved "+username) -} - -// swagger:route DELETE /api/users_pending/user/{username} user deletePendingUser -// -// delete pending user. -// -// Schemes: https -// -// Security: -// oauth -// -// Responses: -// 200: userBodyResponse -func deletePendingUser(w http.ResponseWriter, r *http.Request) { - // set header. - w.Header().Set("Content-Type", "application/json") - var params = mux.Vars(r) - username := params["username"] - users, err := logic.ListPendingUsers() - - if err != nil { - logger.Log(0, "failed to fetch users: ", err.Error()) - logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) - return - } - for _, user := range users { - if user.UserName == username { - err = logic.DeletePendingUser(username) - if err != nil { - logic.ReturnErrorResponse(w, r, logic.FormatError(fmt.Errorf("failed to delete pending user: %s", err), "internal")) - return - } - break - } - } - logic.ReturnSuccessResponse(w, r, "deleted pending "+username) -} - -// swagger:route DELETE /api/users_pending/{username}/pending user deleteAllPendingUsers -// -// delete all pending users. -// -// Schemes: https -// -// Security: -// oauth -// -// Responses: -// 200: userBodyResponse -func deleteAllPendingUsers(w http.ResponseWriter, r *http.Request) { - // set header. - err := database.DeleteAllRecords(database.PENDING_USERS_TABLE_NAME) - if err != nil { - logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("failed to delete all pending users "+err.Error()), "internal")) - return - } - logic.ReturnSuccessResponse(w, r, "cleared all pending users") -} - -// swagger:route POST /api/v1/users/invite-signup user userInviteSignUp -// -// user signup via invite. -// -// Schemes: https -// -// Responses: -// 200: ReturnSuccessResponse -func userInviteSignUp(w http.ResponseWriter, r *http.Request) { - email, _ := url.QueryUnescape(r.URL.Query().Get("email")) - code, _ := url.QueryUnescape(r.URL.Query().Get("invite_code")) - in, err := logic.GetUserInvite(email) - if err != nil { - logger.Log(0, "failed to fetch users: ", err.Error()) - logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) - return - } - if code != in.InviteCode { - logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("invalid invite code"), "badrequest")) - return - } - // check if user already exists - _, err = logic.GetUser(email) - if err == nil { - logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("user already exists"), "badrequest")) - return - } - var user models.User - err = json.NewDecoder(r.Body).Decode(&user) - if err != nil { - logger.Log(0, user.UserName, "error decoding request body: ", - err.Error()) - logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) - return - } - if user.UserName != email { - logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("username not matching with invite"), "badrequest")) - return - } - if user.Password == "" { - logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("password cannot be empty"), "badrequest")) - return - } - - for _, inviteGroupID := range in.Groups { - userG, err := logic.GetUserGroup(inviteGroupID) - if err != nil { - logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("error fetching group id "+inviteGroupID.String()), "badrequest")) - return - } - user.PlatformRoleID = userG.PlatformRole - user.UserGroups = make(map[models.UserGroupID]struct{}) - user.UserGroups[inviteGroupID] = struct{}{} - } - if user.PlatformRoleID == "" { - user.PlatformRoleID = models.ServiceUser - } - user.NetworkRoles = make(map[models.NetworkID]map[models.UserRole]struct{}) - err = logic.CreateUser(&user) - if err != nil { - logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) - return - } - // delete invite - logic.DeleteUserInvite(email) - logic.DeletePendingUser(email) - w.Header().Set("Access-Control-Allow-Origin", "*") - logic.ReturnSuccessResponse(w, r, "created user successfully "+email) -} - -// swagger:route GET /api/v1/users/invite user userInviteVerify -// -// verfies user invite. -// -// Schemes: https -// -// Responses: -// 200: ReturnSuccessResponse -func userInviteVerify(w http.ResponseWriter, r *http.Request) { - email, _ := url.QueryUnescape(r.URL.Query().Get("email")) - code, _ := url.QueryUnescape(r.URL.Query().Get("invite_code")) - err := logic.ValidateAndApproveUserInvite(email, code) - if err != nil { - logger.Log(0, "failed to fetch users: ", err.Error()) - logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) - return - } - logic.ReturnSuccessResponse(w, r, "invite is valid") -} - -// swagger:route POST /api/v1/users/invite user inviteUsers -// -// invite users. -// -// Schemes: https -// -// Security: -// oauth -// -// Responses: -// 200: userBodyResponse -func inviteUsers(w http.ResponseWriter, r *http.Request) { - var inviteReq models.InviteUsersReq - err := json.NewDecoder(r.Body).Decode(&inviteReq) - if err != nil { - slog.Error("error decoding request body", "error", - err.Error()) - logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) - return - } - //validate Req - uniqueGroupsPlatformRole := make(map[models.UserRole]struct{}) - for _, groupID := range inviteReq.Groups { - userG, err := logic.GetUserGroup(groupID) - if err != nil { - logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) - return - } - uniqueGroupsPlatformRole[userG.PlatformRole] = struct{}{} - } - if len(uniqueGroupsPlatformRole) > 1 { - err = errors.New("only groups with same platform role can be assigned to an user") - logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) - return - } - - for _, inviteeEmail := range inviteReq.UserEmails { - // check if user with email exists, then ignore - _, err := logic.GetUser(inviteeEmail) - if err == nil { - // user exists already, so ignore - continue - } - invite := models.UserInvite{ - Email: inviteeEmail, - Groups: inviteReq.Groups, - InviteCode: logic.RandomString(8), - } - u, err := url.Parse(fmt.Sprintf("%s/invite?email=%s&invite_code=%s", - servercfg.GetFrontendURL(), url.QueryEscape(invite.Email), url.QueryEscape(invite.InviteCode))) - if err != nil { - slog.Error("failed to parse to invite url", "error", err) - return - } - invite.InviteURL = u.String() - err = logic.InsertUserInvite(invite) - if err != nil { - slog.Error("failed to insert invite for user", "email", invite.Email, "error", err) - } - // notify user with magic link - go func(invite models.UserInvite) { - // Set E-Mail body. You can set plain text or html with text/html - - e := email.UserInvitedMail{ - BodyBuilder: &email.EmailBodyBuilderWithH1HeadlineAndImage{}, - InviteURL: invite.InviteURL, - } - n := email.Notification{ - RecipientMail: invite.Email, - } - err = email.GetClient().SendEmail(context.Background(), n, e) - if err != nil { - slog.Error("failed to send email invite", "user", invite.Email, "error", err) - } - }(invite) - } - -} - -// swagger:route GET /api/v1/users/invites user listUserInvites -// -// lists all pending invited users. -// -// Schemes: https -// -// Security: -// oauth -// -// Responses: -// 200: ReturnSuccessResponseWithJson -func listUserInvites(w http.ResponseWriter, r *http.Request) { - usersInvites, err := logic.ListUserInvites() - if err != nil { - logger.Log(0, "failed to fetch users: ", err.Error()) - logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) - return - } - logic.ReturnSuccessResponseWithJson(w, r, usersInvites, "fetched pending user invites") -} - -// swagger:route DELETE /api/v1/users/invite user deleteUserInvite -// -// delete pending invite. -// -// Schemes: https -// -// Security: -// oauth -// -// Responses: -// 200: ReturnSuccessResponse -func deleteUserInvite(w http.ResponseWriter, r *http.Request) { - email, _ := url.QueryUnescape(r.URL.Query().Get("invitee_email")) - err := logic.DeleteUserInvite(email) - if err != nil { - logger.Log(0, "failed to delete user invite: ", email, err.Error()) - logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) - return - } - logic.ReturnSuccessResponse(w, r, "deleted user invite") -} - -// swagger:route DELETE /api/v1/users/invites user deleteAllUserInvites -// -// deletes all pending invites. -// -// Schemes: https -// -// Security: -// oauth -// -// Responses: -// 200: ReturnSuccessResponse -func deleteAllUserInvites(w http.ResponseWriter, r *http.Request) { - err := database.DeleteAllRecords(database.USER_INVITES_TABLE_NAME) - if err != nil { - logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("failed to delete all pending user invites "+err.Error()), "internal")) - return - } - logic.ReturnSuccessResponse(w, r, "cleared all pending user invites") -} diff --git a/logic/nodes.go b/logic/nodes.go index 05ba3989c..62f49557c 100644 --- a/logic/nodes.go +++ b/logic/nodes.go @@ -674,71 +674,3 @@ func GetAllFailOvers() ([]models.Node, error) { } return igs, nil } - -func GetFilteredNodesByUserAccess(user models.User, nodes []models.Node) (filteredNodes []models.Node) { - - nodesMap := make(map[string]struct{}) - allNetworkRoles := []models.UserRole{} - if len(user.NetworkRoles) > 0 { - for _, netRoles := range user.NetworkRoles { - for netRoleI := range netRoles { - allNetworkRoles = append(allNetworkRoles, netRoleI) - } - } - } - if len(user.UserGroups) > 0 { - for userGID := range user.UserGroups { - userG, err := GetUserGroup(userGID) - if err == nil { - if len(userG.NetworkRoles) > 0 { - for _, netRoles := range userG.NetworkRoles { - for netRoleI := range netRoles { - allNetworkRoles = append(allNetworkRoles, netRoleI) - } - } - } - } - } - } - for _, networkRoleID := range allNetworkRoles { - userPermTemplate, err := GetRole(networkRoleID) - if err != nil { - continue - } - networkNodes := GetNetworkNodesMemory(nodes, userPermTemplate.NetworkID) - if userPermTemplate.FullAccess { - for _, node := range networkNodes { - nodesMap[node.ID.String()] = struct{}{} - } - filteredNodes = append(filteredNodes, networkNodes...) - continue - } - if rsrcPerms, ok := userPermTemplate.NetworkLevelAccess[models.RemoteAccessGwRsrc]; ok { - if _, ok := rsrcPerms[models.AllRemoteAccessGwRsrcID]; ok { - for _, node := range networkNodes { - if _, ok := nodesMap[node.ID.String()]; ok { - continue - } - if node.IsIngressGateway { - nodesMap[node.ID.String()] = struct{}{} - filteredNodes = append(filteredNodes, node) - } - } - } else { - for gwID, scope := range rsrcPerms { - if _, ok := nodesMap[gwID.String()]; ok { - continue - } - if scope.Read { - gwNode, err := GetNodeByID(gwID.String()) - if err == nil && gwNode.IsIngressGateway { - filteredNodes = append(filteredNodes, gwNode) - } - } - } - } - } - - } - return -} diff --git a/logic/security.go b/logic/security.go index 9600f298d..b16e808c8 100644 --- a/logic/security.go +++ b/logic/security.go @@ -1,8 +1,6 @@ package logic import ( - "errors" - "fmt" "net/http" "strings" @@ -20,189 +18,8 @@ const ( Unauthorized_Err = models.Error(Unauthorized_Msg) ) -func GetSubjectsFromURL(URL string) (rsrcType models.RsrcType, rsrcID models.RsrcID) { - urlSplit := strings.Split(URL, "/") - rsrcType = models.RsrcType(urlSplit[1]) - if len(urlSplit) > 1 { - rsrcID = models.RsrcID(urlSplit[2]) - } - return -} - -func networkPermissionsCheck(username string, r *http.Request) error { - // at this point global checks should be completed - user, err := GetUser(username) - if err != nil { - return err - } - logger.Log(0, "NET MIDDL----> 1") - userRole, err := GetRole(user.PlatformRoleID) - if err != nil { - return errors.New("access denied") - } - if userRole.FullAccess { - return nil - } - logger.Log(0, "NET MIDDL----> 2") - // get info from header to determine the target rsrc - targetRsrc := r.Header.Get("TARGET_RSRC") - targetRsrcID := r.Header.Get("TARGET_RSRC_ID") - netID := r.Header.Get("NET_ID") - if targetRsrc == "" { - return errors.New("target rsrc is missing") - } - if netID == "" { - return errors.New("network id is missing") - } - if r.Method == "" { - r.Method = http.MethodGet - } - if targetRsrc == models.MetricRsrc.String() { - return nil - } - - // check if user has scope for target resource - // TODO - differentitate between global scope and network scope apis - netRoles := user.NetworkRoles[models.NetworkID(netID)] - for netRoleID := range netRoles { - err = checkNetworkAccessPermissions(netRoleID, username, r.Method, targetRsrc, targetRsrcID) - if err == nil { - return nil - } - } - for groupID := range user.UserGroups { - userG, err := GetUserGroup(groupID) - if err == nil { - netRoles := userG.NetworkRoles[models.NetworkID(netID)] - for netRoleID := range netRoles { - err = checkNetworkAccessPermissions(netRoleID, username, r.Method, targetRsrc, targetRsrcID) - if err == nil { - return nil - } - } - } - } - - return errors.New("access denied") -} - -func checkNetworkAccessPermissions(netRoleID models.UserRole, username, reqScope, targetRsrc, targetRsrcID string) error { - networkPermissionScope, err := GetRole(netRoleID) - if err != nil { - return err - } - logger.Log(0, "NET MIDDL----> 3", string(netRoleID)) - if networkPermissionScope.FullAccess { - return nil - } - rsrcPermissionScope, ok := networkPermissionScope.NetworkLevelAccess[models.RsrcType(targetRsrc)] - if targetRsrc == models.HostRsrc.String() && !ok { - rsrcPermissionScope, ok = networkPermissionScope.NetworkLevelAccess[models.RemoteAccessGwRsrc] - } - if !ok { - return errors.New("access denied") - } - logger.Log(0, "NET MIDDL----> 4", string(netRoleID)) - if allRsrcsTypePermissionScope, ok := rsrcPermissionScope[models.RsrcID(fmt.Sprintf("all_%s", targetRsrc))]; ok { - // handle extclient apis here - if models.RsrcType(targetRsrc) == models.ExtClientsRsrc && allRsrcsTypePermissionScope.SelfOnly && targetRsrcID != "" { - extclient, err := GetExtClient(targetRsrcID, networkPermissionScope.NetworkID) - if err != nil { - return err - } - if !IsUserAllowedAccessToExtClient(username, extclient) { - return errors.New("access denied") - } - } - err = checkPermissionScopeWithReqMethod(allRsrcsTypePermissionScope, reqScope) - if err == nil { - return nil - } - - } - if targetRsrc == models.HostRsrc.String() { - if allRsrcsTypePermissionScope, ok := rsrcPermissionScope[models.RsrcID(fmt.Sprintf("all_%s", models.RemoteAccessGwRsrc))]; ok { - err = checkPermissionScopeWithReqMethod(allRsrcsTypePermissionScope, reqScope) - if err == nil { - return nil - } - } - } - logger.Log(0, "NET MIDDL----> 5", string(netRoleID)) - if targetRsrcID == "" { - return errors.New("target rsrc id is empty") - } - if scope, ok := rsrcPermissionScope[models.RsrcID(targetRsrcID)]; ok { - err = checkPermissionScopeWithReqMethod(scope, reqScope) - if err == nil { - return nil - } - } - logger.Log(0, "NET MIDDL----> 6", string(netRoleID)) - return errors.New("access denied") -} - -func globalPermissionsCheck(username string, r *http.Request) error { - user, err := GetUser(username) - if err != nil { - return err - } - userRole, err := GetRole(user.PlatformRoleID) - if err != nil { - return errors.New("access denied") - } - if userRole.FullAccess { - return nil - } - targetRsrc := r.Header.Get("TARGET_RSRC") - targetRsrcID := r.Header.Get("TARGET_RSRC_ID") - if targetRsrc == "" { - return errors.New("target rsrc is missing") - } - if r.Method == "" { - r.Method = http.MethodGet - } - if targetRsrc == models.MetricRsrc.String() { - return nil - } - if (targetRsrc == models.HostRsrc.String() || targetRsrc == models.NetworkRsrc.String()) && r.Method == http.MethodGet && targetRsrcID == "" { - return nil - } - if targetRsrc == models.UserRsrc.String() && username == targetRsrcID && (r.Method != http.MethodDelete) { - return nil - } - rsrcPermissionScope, ok := userRole.GlobalLevelAccess[models.RsrcType(targetRsrc)] - if !ok { - return fmt.Errorf("access denied to %s rsrc", targetRsrc) - } - if allRsrcsTypePermissionScope, ok := rsrcPermissionScope[models.RsrcID(fmt.Sprintf("all_%s", targetRsrc))]; ok { - return checkPermissionScopeWithReqMethod(allRsrcsTypePermissionScope, r.Method) - - } - if targetRsrcID == "" { - return errors.New("target rsrc id is missing") - } - if scope, ok := rsrcPermissionScope[models.RsrcID(targetRsrcID)]; ok { - return checkPermissionScopeWithReqMethod(scope, r.Method) - } - return errors.New("access denied") -} - -func checkPermissionScopeWithReqMethod(scope models.RsrcPermissionScope, reqmethod string) error { - if reqmethod == http.MethodGet && scope.Read { - return nil - } - if (reqmethod == http.MethodPatch || reqmethod == http.MethodPut) && scope.Update { - return nil - } - if reqmethod == http.MethodDelete && scope.Delete { - return nil - } - if reqmethod == http.MethodPost && scope.Create { - return nil - } - return errors.New("operation not permitted") -} +var NetworkPermissionsCheck = func(username string, r *http.Request) error { return nil } +var GlobalPermissionsCheck = func(username string, r *http.Request) error { return nil } // SecurityCheck - Check if user has appropriate permissions func SecurityCheck(reqAdmin bool, next http.Handler) http.HandlerFunc { @@ -223,9 +40,9 @@ func SecurityCheck(reqAdmin bool, next http.Handler) http.HandlerFunc { r.Header.Set("ismaster", "yes") } else { if isGlobalAccesss { - err = globalPermissionsCheck(username, r) + err = GlobalPermissionsCheck(username, r) } else { - err = networkPermissionsCheck(username, r) + err = NetworkPermissionsCheck(username, r) } } w.Header().Set("TARGET_RSRC", r.Header.Get("TARGET_RSRC")) diff --git a/logic/user_mgmt.go b/logic/user_mgmt.go index 70d93a6da..262275f65 100644 --- a/logic/user_mgmt.go +++ b/logic/user_mgmt.go @@ -3,192 +3,19 @@ package logic import ( "encoding/json" "errors" - "fmt" "github.com/gravitl/netmaker/database" - "github.com/gravitl/netmaker/logger" "github.com/gravitl/netmaker/models" ) -// Pre-Define Permission Templates for default Roles -var SuperAdminPermissionTemplate = models.UserRolePermissionTemplate{ - ID: models.SuperAdminRole, - Default: true, - FullAccess: true, -} - -var AdminPermissionTemplate = models.UserRolePermissionTemplate{ - ID: models.AdminRole, - Default: true, - FullAccess: true, -} - -var ServiceUserPermissionTemplate = models.UserRolePermissionTemplate{ - ID: models.ServiceUser, - Default: true, - FullAccess: false, - DenyDashboardAccess: true, -} - -var PlatformUserUserPermissionTemplate = models.UserRolePermissionTemplate{ - ID: models.PlatformUser, - Default: true, - FullAccess: false, -} - -func UserRolesInit() { - d, _ := json.Marshal(SuperAdminPermissionTemplate) - database.Insert(SuperAdminPermissionTemplate.ID.String(), string(d), database.USER_PERMISSIONS_TABLE_NAME) - d, _ = json.Marshal(AdminPermissionTemplate) - database.Insert(AdminPermissionTemplate.ID.String(), string(d), database.USER_PERMISSIONS_TABLE_NAME) - d, _ = json.Marshal(ServiceUserPermissionTemplate) - database.Insert(ServiceUserPermissionTemplate.ID.String(), string(d), database.USER_PERMISSIONS_TABLE_NAME) - d, _ = json.Marshal(PlatformUserUserPermissionTemplate) - database.Insert(PlatformUserUserPermissionTemplate.ID.String(), string(d), database.USER_PERMISSIONS_TABLE_NAME) - -} - -func CreateDefaultNetworkRoles(netID string) { - var NetworkAdminPermissionTemplate = models.UserRolePermissionTemplate{ - ID: models.UserRole(fmt.Sprintf("%s_%s", netID, models.NetworkAdmin)), - Default: false, - NetworkID: netID, - FullAccess: true, - NetworkLevelAccess: make(map[models.RsrcType]map[models.RsrcID]models.RsrcPermissionScope), - } - - var NetworkUserPermissionTemplate = models.UserRolePermissionTemplate{ - ID: models.UserRole(fmt.Sprintf("%s_%s", netID, models.NetworkUser)), - Default: false, - FullAccess: false, - NetworkID: netID, - DenyDashboardAccess: false, - NetworkLevelAccess: map[models.RsrcType]map[models.RsrcID]models.RsrcPermissionScope{ - models.RemoteAccessGwRsrc: { - models.AllRemoteAccessGwRsrcID: models.RsrcPermissionScope{ - Read: true, - VPNaccess: true, - }, - }, - models.ExtClientsRsrc: { - models.AllExtClientsRsrcID: models.RsrcPermissionScope{ - Read: true, - Create: true, - Update: true, - Delete: true, - SelfOnly: true, - }, - }, - }, - } - d, _ := json.Marshal(NetworkAdminPermissionTemplate) - database.Insert(NetworkAdminPermissionTemplate.ID.String(), string(d), database.USER_PERMISSIONS_TABLE_NAME) - d, _ = json.Marshal(NetworkUserPermissionTemplate) - database.Insert(NetworkUserPermissionTemplate.ID.String(), string(d), database.USER_PERMISSIONS_TABLE_NAME) -} - -func DeleteNetworkRoles(netID string) { - users, err := GetUsersDB() - if err != nil { - return - } - for _, user := range users { - if _, ok := user.NetworkRoles[models.NetworkID(netID)]; ok { - delete(user.NetworkRoles, models.NetworkID(netID)) - UpsertUser(user) - } - - } - userGs, _ := ListUserGroups() - for _, userGI := range userGs { - if _, ok := userGI.NetworkRoles[models.NetworkID(netID)]; ok { - delete(userGI.NetworkRoles, models.NetworkID(netID)) - UpdateUserGroup(userGI) - } - } - - roles, _ := ListRoles() - for _, role := range roles { - if role.NetworkID == netID { - DeleteRole(role.ID) - } - } -} - -// ListRoles - lists user roles permission templates -func ListRoles() ([]models.UserRolePermissionTemplate, error) { - data, err := database.FetchRecords(database.USER_PERMISSIONS_TABLE_NAME) - if err != nil && !database.IsEmptyRecord(err) { - return []models.UserRolePermissionTemplate{}, err - } - userRoles := []models.UserRolePermissionTemplate{} - for _, dataI := range data { - userRole := models.UserRolePermissionTemplate{} - err := json.Unmarshal([]byte(dataI), &userRole) - if err != nil { - continue - } - userRoles = append(userRoles, userRole) - } - return userRoles, nil -} - -func ValidateCreateRoleReq(userRole models.UserRolePermissionTemplate) error { - // check if role exists with this id - _, err := GetRole(userRole.ID) - if err == nil { - return fmt.Errorf("role with id `%s` exists already", userRole.ID.String()) - } - if len(userRole.NetworkLevelAccess) > 0 { - for rsrcType := range userRole.NetworkLevelAccess { - if _, ok := models.RsrcTypeMap[rsrcType]; !ok { - return errors.New("invalid rsrc type " + rsrcType.String()) - } - } - } - if userRole.NetworkID == "" { - return errors.New("only network roles are allowed to be created") - } - return nil +var GetFilteredNodesByUserAccess = func(user models.User, nodes []models.Node) (filteredNodes []models.Node) { + return } -func ValidateUpdateRoleReq(userRole models.UserRolePermissionTemplate) error { - roleInDB, err := GetRole(userRole.ID) - if err != nil { - return err - } - if roleInDB.NetworkID != userRole.NetworkID { - return errors.New("network id mismatch") - } - if roleInDB.Default { - return errors.New("cannot update default role") - } - if len(userRole.NetworkLevelAccess) > 0 { - for rsrcType := range userRole.NetworkLevelAccess { - if _, ok := models.RsrcTypeMap[rsrcType]; !ok { - return errors.New("invalid rsrc type " + rsrcType.String()) - } - } - } +var CreateRole = func(r models.UserRolePermissionTemplate) error { return nil } - -// CreateRole - inserts new role into DB -func CreateRole(r models.UserRolePermissionTemplate) error { - // check if role already exists - if r.ID.String() == "" { - return errors.New("role id cannot be empty") - } - _, err := database.FetchRecord(database.USER_PERMISSIONS_TABLE_NAME, r.ID.String()) - if err == nil { - return errors.New("role already exists") - } - d, err := json.Marshal(r) - if err != nil { - return err - } - return database.Insert(r.ID.String(), string(d), database.USER_PERMISSIONS_TABLE_NAME) -} +var DeleteNetworkRoles = func(netID string) {} // GetRole - fetches role template by id func GetRole(roleID models.UserRole) (models.UserRolePermissionTemplate, error) { @@ -205,302 +32,18 @@ func GetRole(roleID models.UserRole) (models.UserRolePermissionTemplate, error) return ur, nil } -// UpdateRole - updates role template -func UpdateRole(r models.UserRolePermissionTemplate) error { - if r.ID.String() == "" { - return errors.New("role id cannot be empty") - } - _, err := database.FetchRecord(database.USER_PERMISSIONS_TABLE_NAME, r.ID.String()) - if err != nil { - return err - } - d, err := json.Marshal(r) - if err != nil { - return err - } - return database.Insert(r.ID.String(), string(d), database.USER_PERMISSIONS_TABLE_NAME) -} - -// DeleteRole - deletes user role -func DeleteRole(rid models.UserRole) error { - if rid.String() == "" { - return errors.New("role id cannot be empty") - } - users, err := GetUsersDB() - if err != nil { - return err - } - role, err := GetRole(rid) - if err != nil { - return err - } - if role.Default { - return errors.New("cannot delete default role") - } - for _, user := range users { - for userG := range user.UserGroups { - ug, err := GetUserGroup(userG) - if err == nil { - if role.NetworkID != "" { - for _, networkRoles := range ug.NetworkRoles { - if _, ok := networkRoles[rid]; ok { - err = errors.New("role cannot be deleted as active user groups are using this role") - return err - } - } - } - - } - } - - if user.PlatformRoleID == rid { - err = errors.New("active roles cannot be deleted.switch existing users to a new role before deleting") +func IsGroupsValid(groups map[models.UserGroupID]struct{}) error { + uniqueGroupsPlatformRole := make(map[models.UserRole]struct{}) + for groupID := range groups { + userG, err := logic.GetUserGroup(groupID) + if err != nil { return err } - for _, networkRoles := range user.NetworkRoles { - if _, ok := networkRoles[rid]; ok { - err = errors.New("active roles cannot be deleted.switch existing users to a new role before deleting") - return err - } - - } + uniqueGroupsPlatformRole[userG.PlatformRole] = struct{}{} } - return database.DeleteRecord(database.USER_PERMISSIONS_TABLE_NAME, rid.String()) -} + if len(uniqueGroupsPlatformRole) > 1 { -func ValidateCreateGroupReq(g models.UserGroup) error { - // check platform role is valid - role, err := GetRole(g.PlatformRole) - if err != nil { - err = fmt.Errorf("invalid platform role") - return err - } - if role.NetworkID != "" { - return errors.New("network role cannot be used as platform role") - } - // check if network roles are valid - for _, roleMap := range g.NetworkRoles { - for roleID := range roleMap { - role, err := GetRole(roleID) - if err != nil { - return fmt.Errorf("invalid network role %s", roleID) - } - if role.NetworkID == "" { - return errors.New("platform role cannot be used as network role") - } - } + return errors.New("only groups with same platform role can be assigned to an user") } return nil } -func ValidateUpdateGroupReq(g models.UserGroup) error { - // check platform role is valid - role, err := GetRole(g.PlatformRole) - if err != nil { - err = fmt.Errorf("invalid platform role") - return err - } - if role.NetworkID != "" { - return errors.New("network role cannot be used as platform role") - } - for networkID := range g.NetworkRoles { - userRolesMap := g.NetworkRoles[networkID] - for roleID := range userRolesMap { - netRole, err := GetRole(roleID) - if err != nil { - err = fmt.Errorf("invalid network role") - return err - } - if netRole.NetworkID == "" { - return errors.New("platform role cannot be used as network role") - } - } - } - return nil -} - -// CreateUserGroup - creates new user group -func CreateUserGroup(g models.UserGroup) error { - // check if role already exists - if g.ID == "" { - return errors.New("group id cannot be empty") - } - _, err := database.FetchRecord(database.USER_GROUPS_TABLE_NAME, g.ID.String()) - if err == nil { - return errors.New("group already exists") - } - d, err := json.Marshal(g) - if err != nil { - return err - } - return database.Insert(g.ID.String(), string(d), database.USER_GROUPS_TABLE_NAME) -} - -// GetUserGroup - fetches user group -func GetUserGroup(gid models.UserGroupID) (models.UserGroup, error) { - d, err := database.FetchRecord(database.USER_GROUPS_TABLE_NAME, gid.String()) - if err != nil { - return models.UserGroup{}, err - } - var ug models.UserGroup - err = json.Unmarshal([]byte(d), &ug) - if err != nil { - return ug, err - } - return ug, nil -} - -// ListUserGroups - lists user groups -func ListUserGroups() ([]models.UserGroup, error) { - data, err := database.FetchRecords(database.USER_GROUPS_TABLE_NAME) - if err != nil && !database.IsEmptyRecord(err) { - return []models.UserGroup{}, err - } - userGroups := []models.UserGroup{} - for _, dataI := range data { - userGroup := models.UserGroup{} - err := json.Unmarshal([]byte(dataI), &userGroup) - if err != nil { - continue - } - userGroups = append(userGroups, userGroup) - } - return userGroups, nil -} - -// UpdateUserGroup - updates new user group -func UpdateUserGroup(g models.UserGroup) error { - // check if group exists - if g.ID == "" { - return errors.New("group id cannot be empty") - } - _, err := database.FetchRecord(database.USER_GROUPS_TABLE_NAME, g.ID.String()) - if err != nil { - return err - } - d, err := json.Marshal(g) - if err != nil { - return err - } - return database.Insert(g.ID.String(), string(d), database.USER_GROUPS_TABLE_NAME) -} - -// DeleteUserGroup - deletes user group -func DeleteUserGroup(gid models.UserGroupID) error { - users, err := GetUsersDB() - if err != nil { - return err - } - for _, user := range users { - delete(user.UserGroups, gid) - UpsertUser(user) - } - return database.DeleteRecord(database.USER_GROUPS_TABLE_NAME, gid.String()) -} - -func HasNetworkRsrcScope(permissionTemplate models.UserRolePermissionTemplate, netid string, rsrcType models.RsrcType, rsrcID models.RsrcID, op string) bool { - if permissionTemplate.FullAccess { - return true - } - - rsrcScope, ok := permissionTemplate.NetworkLevelAccess[rsrcType] - if !ok { - return false - } - _, ok = rsrcScope[rsrcID] - return ok -} -func GetUserRAGNodes(user models.User) (gws map[string]models.Node) { - logger.Log(0, "------------> 7. getUserRemoteAccessGwsV1") - gws = make(map[string]models.Node) - userGwAccessScope := GetUserNetworkRolesWithRemoteVPNAccess(user) - logger.Log(0, fmt.Sprintf("User Gw Access Scope: %+v", userGwAccessScope)) - _, allNetAccess := userGwAccessScope["*"] - nodes, err := GetAllNodes() - if err != nil { - return - } - logger.Log(0, "------------> 8. getUserRemoteAccessGwsV1") - for _, node := range nodes { - if node.IsIngressGateway && !node.PendingDelete { - if allNetAccess { - gws[node.ID.String()] = node - } else { - gwRsrcMap := userGwAccessScope[models.NetworkID(node.Network)] - scope, ok := gwRsrcMap[models.AllRemoteAccessGwRsrcID] - if !ok { - if scope, ok = gwRsrcMap[models.RsrcID(node.ID.String())]; !ok { - continue - } - } - if scope.VPNaccess { - gws[node.ID.String()] = node - } - - } - } - } - logger.Log(0, "------------> 9. getUserRemoteAccessGwsV1") - return -} - -// GetUserNetworkRoles - get user network roles -func GetUserNetworkRolesWithRemoteVPNAccess(user models.User) (gwAccess map[models.NetworkID]map[models.RsrcID]models.RsrcPermissionScope) { - gwAccess = make(map[models.NetworkID]map[models.RsrcID]models.RsrcPermissionScope) - logger.Log(0, "------------> 7.1 getUserRemoteAccessGwsV1") - platformRole, err := GetRole(user.PlatformRoleID) - if err != nil { - return - } - if platformRole.FullAccess { - gwAccess[models.NetworkID("*")] = make(map[models.RsrcID]models.RsrcPermissionScope) - return - } - logger.Log(0, "------------> 7.2 getUserRemoteAccessGwsV1") - for netID, roleMap := range user.NetworkRoles { - for roleID := range roleMap { - role, err := GetRole(roleID) - if err == nil { - if role.FullAccess { - gwAccess[netID] = map[models.RsrcID]models.RsrcPermissionScope{ - models.AllRemoteAccessGwRsrcID: { - Create: true, - Read: true, - Update: true, - VPNaccess: true, - Delete: true, - }, - models.AllExtClientsRsrcID: { - Create: true, - Read: true, - Update: true, - Delete: true, - }, - } - break - } - if rsrcsMap, ok := role.NetworkLevelAccess[models.RemoteAccessGwRsrc]; ok { - if permissions, ok := rsrcsMap[models.AllRemoteAccessGwRsrcID]; ok && permissions.VPNaccess { - if len(gwAccess[netID]) == 0 { - gwAccess[netID] = make(map[models.RsrcID]models.RsrcPermissionScope) - } - gwAccess[netID][models.AllRemoteAccessGwRsrcID] = permissions - break - } else { - for gwID, scope := range rsrcsMap { - if scope.VPNaccess { - if len(gwAccess[netID]) == 0 { - gwAccess[netID] = make(map[models.RsrcID]models.RsrcPermissionScope) - } - gwAccess[netID][gwID] = scope - } - } - } - - } - - } - } - } - logger.Log(0, "------------> 7.3 getUserRemoteAccessGwsV1") - return -} diff --git a/pro/auth/azure-ad.go b/pro/auth/azure-ad.go index d0a46851b..96a782152 100644 --- a/pro/auth/azure-ad.go +++ b/pro/auth/azure-ad.go @@ -14,6 +14,7 @@ import ( "github.com/gravitl/netmaker/logger" "github.com/gravitl/netmaker/logic" "github.com/gravitl/netmaker/models" + proLogic "github.com/gravitl/netmaker/pro/logic" "github.com/gravitl/netmaker/servercfg" "golang.org/x/oauth2" "golang.org/x/oauth2/microsoft" @@ -99,7 +100,7 @@ func handleAzureCallback(w http.ResponseWriter, r *http.Request) { Password: newPass, } for _, inviteGroupID := range in.Groups { - userG, err := logic.GetUserGroup(inviteGroupID) + userG, err := proLogic.GetUserGroup(inviteGroupID) if err != nil { logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("error fetching group id "+inviteGroupID.String()), "badrequest")) return diff --git a/pro/auth/github.go b/pro/auth/github.go index 193a321b2..c8dc851a8 100644 --- a/pro/auth/github.go +++ b/pro/auth/github.go @@ -14,6 +14,7 @@ import ( "github.com/gravitl/netmaker/logger" "github.com/gravitl/netmaker/logic" "github.com/gravitl/netmaker/models" + proLogic "github.com/gravitl/netmaker/pro/logic" "github.com/gravitl/netmaker/servercfg" "golang.org/x/oauth2" "golang.org/x/oauth2/github" @@ -99,7 +100,7 @@ func handleGithubCallback(w http.ResponseWriter, r *http.Request) { } for _, inviteGroupID := range in.Groups { - userG, err := logic.GetUserGroup(inviteGroupID) + userG, err := proLogic.GetUserGroup(inviteGroupID) if err != nil { logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("error fetching group id "+inviteGroupID.String()), "badrequest")) return diff --git a/pro/auth/google.go b/pro/auth/google.go index 295653506..8caf130dd 100644 --- a/pro/auth/google.go +++ b/pro/auth/google.go @@ -15,6 +15,7 @@ import ( "github.com/gravitl/netmaker/logger" "github.com/gravitl/netmaker/logic" "github.com/gravitl/netmaker/models" + proLogic "github.com/gravitl/netmaker/pro/logic" "github.com/gravitl/netmaker/servercfg" "golang.org/x/oauth2" "golang.org/x/oauth2/google" @@ -106,7 +107,7 @@ func handleGoogleCallback(w http.ResponseWriter, r *http.Request) { } logger.Log(0, "CALLBACK ----> 4.1") for _, inviteGroupID := range in.Groups { - userG, err := logic.GetUserGroup(inviteGroupID) + userG, err := proLogic.GetUserGroup(inviteGroupID) if err != nil { logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("error fetching group id "+inviteGroupID.String()), "badrequest")) return diff --git a/pro/auth/oidc.go b/pro/auth/oidc.go index 393e456c3..4858ba186 100644 --- a/pro/auth/oidc.go +++ b/pro/auth/oidc.go @@ -14,6 +14,7 @@ import ( "github.com/gravitl/netmaker/logger" "github.com/gravitl/netmaker/logic" "github.com/gravitl/netmaker/models" + proLogic "github.com/gravitl/netmaker/pro/logic" "github.com/gravitl/netmaker/servercfg" "golang.org/x/oauth2" ) @@ -111,7 +112,7 @@ func handleOIDCCallback(w http.ResponseWriter, r *http.Request) { Password: newPass, } for _, inviteGroupID := range in.Groups { - userG, err := logic.GetUserGroup(inviteGroupID) + userG, err := proLogic.GetUserGroup(inviteGroupID) if err != nil { logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("error fetching group id "+inviteGroupID.String()), "badrequest")) return diff --git a/pro/controllers/users.go b/pro/controllers/users.go index d21a1446c..e83a487b3 100644 --- a/pro/controllers/users.go +++ b/pro/controllers/users.go @@ -1,17 +1,23 @@ package controllers import ( + "context" "encoding/json" "errors" "fmt" "net/http" + "net/url" "github.com/gorilla/mux" + "github.com/gravitl/netmaker/auth" + "github.com/gravitl/netmaker/database" "github.com/gravitl/netmaker/logger" "github.com/gravitl/netmaker/logic" "github.com/gravitl/netmaker/models" "github.com/gravitl/netmaker/mq" - "github.com/gravitl/netmaker/pro/auth" + proAuth "github.com/gravitl/netmaker/pro/auth" + "github.com/gravitl/netmaker/pro/email" + proLogic "github.com/gravitl/netmaker/pro/logic" "github.com/gravitl/netmaker/servercfg" "golang.org/x/exp/slog" ) @@ -21,10 +27,571 @@ func UserHandlers(r *mux.Router) { r.HandleFunc("/api/users/{username}/remote_access_gw/{remote_access_gateway_id}", logic.SecurityCheck(true, http.HandlerFunc(removeUserFromRemoteAccessGW))).Methods(http.MethodDelete) r.HandleFunc("/api/users/{username}/remote_access_gw", logic.SecurityCheck(false, logic.ContinueIfUserMatch(http.HandlerFunc(getUserRemoteAccessGwsV1)))).Methods(http.MethodGet) r.HandleFunc("/api/users/ingress/{ingress_id}", logic.SecurityCheck(true, http.HandlerFunc(ingressGatewayUsers))).Methods(http.MethodGet) - r.HandleFunc("/api/oauth/login", auth.HandleAuthLogin).Methods(http.MethodGet) - r.HandleFunc("/api/oauth/callback", auth.HandleAuthCallback).Methods(http.MethodGet) - r.HandleFunc("/api/oauth/headless", auth.HandleHeadlessSSO) - r.HandleFunc("/api/oauth/register/{regKey}", auth.RegisterHostSSO).Methods(http.MethodGet) + r.HandleFunc("/api/oauth/login", proAuth.HandleAuthLogin).Methods(http.MethodGet) + r.HandleFunc("/api/oauth/callback", proAuth.HandleAuthCallback).Methods(http.MethodGet) + r.HandleFunc("/api/oauth/headless", proAuth.HandleHeadlessSSO) + r.HandleFunc("/api/oauth/register/{regKey}", proAuth.RegisterHostSSO).Methods(http.MethodGet) + + // User Role Handlers + r.HandleFunc("/api/v1/users/roles", logic.SecurityCheck(true, http.HandlerFunc(listRoles))).Methods(http.MethodGet) + r.HandleFunc("/api/v1/users/role", getRole).Methods(http.MethodGet) + r.HandleFunc("/api/v1/users/role", logic.SecurityCheck(true, http.HandlerFunc(createRole))).Methods(http.MethodPost) + r.HandleFunc("/api/v1/users/role", logic.SecurityCheck(true, http.HandlerFunc(updateRole))).Methods(http.MethodPut) + r.HandleFunc("/api/v1/users/role", logic.SecurityCheck(true, http.HandlerFunc(deleteRole))).Methods(http.MethodDelete) + + // User Group Handlers + r.HandleFunc("/api/v1/users/groups", logic.SecurityCheck(true, http.HandlerFunc(listUserGroups))).Methods(http.MethodGet) + r.HandleFunc("/api/v1/users/group", logic.SecurityCheck(true, http.HandlerFunc(getUserGroup))).Methods(http.MethodGet) + r.HandleFunc("/api/v1/users/group", logic.SecurityCheck(true, http.HandlerFunc(createUserGroup))).Methods(http.MethodPost) + r.HandleFunc("/api/v1/users/group", logic.SecurityCheck(true, http.HandlerFunc(updateUserGroup))).Methods(http.MethodPut) + r.HandleFunc("/api/v1/users/group", logic.SecurityCheck(true, http.HandlerFunc(deleteUserGroup))).Methods(http.MethodDelete) + + // User Invite Handlers + r.HandleFunc("/api/v1/users/invite", userInviteVerify).Methods(http.MethodGet) + r.HandleFunc("/api/v1/users/invite-signup", userInviteSignUp).Methods(http.MethodPost) + r.HandleFunc("/api/v1/users/invite", logic.SecurityCheck(true, http.HandlerFunc(inviteUsers))).Methods(http.MethodPost) + r.HandleFunc("/api/v1/users/invites", logic.SecurityCheck(true, http.HandlerFunc(listUserInvites))).Methods(http.MethodGet) + r.HandleFunc("/api/v1/users/invite", logic.SecurityCheck(true, http.HandlerFunc(deleteUserInvite))).Methods(http.MethodDelete) + r.HandleFunc("/api/v1/users/invites", logic.SecurityCheck(true, http.HandlerFunc(deleteAllUserInvites))).Methods(http.MethodDelete) + + r.HandleFunc("/api/users_pending", logic.SecurityCheck(true, http.HandlerFunc(getPendingUsers))).Methods(http.MethodGet) + r.HandleFunc("/api/users_pending", logic.SecurityCheck(true, http.HandlerFunc(deleteAllPendingUsers))).Methods(http.MethodDelete) + r.HandleFunc("/api/users_pending/user/{username}", logic.SecurityCheck(true, http.HandlerFunc(deletePendingUser))).Methods(http.MethodDelete) + r.HandleFunc("/api/users_pending/user/{username}", logic.SecurityCheck(true, http.HandlerFunc(approvePendingUser))).Methods(http.MethodPost) + +} + +// swagger:route POST /api/v1/users/invite-signup user userInviteSignUp +// +// user signup via invite. +// +// Schemes: https +// +// Responses: +// 200: ReturnSuccessResponse +func userInviteSignUp(w http.ResponseWriter, r *http.Request) { + email, _ := url.QueryUnescape(r.URL.Query().Get("email")) + code, _ := url.QueryUnescape(r.URL.Query().Get("invite_code")) + in, err := logic.GetUserInvite(email) + if err != nil { + logger.Log(0, "failed to fetch users: ", err.Error()) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) + return + } + if code != in.InviteCode { + logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("invalid invite code"), "badrequest")) + return + } + // check if user already exists + _, err = logic.GetUser(email) + if err == nil { + logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("user already exists"), "badrequest")) + return + } + var user models.User + err = json.NewDecoder(r.Body).Decode(&user) + if err != nil { + logger.Log(0, user.UserName, "error decoding request body: ", + err.Error()) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) + return + } + if user.UserName != email { + logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("username not matching with invite"), "badrequest")) + return + } + if user.Password == "" { + logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("password cannot be empty"), "badrequest")) + return + } + + for _, inviteGroupID := range in.Groups { + userG, err := proLogic.GetUserGroup(inviteGroupID) + if err != nil { + logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("error fetching group id "+inviteGroupID.String()), "badrequest")) + return + } + user.PlatformRoleID = userG.PlatformRole + user.UserGroups = make(map[models.UserGroupID]struct{}) + user.UserGroups[inviteGroupID] = struct{}{} + } + if user.PlatformRoleID == "" { + user.PlatformRoleID = models.ServiceUser + } + user.NetworkRoles = make(map[models.NetworkID]map[models.UserRole]struct{}) + err = logic.CreateUser(&user) + if err != nil { + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) + return + } + // delete invite + logic.DeleteUserInvite(email) + logic.DeletePendingUser(email) + w.Header().Set("Access-Control-Allow-Origin", "*") + logic.ReturnSuccessResponse(w, r, "created user successfully "+email) +} + +// swagger:route GET /api/v1/users/invite user userInviteVerify +// +// verfies user invite. +// +// Schemes: https +// +// Responses: +// 200: ReturnSuccessResponse +func userInviteVerify(w http.ResponseWriter, r *http.Request) { + email, _ := url.QueryUnescape(r.URL.Query().Get("email")) + code, _ := url.QueryUnescape(r.URL.Query().Get("invite_code")) + err := logic.ValidateAndApproveUserInvite(email, code) + if err != nil { + logger.Log(0, "failed to fetch users: ", err.Error()) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) + return + } + logic.ReturnSuccessResponse(w, r, "invite is valid") +} + +// swagger:route POST /api/v1/users/invite user inviteUsers +// +// invite users. +// +// Schemes: https +// +// Security: +// oauth +// +// Responses: +// 200: userBodyResponse +func inviteUsers(w http.ResponseWriter, r *http.Request) { + var inviteReq models.InviteUsersReq + err := json.NewDecoder(r.Body).Decode(&inviteReq) + if err != nil { + slog.Error("error decoding request body", "error", + err.Error()) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) + return + } + //validate Req + uniqueGroupsPlatformRole := make(map[models.UserRole]struct{}) + for _, groupID := range inviteReq.Groups { + userG, err := proLogic.GetUserGroup(groupID) + if err != nil { + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) + return + } + uniqueGroupsPlatformRole[userG.PlatformRole] = struct{}{} + } + if len(uniqueGroupsPlatformRole) > 1 { + err = errors.New("only groups with same platform role can be assigned to an user") + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) + return + } + + for _, inviteeEmail := range inviteReq.UserEmails { + // check if user with email exists, then ignore + _, err := logic.GetUser(inviteeEmail) + if err == nil { + // user exists already, so ignore + continue + } + invite := models.UserInvite{ + Email: inviteeEmail, + Groups: inviteReq.Groups, + InviteCode: logic.RandomString(8), + } + u, err := url.Parse(fmt.Sprintf("%s/invite?email=%s&invite_code=%s", + servercfg.GetFrontendURL(), url.QueryEscape(invite.Email), url.QueryEscape(invite.InviteCode))) + if err != nil { + slog.Error("failed to parse to invite url", "error", err) + return + } + invite.InviteURL = u.String() + err = logic.InsertUserInvite(invite) + if err != nil { + slog.Error("failed to insert invite for user", "email", invite.Email, "error", err) + } + // notify user with magic link + go func(invite models.UserInvite) { + // Set E-Mail body. You can set plain text or html with text/html + + e := email.UserInvitedMail{ + BodyBuilder: &email.EmailBodyBuilderWithH1HeadlineAndImage{}, + InviteURL: invite.InviteURL, + } + n := email.Notification{ + RecipientMail: invite.Email, + } + err = email.GetClient().SendEmail(context.Background(), n, e) + if err != nil { + slog.Error("failed to send email invite", "user", invite.Email, "error", err) + } + }(invite) + } + +} + +// swagger:route GET /api/v1/users/invites user listUserInvites +// +// lists all pending invited users. +// +// Schemes: https +// +// Security: +// oauth +// +// Responses: +// 200: ReturnSuccessResponseWithJson +func listUserInvites(w http.ResponseWriter, r *http.Request) { + usersInvites, err := logic.ListUserInvites() + if err != nil { + logger.Log(0, "failed to fetch users: ", err.Error()) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) + return + } + logic.ReturnSuccessResponseWithJson(w, r, usersInvites, "fetched pending user invites") +} + +// swagger:route DELETE /api/v1/users/invite user deleteUserInvite +// +// delete pending invite. +// +// Schemes: https +// +// Security: +// oauth +// +// Responses: +// 200: ReturnSuccessResponse +func deleteUserInvite(w http.ResponseWriter, r *http.Request) { + email, _ := url.QueryUnescape(r.URL.Query().Get("invitee_email")) + err := logic.DeleteUserInvite(email) + if err != nil { + logger.Log(0, "failed to delete user invite: ", email, err.Error()) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) + return + } + logic.ReturnSuccessResponse(w, r, "deleted user invite") +} + +// swagger:route DELETE /api/v1/users/invites user deleteAllUserInvites +// +// deletes all pending invites. +// +// Schemes: https +// +// Security: +// oauth +// +// Responses: +// 200: ReturnSuccessResponse +func deleteAllUserInvites(w http.ResponseWriter, r *http.Request) { + err := database.DeleteAllRecords(database.USER_INVITES_TABLE_NAME) + if err != nil { + logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("failed to delete all pending user invites "+err.Error()), "internal")) + return + } + logic.ReturnSuccessResponse(w, r, "cleared all pending user invites") +} + +// swagger:route GET /api/v1/user/groups user listUserGroups +// +// Get all user groups. +// +// Schemes: https +// +// Security: +// oauth +// +// Responses: +// 200: userBodyResponse +func listUserGroups(w http.ResponseWriter, r *http.Request) { + groups, err := proLogic.ListUserGroups() + if err != nil { + logic.ReturnErrorResponse(w, r, models.ErrorResponse{ + Code: http.StatusInternalServerError, + Message: err.Error(), + }) + return + } + logic.ReturnSuccessResponseWithJson(w, r, groups, "successfully fetched user groups") +} + +// swagger:route GET /api/v1/user/group user getUserGroup +// +// Get user group. +// +// Schemes: https +// +// Security: +// oauth +// +// Responses: +// 200: userBodyResponse +func getUserGroup(w http.ResponseWriter, r *http.Request) { + + gid, _ := url.QueryUnescape(r.URL.Query().Get("group_id")) + if gid == "" { + logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("group id is required"), "badrequest")) + return + } + group, err := proLogic.GetUserGroup(models.UserGroupID(gid)) + if err != nil { + logic.ReturnErrorResponse(w, r, models.ErrorResponse{ + Code: http.StatusInternalServerError, + Message: err.Error(), + }) + return + } + logic.ReturnSuccessResponseWithJson(w, r, group, "successfully fetched user group") +} + +// swagger:route POST /api/v1/user/group user createUserGroup +// +// Create user groups. +// +// Schemes: https +// +// Security: +// oauth +// +// Responses: +// 200: userBodyResponse +func createUserGroup(w http.ResponseWriter, r *http.Request) { + var userGroupReq models.CreateGroupReq + err := json.NewDecoder(r.Body).Decode(&userGroupReq) + if err != nil { + slog.Error("error decoding request body", "error", + err.Error()) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) + return + } + err = proLogic.ValidateCreateGroupReq(userGroupReq.Group) + if err != nil { + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) + return + } + err = proLogic.CreateUserGroup(userGroupReq.Group) + if err != nil { + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) + return + } + for _, userID := range userGroupReq.Members { + user, err := logic.GetUser(userID) + if err != nil { + continue + } + if len(user.UserGroups) == 0 { + user.UserGroups = make(map[models.UserGroupID]struct{}) + } + user.UserGroups[userGroupReq.Group.ID] = struct{}{} + logic.UpsertUser(*user) + } + logic.ReturnSuccessResponseWithJson(w, r, userGroupReq.Group, "created user group") +} + +// swagger:route PUT /api/v1/user/group user updateUserGroup +// +// Update user group. +// +// Schemes: https +// +// Security: +// oauth +// +// Responses: +// 200: userBodyResponse +func updateUserGroup(w http.ResponseWriter, r *http.Request) { + var userGroup models.UserGroup + err := json.NewDecoder(r.Body).Decode(&userGroup) + if err != nil { + slog.Error("error decoding request body", "error", + err.Error()) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) + return + } + err = proLogic.ValidateUpdateGroupReq(userGroup) + if err != nil { + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) + return + } + err = proLogic.UpdateUserGroup(userGroup) + if err != nil { + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) + return + } + logic.ReturnSuccessResponseWithJson(w, r, userGroup, "updated user group") +} + +// swagger:route DELETE /api/v1/user/group user deleteUserGroup +// +// delete user group. +// +// Schemes: https +// +// Security: +// oauth +// +// Responses: +// 200: userBodyResponse +func deleteUserGroup(w http.ResponseWriter, r *http.Request) { + + gid, _ := url.QueryUnescape(r.URL.Query().Get("group_id")) + if gid == "" { + logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("role is required"), "badrequest")) + return + } + err := proLogic.DeleteUserGroup(models.UserGroupID(gid)) + if err != nil { + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) + return + } + logic.ReturnSuccessResponseWithJson(w, r, nil, "deleted user group") +} + +// swagger:route GET /api/v1/user/roles user listRoles +// +// lists all user roles. +// +// Schemes: https +// +// Security: +// oauth +// +// Responses: +// 200: userBodyResponse +func listRoles(w http.ResponseWriter, r *http.Request) { + roles, err := proLogic.ListRoles() + if err != nil { + logic.ReturnErrorResponse(w, r, models.ErrorResponse{ + Code: http.StatusInternalServerError, + Message: err.Error(), + }) + return + } + logic.ReturnSuccessResponseWithJson(w, r, roles, "successfully fetched user roles permission templates") +} + +// swagger:route GET /api/v1/user/role user getRole +// +// Get user role permission templates. +// +// Schemes: https +// +// Security: +// oauth +// +// Responses: +// 200: userBodyResponse +func getRole(w http.ResponseWriter, r *http.Request) { + rid, _ := url.QueryUnescape(r.URL.Query().Get("role_id")) + if rid == "" { + logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("role is required"), "badrequest")) + return + } + role, err := logic.GetRole(models.UserRole(rid)) + if err != nil { + logic.ReturnErrorResponse(w, r, models.ErrorResponse{ + Code: http.StatusInternalServerError, + Message: err.Error(), + }) + return + } + logic.ReturnSuccessResponseWithJson(w, r, role, "successfully fetched user role permission templates") +} + +// swagger:route POST /api/v1/user/role user createRole +// +// Create user role permission template. +// +// Schemes: https +// +// Security: +// oauth +// +// Responses: +// 200: userBodyResponse +func createRole(w http.ResponseWriter, r *http.Request) { + var userRole models.UserRolePermissionTemplate + err := json.NewDecoder(r.Body).Decode(&userRole) + if err != nil { + slog.Error("error decoding request body", "error", + err.Error()) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) + return + } + err = proLogic.ValidateCreateRoleReq(userRole) + if err != nil { + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) + return + } + userRole.Default = false + userRole.GlobalLevelAccess = make(map[models.RsrcType]map[models.RsrcID]models.RsrcPermissionScope) + err = proLogic.CreateRole(userRole) + if err != nil { + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) + return + } + logic.ReturnSuccessResponseWithJson(w, r, userRole, "created user role") +} + +// swagger:route PUT /api/v1/user/role user updateRole +// +// Update user role permission template. +// +// Schemes: https +// +// Security: +// oauth +// +// Responses: +// 200: userBodyResponse +func updateRole(w http.ResponseWriter, r *http.Request) { + var userRole models.UserRolePermissionTemplate + err := json.NewDecoder(r.Body).Decode(&userRole) + if err != nil { + slog.Error("error decoding request body", "error", + err.Error()) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) + return + } + err = proLogic.ValidateUpdateRoleReq(userRole) + if err != nil { + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) + return + } + userRole.GlobalLevelAccess = make(map[models.RsrcType]map[models.RsrcID]models.RsrcPermissionScope) + err = proLogic.UpdateRole(userRole) + if err != nil { + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) + return + } + logic.ReturnSuccessResponseWithJson(w, r, userRole, "updated user role") +} + +// swagger:route DELETE /api/v1/user/role user deleteRole +// +// Delete user role permission template. +// +// Schemes: https +// +// Security: +// oauth +// +// Responses: +// 200: userBodyResponse +func deleteRole(w http.ResponseWriter, r *http.Request) { + + rid, _ := url.QueryUnescape(r.URL.Query().Get("role_id")) + if rid == "" { + logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("role is required"), "badrequest")) + return + } + err := proLogic.DeleteRole(models.UserRole(rid)) + if err != nil { + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) + return + } + logic.ReturnSuccessResponseWithJson(w, r, nil, "created user role") } // swagger:route POST /api/users/{username}/remote_access_gw user attachUserToRemoteAccessGateway @@ -190,7 +757,7 @@ func getUserRemoteAccessGwsV1(w http.ResponseWriter, r *http.Request) { return } logger.Log(0, "------------> 6. getUserRemoteAccessGwsV1") - userGwNodes := logic.GetUserRAGNodes(*user) + userGwNodes := proLogic.GetUserRAGNodes(*user) logger.Log(0, fmt.Sprintf("1. User Gw Nodes: %+v", userGwNodes)) for _, extClient := range allextClients { node, ok := userGwNodes[extClient.IngressGatewayID] @@ -528,3 +1095,135 @@ func getAllowedRagEndpoints(ragNode *models.Node, ragHost *models.Host) []string } return endpoints } + +// swagger:route GET /api/users_pending user getPendingUsers +// +// Get all pending users. +// +// Schemes: https +// +// Security: +// oauth +// +// Responses: +// 200: userBodyResponse +func getPendingUsers(w http.ResponseWriter, r *http.Request) { + // set header. + w.Header().Set("Content-Type", "application/json") + + users, err := logic.ListPendingUsers() + if err != nil { + logger.Log(0, "failed to fetch users: ", err.Error()) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) + return + } + + logic.SortUsers(users[:]) + logger.Log(2, r.Header.Get("user"), "fetched pending users") + json.NewEncoder(w).Encode(users) +} + +// swagger:route POST /api/users_pending/user/{username} user approvePendingUser +// +// approve pending user. +// +// Schemes: https +// +// Security: +// oauth +// +// Responses: +// 200: userBodyResponse +func approvePendingUser(w http.ResponseWriter, r *http.Request) { + // set header. + w.Header().Set("Content-Type", "application/json") + var params = mux.Vars(r) + username := params["username"] + users, err := logic.ListPendingUsers() + + if err != nil { + logger.Log(0, "failed to fetch users: ", err.Error()) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) + return + } + for _, user := range users { + if user.UserName == username { + var newPass, fetchErr = auth.FetchPassValue("") + if fetchErr != nil { + logic.ReturnErrorResponse(w, r, logic.FormatError(fetchErr, "internal")) + return + } + if err = logic.CreateUser(&models.User{ + UserName: user.UserName, + Password: newPass, + }); err != nil { + logic.ReturnErrorResponse(w, r, logic.FormatError(fmt.Errorf("failed to create user: %s", err), "internal")) + return + } + err = logic.DeletePendingUser(username) + if err != nil { + logic.ReturnErrorResponse(w, r, logic.FormatError(fmt.Errorf("failed to delete pending user: %s", err), "internal")) + return + } + break + } + } + logic.ReturnSuccessResponse(w, r, "approved "+username) +} + +// swagger:route DELETE /api/users_pending/user/{username} user deletePendingUser +// +// delete pending user. +// +// Schemes: https +// +// Security: +// oauth +// +// Responses: +// 200: userBodyResponse +func deletePendingUser(w http.ResponseWriter, r *http.Request) { + // set header. + w.Header().Set("Content-Type", "application/json") + var params = mux.Vars(r) + username := params["username"] + users, err := logic.ListPendingUsers() + + if err != nil { + logger.Log(0, "failed to fetch users: ", err.Error()) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) + return + } + for _, user := range users { + if user.UserName == username { + err = logic.DeletePendingUser(username) + if err != nil { + logic.ReturnErrorResponse(w, r, logic.FormatError(fmt.Errorf("failed to delete pending user: %s", err), "internal")) + return + } + break + } + } + logic.ReturnSuccessResponse(w, r, "deleted pending "+username) +} + +// swagger:route DELETE /api/users_pending/{username}/pending user deleteAllPendingUsers +// +// delete all pending users. +// +// Schemes: https +// +// Security: +// oauth +// +// Responses: +// 200: userBodyResponse +func deleteAllPendingUsers(w http.ResponseWriter, r *http.Request) { + // set header. + err := database.DeleteAllRecords(database.PENDING_USERS_TABLE_NAME) + if err != nil { + logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("failed to delete all pending users "+err.Error()), "internal")) + return + } + logic.ReturnSuccessResponse(w, r, "cleared all pending users") +} diff --git a/email/email.go b/pro/email/email.go similarity index 100% rename from email/email.go rename to pro/email/email.go diff --git a/email/invite.go b/pro/email/invite.go similarity index 100% rename from email/invite.go rename to pro/email/invite.go diff --git a/email/resend.go b/pro/email/resend.go similarity index 100% rename from email/resend.go rename to pro/email/resend.go diff --git a/email/smtp.go b/pro/email/smtp.go similarity index 100% rename from email/smtp.go rename to pro/email/smtp.go diff --git a/email/utils.go b/pro/email/utils.go similarity index 100% rename from email/utils.go rename to pro/email/utils.go diff --git a/pro/initialize.go b/pro/initialize.go index ffd28c6c2..71e748b35 100644 --- a/pro/initialize.go +++ b/pro/initialize.go @@ -119,6 +119,11 @@ func InitPro() { logic.GetAllowedIpForInetNodeClient = proLogic.GetAllowedIpForInetNodeClient mq.UpdateMetrics = proLogic.MQUpdateMetrics mq.UpdateMetricsFallBack = proLogic.MQUpdateMetricsFallBack + logic.GetFilteredNodesByUserAccess = proLogic.GetFilteredNodesByUserAccess + logic.CreateRole = proLogic.CreateRole + logic.NetworkPermissionsCheck = proLogic.NetworkPermissionsCheck + logic.GlobalPermissionsCheck = proLogic.GlobalPermissionsCheck + logic.DeleteNetworkRoles = proLogic.DeleteNetworkRoles } func retrieveProLogo() string { diff --git a/pro/logic/security.go b/pro/logic/security.go new file mode 100644 index 000000000..e025580e5 --- /dev/null +++ b/pro/logic/security.go @@ -0,0 +1,186 @@ +package logic + +import ( + "errors" + "fmt" + "net/http" + + "github.com/gravitl/netmaker/logger" + "github.com/gravitl/netmaker/logic" + "github.com/gravitl/netmaker/models" +) + +func NetworkPermissionsCheck(username string, r *http.Request) error { + // at this point global checks should be completed + user, err := logic.GetUser(username) + if err != nil { + return err + } + logger.Log(0, "NET MIDDL----> 1") + userRole, err := logic.GetRole(user.PlatformRoleID) + if err != nil { + return errors.New("access denied") + } + if userRole.FullAccess { + return nil + } + logger.Log(0, "NET MIDDL----> 2") + // get info from header to determine the target rsrc + targetRsrc := r.Header.Get("TARGET_RSRC") + targetRsrcID := r.Header.Get("TARGET_RSRC_ID") + netID := r.Header.Get("NET_ID") + if targetRsrc == "" { + return errors.New("target rsrc is missing") + } + if netID == "" { + return errors.New("network id is missing") + } + if r.Method == "" { + r.Method = http.MethodGet + } + if targetRsrc == models.MetricRsrc.String() { + return nil + } + + // check if user has scope for target resource + // TODO - differentitate between global scope and network scope apis + netRoles := user.NetworkRoles[models.NetworkID(netID)] + for netRoleID := range netRoles { + err = checkNetworkAccessPermissions(netRoleID, username, r.Method, targetRsrc, targetRsrcID) + if err == nil { + return nil + } + } + for groupID := range user.UserGroups { + userG, err := GetUserGroup(groupID) + if err == nil { + netRoles := userG.NetworkRoles[models.NetworkID(netID)] + for netRoleID := range netRoles { + err = checkNetworkAccessPermissions(netRoleID, username, r.Method, targetRsrc, targetRsrcID) + if err == nil { + return nil + } + } + } + } + + return errors.New("access denied") +} + +func checkNetworkAccessPermissions(netRoleID models.UserRole, username, reqScope, targetRsrc, targetRsrcID string) error { + networkPermissionScope, err := logic.GetRole(netRoleID) + if err != nil { + return err + } + logger.Log(0, "NET MIDDL----> 3", string(netRoleID)) + if networkPermissionScope.FullAccess { + return nil + } + rsrcPermissionScope, ok := networkPermissionScope.NetworkLevelAccess[models.RsrcType(targetRsrc)] + if targetRsrc == models.HostRsrc.String() && !ok { + rsrcPermissionScope, ok = networkPermissionScope.NetworkLevelAccess[models.RemoteAccessGwRsrc] + } + if !ok { + return errors.New("access denied") + } + logger.Log(0, "NET MIDDL----> 4", string(netRoleID)) + if allRsrcsTypePermissionScope, ok := rsrcPermissionScope[models.RsrcID(fmt.Sprintf("all_%s", targetRsrc))]; ok { + // handle extclient apis here + if models.RsrcType(targetRsrc) == models.ExtClientsRsrc && allRsrcsTypePermissionScope.SelfOnly && targetRsrcID != "" { + extclient, err := logic.GetExtClient(targetRsrcID, networkPermissionScope.NetworkID) + if err != nil { + return err + } + if !logic.IsUserAllowedAccessToExtClient(username, extclient) { + return errors.New("access denied") + } + } + err = checkPermissionScopeWithReqMethod(allRsrcsTypePermissionScope, reqScope) + if err == nil { + return nil + } + + } + if targetRsrc == models.HostRsrc.String() { + if allRsrcsTypePermissionScope, ok := rsrcPermissionScope[models.RsrcID(fmt.Sprintf("all_%s", models.RemoteAccessGwRsrc))]; ok { + err = checkPermissionScopeWithReqMethod(allRsrcsTypePermissionScope, reqScope) + if err == nil { + return nil + } + } + } + logger.Log(0, "NET MIDDL----> 5", string(netRoleID)) + if targetRsrcID == "" { + return errors.New("target rsrc id is empty") + } + if scope, ok := rsrcPermissionScope[models.RsrcID(targetRsrcID)]; ok { + err = checkPermissionScopeWithReqMethod(scope, reqScope) + if err == nil { + return nil + } + } + logger.Log(0, "NET MIDDL----> 6", string(netRoleID)) + return errors.New("access denied") +} + +func GlobalPermissionsCheck(username string, r *http.Request) error { + user, err := logic.GetUser(username) + if err != nil { + return err + } + userRole, err := logic.GetRole(user.PlatformRoleID) + if err != nil { + return errors.New("access denied") + } + if userRole.FullAccess { + return nil + } + targetRsrc := r.Header.Get("TARGET_RSRC") + targetRsrcID := r.Header.Get("TARGET_RSRC_ID") + if targetRsrc == "" { + return errors.New("target rsrc is missing") + } + if r.Method == "" { + r.Method = http.MethodGet + } + if targetRsrc == models.MetricRsrc.String() { + return nil + } + if (targetRsrc == models.HostRsrc.String() || targetRsrc == models.NetworkRsrc.String()) && r.Method == http.MethodGet && targetRsrcID == "" { + return nil + } + if targetRsrc == models.UserRsrc.String() && username == targetRsrcID && (r.Method != http.MethodDelete) { + return nil + } + rsrcPermissionScope, ok := userRole.GlobalLevelAccess[models.RsrcType(targetRsrc)] + if !ok { + return fmt.Errorf("access denied to %s rsrc", targetRsrc) + } + if allRsrcsTypePermissionScope, ok := rsrcPermissionScope[models.RsrcID(fmt.Sprintf("all_%s", targetRsrc))]; ok { + return checkPermissionScopeWithReqMethod(allRsrcsTypePermissionScope, r.Method) + + } + if targetRsrcID == "" { + return errors.New("target rsrc id is missing") + } + if scope, ok := rsrcPermissionScope[models.RsrcID(targetRsrcID)]; ok { + return checkPermissionScopeWithReqMethod(scope, r.Method) + } + return errors.New("access denied") +} + +func checkPermissionScopeWithReqMethod(scope models.RsrcPermissionScope, reqmethod string) error { + if reqmethod == http.MethodGet && scope.Read { + return nil + } + if (reqmethod == http.MethodPatch || reqmethod == http.MethodPut) && scope.Update { + return nil + } + if reqmethod == http.MethodDelete && scope.Delete { + return nil + } + if reqmethod == http.MethodPost && scope.Create { + return nil + } + return errors.New("operation not permitted") +} diff --git a/pro/logic/user_mgmt.go b/pro/logic/user_mgmt.go new file mode 100644 index 000000000..aca027b35 --- /dev/null +++ b/pro/logic/user_mgmt.go @@ -0,0 +1,560 @@ +package logic + +import ( + "encoding/json" + "errors" + "fmt" + + "github.com/gravitl/netmaker/database" + "github.com/gravitl/netmaker/logger" + "github.com/gravitl/netmaker/logic" + "github.com/gravitl/netmaker/models" +) + +// Pre-Define Permission Templates for default Roles +var SuperAdminPermissionTemplate = models.UserRolePermissionTemplate{ + ID: models.SuperAdminRole, + Default: true, + FullAccess: true, +} + +var AdminPermissionTemplate = models.UserRolePermissionTemplate{ + ID: models.AdminRole, + Default: true, + FullAccess: true, +} + +var ServiceUserPermissionTemplate = models.UserRolePermissionTemplate{ + ID: models.ServiceUser, + Default: true, + FullAccess: false, + DenyDashboardAccess: true, +} + +var PlatformUserUserPermissionTemplate = models.UserRolePermissionTemplate{ + ID: models.PlatformUser, + Default: true, + FullAccess: false, +} + +func UserRolesInit() { + d, _ := json.Marshal(SuperAdminPermissionTemplate) + database.Insert(SuperAdminPermissionTemplate.ID.String(), string(d), database.USER_PERMISSIONS_TABLE_NAME) + d, _ = json.Marshal(AdminPermissionTemplate) + database.Insert(AdminPermissionTemplate.ID.String(), string(d), database.USER_PERMISSIONS_TABLE_NAME) + d, _ = json.Marshal(ServiceUserPermissionTemplate) + database.Insert(ServiceUserPermissionTemplate.ID.String(), string(d), database.USER_PERMISSIONS_TABLE_NAME) + d, _ = json.Marshal(PlatformUserUserPermissionTemplate) + database.Insert(PlatformUserUserPermissionTemplate.ID.String(), string(d), database.USER_PERMISSIONS_TABLE_NAME) + +} + +func CreateDefaultNetworkRoles(netID string) { + var NetworkAdminPermissionTemplate = models.UserRolePermissionTemplate{ + ID: models.UserRole(fmt.Sprintf("%s_%s", netID, models.NetworkAdmin)), + Default: false, + NetworkID: netID, + FullAccess: true, + NetworkLevelAccess: make(map[models.RsrcType]map[models.RsrcID]models.RsrcPermissionScope), + } + + var NetworkUserPermissionTemplate = models.UserRolePermissionTemplate{ + ID: models.UserRole(fmt.Sprintf("%s_%s", netID, models.NetworkUser)), + Default: false, + FullAccess: false, + NetworkID: netID, + DenyDashboardAccess: false, + NetworkLevelAccess: map[models.RsrcType]map[models.RsrcID]models.RsrcPermissionScope{ + models.RemoteAccessGwRsrc: { + models.AllRemoteAccessGwRsrcID: models.RsrcPermissionScope{ + Read: true, + VPNaccess: true, + }, + }, + models.ExtClientsRsrc: { + models.AllExtClientsRsrcID: models.RsrcPermissionScope{ + Read: true, + Create: true, + Update: true, + Delete: true, + SelfOnly: true, + }, + }, + }, + } + d, _ := json.Marshal(NetworkAdminPermissionTemplate) + database.Insert(NetworkAdminPermissionTemplate.ID.String(), string(d), database.USER_PERMISSIONS_TABLE_NAME) + d, _ = json.Marshal(NetworkUserPermissionTemplate) + database.Insert(NetworkUserPermissionTemplate.ID.String(), string(d), database.USER_PERMISSIONS_TABLE_NAME) +} + +func DeleteNetworkRoles(netID string) { + users, err := logic.GetUsersDB() + if err != nil { + return + } + for _, user := range users { + if _, ok := user.NetworkRoles[models.NetworkID(netID)]; ok { + delete(user.NetworkRoles, models.NetworkID(netID)) + logic.UpsertUser(user) + } + + } + userGs, _ := ListUserGroups() + for _, userGI := range userGs { + if _, ok := userGI.NetworkRoles[models.NetworkID(netID)]; ok { + delete(userGI.NetworkRoles, models.NetworkID(netID)) + UpdateUserGroup(userGI) + } + } + + roles, _ := ListRoles() + for _, role := range roles { + if role.NetworkID == netID { + DeleteRole(role.ID) + } + } +} + +// ListRoles - lists user roles permission templates +func ListRoles() ([]models.UserRolePermissionTemplate, error) { + data, err := database.FetchRecords(database.USER_PERMISSIONS_TABLE_NAME) + if err != nil && !database.IsEmptyRecord(err) { + return []models.UserRolePermissionTemplate{}, err + } + userRoles := []models.UserRolePermissionTemplate{} + for _, dataI := range data { + userRole := models.UserRolePermissionTemplate{} + err := json.Unmarshal([]byte(dataI), &userRole) + if err != nil { + continue + } + userRoles = append(userRoles, userRole) + } + return userRoles, nil +} + +func ValidateCreateRoleReq(userRole models.UserRolePermissionTemplate) error { + // check if role exists with this id + _, err := logic.GetRole(userRole.ID) + if err == nil { + return fmt.Errorf("role with id `%s` exists already", userRole.ID.String()) + } + if len(userRole.NetworkLevelAccess) > 0 { + for rsrcType := range userRole.NetworkLevelAccess { + if _, ok := models.RsrcTypeMap[rsrcType]; !ok { + return errors.New("invalid rsrc type " + rsrcType.String()) + } + } + } + if userRole.NetworkID == "" { + return errors.New("only network roles are allowed to be created") + } + return nil +} + +func ValidateUpdateRoleReq(userRole models.UserRolePermissionTemplate) error { + roleInDB, err := logic.GetRole(userRole.ID) + if err != nil { + return err + } + if roleInDB.NetworkID != userRole.NetworkID { + return errors.New("network id mismatch") + } + if roleInDB.Default { + return errors.New("cannot update default role") + } + if len(userRole.NetworkLevelAccess) > 0 { + for rsrcType := range userRole.NetworkLevelAccess { + if _, ok := models.RsrcTypeMap[rsrcType]; !ok { + return errors.New("invalid rsrc type " + rsrcType.String()) + } + } + } + return nil +} + +// CreateRole - inserts new role into DB +func CreateRole(r models.UserRolePermissionTemplate) error { + // check if role already exists + if r.ID.String() == "" { + return errors.New("role id cannot be empty") + } + _, err := database.FetchRecord(database.USER_PERMISSIONS_TABLE_NAME, r.ID.String()) + if err == nil { + return errors.New("role already exists") + } + d, err := json.Marshal(r) + if err != nil { + return err + } + return database.Insert(r.ID.String(), string(d), database.USER_PERMISSIONS_TABLE_NAME) +} + +// UpdateRole - updates role template +func UpdateRole(r models.UserRolePermissionTemplate) error { + if r.ID.String() == "" { + return errors.New("role id cannot be empty") + } + _, err := database.FetchRecord(database.USER_PERMISSIONS_TABLE_NAME, r.ID.String()) + if err != nil { + return err + } + d, err := json.Marshal(r) + if err != nil { + return err + } + return database.Insert(r.ID.String(), string(d), database.USER_PERMISSIONS_TABLE_NAME) +} + +// DeleteRole - deletes user role +func DeleteRole(rid models.UserRole) error { + if rid.String() == "" { + return errors.New("role id cannot be empty") + } + users, err := logic.GetUsersDB() + if err != nil { + return err + } + role, err := logic.GetRole(rid) + if err != nil { + return err + } + if role.Default { + return errors.New("cannot delete default role") + } + for _, user := range users { + for userG := range user.UserGroups { + ug, err := GetUserGroup(userG) + if err == nil { + if role.NetworkID != "" { + for _, networkRoles := range ug.NetworkRoles { + if _, ok := networkRoles[rid]; ok { + err = errors.New("role cannot be deleted as active user groups are using this role") + return err + } + } + } + + } + } + + if user.PlatformRoleID == rid { + err = errors.New("active roles cannot be deleted.switch existing users to a new role before deleting") + return err + } + for _, networkRoles := range user.NetworkRoles { + if _, ok := networkRoles[rid]; ok { + err = errors.New("active roles cannot be deleted.switch existing users to a new role before deleting") + return err + } + + } + } + return database.DeleteRecord(database.USER_PERMISSIONS_TABLE_NAME, rid.String()) +} + +func ValidateCreateGroupReq(g models.UserGroup) error { + // check platform role is valid + role, err := logic.GetRole(g.PlatformRole) + if err != nil { + err = fmt.Errorf("invalid platform role") + return err + } + if role.NetworkID != "" { + return errors.New("network role cannot be used as platform role") + } + // check if network roles are valid + for _, roleMap := range g.NetworkRoles { + for roleID := range roleMap { + role, err := logic.GetRole(roleID) + if err != nil { + return fmt.Errorf("invalid network role %s", roleID) + } + if role.NetworkID == "" { + return errors.New("platform role cannot be used as network role") + } + } + } + return nil +} +func ValidateUpdateGroupReq(g models.UserGroup) error { + // check platform role is valid + role, err := logic.GetRole(g.PlatformRole) + if err != nil { + err = fmt.Errorf("invalid platform role") + return err + } + if role.NetworkID != "" { + return errors.New("network role cannot be used as platform role") + } + for networkID := range g.NetworkRoles { + userRolesMap := g.NetworkRoles[networkID] + for roleID := range userRolesMap { + netRole, err := logic.GetRole(roleID) + if err != nil { + err = fmt.Errorf("invalid network role") + return err + } + if netRole.NetworkID == "" { + return errors.New("platform role cannot be used as network role") + } + } + } + return nil +} + +// CreateUserGroup - creates new user group +func CreateUserGroup(g models.UserGroup) error { + // check if role already exists + if g.ID == "" { + return errors.New("group id cannot be empty") + } + _, err := database.FetchRecord(database.USER_GROUPS_TABLE_NAME, g.ID.String()) + if err == nil { + return errors.New("group already exists") + } + d, err := json.Marshal(g) + if err != nil { + return err + } + return database.Insert(g.ID.String(), string(d), database.USER_GROUPS_TABLE_NAME) +} + +// GetUserGroup - fetches user group +func GetUserGroup(gid models.UserGroupID) (models.UserGroup, error) { + d, err := database.FetchRecord(database.USER_GROUPS_TABLE_NAME, gid.String()) + if err != nil { + return models.UserGroup{}, err + } + var ug models.UserGroup + err = json.Unmarshal([]byte(d), &ug) + if err != nil { + return ug, err + } + return ug, nil +} + +// ListUserGroups - lists user groups +func ListUserGroups() ([]models.UserGroup, error) { + data, err := database.FetchRecords(database.USER_GROUPS_TABLE_NAME) + if err != nil && !database.IsEmptyRecord(err) { + return []models.UserGroup{}, err + } + userGroups := []models.UserGroup{} + for _, dataI := range data { + userGroup := models.UserGroup{} + err := json.Unmarshal([]byte(dataI), &userGroup) + if err != nil { + continue + } + userGroups = append(userGroups, userGroup) + } + return userGroups, nil +} + +// UpdateUserGroup - updates new user group +func UpdateUserGroup(g models.UserGroup) error { + // check if group exists + if g.ID == "" { + return errors.New("group id cannot be empty") + } + _, err := database.FetchRecord(database.USER_GROUPS_TABLE_NAME, g.ID.String()) + if err != nil { + return err + } + d, err := json.Marshal(g) + if err != nil { + return err + } + return database.Insert(g.ID.String(), string(d), database.USER_GROUPS_TABLE_NAME) +} + +// DeleteUserGroup - deletes user group +func DeleteUserGroup(gid models.UserGroupID) error { + users, err := logic.GetUsersDB() + if err != nil { + return err + } + for _, user := range users { + delete(user.UserGroups, gid) + logic.UpsertUser(user) + } + return database.DeleteRecord(database.USER_GROUPS_TABLE_NAME, gid.String()) +} + +func HasNetworkRsrcScope(permissionTemplate models.UserRolePermissionTemplate, netid string, rsrcType models.RsrcType, rsrcID models.RsrcID, op string) bool { + if permissionTemplate.FullAccess { + return true + } + + rsrcScope, ok := permissionTemplate.NetworkLevelAccess[rsrcType] + if !ok { + return false + } + _, ok = rsrcScope[rsrcID] + return ok +} +func GetUserRAGNodes(user models.User) (gws map[string]models.Node) { + logger.Log(0, "------------> 7. getUserRemoteAccessGwsV1") + gws = make(map[string]models.Node) + userGwAccessScope := GetUserNetworkRolesWithRemoteVPNAccess(user) + logger.Log(0, fmt.Sprintf("User Gw Access Scope: %+v", userGwAccessScope)) + _, allNetAccess := userGwAccessScope["*"] + nodes, err := logic.GetAllNodes() + if err != nil { + return + } + logger.Log(0, "------------> 8. getUserRemoteAccessGwsV1") + for _, node := range nodes { + if node.IsIngressGateway && !node.PendingDelete { + if allNetAccess { + gws[node.ID.String()] = node + } else { + gwRsrcMap := userGwAccessScope[models.NetworkID(node.Network)] + scope, ok := gwRsrcMap[models.AllRemoteAccessGwRsrcID] + if !ok { + if scope, ok = gwRsrcMap[models.RsrcID(node.ID.String())]; !ok { + continue + } + } + if scope.VPNaccess { + gws[node.ID.String()] = node + } + + } + } + } + logger.Log(0, "------------> 9. getUserRemoteAccessGwsV1") + return +} + +// GetUserNetworkRoles - get user network roles +func GetUserNetworkRolesWithRemoteVPNAccess(user models.User) (gwAccess map[models.NetworkID]map[models.RsrcID]models.RsrcPermissionScope) { + gwAccess = make(map[models.NetworkID]map[models.RsrcID]models.RsrcPermissionScope) + logger.Log(0, "------------> 7.1 getUserRemoteAccessGwsV1") + platformRole, err := logic.GetRole(user.PlatformRoleID) + if err != nil { + return + } + if platformRole.FullAccess { + gwAccess[models.NetworkID("*")] = make(map[models.RsrcID]models.RsrcPermissionScope) + return + } + logger.Log(0, "------------> 7.2 getUserRemoteAccessGwsV1") + for netID, roleMap := range user.NetworkRoles { + for roleID := range roleMap { + role, err := logic.GetRole(roleID) + if err == nil { + if role.FullAccess { + gwAccess[netID] = map[models.RsrcID]models.RsrcPermissionScope{ + models.AllRemoteAccessGwRsrcID: { + Create: true, + Read: true, + Update: true, + VPNaccess: true, + Delete: true, + }, + models.AllExtClientsRsrcID: { + Create: true, + Read: true, + Update: true, + Delete: true, + }, + } + break + } + if rsrcsMap, ok := role.NetworkLevelAccess[models.RemoteAccessGwRsrc]; ok { + if permissions, ok := rsrcsMap[models.AllRemoteAccessGwRsrcID]; ok && permissions.VPNaccess { + if len(gwAccess[netID]) == 0 { + gwAccess[netID] = make(map[models.RsrcID]models.RsrcPermissionScope) + } + gwAccess[netID][models.AllRemoteAccessGwRsrcID] = permissions + break + } else { + for gwID, scope := range rsrcsMap { + if scope.VPNaccess { + if len(gwAccess[netID]) == 0 { + gwAccess[netID] = make(map[models.RsrcID]models.RsrcPermissionScope) + } + gwAccess[netID][gwID] = scope + } + } + } + + } + + } + } + } + logger.Log(0, "------------> 7.3 getUserRemoteAccessGwsV1") + return +} + +func GetFilteredNodesByUserAccess(user models.User, nodes []models.Node) (filteredNodes []models.Node) { + + nodesMap := make(map[string]struct{}) + allNetworkRoles := []models.UserRole{} + if len(user.NetworkRoles) > 0 { + for _, netRoles := range user.NetworkRoles { + for netRoleI := range netRoles { + allNetworkRoles = append(allNetworkRoles, netRoleI) + } + } + } + if len(user.UserGroups) > 0 { + for userGID := range user.UserGroups { + userG, err := GetUserGroup(userGID) + if err == nil { + if len(userG.NetworkRoles) > 0 { + for _, netRoles := range userG.NetworkRoles { + for netRoleI := range netRoles { + allNetworkRoles = append(allNetworkRoles, netRoleI) + } + } + } + } + } + } + for _, networkRoleID := range allNetworkRoles { + userPermTemplate, err := logic.GetRole(networkRoleID) + if err != nil { + continue + } + networkNodes := logic.GetNetworkNodesMemory(nodes, userPermTemplate.NetworkID) + if userPermTemplate.FullAccess { + for _, node := range networkNodes { + nodesMap[node.ID.String()] = struct{}{} + } + filteredNodes = append(filteredNodes, networkNodes...) + continue + } + if rsrcPerms, ok := userPermTemplate.NetworkLevelAccess[models.RemoteAccessGwRsrc]; ok { + if _, ok := rsrcPerms[models.AllRemoteAccessGwRsrcID]; ok { + for _, node := range networkNodes { + if _, ok := nodesMap[node.ID.String()]; ok { + continue + } + if node.IsIngressGateway { + nodesMap[node.ID.String()] = struct{}{} + filteredNodes = append(filteredNodes, node) + } + } + } else { + for gwID, scope := range rsrcPerms { + if _, ok := nodesMap[gwID.String()]; ok { + continue + } + if scope.Read { + gwNode, err := logic.GetNodeByID(gwID.String()) + if err == nil && gwNode.IsIngressGateway { + filteredNodes = append(filteredNodes, gwNode) + } + } + } + } + } + + } + return +} From b797067f5beba294402a9d6bc01b18158473b129 Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Mon, 29 Jul 2024 17:07:00 +0530 Subject: [PATCH 094/139] fix get user v1 api --- controllers/middleware.go | 7 +++++++ controllers/user.go | 14 ++++++++------ logic/security.go | 4 ++++ models/user_mgmt.go | 2 +- 4 files changed, 20 insertions(+), 7 deletions(-) diff --git a/controllers/middleware.go b/controllers/middleware.go index 8576d9cc9..a7e6902c9 100644 --- a/controllers/middleware.go +++ b/controllers/middleware.go @@ -2,6 +2,7 @@ package controller import ( "net/http" + "net/url" "strings" "github.com/gorilla/mux" @@ -76,7 +77,13 @@ func userMiddleWare(handler http.Handler) http.Handler { if userID, ok := params["username"]; ok { r.Header.Set("TARGET_RSRC_ID", userID) + } else { + username, _ := url.QueryUnescape(r.URL.Query().Get("username")) + if username != "" { + r.Header.Set("TARGET_RSRC_ID", username) + } } + if r.Header.Get("NET_ID") == "" && (r.Header.Get("TARGET_RSRC_ID") == "" || r.Header.Get("TARGET_RSRC") == models.EnrollmentKeysRsrc.String() || r.Header.Get("TARGET_RSRC") == models.UserRsrc.String()) { diff --git a/controllers/user.go b/controllers/user.go index 266608eff..7552fdf90 100644 --- a/controllers/user.go +++ b/controllers/user.go @@ -34,7 +34,7 @@ func userHandlers(r *mux.Router) { r.HandleFunc("/api/users/{username}", logic.SecurityCheck(true, checkFreeTierLimits(limitChoiceUsers, http.HandlerFunc(createUser)))).Methods(http.MethodPost) r.HandleFunc("/api/users/{username}", logic.SecurityCheck(true, http.HandlerFunc(deleteUser))).Methods(http.MethodDelete) r.HandleFunc("/api/users/{username}", logic.SecurityCheck(false, logic.ContinueIfUserMatch(http.HandlerFunc(getUser)))).Methods(http.MethodGet) - //r.HandleFunc("/api/v1/users/{username}", logic.SecurityCheck(false, logic.ContinueIfUserMatch(http.HandlerFunc(getUserV1)))).Methods(http.MethodGet) + r.HandleFunc("/api/v1/users", logic.SecurityCheck(false, logic.ContinueIfUserMatch(http.HandlerFunc(getUserV1)))).Methods(http.MethodGet) r.HandleFunc("/api/users", logic.SecurityCheck(true, http.HandlerFunc(getUsers))).Methods(http.MethodGet) r.HandleFunc("/api/users_pending", logic.SecurityCheck(true, http.HandlerFunc(getPendingUsers))).Methods(http.MethodGet) r.HandleFunc("/api/users_pending", logic.SecurityCheck(true, http.HandlerFunc(deleteAllPendingUsers))).Methods(http.MethodDelete) @@ -537,7 +537,7 @@ func getUser(w http.ResponseWriter, r *http.Request) { json.NewEncoder(w).Encode(user) } -// swagger:route GET /api/v1/users/{username} user getUser +// swagger:route GET /api/v1/users user getUserV1 // // Get an individual user with role info. // @@ -547,13 +547,15 @@ func getUser(w http.ResponseWriter, r *http.Request) { // oauth // // Responses: -// 200: userBodyResponse +// 200: ReturnUserWithRolesAndGroups func getUserV1(w http.ResponseWriter, r *http.Request) { // set header. w.Header().Set("Content-Type", "application/json") - - var params = mux.Vars(r) - usernameFetched := params["username"] + usernameFetched, _ := url.QueryUnescape(r.URL.Query().Get("username")) + if usernameFetched == "" { + logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("username is required"), "badrequest")) + return + } user, err := logic.GetReturnUser(usernameFetched) if err != nil { logger.Log(0, usernameFetched, "failed to fetch user: ", err.Error()) diff --git a/logic/security.go b/logic/security.go index 9600f298d..b10dcc860 100644 --- a/logic/security.go +++ b/logic/security.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" "net/http" + "net/url" "strings" "github.com/gorilla/mux" @@ -281,6 +282,9 @@ func ContinueIfUserMatch(next http.Handler) http.HandlerFunc { } var params = mux.Vars(r) var requestedUser = params["username"] + if requestedUser == "" { + requestedUser, _ = url.QueryUnescape(r.URL.Query().Get("username")) + } if requestedUser != r.Header.Get("user") { logger.Log(0, "next 2", r.URL.String(), errorResponse.Message) ReturnErrorResponse(w, r, errorResponse) diff --git a/models/user_mgmt.go b/models/user_mgmt.go index 0c3f391e7..4c1065320 100644 --- a/models/user_mgmt.go +++ b/models/user_mgmt.go @@ -136,7 +136,7 @@ type User struct { type ReturnUserWithRolesAndGroups struct { ReturnUser - PlatformRole UserRolePermissionTemplate + PlatformRole UserRolePermissionTemplate `json:"platform_role"` } // ReturnUser - return user struct From 0f97dc6c7fa005cd411069dc2b919eb3e7a6ee60 Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Tue, 30 Jul 2024 18:04:27 +0530 Subject: [PATCH 095/139] move user mgmt func to pro --- controllers/network.go | 36 +--------------- controllers/node.go | 23 +--------- logic/user_mgmt.go | 44 +++++++++++++------ pro/initialize.go | 5 +++ pro/logic/user_mgmt.go | 96 ++++++++++++++++++++++++++++++++++-------- 5 files changed, 116 insertions(+), 88 deletions(-) diff --git a/controllers/network.go b/controllers/network.go index 41c0cf0d3..09fadc163 100644 --- a/controllers/network.go +++ b/controllers/network.go @@ -60,41 +60,7 @@ func getNetworks(w http.ResponseWriter, r *http.Request) { logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } - platformRole, err := logic.GetRole(user.PlatformRoleID) - if err != nil { - logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) - return - } - if !platformRole.FullAccess { - allNetworkRoles := make(map[models.NetworkID]struct{}) - if len(user.NetworkRoles) > 0 { - for netID := range user.NetworkRoles { - allNetworkRoles[netID] = struct{}{} - - } - } - if len(user.UserGroups) > 0 { - for userGID := range user.UserGroups { - userG, err := logic.GetUserGroup(userGID) - if err == nil { - if len(userG.NetworkRoles) > 0 { - for netID := range userG.NetworkRoles { - allNetworkRoles[netID] = struct{}{} - - } - } - } - } - } - filteredNetworks := []models.Network{} - for _, networkI := range allnetworks { - if _, ok := allNetworkRoles[models.NetworkID(networkI.NetID)]; ok { - filteredNetworks = append(filteredNetworks, networkI) - } - } - allnetworks = filteredNetworks - } - + allnetworks = logic.FilterNetworksByRole(allnetworks, *user) logger.Log(2, r.Header.Get("user"), "fetched networks.") logic.SortNetworks(allnetworks[:]) w.WriteHeader(http.StatusOK) diff --git a/controllers/node.go b/controllers/node.go index 391843425..9b1dfaec9 100644 --- a/controllers/node.go +++ b/controllers/node.go @@ -650,27 +650,8 @@ func deleteIngressGateway(w http.ResponseWriter, r *http.Request) { logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } - if servercfg.IsPro { - go func() { - users, err := logic.GetUsersDB() - if err == nil { - for _, user := range users { - // delete role from user - if netRoles, ok := user.NetworkRoles[models.NetworkID(node.Network)]; ok { - delete(netRoles, models.GetRAGRoleName(node.Network, host.Name)) - user.NetworkRoles[models.NetworkID(node.Network)] = netRoles - err = logic.UpsertUser(user) - if err != nil { - slog.Error("failed to get user", "user", user.UserName, "error", err) - } - } - } - } else { - slog.Error("failed to get users", "error", err) - } - logic.DeleteRole(models.GetRAGRoleName(node.Network, host.Name)) - }() - } + + go logic.RemoveNetworkRoleFromUsers(*host, node) apiNode := node.ConvertToAPINode() logger.Log(1, r.Header.Get("user"), "deleted ingress gateway", nodeid) diff --git a/logic/user_mgmt.go b/logic/user_mgmt.go index 262275f65..446aaeba2 100644 --- a/logic/user_mgmt.go +++ b/logic/user_mgmt.go @@ -2,12 +2,24 @@ package logic import ( "encoding/json" - "errors" "github.com/gravitl/netmaker/database" "github.com/gravitl/netmaker/models" ) +// Pre-Define Permission Templates for default Roles +var SuperAdminPermissionTemplate = models.UserRolePermissionTemplate{ + ID: models.SuperAdminRole, + Default: true, + FullAccess: true, +} + +var AdminPermissionTemplate = models.UserRolePermissionTemplate{ + ID: models.AdminRole, + Default: true, + FullAccess: true, +} + var GetFilteredNodesByUserAccess = func(user models.User, nodes []models.Node) (filteredNodes []models.Node) { return } @@ -15,7 +27,19 @@ var GetFilteredNodesByUserAccess = func(user models.User, nodes []models.Node) ( var CreateRole = func(r models.UserRolePermissionTemplate) error { return nil } + +var FilterNetworksByRole = func(allnetworks []models.Network, user models.User) []models.Network { + return allnetworks +} + +var IsGroupsValid = func(groups map[models.UserGroupID]struct{}) error { + return nil +} +var RemoveNetworkRoleFromUsers = func(host models.Host, node models.Node) {} + +var InitialiseRoles = func() {} var DeleteNetworkRoles = func(netID string) {} +var CreateDefaultNetworkRoles = func(netID string) {} // GetRole - fetches role template by id func GetRole(roleID models.UserRole) (models.UserRolePermissionTemplate, error) { @@ -32,18 +56,10 @@ func GetRole(roleID models.UserRole) (models.UserRolePermissionTemplate, error) return ur, nil } -func IsGroupsValid(groups map[models.UserGroupID]struct{}) error { - uniqueGroupsPlatformRole := make(map[models.UserRole]struct{}) - for groupID := range groups { - userG, err := logic.GetUserGroup(groupID) - if err != nil { - return err - } - uniqueGroupsPlatformRole[userG.PlatformRole] = struct{}{} - } - if len(uniqueGroupsPlatformRole) > 1 { +func UserRolesInit() { + d, _ := json.Marshal(SuperAdminPermissionTemplate) + database.Insert(SuperAdminPermissionTemplate.ID.String(), string(d), database.USER_PERMISSIONS_TABLE_NAME) + d, _ = json.Marshal(AdminPermissionTemplate) + database.Insert(AdminPermissionTemplate.ID.String(), string(d), database.USER_PERMISSIONS_TABLE_NAME) - return errors.New("only groups with same platform role can be assigned to an user") - } - return nil } diff --git a/pro/initialize.go b/pro/initialize.go index 71e748b35..bc35254a6 100644 --- a/pro/initialize.go +++ b/pro/initialize.go @@ -22,6 +22,7 @@ import ( func InitPro() { servercfg.IsPro = true models.SetLogo(retrieveProLogo()) + proLogic.UserRolesInit() controller.HttpMiddlewares = append( controller.HttpMiddlewares, proControllers.OnlyServerAPIWhenUnlicensedMiddleware, @@ -124,6 +125,10 @@ func InitPro() { logic.NetworkPermissionsCheck = proLogic.NetworkPermissionsCheck logic.GlobalPermissionsCheck = proLogic.GlobalPermissionsCheck logic.DeleteNetworkRoles = proLogic.DeleteNetworkRoles + logic.CreateDefaultNetworkRoles = proLogic.CreateDefaultNetworkRoles + logic.FilterNetworksByRole = proLogic.FilterNetworksByRole + logic.IsGroupsValid = proLogic.IsGroupsValid + logic.RemoveNetworkRoleFromUsers = proLogic.RemoveNetworkRoleFromUsers } func retrieveProLogo() string { diff --git a/pro/logic/user_mgmt.go b/pro/logic/user_mgmt.go index aca027b35..0f25e760a 100644 --- a/pro/logic/user_mgmt.go +++ b/pro/logic/user_mgmt.go @@ -9,21 +9,9 @@ import ( "github.com/gravitl/netmaker/logger" "github.com/gravitl/netmaker/logic" "github.com/gravitl/netmaker/models" + "golang.org/x/exp/slog" ) -// Pre-Define Permission Templates for default Roles -var SuperAdminPermissionTemplate = models.UserRolePermissionTemplate{ - ID: models.SuperAdminRole, - Default: true, - FullAccess: true, -} - -var AdminPermissionTemplate = models.UserRolePermissionTemplate{ - ID: models.AdminRole, - Default: true, - FullAccess: true, -} - var ServiceUserPermissionTemplate = models.UserRolePermissionTemplate{ ID: models.ServiceUser, Default: true, @@ -38,11 +26,7 @@ var PlatformUserUserPermissionTemplate = models.UserRolePermissionTemplate{ } func UserRolesInit() { - d, _ := json.Marshal(SuperAdminPermissionTemplate) - database.Insert(SuperAdminPermissionTemplate.ID.String(), string(d), database.USER_PERMISSIONS_TABLE_NAME) - d, _ = json.Marshal(AdminPermissionTemplate) - database.Insert(AdminPermissionTemplate.ID.String(), string(d), database.USER_PERMISSIONS_TABLE_NAME) - d, _ = json.Marshal(ServiceUserPermissionTemplate) + d, _ := json.Marshal(ServiceUserPermissionTemplate) database.Insert(ServiceUserPermissionTemplate.ID.String(), string(d), database.USER_PERMISSIONS_TABLE_NAME) d, _ = json.Marshal(PlatformUserUserPermissionTemplate) database.Insert(PlatformUserUserPermissionTemplate.ID.String(), string(d), database.USER_PERMISSIONS_TABLE_NAME) @@ -558,3 +542,79 @@ func GetFilteredNodesByUserAccess(user models.User, nodes []models.Node) (filter } return } + +func FilterNetworksByRole(allnetworks []models.Network, user models.User) []models.Network { + platformRole, err := logic.GetRole(user.PlatformRoleID) + if err != nil { + return []models.Network{} + } + if !platformRole.FullAccess { + allNetworkRoles := make(map[models.NetworkID]struct{}) + if len(user.NetworkRoles) > 0 { + for netID := range user.NetworkRoles { + allNetworkRoles[netID] = struct{}{} + + } + } + if len(user.UserGroups) > 0 { + for userGID := range user.UserGroups { + userG, err := GetUserGroup(userGID) + if err == nil { + if len(userG.NetworkRoles) > 0 { + for netID := range userG.NetworkRoles { + allNetworkRoles[netID] = struct{}{} + + } + } + } + } + } + filteredNetworks := []models.Network{} + for _, networkI := range allnetworks { + if _, ok := allNetworkRoles[models.NetworkID(networkI.NetID)]; ok { + filteredNetworks = append(filteredNetworks, networkI) + } + } + allnetworks = filteredNetworks + } + return allnetworks +} + +func IsGroupsValid(groups map[models.UserGroupID]struct{}) error { + uniqueGroupsPlatformRole := make(map[models.UserRole]struct{}) + for groupID := range groups { + userG, err := GetUserGroup(groupID) + if err != nil { + return err + } + uniqueGroupsPlatformRole[userG.PlatformRole] = struct{}{} + } + if len(uniqueGroupsPlatformRole) > 1 { + + return errors.New("only groups with same platform role can be assigned to an user") + } + return nil +} + +func RemoveNetworkRoleFromUsers(host models.Host, node models.Node) { + users, err := logic.GetUsersDB() + if err == nil { + for _, user := range users { + // delete role from user + if netRoles, ok := user.NetworkRoles[models.NetworkID(node.Network)]; ok { + delete(netRoles, models.GetRAGRoleName(node.Network, host.Name)) + user.NetworkRoles[models.NetworkID(node.Network)] = netRoles + err = logic.UpsertUser(user) + if err != nil { + slog.Error("failed to get user", "user", user.UserName, "error", err) + } + } + } + } else { + slog.Error("failed to get users", "error", err) + } + err = DeleteRole(models.GetRAGRoleName(node.Network, host.Name)) + if err != nil { + slog.Error("failed to delete role: ", models.GetRAGRoleName(node.Network, host.Name), err) + } +} From eef24cb7f3f3ced4ea9a42803e41068892218f59 Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Wed, 31 Jul 2024 10:56:42 +0530 Subject: [PATCH 096/139] add user auth type to user model --- auth/auth.go | 38 ----------------------------------- controllers/user.go | 3 ++- logic/auth.go | 38 ++++++++++++++++++++++++++++++++++- migrate/migrate.go | 4 ++++ models/user_mgmt.go | 8 ++++++++ pro/auth/azure-ad.go | 5 ++--- pro/auth/github.go | 5 ++--- pro/auth/google.go | 5 ++--- pro/auth/headless_callback.go | 3 +-- pro/auth/oidc.go | 5 ++--- 10 files changed, 60 insertions(+), 54 deletions(-) diff --git a/auth/auth.go b/auth/auth.go index 0f89fdc19..40657e530 100644 --- a/auth/auth.go +++ b/auth/auth.go @@ -1,13 +1,8 @@ package auth import ( - "encoding/base64" - "encoding/json" - - "github.com/gravitl/netmaker/logger" "github.com/gravitl/netmaker/logic" "github.com/gravitl/netmaker/models" - "golang.org/x/crypto/bcrypt" "golang.org/x/oauth2" ) @@ -20,39 +15,6 @@ var ( auth_provider *oauth2.Config ) -// IsOauthUser - returns -func IsOauthUser(user *models.User) error { - var currentValue, err = FetchPassValue("") - if err != nil { - return err - } - var bCryptErr = bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(currentValue)) - return bCryptErr -} - -func FetchPassValue(newValue string) (string, error) { - - type valueHolder struct { - Value string `json:"value" bson:"value"` - } - newValueHolder := valueHolder{} - var currentValue, err = logic.FetchAuthSecret() - if err != nil { - return "", err - } - var unmarshErr = json.Unmarshal([]byte(currentValue), &newValueHolder) - if unmarshErr != nil { - return "", unmarshErr - } - - var b64CurrentValue, b64Err = base64.StdEncoding.DecodeString(newValueHolder.Value) - if b64Err != nil { - logger.Log(0, "could not decode pass") - return "", nil - } - return string(b64CurrentValue), nil -} - func isUserIsAllowed(username, network string) (*models.User, error) { user, err := logic.GetUser(username) diff --git a/controllers/user.go b/controllers/user.go index 86c2172b8..d0cf02c32 100644 --- a/controllers/user.go +++ b/controllers/user.go @@ -422,6 +422,7 @@ func createUser(w http.ResponseWriter, r *http.Request) { logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("non-admins users can only be created on Pro version"), "forbidden")) return } + err = logic.CreateUser(&user) if err != nil { slog.Error("error creating new user: ", "user", user.UserName, "error", err.Error()) @@ -526,7 +527,7 @@ func updateUser(w http.ResponseWriter, r *http.Request) { } } - if auth.IsOauthUser(user) == nil && userchange.Password != "" { + if logic.IsOauthUser(user) == nil && userchange.Password != "" { err := fmt.Errorf("cannot update user's password for an oauth user %s", username) logic.ReturnErrorResponse(w, r, logic.FormatError(err, "forbidden")) return diff --git a/logic/auth.go b/logic/auth.go index 2a07e56a3..fab960948 100644 --- a/logic/auth.go +++ b/logic/auth.go @@ -93,18 +93,54 @@ func GetUsers() ([]models.ReturnUser, error) { return users, err } +// IsOauthUser - returns +func IsOauthUser(user *models.User) error { + var currentValue, err = FetchPassValue("") + if err != nil { + return err + } + var bCryptErr = bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(currentValue)) + return bCryptErr +} + +func FetchPassValue(newValue string) (string, error) { + + type valueHolder struct { + Value string `json:"value" bson:"value"` + } + newValueHolder := valueHolder{} + var currentValue, err = FetchAuthSecret() + if err != nil { + return "", err + } + var unmarshErr = json.Unmarshal([]byte(currentValue), &newValueHolder) + if unmarshErr != nil { + return "", unmarshErr + } + + var b64CurrentValue, b64Err = base64.StdEncoding.DecodeString(newValueHolder.Value) + if b64Err != nil { + logger.Log(0, "could not decode pass") + return "", nil + } + return string(b64CurrentValue), nil +} + // 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") } + user.AuthType = models.BasicAuth + if IsOauthUser(user) == nil { + user.AuthType = models.OAuth + } var err = ValidateUser(user) if err != nil { logger.Log(0, "failed to validate user", err.Error()) return err } - // encrypt that password so we never see it again hash, err := bcrypt.GenerateFromPassword([]byte(user.Password), 5) if err != nil { diff --git a/migrate/migrate.go b/migrate/migrate.go index a4d4600f5..e61f71604 100644 --- a/migrate/migrate.go +++ b/migrate/migrate.go @@ -356,6 +356,10 @@ func syncUsers() { if user.PlatformRoleID.String() != "" { continue } + user.AuthType = models.BasicAuth + if logic.IsOauthUser(&user) == nil { + user.AuthType = models.OAuth + } if len(user.NetworkRoles) == 0 { user.NetworkRoles = make(map[models.NetworkID]map[models.UserRole]struct{}) } diff --git a/models/user_mgmt.go b/models/user_mgmt.go index 0c3f391e7..9896b5b98 100644 --- a/models/user_mgmt.go +++ b/models/user_mgmt.go @@ -12,6 +12,12 @@ type RsrcType string type RsrcID string type UserRole string type UserGroupID string +type AuthType string + +var ( + BasicAuth AuthType = "basic_auth" + OAuth AuthType = "oauth" +) func (r RsrcType) String() string { return string(r) @@ -128,6 +134,7 @@ type User struct { IsAdmin bool `json:"isadmin" bson:"isadmin"` // deprecated IsSuperAdmin bool `json:"issuperadmin"` // deprecated RemoteGwIDs map[string]struct{} `json:"remote_gw_ids"` // deprecated + AuthType AuthType `json:"auth_type"` UserGroups map[UserGroupID]struct{} `json:"user_group_ids"` PlatformRoleID UserRole `json:"platform_role_id"` NetworkRoles map[NetworkID]map[UserRole]struct{} `json:"network_roles"` @@ -144,6 +151,7 @@ type ReturnUser struct { UserName string `json:"username"` IsAdmin bool `json:"isadmin"` IsSuperAdmin bool `json:"issuperadmin"` + AuthType AuthType `json:"auth_type"` RemoteGwIDs map[string]struct{} `json:"remote_gw_ids"` // deprecated UserGroups map[UserGroupID]struct{} `json:"user_group_ids"` PlatformRoleID UserRole `json:"platform_role_id"` diff --git a/pro/auth/azure-ad.go b/pro/auth/azure-ad.go index 96a782152..ef342994d 100644 --- a/pro/auth/azure-ad.go +++ b/pro/auth/azure-ad.go @@ -9,7 +9,6 @@ import ( "net/http" "strings" - "github.com/gravitl/netmaker/auth" "github.com/gravitl/netmaker/database" "github.com/gravitl/netmaker/logger" "github.com/gravitl/netmaker/logic" @@ -90,7 +89,7 @@ func handleAzureCallback(w http.ResponseWriter, r *http.Request) { if database.IsEmptyRecord(err) { // user must not exist, so try to make one if inviteExists { // create user - var newPass, fetchErr = auth.FetchPassValue("") + var newPass, fetchErr = logic.FetchPassValue("") if fetchErr != nil { logic.ReturnErrorResponse(w, r, logic.FormatError(fetchErr, "internal")) return @@ -147,7 +146,7 @@ func handleAzureCallback(w http.ResponseWriter, r *http.Request) { handleOauthUserNotAllowed(w) return } - var newPass, fetchErr = auth.FetchPassValue("") + var newPass, fetchErr = logic.FetchPassValue("") if fetchErr != nil { return } diff --git a/pro/auth/github.go b/pro/auth/github.go index c8dc851a8..a39d174d8 100644 --- a/pro/auth/github.go +++ b/pro/auth/github.go @@ -9,7 +9,6 @@ import ( "net/http" "strings" - "github.com/gravitl/netmaker/auth" "github.com/gravitl/netmaker/database" "github.com/gravitl/netmaker/logger" "github.com/gravitl/netmaker/logic" @@ -89,7 +88,7 @@ func handleGithubCallback(w http.ResponseWriter, r *http.Request) { if database.IsEmptyRecord(err) { // user must not exist, so try to make one if inviteExists { // create user - var newPass, fetchErr = auth.FetchPassValue("") + var newPass, fetchErr = logic.FetchPassValue("") if fetchErr != nil { logic.ReturnErrorResponse(w, r, logic.FormatError(fetchErr, "internal")) return @@ -147,7 +146,7 @@ func handleGithubCallback(w http.ResponseWriter, r *http.Request) { handleOauthUserNotAllowed(w) return } - var newPass, fetchErr = auth.FetchPassValue("") + var newPass, fetchErr = logic.FetchPassValue("") if fetchErr != nil { return } diff --git a/pro/auth/google.go b/pro/auth/google.go index 8caf130dd..34dc23965 100644 --- a/pro/auth/google.go +++ b/pro/auth/google.go @@ -10,7 +10,6 @@ import ( "strings" "time" - "github.com/gravitl/netmaker/auth" "github.com/gravitl/netmaker/database" "github.com/gravitl/netmaker/logger" "github.com/gravitl/netmaker/logic" @@ -96,7 +95,7 @@ func handleGoogleCallback(w http.ResponseWriter, r *http.Request) { if inviteExists { // create user logger.Log(0, "CALLBACK ----> 4.0") - var newPass, fetchErr = auth.FetchPassValue("") + var newPass, fetchErr = logic.FetchPassValue("") if fetchErr != nil { logic.ReturnErrorResponse(w, r, logic.FormatError(fetchErr, "internal")) return @@ -159,7 +158,7 @@ func handleGoogleCallback(w http.ResponseWriter, r *http.Request) { handleOauthUserNotAllowed(w) return } - var newPass, fetchErr = auth.FetchPassValue("") + var newPass, fetchErr = logic.FetchPassValue("") if fetchErr != nil { return } diff --git a/pro/auth/headless_callback.go b/pro/auth/headless_callback.go index 7fb04aa6e..de0627ca9 100644 --- a/pro/auth/headless_callback.go +++ b/pro/auth/headless_callback.go @@ -5,7 +5,6 @@ import ( "fmt" "net/http" - "github.com/gravitl/netmaker/auth" "github.com/gravitl/netmaker/database" "github.com/gravitl/netmaker/logger" "github.com/gravitl/netmaker/logic" @@ -78,7 +77,7 @@ func HandleHeadlessSSOCallback(w http.ResponseWriter, r *http.Request) { return } } - newPass, fetchErr := auth.FetchPassValue("") + newPass, fetchErr := logic.FetchPassValue("") if fetchErr != nil { return } diff --git a/pro/auth/oidc.go b/pro/auth/oidc.go index 4858ba186..0b62a91bf 100644 --- a/pro/auth/oidc.go +++ b/pro/auth/oidc.go @@ -9,7 +9,6 @@ import ( "time" "github.com/coreos/go-oidc/v3/oidc" - "github.com/gravitl/netmaker/auth" "github.com/gravitl/netmaker/database" "github.com/gravitl/netmaker/logger" "github.com/gravitl/netmaker/logic" @@ -102,7 +101,7 @@ func handleOIDCCallback(w http.ResponseWriter, r *http.Request) { if database.IsEmptyRecord(err) { // user must not exist, so try to make one if inviteExists { // create user - var newPass, fetchErr = auth.FetchPassValue("") + var newPass, fetchErr = logic.FetchPassValue("") if fetchErr != nil { logic.ReturnErrorResponse(w, r, logic.FormatError(fetchErr, "internal")) return @@ -159,7 +158,7 @@ func handleOIDCCallback(w http.ResponseWriter, r *http.Request) { handleOauthUserNotAllowed(w) return } - var newPass, fetchErr = auth.FetchPassValue("") + var newPass, fetchErr = logic.FetchPassValue("") if fetchErr != nil { return } From a8c796b73560e953e054d75b9aeb5f928cd8f9d1 Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Wed, 31 Jul 2024 11:33:09 +0530 Subject: [PATCH 097/139] fix roles init --- logic/user_mgmt.go | 4 ++-- main.go | 2 +- pro/controllers/users.go | 3 +-- pro/initialize.go | 2 +- pro/logic/user_mgmt.go | 6 +++++- 5 files changed, 10 insertions(+), 7 deletions(-) diff --git a/logic/user_mgmt.go b/logic/user_mgmt.go index 446aaeba2..af8195202 100644 --- a/logic/user_mgmt.go +++ b/logic/user_mgmt.go @@ -37,7 +37,7 @@ var IsGroupsValid = func(groups map[models.UserGroupID]struct{}) error { } var RemoveNetworkRoleFromUsers = func(host models.Host, node models.Node) {} -var InitialiseRoles = func() {} +var InitialiseRoles = userRolesInit var DeleteNetworkRoles = func(netID string) {} var CreateDefaultNetworkRoles = func(netID string) {} @@ -56,7 +56,7 @@ func GetRole(roleID models.UserRole) (models.UserRolePermissionTemplate, error) return ur, nil } -func UserRolesInit() { +func userRolesInit() { d, _ := json.Marshal(SuperAdminPermissionTemplate) database.Insert(SuperAdminPermissionTemplate.ID.String(), string(d), database.USER_PERMISSIONS_TABLE_NAME) d, _ = json.Marshal(AdminPermissionTemplate) diff --git a/main.go b/main.go index 6544cc1e5..4e330f900 100644 --- a/main.go +++ b/main.go @@ -89,7 +89,7 @@ func initialize() { // Client Mode Prereq Check migrate.Run() logic.SetJWTSecret() - logic.UserRolesInit() + logic.InitialiseRoles() err = serverctl.SetDefaults() if err != nil { logger.FatalLog("error setting defaults: ", err.Error()) diff --git a/pro/controllers/users.go b/pro/controllers/users.go index e83a487b3..2622396ea 100644 --- a/pro/controllers/users.go +++ b/pro/controllers/users.go @@ -9,7 +9,6 @@ import ( "net/url" "github.com/gorilla/mux" - "github.com/gravitl/netmaker/auth" "github.com/gravitl/netmaker/database" "github.com/gravitl/netmaker/logger" "github.com/gravitl/netmaker/logic" @@ -1148,7 +1147,7 @@ func approvePendingUser(w http.ResponseWriter, r *http.Request) { } for _, user := range users { if user.UserName == username { - var newPass, fetchErr = auth.FetchPassValue("") + var newPass, fetchErr = logic.FetchPassValue("") if fetchErr != nil { logic.ReturnErrorResponse(w, r, logic.FormatError(fetchErr, "internal")) return diff --git a/pro/initialize.go b/pro/initialize.go index bc35254a6..8abf4a125 100644 --- a/pro/initialize.go +++ b/pro/initialize.go @@ -22,7 +22,6 @@ import ( func InitPro() { servercfg.IsPro = true models.SetLogo(retrieveProLogo()) - proLogic.UserRolesInit() controller.HttpMiddlewares = append( controller.HttpMiddlewares, proControllers.OnlyServerAPIWhenUnlicensedMiddleware, @@ -129,6 +128,7 @@ func InitPro() { logic.FilterNetworksByRole = proLogic.FilterNetworksByRole logic.IsGroupsValid = proLogic.IsGroupsValid logic.RemoveNetworkRoleFromUsers = proLogic.RemoveNetworkRoleFromUsers + logic.InitialiseRoles = proLogic.UserRolesInit } func retrieveProLogo() string { diff --git a/pro/logic/user_mgmt.go b/pro/logic/user_mgmt.go index 0f25e760a..b5880e5cb 100644 --- a/pro/logic/user_mgmt.go +++ b/pro/logic/user_mgmt.go @@ -26,7 +26,11 @@ var PlatformUserUserPermissionTemplate = models.UserRolePermissionTemplate{ } func UserRolesInit() { - d, _ := json.Marshal(ServiceUserPermissionTemplate) + d, _ := json.Marshal(logic.SuperAdminPermissionTemplate) + database.Insert(logic.SuperAdminPermissionTemplate.ID.String(), string(d), database.USER_PERMISSIONS_TABLE_NAME) + d, _ = json.Marshal(logic.AdminPermissionTemplate) + database.Insert(logic.AdminPermissionTemplate.ID.String(), string(d), database.USER_PERMISSIONS_TABLE_NAME) + d, _ = json.Marshal(ServiceUserPermissionTemplate) database.Insert(ServiceUserPermissionTemplate.ID.String(), string(d), database.USER_PERMISSIONS_TABLE_NAME) d, _ = json.Marshal(PlatformUserUserPermissionTemplate) database.Insert(PlatformUserUserPermissionTemplate.ID.String(), string(d), database.USER_PERMISSIONS_TABLE_NAME) From eed4a7fa18debd8ab63a9669750c629066c5af4b Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Wed, 31 Jul 2024 12:33:46 +0530 Subject: [PATCH 098/139] remove platform role from group object --- controllers/user.go | 4 ++++ models/user_mgmt.go | 15 ++++++++------- pro/auth/azure-ad.go | 5 +++-- pro/auth/github.go | 4 ++-- pro/auth/google.go | 4 ++-- pro/auth/oidc.go | 4 ++-- pro/controllers/users.go | 17 +++++------------ pro/logic/user_mgmt.go | 29 ++++------------------------- 8 files changed, 30 insertions(+), 52 deletions(-) diff --git a/controllers/user.go b/controllers/user.go index d0cf02c32..8b7b892ac 100644 --- a/controllers/user.go +++ b/controllers/user.go @@ -397,6 +397,10 @@ func createUser(w http.ResponseWriter, r *http.Request) { logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) return } + if user.PlatformRoleID == "" { + logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("platform role is missing"), "badrequest")) + return + } userRole, err := logic.GetRole(user.PlatformRoleID) if err != nil { err = errors.New("error fetching role " + user.PlatformRoleID.String() + " " + err.Error()) diff --git a/models/user_mgmt.go b/models/user_mgmt.go index bd15302a7..573fee1a5 100644 --- a/models/user_mgmt.go +++ b/models/user_mgmt.go @@ -122,7 +122,6 @@ type CreateGroupReq struct { type UserGroup struct { ID UserGroupID `json:"id"` - PlatformRole UserRole `json:"platform_role"` NetworkRoles map[NetworkID]map[UserRole]struct{} `json:"network_roles"` MetaData string `json:"meta_data"` } @@ -173,14 +172,16 @@ type UserClaims struct { } type InviteUsersReq struct { - UserEmails []string `json:"user_emails"` - Groups []UserGroupID + UserEmails []string `json:"user_emails"` + PlatformRoleID string `json:"platform_role_id"` + Groups []UserGroupID } // UserInvite - model for user invite type UserInvite struct { - Email string `json:"email"` - Groups []UserGroupID `json:"groups"` - InviteCode string `json:"invite_code"` - InviteURL string `json:"invite_url"` + Email string `json:"email"` + PlatformRoleID string `json:"platform_role_id"` + Groups []UserGroupID `json:"groups"` + InviteCode string `json:"invite_code"` + InviteURL string `json:"invite_url"` } diff --git a/pro/auth/azure-ad.go b/pro/auth/azure-ad.go index ef342994d..2f27023e3 100644 --- a/pro/auth/azure-ad.go +++ b/pro/auth/azure-ad.go @@ -99,14 +99,15 @@ func handleAzureCallback(w http.ResponseWriter, r *http.Request) { Password: newPass, } for _, inviteGroupID := range in.Groups { - userG, err := proLogic.GetUserGroup(inviteGroupID) + _, err := proLogic.GetUserGroup(inviteGroupID) if err != nil { logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("error fetching group id "+inviteGroupID.String()), "badrequest")) return } - user.PlatformRoleID = userG.PlatformRole + user.UserGroups[inviteGroupID] = struct{}{} } + user.PlatformRoleID = models.UserRole(in.PlatformRoleID) if user.PlatformRoleID == "" { user.PlatformRoleID = models.ServiceUser } diff --git a/pro/auth/github.go b/pro/auth/github.go index a39d174d8..5f10746d2 100644 --- a/pro/auth/github.go +++ b/pro/auth/github.go @@ -99,14 +99,14 @@ func handleGithubCallback(w http.ResponseWriter, r *http.Request) { } for _, inviteGroupID := range in.Groups { - userG, err := proLogic.GetUserGroup(inviteGroupID) + _, err := proLogic.GetUserGroup(inviteGroupID) if err != nil { logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("error fetching group id "+inviteGroupID.String()), "badrequest")) return } - user.PlatformRoleID = userG.PlatformRole user.UserGroups[inviteGroupID] = struct{}{} } + user.PlatformRoleID = models.UserRole(in.PlatformRoleID) if user.PlatformRoleID == "" { user.PlatformRoleID = models.ServiceUser } diff --git a/pro/auth/google.go b/pro/auth/google.go index 34dc23965..7883d3a61 100644 --- a/pro/auth/google.go +++ b/pro/auth/google.go @@ -106,16 +106,16 @@ func handleGoogleCallback(w http.ResponseWriter, r *http.Request) { } logger.Log(0, "CALLBACK ----> 4.1") for _, inviteGroupID := range in.Groups { - userG, err := proLogic.GetUserGroup(inviteGroupID) + _, err := proLogic.GetUserGroup(inviteGroupID) if err != nil { logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("error fetching group id "+inviteGroupID.String()), "badrequest")) return } - user.PlatformRoleID = userG.PlatformRole user.UserGroups = make(map[models.UserGroupID]struct{}) user.UserGroups[inviteGroupID] = struct{}{} } logger.Log(0, "CALLBACK ----> 5") + user.PlatformRoleID = models.UserRole(in.PlatformRoleID) if user.PlatformRoleID == "" { user.PlatformRoleID = models.ServiceUser } diff --git a/pro/auth/oidc.go b/pro/auth/oidc.go index 0b62a91bf..fd48823ab 100644 --- a/pro/auth/oidc.go +++ b/pro/auth/oidc.go @@ -111,14 +111,14 @@ func handleOIDCCallback(w http.ResponseWriter, r *http.Request) { Password: newPass, } for _, inviteGroupID := range in.Groups { - userG, err := proLogic.GetUserGroup(inviteGroupID) + _, err := proLogic.GetUserGroup(inviteGroupID) if err != nil { logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("error fetching group id "+inviteGroupID.String()), "badrequest")) return } - user.PlatformRoleID = userG.PlatformRole user.UserGroups[inviteGroupID] = struct{}{} } + user.PlatformRoleID = models.UserRole(in.PlatformRoleID) if user.PlatformRoleID == "" { user.PlatformRoleID = models.ServiceUser } diff --git a/pro/controllers/users.go b/pro/controllers/users.go index 2622396ea..1fad3aa1f 100644 --- a/pro/controllers/users.go +++ b/pro/controllers/users.go @@ -105,15 +105,14 @@ func userInviteSignUp(w http.ResponseWriter, r *http.Request) { } for _, inviteGroupID := range in.Groups { - userG, err := proLogic.GetUserGroup(inviteGroupID) + _, err := proLogic.GetUserGroup(inviteGroupID) if err != nil { - logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("error fetching group id "+inviteGroupID.String()), "badrequest")) - return + continue } - user.PlatformRoleID = userG.PlatformRole user.UserGroups = make(map[models.UserGroupID]struct{}) user.UserGroups[inviteGroupID] = struct{}{} } + user.PlatformRoleID = models.UserRole(in.PlatformRoleID) if user.PlatformRoleID == "" { user.PlatformRoleID = models.ServiceUser } @@ -171,19 +170,13 @@ func inviteUsers(w http.ResponseWriter, r *http.Request) { return } //validate Req - uniqueGroupsPlatformRole := make(map[models.UserRole]struct{}) for _, groupID := range inviteReq.Groups { - userG, err := proLogic.GetUserGroup(groupID) + _, err := proLogic.GetUserGroup(groupID) if err != nil { logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) return } - uniqueGroupsPlatformRole[userG.PlatformRole] = struct{}{} - } - if len(uniqueGroupsPlatformRole) > 1 { - err = errors.New("only groups with same platform role can be assigned to an user") - logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) - return + } for _, inviteeEmail := range inviteReq.UserEmails { diff --git a/pro/logic/user_mgmt.go b/pro/logic/user_mgmt.go index b5880e5cb..6a30db0a2 100644 --- a/pro/logic/user_mgmt.go +++ b/pro/logic/user_mgmt.go @@ -243,15 +243,7 @@ func DeleteRole(rid models.UserRole) error { } func ValidateCreateGroupReq(g models.UserGroup) error { - // check platform role is valid - role, err := logic.GetRole(g.PlatformRole) - if err != nil { - err = fmt.Errorf("invalid platform role") - return err - } - if role.NetworkID != "" { - return errors.New("network role cannot be used as platform role") - } + // check if network roles are valid for _, roleMap := range g.NetworkRoles { for roleID := range roleMap { @@ -267,15 +259,7 @@ func ValidateCreateGroupReq(g models.UserGroup) error { return nil } func ValidateUpdateGroupReq(g models.UserGroup) error { - // check platform role is valid - role, err := logic.GetRole(g.PlatformRole) - if err != nil { - err = fmt.Errorf("invalid platform role") - return err - } - if role.NetworkID != "" { - return errors.New("network role cannot be used as platform role") - } + for networkID := range g.NetworkRoles { userRolesMap := g.NetworkRoles[networkID] for roleID := range userRolesMap { @@ -585,17 +569,12 @@ func FilterNetworksByRole(allnetworks []models.Network, user models.User) []mode } func IsGroupsValid(groups map[models.UserGroupID]struct{}) error { - uniqueGroupsPlatformRole := make(map[models.UserRole]struct{}) + for groupID := range groups { - userG, err := GetUserGroup(groupID) + _, err := GetUserGroup(groupID) if err != nil { return err } - uniqueGroupsPlatformRole[userG.PlatformRole] = struct{}{} - } - if len(uniqueGroupsPlatformRole) > 1 { - - return errors.New("only groups with same platform role can be assigned to an user") } return nil } From ad4b6f9cfc7f2cc0d3e0fbdd3114027a0d957a9d Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Wed, 31 Jul 2024 12:56:15 +0530 Subject: [PATCH 099/139] list only platform roles --- pro/controllers/users.go | 10 +++++++++- pro/logic/user_mgmt.go | 30 +++++++++++++++++++++++++++--- 2 files changed, 36 insertions(+), 4 deletions(-) diff --git a/pro/controllers/users.go b/pro/controllers/users.go index 1fad3aa1f..9e508c50c 100644 --- a/pro/controllers/users.go +++ b/pro/controllers/users.go @@ -452,7 +452,14 @@ func deleteUserGroup(w http.ResponseWriter, r *http.Request) { // Responses: // 200: userBodyResponse func listRoles(w http.ResponseWriter, r *http.Request) { - roles, err := proLogic.ListRoles() + platform, _ := url.QueryUnescape(r.URL.Query().Get("platform")) + var roles []models.UserRolePermissionTemplate + var err error + if platform == "true" { + roles, err = proLogic.ListPlatformRoles() + } else { + roles, err = proLogic.ListNetworkRoles() + } if err != nil { logic.ReturnErrorResponse(w, r, models.ErrorResponse{ Code: http.StatusInternalServerError, @@ -460,6 +467,7 @@ func listRoles(w http.ResponseWriter, r *http.Request) { }) return } + logic.ReturnSuccessResponseWithJson(w, r, roles, "successfully fetched user roles permission templates") } diff --git a/pro/logic/user_mgmt.go b/pro/logic/user_mgmt.go index 6a30db0a2..264d27162 100644 --- a/pro/logic/user_mgmt.go +++ b/pro/logic/user_mgmt.go @@ -96,7 +96,7 @@ func DeleteNetworkRoles(netID string) { } } - roles, _ := ListRoles() + roles, _ := ListNetworkRoles() for _, role := range roles { if role.NetworkID == netID { DeleteRole(role.ID) @@ -104,8 +104,8 @@ func DeleteNetworkRoles(netID string) { } } -// ListRoles - lists user roles permission templates -func ListRoles() ([]models.UserRolePermissionTemplate, error) { +// ListNetworkRoles - lists user network roles permission templates +func ListNetworkRoles() ([]models.UserRolePermissionTemplate, error) { data, err := database.FetchRecords(database.USER_PERMISSIONS_TABLE_NAME) if err != nil && !database.IsEmptyRecord(err) { return []models.UserRolePermissionTemplate{}, err @@ -117,6 +117,30 @@ func ListRoles() ([]models.UserRolePermissionTemplate, error) { if err != nil { continue } + if userRole.NetworkID == "" { + continue + } + userRoles = append(userRoles, userRole) + } + return userRoles, nil +} + +// ListPlatformRoles - lists user platform roles permission templates +func ListPlatformRoles() ([]models.UserRolePermissionTemplate, error) { + data, err := database.FetchRecords(database.USER_PERMISSIONS_TABLE_NAME) + if err != nil && !database.IsEmptyRecord(err) { + return []models.UserRolePermissionTemplate{}, err + } + userRoles := []models.UserRolePermissionTemplate{} + for _, dataI := range data { + userRole := models.UserRolePermissionTemplate{} + err := json.Unmarshal([]byte(dataI), &userRole) + if err != nil { + continue + } + if userRole.NetworkID != "" { + continue + } userRoles = append(userRoles, userRole) } return userRoles, nil From ce226122efdf5eac636577eb98977128337c7be4 Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Thu, 1 Aug 2024 00:01:01 +0530 Subject: [PATCH 100/139] add network roles to invite req --- controllers/node.go | 2 +- controllers/user.go | 5 +-- logic/auth.go | 16 +++---- logic/jwts.go | 2 +- logic/user_mgmt.go | 5 ++- logic/users.go | 6 +++ migrate/migrate.go | 6 +-- models/user_mgmt.go | 93 +++++++++++++++++++++------------------- pro/auth/azure-ad.go | 26 ++--------- pro/auth/github.go | 26 ++--------- pro/auth/google.go | 31 +++----------- pro/auth/oidc.go | 25 ++--------- pro/controllers/users.go | 40 ++++++++--------- pro/initialize.go | 1 + pro/logic/security.go | 13 +++++- pro/logic/user_mgmt.go | 89 ++++++++++++++++++++++++++++++++++---- 16 files changed, 203 insertions(+), 183 deletions(-) diff --git a/controllers/node.go b/controllers/node.go index 9b1dfaec9..400eb85f5 100644 --- a/controllers/node.go +++ b/controllers/node.go @@ -585,7 +585,7 @@ func createIngressGateway(w http.ResponseWriter, r *http.Request) { // create network role for this gateway logic.CreateRole(models.UserRolePermissionTemplate{ ID: models.GetRAGRoleName(node.Network, host.Name), - NetworkID: node.Network, + NetworkID: models.NetworkID(node.Network), NetworkLevelAccess: map[models.RsrcType]map[models.RsrcID]models.RsrcPermissionScope{ models.RemoteAccessGwRsrc: { models.RsrcID(node.ID.String()): models.RsrcPermissionScope{ diff --git a/controllers/user.go b/controllers/user.go index 8b7b892ac..83ce23250 100644 --- a/controllers/user.go +++ b/controllers/user.go @@ -393,10 +393,7 @@ func createUser(w http.ResponseWriter, r *http.Request) { logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) return } - if err = logic.IsGroupsValid(user.UserGroups); err != nil { - logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) - return - } + if user.PlatformRoleID == "" { logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("platform role is missing"), "badrequest")) return diff --git a/logic/auth.go b/logic/auth.go index fab960948..2cbdc3839 100644 --- a/logic/auth.go +++ b/logic/auth.go @@ -132,6 +132,13 @@ func CreateUser(user *models.User) error { if _, err := GetUser(user.UserName); err == nil { return errors.New("user exists") } + SetUserDefaults(user) + if err := IsGroupsValid(user.UserGroups); err != nil { + return errors.New("invalid groups: " + err.Error()) + } + if err := IsNetworkRolesValid(user.NetworkRoles); err != nil { + return errors.New("invalid network roles: " + err.Error()) + } user.AuthType = models.BasicAuth if IsOauthUser(user) == nil { user.AuthType = models.OAuth @@ -149,20 +156,13 @@ func CreateUser(user *models.User) error { } // set password to encrypted password user.Password = string(hash) - if len(user.NetworkRoles) == 0 { - user.NetworkRoles = make(map[models.NetworkID]map[models.UserRole]struct{}) - } - if len(user.UserGroups) == 0 { - user.UserGroups = make(map[models.UserGroupID]struct{}) - } + _, err = CreateUserJWT(user.UserName, user.PlatformRoleID) if err != nil { logger.Log(0, "failed to generate token", err.Error()) return err } - SetUserDefaults(user) - // connect db data, err := json.Marshal(user) if err != nil { diff --git a/logic/jwts.go b/logic/jwts.go index 61aca21e7..77ee8c22d 100644 --- a/logic/jwts.go +++ b/logic/jwts.go @@ -53,7 +53,7 @@ func CreateJWT(uuid string, macAddress string, network string) (response string, } // CreateUserJWT - creates a user jwt token -func CreateUserJWT(username string, role models.UserRole) (response string, err error) { +func CreateUserJWT(username string, role models.UserRoleID) (response string, err error) { expirationTime := time.Now().Add(servercfg.GetServerConfig().JwtValidityDuration) claims := &models.UserClaims{ UserName: username, diff --git a/logic/user_mgmt.go b/logic/user_mgmt.go index af8195202..c5559b7ad 100644 --- a/logic/user_mgmt.go +++ b/logic/user_mgmt.go @@ -35,6 +35,9 @@ var FilterNetworksByRole = func(allnetworks []models.Network, user models.User) var IsGroupsValid = func(groups map[models.UserGroupID]struct{}) error { return nil } +var IsNetworkRolesValid = func(networkRoles map[models.NetworkID]map[models.UserRoleID]struct{}) error { + return nil +} var RemoveNetworkRoleFromUsers = func(host models.Host, node models.Node) {} var InitialiseRoles = userRolesInit @@ -42,7 +45,7 @@ var DeleteNetworkRoles = func(netID string) {} var CreateDefaultNetworkRoles = func(netID string) {} // GetRole - fetches role template by id -func GetRole(roleID models.UserRole) (models.UserRolePermissionTemplate, error) { +func GetRole(roleID models.UserRoleID) (models.UserRolePermissionTemplate, error) { // check if role already exists data, err := database.FetchRecord(database.USER_PERMISSIONS_TABLE_NAME, roleID.String()) if err != nil { diff --git a/logic/users.go b/logic/users.go index 051624e92..1b009fc72 100644 --- a/logic/users.go +++ b/logic/users.go @@ -55,6 +55,12 @@ func SetUserDefaults(user *models.User) { if user.RemoteGwIDs == nil { user.RemoteGwIDs = make(map[string]struct{}) } + if len(user.NetworkRoles) == 0 { + user.NetworkRoles = make(map[models.NetworkID]map[models.UserRoleID]struct{}) + } + if len(user.UserGroups) == 0 { + user.UserGroups = make(map[models.UserGroupID]struct{}) + } } // SortUsers - Sorts slice of Users by username diff --git a/migrate/migrate.go b/migrate/migrate.go index e61f71604..1ee4aaf17 100644 --- a/migrate/migrate.go +++ b/migrate/migrate.go @@ -324,7 +324,7 @@ func syncUsers() { if err == nil { logic.CreateRole(models.UserRolePermissionTemplate{ ID: models.GetRAGRoleName(networkNodeI.Network, h.Name), - NetworkID: netI.NetID, + NetworkID: models.NetworkID(netI.NetID), NetworkLevelAccess: map[models.RsrcType]map[models.RsrcID]models.RsrcPermissionScope{ models.RemoteAccessGwRsrc: { models.RsrcID(networkNodeI.ID.String()): models.RsrcPermissionScope{ @@ -361,7 +361,7 @@ func syncUsers() { user.AuthType = models.OAuth } if len(user.NetworkRoles) == 0 { - user.NetworkRoles = make(map[models.NetworkID]map[models.UserRole]struct{}) + user.NetworkRoles = make(map[models.NetworkID]map[models.UserRoleID]struct{}) } if len(user.UserGroups) == 0 { user.UserGroups = make(map[models.UserGroupID]struct{}) @@ -394,7 +394,7 @@ func syncUsers() { if netRoles, ok := user.NetworkRoles[models.NetworkID(gwNode.Network)]; ok { netRoles[r.ID] = struct{}{} } else { - user.NetworkRoles[models.NetworkID(gwNode.Network)] = map[models.UserRole]struct{}{ + user.NetworkRoles[models.NetworkID(gwNode.Network)] = map[models.UserRoleID]struct{}{ r.ID: {}, } } diff --git a/models/user_mgmt.go b/models/user_mgmt.go index 573fee1a5..62055ad90 100644 --- a/models/user_mgmt.go +++ b/models/user_mgmt.go @@ -10,7 +10,7 @@ import ( type NetworkID string type RsrcType string type RsrcID string -type UserRole string +type UserRoleID string type UserGroupID string type AuthType string @@ -27,8 +27,8 @@ func (rid RsrcID) String() string { return string(rid) } -func GetRAGRoleName(netID, hostName string) UserRole { - return UserRole(fmt.Sprintf("netID-%s-rag-%s", netID, hostName)) +func GetRAGRoleName(netID, hostName string) UserRoleID { + return UserRoleID(fmt.Sprintf("netID-%s-rag-%s", netID, hostName)) } var RsrcTypeMap = map[RsrcType]struct{}{ @@ -46,6 +46,7 @@ var RsrcTypeMap = map[RsrcType]struct{}{ FailOverRsrc: {}, } +const AllNetworks NetworkID = "all_networks" const ( HostRsrc RsrcType = "hosts" RelayRsrc RsrcType = "relays" @@ -80,15 +81,15 @@ const ( // Pre-Defined User Roles const ( - SuperAdminRole UserRole = "super_admin" - AdminRole UserRole = "admin" - ServiceUser UserRole = "service_user" - PlatformUser UserRole = "platform_user" - NetworkAdmin UserRole = "network_admin" - NetworkUser UserRole = "network_user" + SuperAdminRole UserRoleID = "super_admin" + AdminRole UserRoleID = "admin" + ServiceUser UserRoleID = "service_user" + PlatformUser UserRoleID = "platform_user" + NetworkAdmin UserRoleID = "network_admin" + NetworkUser UserRoleID = "network_user" ) -func (r UserRole) String() string { +func (r UserRoleID) String() string { return string(r) } @@ -96,6 +97,10 @@ func (g UserGroupID) String() string { return string(g) } +func (n NetworkID) String() string { + return string(n) +} + type RsrcPermissionScope struct { Create bool `json:"create"` Read bool `json:"read"` @@ -106,11 +111,11 @@ type RsrcPermissionScope struct { } type UserRolePermissionTemplate struct { - ID UserRole `json:"id"` + ID UserRoleID `json:"id"` Default bool `json:"default"` DenyDashboardAccess bool `json:"deny_dashboard_access"` FullAccess bool `json:"full_access"` - NetworkID string `json:"network_id"` + NetworkID NetworkID `json:"network_id"` NetworkLevelAccess map[RsrcType]map[RsrcID]RsrcPermissionScope `json:"network_level_access"` GlobalLevelAccess map[RsrcType]map[RsrcID]RsrcPermissionScope `json:"global_level_access"` } @@ -121,23 +126,23 @@ type CreateGroupReq struct { } type UserGroup struct { - ID UserGroupID `json:"id"` - NetworkRoles map[NetworkID]map[UserRole]struct{} `json:"network_roles"` - MetaData string `json:"meta_data"` + ID UserGroupID `json:"id"` + NetworkRoles map[NetworkID]map[UserRoleID]struct{} `json:"network_roles"` + MetaData string `json:"meta_data"` } // User struct - struct for Users type User struct { - UserName string `json:"username" bson:"username" validate:"min=3,max=40,in_charset|email"` - Password string `json:"password" bson:"password" validate:"required,min=5"` - IsAdmin bool `json:"isadmin" bson:"isadmin"` // deprecated - IsSuperAdmin bool `json:"issuperadmin"` // deprecated - RemoteGwIDs map[string]struct{} `json:"remote_gw_ids"` // deprecated - AuthType AuthType `json:"auth_type"` - UserGroups map[UserGroupID]struct{} `json:"user_group_ids"` - PlatformRoleID UserRole `json:"platform_role_id"` - NetworkRoles map[NetworkID]map[UserRole]struct{} `json:"network_roles"` - LastLoginTime time.Time `json:"last_login_time"` + UserName string `json:"username" bson:"username" validate:"min=3,max=40,in_charset|email"` + Password string `json:"password" bson:"password" validate:"required,min=5"` + IsAdmin bool `json:"isadmin" bson:"isadmin"` // deprecated + IsSuperAdmin bool `json:"issuperadmin"` // deprecated + RemoteGwIDs map[string]struct{} `json:"remote_gw_ids"` // deprecated + AuthType AuthType `json:"auth_type"` + UserGroups map[UserGroupID]struct{} `json:"user_group_ids"` + PlatformRoleID UserRoleID `json:"platform_role_id"` + NetworkRoles map[NetworkID]map[UserRoleID]struct{} `json:"network_roles"` + LastLoginTime time.Time `json:"last_login_time"` } type ReturnUserWithRolesAndGroups struct { @@ -147,15 +152,15 @@ type ReturnUserWithRolesAndGroups struct { // ReturnUser - return user struct type ReturnUser struct { - UserName string `json:"username"` - IsAdmin bool `json:"isadmin"` - IsSuperAdmin bool `json:"issuperadmin"` - AuthType AuthType `json:"auth_type"` - RemoteGwIDs map[string]struct{} `json:"remote_gw_ids"` // deprecated - UserGroups map[UserGroupID]struct{} `json:"user_group_ids"` - PlatformRoleID UserRole `json:"platform_role_id"` - NetworkRoles map[NetworkID]map[UserRole]struct{} `json:"network_roles"` - LastLoginTime time.Time `json:"last_login_time"` + UserName string `json:"username"` + IsAdmin bool `json:"isadmin"` + IsSuperAdmin bool `json:"issuperadmin"` + AuthType AuthType `json:"auth_type"` + RemoteGwIDs map[string]struct{} `json:"remote_gw_ids"` // deprecated + UserGroups map[UserGroupID]struct{} `json:"user_group_ids"` + PlatformRoleID UserRoleID `json:"platform_role_id"` + NetworkRoles map[NetworkID]map[UserRoleID]struct{} `json:"network_roles"` + LastLoginTime time.Time `json:"last_login_time"` } // UserAuthParams - user auth params struct @@ -166,22 +171,24 @@ type UserAuthParams struct { // UserClaims - user claims struct type UserClaims struct { - Role UserRole + Role UserRoleID UserName string jwt.RegisteredClaims } type InviteUsersReq struct { - UserEmails []string `json:"user_emails"` - PlatformRoleID string `json:"platform_role_id"` - Groups []UserGroupID + UserEmails []string `json:"user_emails"` + PlatformRoleID string `json:"platform_role_id"` + UserGroups map[UserGroupID]struct{} `json:"user_group_ids"` + NetworkRoles map[NetworkID]map[UserRoleID]struct{} `json:"network_roles"` } // UserInvite - model for user invite type UserInvite struct { - Email string `json:"email"` - PlatformRoleID string `json:"platform_role_id"` - Groups []UserGroupID `json:"groups"` - InviteCode string `json:"invite_code"` - InviteURL string `json:"invite_url"` + Email string `json:"email"` + PlatformRoleID string `json:"platform_role_id"` + UserGroups map[UserGroupID]struct{} `json:"user_group_ids"` + NetworkRoles map[NetworkID]map[UserRoleID]struct{} `json:"network_roles"` + InviteCode string `json:"invite_code"` + InviteURL string `json:"invite_url"` } diff --git a/pro/auth/azure-ad.go b/pro/auth/azure-ad.go index 2f27023e3..051f5f528 100644 --- a/pro/auth/azure-ad.go +++ b/pro/auth/azure-ad.go @@ -3,7 +3,6 @@ package auth import ( "context" "encoding/json" - "errors" "fmt" "io" "net/http" @@ -89,29 +88,12 @@ func handleAzureCallback(w http.ResponseWriter, r *http.Request) { if database.IsEmptyRecord(err) { // user must not exist, so try to make one if inviteExists { // create user - var newPass, fetchErr = logic.FetchPassValue("") - if fetchErr != nil { - logic.ReturnErrorResponse(w, r, logic.FormatError(fetchErr, "internal")) + user, err := proLogic.PrepareOauthUserFromInvite(in) + if err != nil { + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } - user := &models.User{ - UserName: content.UserPrincipalName, - Password: newPass, - } - for _, inviteGroupID := range in.Groups { - _, err := proLogic.GetUserGroup(inviteGroupID) - if err != nil { - logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("error fetching group id "+inviteGroupID.String()), "badrequest")) - return - } - - user.UserGroups[inviteGroupID] = struct{}{} - } - user.PlatformRoleID = models.UserRole(in.PlatformRoleID) - if user.PlatformRoleID == "" { - user.PlatformRoleID = models.ServiceUser - } - if err = logic.CreateUser(user); err != nil { + if err = logic.CreateUser(&user); err != nil { handleSomethingWentWrong(w) return } diff --git a/pro/auth/github.go b/pro/auth/github.go index 5f10746d2..2c40837e4 100644 --- a/pro/auth/github.go +++ b/pro/auth/github.go @@ -3,7 +3,6 @@ package auth import ( "context" "encoding/json" - "errors" "fmt" "io" "net/http" @@ -88,29 +87,12 @@ func handleGithubCallback(w http.ResponseWriter, r *http.Request) { if database.IsEmptyRecord(err) { // user must not exist, so try to make one if inviteExists { // create user - var newPass, fetchErr = logic.FetchPassValue("") - if fetchErr != nil { - logic.ReturnErrorResponse(w, r, logic.FormatError(fetchErr, "internal")) + user, err := proLogic.PrepareOauthUserFromInvite(in) + if err != nil { + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } - user := &models.User{ - UserName: content.Login, - Password: newPass, - } - - for _, inviteGroupID := range in.Groups { - _, err := proLogic.GetUserGroup(inviteGroupID) - if err != nil { - logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("error fetching group id "+inviteGroupID.String()), "badrequest")) - return - } - user.UserGroups[inviteGroupID] = struct{}{} - } - user.PlatformRoleID = models.UserRole(in.PlatformRoleID) - if user.PlatformRoleID == "" { - user.PlatformRoleID = models.ServiceUser - } - if err = logic.CreateUser(user); err != nil { + if err = logic.CreateUser(&user); err != nil { handleSomethingWentWrong(w) return } diff --git a/pro/auth/google.go b/pro/auth/google.go index 7883d3a61..b5f6197c4 100644 --- a/pro/auth/google.go +++ b/pro/auth/google.go @@ -3,7 +3,6 @@ package auth import ( "context" "encoding/json" - "errors" "fmt" "io" "net/http" @@ -94,32 +93,14 @@ func handleGoogleCallback(w http.ResponseWriter, r *http.Request) { if database.IsEmptyRecord(err) { // user must not exist, so try to make one if inviteExists { // create user - logger.Log(0, "CALLBACK ----> 4.0") - var newPass, fetchErr = logic.FetchPassValue("") - if fetchErr != nil { - logic.ReturnErrorResponse(w, r, logic.FormatError(fetchErr, "internal")) + user, err := proLogic.PrepareOauthUserFromInvite(in) + if err != nil { + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } - user := &models.User{ - UserName: content.Email, - Password: newPass, - } - logger.Log(0, "CALLBACK ----> 4.1") - for _, inviteGroupID := range in.Groups { - _, err := proLogic.GetUserGroup(inviteGroupID) - if err != nil { - logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("error fetching group id "+inviteGroupID.String()), "badrequest")) - return - } - user.UserGroups = make(map[models.UserGroupID]struct{}) - user.UserGroups[inviteGroupID] = struct{}{} - } - logger.Log(0, "CALLBACK ----> 5") - user.PlatformRoleID = models.UserRole(in.PlatformRoleID) - if user.PlatformRoleID == "" { - user.PlatformRoleID = models.ServiceUser - } - if err = logic.CreateUser(user); err != nil { + logger.Log(0, "CALLBACK ----> 4.0") + + if err = logic.CreateUser(&user); err != nil { handleSomethingWentWrong(w) return } diff --git a/pro/auth/oidc.go b/pro/auth/oidc.go index fd48823ab..355b41103 100644 --- a/pro/auth/oidc.go +++ b/pro/auth/oidc.go @@ -2,7 +2,6 @@ package auth import ( "context" - "errors" "fmt" "net/http" "strings" @@ -101,28 +100,12 @@ func handleOIDCCallback(w http.ResponseWriter, r *http.Request) { if database.IsEmptyRecord(err) { // user must not exist, so try to make one if inviteExists { // create user - var newPass, fetchErr = logic.FetchPassValue("") - if fetchErr != nil { - logic.ReturnErrorResponse(w, r, logic.FormatError(fetchErr, "internal")) + user, err := proLogic.PrepareOauthUserFromInvite(in) + if err != nil { + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } - user := &models.User{ - UserName: content.Email, - Password: newPass, - } - for _, inviteGroupID := range in.Groups { - _, err := proLogic.GetUserGroup(inviteGroupID) - if err != nil { - logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("error fetching group id "+inviteGroupID.String()), "badrequest")) - return - } - user.UserGroups[inviteGroupID] = struct{}{} - } - user.PlatformRoleID = models.UserRole(in.PlatformRoleID) - if user.PlatformRoleID == "" { - user.PlatformRoleID = models.ServiceUser - } - if err = logic.CreateUser(user); err != nil { + if err = logic.CreateUser(&user); err != nil { handleSomethingWentWrong(w) return } diff --git a/pro/controllers/users.go b/pro/controllers/users.go index 9e508c50c..f5c6982e0 100644 --- a/pro/controllers/users.go +++ b/pro/controllers/users.go @@ -104,19 +104,12 @@ func userInviteSignUp(w http.ResponseWriter, r *http.Request) { return } - for _, inviteGroupID := range in.Groups { - _, err := proLogic.GetUserGroup(inviteGroupID) - if err != nil { - continue - } - user.UserGroups = make(map[models.UserGroupID]struct{}) - user.UserGroups[inviteGroupID] = struct{}{} - } - user.PlatformRoleID = models.UserRole(in.PlatformRoleID) + user.UserGroups = in.UserGroups + user.PlatformRoleID = models.UserRoleID(in.PlatformRoleID) if user.PlatformRoleID == "" { user.PlatformRoleID = models.ServiceUser } - user.NetworkRoles = make(map[models.NetworkID]map[models.UserRole]struct{}) + user.NetworkRoles = in.NetworkRoles err = logic.CreateUser(&user) if err != nil { logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) @@ -170,13 +163,15 @@ func inviteUsers(w http.ResponseWriter, r *http.Request) { return } //validate Req - for _, groupID := range inviteReq.Groups { - _, err := proLogic.GetUserGroup(groupID) - if err != nil { - logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) - return - } - + err = proLogic.IsGroupsValid(inviteReq.UserGroups) + if err != nil { + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) + return + } + err = proLogic.IsNetworkRolesValid(inviteReq.NetworkRoles) + if err != nil { + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) + return } for _, inviteeEmail := range inviteReq.UserEmails { @@ -187,9 +182,10 @@ func inviteUsers(w http.ResponseWriter, r *http.Request) { continue } invite := models.UserInvite{ - Email: inviteeEmail, - Groups: inviteReq.Groups, - InviteCode: logic.RandomString(8), + Email: inviteeEmail, + UserGroups: inviteReq.UserGroups, + NetworkRoles: inviteReq.NetworkRoles, + InviteCode: logic.RandomString(8), } u, err := url.Parse(fmt.Sprintf("%s/invite?email=%s&invite_code=%s", servercfg.GetFrontendURL(), url.QueryEscape(invite.Email), url.QueryEscape(invite.InviteCode))) @@ -488,7 +484,7 @@ func getRole(w http.ResponseWriter, r *http.Request) { logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("role is required"), "badrequest")) return } - role, err := logic.GetRole(models.UserRole(rid)) + role, err := logic.GetRole(models.UserRoleID(rid)) if err != nil { logic.ReturnErrorResponse(w, r, models.ErrorResponse{ Code: http.StatusInternalServerError, @@ -586,7 +582,7 @@ func deleteRole(w http.ResponseWriter, r *http.Request) { logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("role is required"), "badrequest")) return } - err := proLogic.DeleteRole(models.UserRole(rid)) + err := proLogic.DeleteRole(models.UserRoleID(rid)) if err != nil { logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return diff --git a/pro/initialize.go b/pro/initialize.go index 8abf4a125..d59ab4e57 100644 --- a/pro/initialize.go +++ b/pro/initialize.go @@ -127,6 +127,7 @@ func InitPro() { logic.CreateDefaultNetworkRoles = proLogic.CreateDefaultNetworkRoles logic.FilterNetworksByRole = proLogic.FilterNetworksByRole logic.IsGroupsValid = proLogic.IsGroupsValid + logic.IsNetworkRolesValid = proLogic.IsNetworkRolesValid logic.RemoveNetworkRoleFromUsers = proLogic.RemoveNetworkRoleFromUsers logic.InitialiseRoles = proLogic.UserRolesInit } diff --git a/pro/logic/security.go b/pro/logic/security.go index e025580e5..4eb571de3 100644 --- a/pro/logic/security.go +++ b/pro/logic/security.go @@ -44,6 +44,15 @@ func NetworkPermissionsCheck(username string, r *http.Request) error { // check if user has scope for target resource // TODO - differentitate between global scope and network scope apis + // check for global network role + if netRoles, ok := user.NetworkRoles[models.AllNetworks]; ok { + for netRoleID := range netRoles { + err = checkNetworkAccessPermissions(netRoleID, username, r.Method, targetRsrc, targetRsrcID) + if err == nil { + return nil + } + } + } netRoles := user.NetworkRoles[models.NetworkID(netID)] for netRoleID := range netRoles { err = checkNetworkAccessPermissions(netRoleID, username, r.Method, targetRsrc, targetRsrcID) @@ -67,7 +76,7 @@ func NetworkPermissionsCheck(username string, r *http.Request) error { return errors.New("access denied") } -func checkNetworkAccessPermissions(netRoleID models.UserRole, username, reqScope, targetRsrc, targetRsrcID string) error { +func checkNetworkAccessPermissions(netRoleID models.UserRoleID, username, reqScope, targetRsrc, targetRsrcID string) error { networkPermissionScope, err := logic.GetRole(netRoleID) if err != nil { return err @@ -87,7 +96,7 @@ func checkNetworkAccessPermissions(netRoleID models.UserRole, username, reqScope if allRsrcsTypePermissionScope, ok := rsrcPermissionScope[models.RsrcID(fmt.Sprintf("all_%s", targetRsrc))]; ok { // handle extclient apis here if models.RsrcType(targetRsrc) == models.ExtClientsRsrc && allRsrcsTypePermissionScope.SelfOnly && targetRsrcID != "" { - extclient, err := logic.GetExtClient(targetRsrcID, networkPermissionScope.NetworkID) + extclient, err := logic.GetExtClient(targetRsrcID, networkPermissionScope.NetworkID.String()) if err != nil { return err } diff --git a/pro/logic/user_mgmt.go b/pro/logic/user_mgmt.go index 264d27162..a28c9570c 100644 --- a/pro/logic/user_mgmt.go +++ b/pro/logic/user_mgmt.go @@ -25,6 +25,37 @@ var PlatformUserUserPermissionTemplate = models.UserRolePermissionTemplate{ FullAccess: false, } +var NetworkAdminAllPermissionTemplate = models.UserRolePermissionTemplate{ + ID: models.UserRoleID(fmt.Sprintf("global_%s", models.NetworkAdmin)), + Default: true, + FullAccess: true, + NetworkID: models.AllNetworks, +} + +var NetworkUserAllPermissionTemplate = models.UserRolePermissionTemplate{ + ID: models.UserRoleID(fmt.Sprintf("global_%s", models.NetworkUser)), + Default: true, + FullAccess: false, + NetworkID: models.AllNetworks, + NetworkLevelAccess: map[models.RsrcType]map[models.RsrcID]models.RsrcPermissionScope{ + models.RemoteAccessGwRsrc: { + models.AllRemoteAccessGwRsrcID: models.RsrcPermissionScope{ + Read: true, + VPNaccess: true, + }, + }, + models.ExtClientsRsrc: { + models.AllExtClientsRsrcID: models.RsrcPermissionScope{ + Read: true, + Create: true, + Update: true, + Delete: true, + SelfOnly: true, + }, + }, + }, +} + func UserRolesInit() { d, _ := json.Marshal(logic.SuperAdminPermissionTemplate) database.Insert(logic.SuperAdminPermissionTemplate.ID.String(), string(d), database.USER_PERMISSIONS_TABLE_NAME) @@ -34,23 +65,27 @@ func UserRolesInit() { database.Insert(ServiceUserPermissionTemplate.ID.String(), string(d), database.USER_PERMISSIONS_TABLE_NAME) d, _ = json.Marshal(PlatformUserUserPermissionTemplate) database.Insert(PlatformUserUserPermissionTemplate.ID.String(), string(d), database.USER_PERMISSIONS_TABLE_NAME) + d, _ = json.Marshal(NetworkAdminAllPermissionTemplate) + database.Insert(NetworkAdminAllPermissionTemplate.ID.String(), string(d), database.USER_PERMISSIONS_TABLE_NAME) + d, _ = json.Marshal(NetworkUserAllPermissionTemplate) + database.Insert(NetworkUserAllPermissionTemplate.ID.String(), string(d), database.USER_PERMISSIONS_TABLE_NAME) } func CreateDefaultNetworkRoles(netID string) { var NetworkAdminPermissionTemplate = models.UserRolePermissionTemplate{ - ID: models.UserRole(fmt.Sprintf("%s_%s", netID, models.NetworkAdmin)), + ID: models.UserRoleID(fmt.Sprintf("%s_%s", netID, models.NetworkAdmin)), Default: false, - NetworkID: netID, + NetworkID: models.NetworkID(netID), FullAccess: true, NetworkLevelAccess: make(map[models.RsrcType]map[models.RsrcID]models.RsrcPermissionScope), } var NetworkUserPermissionTemplate = models.UserRolePermissionTemplate{ - ID: models.UserRole(fmt.Sprintf("%s_%s", netID, models.NetworkUser)), + ID: models.UserRoleID(fmt.Sprintf("%s_%s", netID, models.NetworkUser)), Default: false, FullAccess: false, - NetworkID: netID, + NetworkID: models.NetworkID(netID), DenyDashboardAccess: false, NetworkLevelAccess: map[models.RsrcType]map[models.RsrcID]models.RsrcPermissionScope{ models.RemoteAccessGwRsrc: { @@ -98,7 +133,7 @@ func DeleteNetworkRoles(netID string) { roles, _ := ListNetworkRoles() for _, role := range roles { - if role.NetworkID == netID { + if role.NetworkID.String() == netID { DeleteRole(role.ID) } } @@ -220,7 +255,7 @@ func UpdateRole(r models.UserRolePermissionTemplate) error { } // DeleteRole - deletes user role -func DeleteRole(rid models.UserRole) error { +func DeleteRole(rid models.UserRoleID) error { if rid.String() == "" { return errors.New("role id cannot be empty") } @@ -490,7 +525,7 @@ func GetUserNetworkRolesWithRemoteVPNAccess(user models.User) (gwAccess map[mode func GetFilteredNodesByUserAccess(user models.User, nodes []models.Node) (filteredNodes []models.Node) { nodesMap := make(map[string]struct{}) - allNetworkRoles := []models.UserRole{} + allNetworkRoles := []models.UserRoleID{} if len(user.NetworkRoles) > 0 { for _, netRoles := range user.NetworkRoles { for netRoleI := range netRoles { @@ -517,7 +552,7 @@ func GetFilteredNodesByUserAccess(user models.User, nodes []models.Node) (filter if err != nil { continue } - networkNodes := logic.GetNetworkNodesMemory(nodes, userPermTemplate.NetworkID) + networkNodes := logic.GetNetworkNodesMemory(nodes, userPermTemplate.NetworkID.String()) if userPermTemplate.FullAccess { for _, node := range networkNodes { nodesMap[node.ID.String()] = struct{}{} @@ -603,6 +638,25 @@ func IsGroupsValid(groups map[models.UserGroupID]struct{}) error { return nil } +func IsNetworkRolesValid(networkRoles map[models.NetworkID]map[models.UserRoleID]struct{}) error { + for netID, netRoles := range networkRoles { + _, err := logic.GetNetwork(netID.String()) + if err != nil { + return fmt.Errorf("failed to fetch network %s ", netID) + } + for netRoleID := range netRoles { + role, err := logic.GetRole(netRoleID) + if err != nil { + return fmt.Errorf("failed to fetch role %s ", netRoleID) + } + if role.NetworkID == "" { + return fmt.Errorf("cannot use platform as network role %s", netRoleID) + } + } + } + return nil +} + func RemoveNetworkRoleFromUsers(host models.Host, node models.Node) { users, err := logic.GetUsersDB() if err == nil { @@ -625,3 +679,22 @@ func RemoveNetworkRoleFromUsers(host models.Host, node models.Node) { slog.Error("failed to delete role: ", models.GetRAGRoleName(node.Network, host.Name), err) } } + +// PrepareOauthUserFromInvite - init oauth user before create +func PrepareOauthUserFromInvite(in models.UserInvite) (models.User, error) { + var newPass, fetchErr = logic.FetchPassValue("") + if fetchErr != nil { + return models.User{}, fetchErr + } + user := models.User{ + UserName: in.Email, + Password: newPass, + } + user.UserGroups = in.UserGroups + user.NetworkRoles = in.NetworkRoles + user.PlatformRoleID = models.UserRoleID(in.PlatformRoleID) + if user.PlatformRoleID == "" { + user.PlatformRoleID = models.ServiceUser + } + return user, nil +} From 3820e7dcfe94031c416cd2be15d40066c6ac2196 Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Thu, 1 Aug 2024 08:21:46 +0530 Subject: [PATCH 101/139] create default groups and roles --- controllers/network.go | 2 +- logic/user_mgmt.go | 2 +- pro/controllers/users.go | 15 +++++++++++---- pro/initialize.go | 2 +- pro/logic/user_mgmt.go | 34 +++++++++++++++++++++++++++++----- 5 files changed, 43 insertions(+), 12 deletions(-) diff --git a/controllers/network.go b/controllers/network.go index 09fadc163..ff6ab9eb1 100644 --- a/controllers/network.go +++ b/controllers/network.go @@ -473,7 +473,7 @@ func createNetwork(w http.ResponseWriter, r *http.Request) { logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) return } - logic.CreateDefaultNetworkRoles(network.NetID) + logic.CreateDefaultNetworkRolesAndGroups(models.NetworkID(network.NetID)) go func() { defaultHosts := logic.GetDefaultHosts() for i := range defaultHosts { diff --git a/logic/user_mgmt.go b/logic/user_mgmt.go index c5559b7ad..9fa5a10ea 100644 --- a/logic/user_mgmt.go +++ b/logic/user_mgmt.go @@ -42,7 +42,7 @@ var RemoveNetworkRoleFromUsers = func(host models.Host, node models.Node) {} var InitialiseRoles = userRolesInit var DeleteNetworkRoles = func(netID string) {} -var CreateDefaultNetworkRoles = func(netID string) {} +var CreateDefaultNetworkRolesAndGroups = func(netID models.NetworkID) {} // GetRole - fetches role template by id func GetRole(roleID models.UserRoleID) (models.UserRolePermissionTemplate, error) { diff --git a/pro/controllers/users.go b/pro/controllers/users.go index f5c6982e0..ff9d78082 100644 --- a/pro/controllers/users.go +++ b/pro/controllers/users.go @@ -174,6 +174,12 @@ func inviteUsers(w http.ResponseWriter, r *http.Request) { return } + // check platform role + _, err = logic.GetRole(models.UserRoleID(inviteReq.PlatformRoleID)) + if err != nil { + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) + return + } for _, inviteeEmail := range inviteReq.UserEmails { // check if user with email exists, then ignore _, err := logic.GetUser(inviteeEmail) @@ -182,10 +188,11 @@ func inviteUsers(w http.ResponseWriter, r *http.Request) { continue } invite := models.UserInvite{ - Email: inviteeEmail, - UserGroups: inviteReq.UserGroups, - NetworkRoles: inviteReq.NetworkRoles, - InviteCode: logic.RandomString(8), + Email: inviteeEmail, + PlatformRoleID: inviteReq.PlatformRoleID, + UserGroups: inviteReq.UserGroups, + NetworkRoles: inviteReq.NetworkRoles, + InviteCode: logic.RandomString(8), } u, err := url.Parse(fmt.Sprintf("%s/invite?email=%s&invite_code=%s", servercfg.GetFrontendURL(), url.QueryEscape(invite.Email), url.QueryEscape(invite.InviteCode))) diff --git a/pro/initialize.go b/pro/initialize.go index d59ab4e57..689526764 100644 --- a/pro/initialize.go +++ b/pro/initialize.go @@ -124,7 +124,7 @@ func InitPro() { logic.NetworkPermissionsCheck = proLogic.NetworkPermissionsCheck logic.GlobalPermissionsCheck = proLogic.GlobalPermissionsCheck logic.DeleteNetworkRoles = proLogic.DeleteNetworkRoles - logic.CreateDefaultNetworkRoles = proLogic.CreateDefaultNetworkRoles + logic.CreateDefaultNetworkRolesAndGroups = proLogic.CreateDefaultNetworkRolesAndGroups logic.FilterNetworksByRole = proLogic.FilterNetworksByRole logic.IsGroupsValid = proLogic.IsGroupsValid logic.IsNetworkRolesValid = proLogic.IsNetworkRolesValid diff --git a/pro/logic/user_mgmt.go b/pro/logic/user_mgmt.go index a28c9570c..5bf90a3a9 100644 --- a/pro/logic/user_mgmt.go +++ b/pro/logic/user_mgmt.go @@ -72,20 +72,20 @@ func UserRolesInit() { } -func CreateDefaultNetworkRoles(netID string) { +func CreateDefaultNetworkRolesAndGroups(netID models.NetworkID) { var NetworkAdminPermissionTemplate = models.UserRolePermissionTemplate{ - ID: models.UserRoleID(fmt.Sprintf("%s_%s", netID, models.NetworkAdmin)), + ID: models.UserRoleID(fmt.Sprintf("%s-%s", netID, models.NetworkAdmin)), Default: false, - NetworkID: models.NetworkID(netID), + NetworkID: netID, FullAccess: true, NetworkLevelAccess: make(map[models.RsrcType]map[models.RsrcID]models.RsrcPermissionScope), } var NetworkUserPermissionTemplate = models.UserRolePermissionTemplate{ - ID: models.UserRoleID(fmt.Sprintf("%s_%s", netID, models.NetworkUser)), + ID: models.UserRoleID(fmt.Sprintf("%s-%s", netID, models.NetworkUser)), Default: false, FullAccess: false, - NetworkID: models.NetworkID(netID), + NetworkID: netID, DenyDashboardAccess: false, NetworkLevelAccess: map[models.RsrcType]map[models.RsrcID]models.RsrcPermissionScope{ models.RemoteAccessGwRsrc: { @@ -109,6 +109,30 @@ func CreateDefaultNetworkRoles(netID string) { database.Insert(NetworkAdminPermissionTemplate.ID.String(), string(d), database.USER_PERMISSIONS_TABLE_NAME) d, _ = json.Marshal(NetworkUserPermissionTemplate) database.Insert(NetworkUserPermissionTemplate.ID.String(), string(d), database.USER_PERMISSIONS_TABLE_NAME) + + // create default network groups + var NetworkAdminGroup = models.UserGroup{ + ID: models.UserGroupID(fmt.Sprintf("%s-%s-grp", netID, models.NetworkAdmin)), + NetworkRoles: map[models.NetworkID]map[models.UserRoleID]struct{}{ + netID: { + models.UserRoleID(fmt.Sprintf("%s-%s", netID, models.NetworkAdmin)): {}, + }, + }, + MetaData: "The network role was automatically created by Netmaker.", + } + var NetworkUserGroup = models.UserGroup{ + ID: models.UserGroupID(fmt.Sprintf("%s-%s-grp", netID, models.NetworkUser)), + NetworkRoles: map[models.NetworkID]map[models.UserRoleID]struct{}{ + netID: { + models.UserRoleID(fmt.Sprintf("%s-%s", netID, models.NetworkUser)): {}, + }, + }, + MetaData: "The network role was automatically created by Netmaker.", + } + d, _ = json.Marshal(NetworkAdminGroup) + database.Insert(NetworkAdminGroup.ID.String(), string(d), database.USER_GROUPS_TABLE_NAME) + d, _ = json.Marshal(NetworkUserGroup) + database.Insert(NetworkUserGroup.ID.String(), string(d), database.USER_GROUPS_TABLE_NAME) } func DeleteNetworkRoles(netID string) { From 5f53887c0e71753bc57130bddc92c360d8cd30cf Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Thu, 1 Aug 2024 17:42:37 +0530 Subject: [PATCH 102/139] fix middleware for global access --- controllers/middleware.go | 3 +++ pro/controllers/relay.go | 4 ++-- pro/controllers/users.go | 2 +- pro/logic/security.go | 10 +++++----- pro/logic/user_mgmt.go | 8 +++++++- 5 files changed, 18 insertions(+), 9 deletions(-) diff --git a/controllers/middleware.go b/controllers/middleware.go index a7e6902c9..2e321d6f8 100644 --- a/controllers/middleware.go +++ b/controllers/middleware.go @@ -33,6 +33,9 @@ func userMiddleWare(handler http.Handler) http.Handler { if strings.Contains(r.URL.Path, "ingress") { r.Header.Set("TARGET_RSRC", models.RemoteAccessGwRsrc.String()) } + if strings.Contains(r.URL.Path, "createrelay") || strings.Contains(r.URL.Path, "deleterelay") { + r.Header.Set("TARGET_RSRC", models.RelayRsrc.String()) + } if strings.Contains(r.URL.Path, "gateway") { r.Header.Set("TARGET_RSRC", models.EgressGwRsrc.String()) } diff --git a/pro/controllers/relay.go b/pro/controllers/relay.go index a3bacab6e..8fb5ba3fb 100644 --- a/pro/controllers/relay.go +++ b/pro/controllers/relay.go @@ -19,8 +19,8 @@ import ( // RelayHandlers - handle Pro Relays func RelayHandlers(r *mux.Router) { - r.HandleFunc("/api/nodes/{network}/{nodeid}/createrelay", controller.Authorize(false, true, "user", http.HandlerFunc(createRelay))).Methods(http.MethodPost) - r.HandleFunc("/api/nodes/{network}/{nodeid}/deleterelay", controller.Authorize(false, true, "user", http.HandlerFunc(deleteRelay))).Methods(http.MethodDelete) + r.HandleFunc("/api/nodes/{network}/{nodeid}/createrelay", logic.SecurityCheck(true, http.HandlerFunc(createRelay))).Methods(http.MethodPost) + r.HandleFunc("/api/nodes/{network}/{nodeid}/deleterelay", logic.SecurityCheck(true, http.HandlerFunc(deleteRelay))).Methods(http.MethodDelete) r.HandleFunc("/api/v1/host/{hostid}/failoverme", controller.Authorize(true, false, "host", http.HandlerFunc(failOverME))).Methods(http.MethodPost) } diff --git a/pro/controllers/users.go b/pro/controllers/users.go index ff9d78082..ca8edc3fd 100644 --- a/pro/controllers/users.go +++ b/pro/controllers/users.go @@ -33,7 +33,7 @@ func UserHandlers(r *mux.Router) { // User Role Handlers r.HandleFunc("/api/v1/users/roles", logic.SecurityCheck(true, http.HandlerFunc(listRoles))).Methods(http.MethodGet) - r.HandleFunc("/api/v1/users/role", getRole).Methods(http.MethodGet) + r.HandleFunc("/api/v1/users/role", logic.SecurityCheck(true, http.HandlerFunc(getRole))).Methods(http.MethodGet) r.HandleFunc("/api/v1/users/role", logic.SecurityCheck(true, http.HandlerFunc(createRole))).Methods(http.MethodPost) r.HandleFunc("/api/v1/users/role", logic.SecurityCheck(true, http.HandlerFunc(updateRole))).Methods(http.MethodPut) r.HandleFunc("/api/v1/users/role", logic.SecurityCheck(true, http.HandlerFunc(deleteRole))).Methods(http.MethodDelete) diff --git a/pro/logic/security.go b/pro/logic/security.go index 4eb571de3..4d328bcd8 100644 --- a/pro/logic/security.go +++ b/pro/logic/security.go @@ -47,7 +47,7 @@ func NetworkPermissionsCheck(username string, r *http.Request) error { // check for global network role if netRoles, ok := user.NetworkRoles[models.AllNetworks]; ok { for netRoleID := range netRoles { - err = checkNetworkAccessPermissions(netRoleID, username, r.Method, targetRsrc, targetRsrcID) + err = checkNetworkAccessPermissions(netRoleID, username, r.Method, targetRsrc, targetRsrcID, netID) if err == nil { return nil } @@ -55,7 +55,7 @@ func NetworkPermissionsCheck(username string, r *http.Request) error { } netRoles := user.NetworkRoles[models.NetworkID(netID)] for netRoleID := range netRoles { - err = checkNetworkAccessPermissions(netRoleID, username, r.Method, targetRsrc, targetRsrcID) + err = checkNetworkAccessPermissions(netRoleID, username, r.Method, targetRsrc, targetRsrcID, netID) if err == nil { return nil } @@ -65,7 +65,7 @@ func NetworkPermissionsCheck(username string, r *http.Request) error { if err == nil { netRoles := userG.NetworkRoles[models.NetworkID(netID)] for netRoleID := range netRoles { - err = checkNetworkAccessPermissions(netRoleID, username, r.Method, targetRsrc, targetRsrcID) + err = checkNetworkAccessPermissions(netRoleID, username, r.Method, targetRsrc, targetRsrcID, netID) if err == nil { return nil } @@ -76,7 +76,7 @@ func NetworkPermissionsCheck(username string, r *http.Request) error { return errors.New("access denied") } -func checkNetworkAccessPermissions(netRoleID models.UserRoleID, username, reqScope, targetRsrc, targetRsrcID string) error { +func checkNetworkAccessPermissions(netRoleID models.UserRoleID, username, reqScope, targetRsrc, targetRsrcID, netID string) error { networkPermissionScope, err := logic.GetRole(netRoleID) if err != nil { return err @@ -96,7 +96,7 @@ func checkNetworkAccessPermissions(netRoleID models.UserRoleID, username, reqSco if allRsrcsTypePermissionScope, ok := rsrcPermissionScope[models.RsrcID(fmt.Sprintf("all_%s", targetRsrc))]; ok { // handle extclient apis here if models.RsrcType(targetRsrc) == models.ExtClientsRsrc && allRsrcsTypePermissionScope.SelfOnly && targetRsrcID != "" { - extclient, err := logic.GetExtClient(targetRsrcID, networkPermissionScope.NetworkID.String()) + extclient, err := logic.GetExtClient(targetRsrcID, netID) if err != nil { return err } diff --git a/pro/logic/user_mgmt.go b/pro/logic/user_mgmt.go index 5bf90a3a9..0578a87e2 100644 --- a/pro/logic/user_mgmt.go +++ b/pro/logic/user_mgmt.go @@ -576,7 +576,13 @@ func GetFilteredNodesByUserAccess(user models.User, nodes []models.Node) (filter if err != nil { continue } - networkNodes := logic.GetNetworkNodesMemory(nodes, userPermTemplate.NetworkID.String()) + var networkNodes []models.Node + if userPermTemplate.NetworkID == models.AllNetworks { + networkNodes = nodes + } else { + networkNodes = logic.GetNetworkNodesMemory(nodes, userPermTemplate.NetworkID.String()) + } + if userPermTemplate.FullAccess { for _, node := range networkNodes { nodesMap[node.ID.String()] = struct{}{} From c7fb3297fd9832d105dbe0aa7e41537eb079052f Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Fri, 2 Aug 2024 10:06:27 +0530 Subject: [PATCH 103/139] create default role --- pro/logic/user_mgmt.go | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/pro/logic/user_mgmt.go b/pro/logic/user_mgmt.go index 0578a87e2..808f1bdde 100644 --- a/pro/logic/user_mgmt.go +++ b/pro/logic/user_mgmt.go @@ -75,7 +75,7 @@ func UserRolesInit() { func CreateDefaultNetworkRolesAndGroups(netID models.NetworkID) { var NetworkAdminPermissionTemplate = models.UserRolePermissionTemplate{ ID: models.UserRoleID(fmt.Sprintf("%s-%s", netID, models.NetworkAdmin)), - Default: false, + Default: true, NetworkID: netID, FullAccess: true, NetworkLevelAccess: make(map[models.RsrcType]map[models.RsrcID]models.RsrcPermissionScope), @@ -83,7 +83,7 @@ func CreateDefaultNetworkRolesAndGroups(netID models.NetworkID) { var NetworkUserPermissionTemplate = models.UserRolePermissionTemplate{ ID: models.UserRoleID(fmt.Sprintf("%s-%s", netID, models.NetworkUser)), - Default: false, + Default: true, FullAccess: false, NetworkID: netID, DenyDashboardAccess: false, @@ -670,9 +670,11 @@ func IsGroupsValid(groups map[models.UserGroupID]struct{}) error { func IsNetworkRolesValid(networkRoles map[models.NetworkID]map[models.UserRoleID]struct{}) error { for netID, netRoles := range networkRoles { - _, err := logic.GetNetwork(netID.String()) - if err != nil { - return fmt.Errorf("failed to fetch network %s ", netID) + if netID != models.AllNetworks { + _, err := logic.GetNetwork(netID.String()) + if err != nil { + return fmt.Errorf("failed to fetch network %s ", netID) + } } for netRoleID := range netRoles { role, err := logic.GetRole(netRoleID) From d673c8ac8e926626aaa58f721285a0f35bd6975d Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Fri, 2 Aug 2024 13:08:34 +0530 Subject: [PATCH 104/139] fix nodes filter with global network roles --- pro/logic/user_mgmt.go | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/pro/logic/user_mgmt.go b/pro/logic/user_mgmt.go index 808f1bdde..ac68a918f 100644 --- a/pro/logic/user_mgmt.go +++ b/pro/logic/user_mgmt.go @@ -496,6 +496,9 @@ func GetUserNetworkRolesWithRemoteVPNAccess(user models.User) (gwAccess map[mode gwAccess[models.NetworkID("*")] = make(map[models.RsrcID]models.RsrcPermissionScope) return } + if _, ok := user.NetworkRoles[models.AllNetworks]; ok { + gwAccess[models.NetworkID("*")] = make(map[models.RsrcID]models.RsrcPermissionScope) + } logger.Log(0, "------------> 7.2 getUserRemoteAccessGwsV1") for netID, roleMap := range user.NetworkRoles { for roleID := range roleMap { @@ -557,11 +560,17 @@ func GetFilteredNodesByUserAccess(user models.User, nodes []models.Node) (filter } } } + if _, ok := user.NetworkRoles[models.AllNetworks]; ok { + return nodes + } if len(user.UserGroups) > 0 { for userGID := range user.UserGroups { userG, err := GetUserGroup(userGID) if err == nil { if len(userG.NetworkRoles) > 0 { + if _, ok := userG.NetworkRoles[models.AllNetworks]; ok { + return nodes + } for _, netRoles := range userG.NetworkRoles { for netRoleI := range netRoles { allNetworkRoles = append(allNetworkRoles, netRoleI) @@ -576,13 +585,7 @@ func GetFilteredNodesByUserAccess(user models.User, nodes []models.Node) (filter if err != nil { continue } - var networkNodes []models.Node - if userPermTemplate.NetworkID == models.AllNetworks { - networkNodes = nodes - } else { - networkNodes = logic.GetNetworkNodesMemory(nodes, userPermTemplate.NetworkID.String()) - } - + networkNodes := logic.GetNetworkNodesMemory(nodes, userPermTemplate.NetworkID.String()) if userPermTemplate.FullAccess { for _, node := range networkNodes { nodesMap[node.ID.String()] = struct{}{} @@ -629,6 +632,9 @@ func FilterNetworksByRole(allnetworks []models.Network, user models.User) []mode allNetworkRoles := make(map[models.NetworkID]struct{}) if len(user.NetworkRoles) > 0 { for netID := range user.NetworkRoles { + if netID == models.AllNetworks { + return allnetworks + } allNetworkRoles[netID] = struct{}{} } @@ -639,6 +645,9 @@ func FilterNetworksByRole(allnetworks []models.Network, user models.User) []mode if err == nil { if len(userG.NetworkRoles) > 0 { for netID := range userG.NetworkRoles { + if netID == models.AllNetworks { + return allnetworks + } allNetworkRoles[netID] = struct{}{} } From f7453f8cb67e9f80ef2b8cc8cbc43b7f5b730f84 Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Fri, 2 Aug 2024 13:31:47 +0530 Subject: [PATCH 105/139] block selfupdate of groups and network roles --- controllers/user.go | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/controllers/user.go b/controllers/user.go index 83ce23250..2d2fcdcdb 100644 --- a/controllers/user.go +++ b/controllers/user.go @@ -6,6 +6,7 @@ import ( "fmt" "net/http" "net/url" + "reflect" "github.com/gorilla/mux" "github.com/gorilla/websocket" @@ -519,6 +520,20 @@ func updateUser(w http.ResponseWriter, r *http.Request) { return } + // user cannot update his own roles and groups + if len(user.NetworkRoles) != len(userchange.NetworkRoles) || !reflect.DeepEqual(user.NetworkRoles, userchange.NetworkRoles) { + err = errors.New("user cannot update self update their network roles") + slog.Error("failed to update user", "caller", caller.UserName, "attempted to update user", username, "error", err) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "forbidden")) + return + } + // user cannot update his own roles and groups + if len(user.UserGroups) != len(userchange.UserGroups) || !reflect.DeepEqual(user.UserGroups, userchange.UserGroups) { + err = errors.New("user cannot update self update their groups") + slog.Error("failed to update user", "caller", caller.UserName, "attempted to update user", username, "error", err) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "forbidden")) + return + } } if ismaster { if user.PlatformRoleID != models.SuperAdminRole && userchange.PlatformRoleID == models.SuperAdminRole { From 9adb36b82c574d8566874ceb6b69400d92e008c2 Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Fri, 2 Aug 2024 13:53:41 +0530 Subject: [PATCH 106/139] delete netID if net roles are empty --- pro/controllers/users.go | 10 ++++++---- pro/logic/user_mgmt.go | 5 +++++ 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/pro/controllers/users.go b/pro/controllers/users.go index ca8edc3fd..bef5e3b23 100644 --- a/pro/controllers/users.go +++ b/pro/controllers/users.go @@ -22,10 +22,7 @@ import ( ) func UserHandlers(r *mux.Router) { - r.HandleFunc("/api/users/{username}/remote_access_gw/{remote_access_gateway_id}", logic.SecurityCheck(true, http.HandlerFunc(attachUserToRemoteAccessGw))).Methods(http.MethodPost) - r.HandleFunc("/api/users/{username}/remote_access_gw/{remote_access_gateway_id}", logic.SecurityCheck(true, http.HandlerFunc(removeUserFromRemoteAccessGW))).Methods(http.MethodDelete) - r.HandleFunc("/api/users/{username}/remote_access_gw", logic.SecurityCheck(false, logic.ContinueIfUserMatch(http.HandlerFunc(getUserRemoteAccessGwsV1)))).Methods(http.MethodGet) - r.HandleFunc("/api/users/ingress/{ingress_id}", logic.SecurityCheck(true, http.HandlerFunc(ingressGatewayUsers))).Methods(http.MethodGet) + r.HandleFunc("/api/oauth/login", proAuth.HandleAuthLogin).Methods(http.MethodGet) r.HandleFunc("/api/oauth/callback", proAuth.HandleAuthCallback).Methods(http.MethodGet) r.HandleFunc("/api/oauth/headless", proAuth.HandleHeadlessSSO) @@ -58,6 +55,11 @@ func UserHandlers(r *mux.Router) { r.HandleFunc("/api/users_pending/user/{username}", logic.SecurityCheck(true, http.HandlerFunc(deletePendingUser))).Methods(http.MethodDelete) r.HandleFunc("/api/users_pending/user/{username}", logic.SecurityCheck(true, http.HandlerFunc(approvePendingUser))).Methods(http.MethodPost) + r.HandleFunc("/api/users/{username}/remote_access_gw/{remote_access_gateway_id}", logic.SecurityCheck(true, http.HandlerFunc(attachUserToRemoteAccessGw))).Methods(http.MethodPost) + r.HandleFunc("/api/users/{username}/remote_access_gw/{remote_access_gateway_id}", logic.SecurityCheck(true, http.HandlerFunc(removeUserFromRemoteAccessGW))).Methods(http.MethodDelete) + r.HandleFunc("/api/users/{username}/remote_access_gw", logic.SecurityCheck(false, logic.ContinueIfUserMatch(http.HandlerFunc(getUserRemoteAccessGwsV1)))).Methods(http.MethodGet) + r.HandleFunc("/api/users/ingress/{ingress_id}", logic.SecurityCheck(true, http.HandlerFunc(ingressGatewayUsers))).Methods(http.MethodGet) + } // swagger:route POST /api/v1/users/invite-signup user userInviteSignUp diff --git a/pro/logic/user_mgmt.go b/pro/logic/user_mgmt.go index ac68a918f..fb928e0a2 100644 --- a/pro/logic/user_mgmt.go +++ b/pro/logic/user_mgmt.go @@ -679,11 +679,16 @@ func IsGroupsValid(groups map[models.UserGroupID]struct{}) error { func IsNetworkRolesValid(networkRoles map[models.NetworkID]map[models.UserRoleID]struct{}) error { for netID, netRoles := range networkRoles { + if netID != models.AllNetworks { _, err := logic.GetNetwork(netID.String()) if err != nil { return fmt.Errorf("failed to fetch network %s ", netID) } + if len(netRoles) == 0 { + delete(networkRoles, netID) + continue + } } for netRoleID := range netRoles { role, err := logic.GetRole(netRoleID) From a392980253fe6a1cb408151de9b046614aad1f7b Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Fri, 2 Aug 2024 13:54:00 +0530 Subject: [PATCH 107/139] validate user roles nd groups on update --- logic/auth.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/logic/auth.go b/logic/auth.go index 2cbdc3839..49130d272 100644 --- a/logic/auth.go +++ b/logic/auth.go @@ -274,6 +274,12 @@ func UpdateUser(userchange, user *models.User) (*models.User, error) { user.Password = userchange.Password } + if err := IsGroupsValid(userchange.UserGroups); err != nil { + return userchange, errors.New("invalid groups: " + err.Error()) + } + if err := IsNetworkRolesValid(userchange.NetworkRoles); err != nil { + return userchange, errors.New("invalid network roles: " + err.Error()) + } user.PlatformRoleID = userchange.PlatformRoleID user.UserGroups = userchange.UserGroups user.NetworkRoles = userchange.NetworkRoles From 43a0ca20d7872879997574226ec6f73d4893458c Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Fri, 2 Aug 2024 14:25:58 +0530 Subject: [PATCH 108/139] set extclient permission scope when rag vpn access is set --- pro/controllers/users.go | 4 ++-- pro/logic/user_mgmt.go | 50 ++++++++++++++++++++++++++++++++++++++-- 2 files changed, 50 insertions(+), 4 deletions(-) diff --git a/pro/controllers/users.go b/pro/controllers/users.go index bef5e3b23..bb893aaa9 100644 --- a/pro/controllers/users.go +++ b/pro/controllers/users.go @@ -524,7 +524,7 @@ func createRole(w http.ResponseWriter, r *http.Request) { logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) return } - err = proLogic.ValidateCreateRoleReq(userRole) + err = proLogic.ValidateCreateRoleReq(&userRole) if err != nil { logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) return @@ -559,7 +559,7 @@ func updateRole(w http.ResponseWriter, r *http.Request) { logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) return } - err = proLogic.ValidateUpdateRoleReq(userRole) + err = proLogic.ValidateUpdateRoleReq(&userRole) if err != nil { logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) return diff --git a/pro/logic/user_mgmt.go b/pro/logic/user_mgmt.go index fb928e0a2..5abd753c5 100644 --- a/pro/logic/user_mgmt.go +++ b/pro/logic/user_mgmt.go @@ -205,7 +205,7 @@ func ListPlatformRoles() ([]models.UserRolePermissionTemplate, error) { return userRoles, nil } -func ValidateCreateRoleReq(userRole models.UserRolePermissionTemplate) error { +func ValidateCreateRoleReq(userRole *models.UserRolePermissionTemplate) error { // check if role exists with this id _, err := logic.GetRole(userRole.ID) if err == nil { @@ -216,6 +216,29 @@ func ValidateCreateRoleReq(userRole models.UserRolePermissionTemplate) error { if _, ok := models.RsrcTypeMap[rsrcType]; !ok { return errors.New("invalid rsrc type " + rsrcType.String()) } + if rsrcType == models.RemoteAccessGwRsrc { + userRsrcPermissions := userRole.NetworkLevelAccess[models.RemoteAccessGwRsrc] + var vpnAccess bool + for _, scope := range userRsrcPermissions { + if scope.VPNaccess { + vpnAccess = true + break + } + } + if vpnAccess { + userRole.NetworkLevelAccess[models.ExtClientsRsrc] = map[models.RsrcID]models.RsrcPermissionScope{ + models.AllExtClientsRsrcID: { + Read: true, + Create: true, + Update: true, + Delete: true, + SelfOnly: true, + }, + } + + } + + } } } if userRole.NetworkID == "" { @@ -224,7 +247,7 @@ func ValidateCreateRoleReq(userRole models.UserRolePermissionTemplate) error { return nil } -func ValidateUpdateRoleReq(userRole models.UserRolePermissionTemplate) error { +func ValidateUpdateRoleReq(userRole *models.UserRolePermissionTemplate) error { roleInDB, err := logic.GetRole(userRole.ID) if err != nil { return err @@ -240,6 +263,29 @@ func ValidateUpdateRoleReq(userRole models.UserRolePermissionTemplate) error { if _, ok := models.RsrcTypeMap[rsrcType]; !ok { return errors.New("invalid rsrc type " + rsrcType.String()) } + if rsrcType == models.RemoteAccessGwRsrc { + userRsrcPermissions := userRole.NetworkLevelAccess[models.RemoteAccessGwRsrc] + var vpnAccess bool + for _, scope := range userRsrcPermissions { + if scope.VPNaccess { + vpnAccess = true + break + } + } + if vpnAccess { + userRole.NetworkLevelAccess[models.ExtClientsRsrc] = map[models.RsrcID]models.RsrcPermissionScope{ + models.AllExtClientsRsrcID: { + Read: true, + Create: true, + Update: true, + Delete: true, + SelfOnly: true, + }, + } + + } + + } } } return nil From aa6c0aee7bc67567d014ee0de09edac89be22b87 Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Fri, 2 Aug 2024 14:59:51 +0530 Subject: [PATCH 109/139] allow deletion of roles and groups --- pro/logic/user_mgmt.go | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/pro/logic/user_mgmt.go b/pro/logic/user_mgmt.go index 5abd753c5..2131310bb 100644 --- a/pro/logic/user_mgmt.go +++ b/pro/logic/user_mgmt.go @@ -345,11 +345,13 @@ func DeleteRole(rid models.UserRoleID) error { ug, err := GetUserGroup(userG) if err == nil { if role.NetworkID != "" { - for _, networkRoles := range ug.NetworkRoles { + for netID, networkRoles := range ug.NetworkRoles { if _, ok := networkRoles[rid]; ok { - err = errors.New("role cannot be deleted as active user groups are using this role") - return err + delete(networkRoles, rid) + ug.NetworkRoles[netID] = networkRoles + UpdateUserGroup(ug) } + } } @@ -360,10 +362,11 @@ func DeleteRole(rid models.UserRoleID) error { err = errors.New("active roles cannot be deleted.switch existing users to a new role before deleting") return err } - for _, networkRoles := range user.NetworkRoles { + for netID, networkRoles := range user.NetworkRoles { if _, ok := networkRoles[rid]; ok { - err = errors.New("active roles cannot be deleted.switch existing users to a new role before deleting") - return err + delete(networkRoles, rid) + user.NetworkRoles[netID] = networkRoles + logic.UpsertUser(user) } } From 239d0dfc6f08b40d7d3cef606f430f775d639aa7 Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Fri, 2 Aug 2024 15:02:35 +0530 Subject: [PATCH 110/139] replace _ with - in role naming convention --- models/user_mgmt.go | 10 +++++----- pro/logic/user_mgmt.go | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/models/user_mgmt.go b/models/user_mgmt.go index 62055ad90..b08f800b1 100644 --- a/models/user_mgmt.go +++ b/models/user_mgmt.go @@ -81,12 +81,12 @@ const ( // Pre-Defined User Roles const ( - SuperAdminRole UserRoleID = "super_admin" + SuperAdminRole UserRoleID = "super-admin" AdminRole UserRoleID = "admin" - ServiceUser UserRoleID = "service_user" - PlatformUser UserRoleID = "platform_user" - NetworkAdmin UserRoleID = "network_admin" - NetworkUser UserRoleID = "network_user" + ServiceUser UserRoleID = "service-user" + PlatformUser UserRoleID = "platform-user" + NetworkAdmin UserRoleID = "network-admin" + NetworkUser UserRoleID = "network-user" ) func (r UserRoleID) String() string { diff --git a/pro/logic/user_mgmt.go b/pro/logic/user_mgmt.go index 2131310bb..e798decc1 100644 --- a/pro/logic/user_mgmt.go +++ b/pro/logic/user_mgmt.go @@ -26,14 +26,14 @@ var PlatformUserUserPermissionTemplate = models.UserRolePermissionTemplate{ } var NetworkAdminAllPermissionTemplate = models.UserRolePermissionTemplate{ - ID: models.UserRoleID(fmt.Sprintf("global_%s", models.NetworkAdmin)), + ID: models.UserRoleID(fmt.Sprintf("global-%s", models.NetworkAdmin)), Default: true, FullAccess: true, NetworkID: models.AllNetworks, } var NetworkUserAllPermissionTemplate = models.UserRolePermissionTemplate{ - ID: models.UserRoleID(fmt.Sprintf("global_%s", models.NetworkUser)), + ID: models.UserRoleID(fmt.Sprintf("global-%s", models.NetworkUser)), Default: true, FullAccess: false, NetworkID: models.AllNetworks, From 2c6403543a060ea1d007fb9372024e52c22defaf Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Fri, 2 Aug 2024 16:33:36 +0530 Subject: [PATCH 111/139] fix failover middleware mgmt --- controllers/middleware.go | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/controllers/middleware.go b/controllers/middleware.go index 2e321d6f8..a057fb28d 100644 --- a/controllers/middleware.go +++ b/controllers/middleware.go @@ -7,6 +7,7 @@ import ( "github.com/gorilla/mux" "github.com/gravitl/netmaker/logger" + "github.com/gravitl/netmaker/logic" "github.com/gravitl/netmaker/models" ) @@ -20,14 +21,11 @@ func userMiddleWare(handler http.Handler) http.Handler { r.Header.Set("NET_ID", params["network"]) if strings.Contains(r.URL.Path, "hosts") || strings.Contains(r.URL.Path, "nodes") { r.Header.Set("TARGET_RSRC", models.HostRsrc.String()) - r.Header.Set("RSRC_TYPE", models.HostRsrc.String()) } if strings.Contains(r.URL.Path, "dns") { - r.Header.Set("RSRC_TYPE", models.DnsRsrc.String()) r.Header.Set("TARGET_RSRC", models.DnsRsrc.String()) } if strings.Contains(r.URL.Path, "users") { - r.Header.Set("RSRC_TYPE", models.UserRsrc.String()) r.Header.Set("TARGET_RSRC", models.UserRsrc.String()) } if strings.Contains(r.URL.Path, "ingress") { @@ -36,27 +34,23 @@ func userMiddleWare(handler http.Handler) http.Handler { if strings.Contains(r.URL.Path, "createrelay") || strings.Contains(r.URL.Path, "deleterelay") { r.Header.Set("TARGET_RSRC", models.RelayRsrc.String()) } + if strings.Contains(r.URL.Path, "gateway") { r.Header.Set("TARGET_RSRC", models.EgressGwRsrc.String()) } if strings.Contains(r.URL.Path, "networks") { r.Header.Set("TARGET_RSRC", models.NetworkRsrc.String()) - r.Header.Set("RSRC_TYPE", models.NetworkRsrc.String()) } if strings.Contains(r.URL.Path, "acls") { r.Header.Set("TARGET_RSRC", models.AclRsrc.String()) - r.Header.Set("RSRC_TYPE", models.NetworkRsrc.String()) } if strings.Contains(r.URL.Path, "extclients") { r.Header.Set("TARGET_RSRC", models.ExtClientsRsrc.String()) - r.Header.Set("RSRC_TYPE", models.ExtClientsRsrc.String()) } if strings.Contains(r.URL.Path, "enrollment-keys") { r.Header.Set("TARGET_RSRC", models.EnrollmentKeysRsrc.String()) - r.Header.Set("RSRC_TYPE", models.EnrollmentKeysRsrc.String()) } if strings.Contains(r.URL.Path, "metrics") { - r.Header.Set("RSRC_TYPE", models.MetricRsrc.String()) r.Header.Set("TARGET_RSRC", models.MetricRsrc.String()) } if keyID, ok := params["keyID"]; ok { @@ -65,6 +59,13 @@ func userMiddleWare(handler http.Handler) http.Handler { if nodeID, ok := params["nodeid"]; ok && r.Header.Get("TARGET_RSRC") != models.ExtClientsRsrc.String() { r.Header.Set("TARGET_RSRC_ID", nodeID) } + if strings.Contains(r.URL.Path, "failover") { + r.Header.Set("TARGET_RSRC", models.FailOverRsrc.String()) + nodeID := r.Header.Get("TARGET_RSRC_ID") + node, _ := logic.GetNodeByID(nodeID) + r.Header.Set("NET_ID", node.Network) + + } if hostID, ok := params["hostid"]; ok { r.Header.Set("TARGET_RSRC_ID", hostID) } @@ -86,12 +87,13 @@ func userMiddleWare(handler http.Handler) http.Handler { r.Header.Set("TARGET_RSRC_ID", username) } } - if r.Header.Get("NET_ID") == "" && (r.Header.Get("TARGET_RSRC_ID") == "" || r.Header.Get("TARGET_RSRC") == models.EnrollmentKeysRsrc.String() || r.Header.Get("TARGET_RSRC") == models.UserRsrc.String()) { r.Header.Set("IS_GLOBAL_ACCESS", "yes") } + + r.Header.Set("RSRC_TYPE", r.Header.Get("TARGET_RSRC")) logger.Log(0, "URL ------> ", r.URL.String()) handler.ServeHTTP(w, r) }) From fc727d454341b2c51e50b0c0f932a936c0b3d5b0 Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Mon, 5 Aug 2024 00:31:11 +0530 Subject: [PATCH 112/139] format oauth templates --- pro/auth/error.go | 142 ++++++++++++++++++++++++++++++++-------------- 1 file changed, 98 insertions(+), 44 deletions(-) diff --git a/pro/auth/error.go b/pro/auth/error.go index 8d5d196b8..d1f151317 100644 --- a/pro/auth/error.go +++ b/pro/auth/error.go @@ -1,60 +1,114 @@ package auth -import "net/http" +import ( + "fmt" + "net/http" + + "github.com/gravitl/netmaker/servercfg" +) + +var htmlBaseTemplate = ` + + + + + + + Netmaker :: SSO + + + -// == define error HTML here == -const oauthNotConfigured = ` -

Your Netmaker server does not have OAuth configured.

-

Please visit the docs here to learn how to.

+ + %s + + ` -const oauthStateInvalid = ` - -

Invalid OAuth Session. Please re-try again.

- -` +var oauthNotConfigured = fmt.Sprintf(htmlBaseTemplate, `

Your Netmaker server does not have OAuth configured.

+

Please visit the docs here to learn how to.

`) -const userNotAllowed = ` - -

Your account does not have access to the dashboard. Please contact your administrator for more information about your account.

-

Non-Admins can access the netmaker networks using RemoteAccessClient.

- - -` +var oauthStateInvalid = fmt.Sprintf(htmlBaseTemplate, `

Invalid OAuth Session. Please re-try again.

`) -const userFirstTimeSignUp = ` - -

Thank you for signing up. Please contact your administrator for access.

- - -` +var userNotAllowed = fmt.Sprintf(htmlBaseTemplate, `h2>Your account does not have access to the dashboard. Please contact your administrator for more information about your account. +

Non-Admins can access the netmaker networks using RemoteAccessClient.

`) -const userSignUpApprovalPending = ` - -

Your account is yet to be approved. Please contact your administrator for access.

- - -` +var userFirstTimeSignUp = fmt.Sprintf(htmlBaseTemplate, `

Thank you for signing up. Please contact your administrator for access.

`) -const userNotFound = ` - -

User Not Found.

- -` +var userSignUpApprovalPending = fmt.Sprintf(htmlBaseTemplate, `

Your account is yet to be approved. Please contact your administrator for access.

`) -const somethingwentwrong = ` - -

Something went wrong. Contact Admin.

- -` +var userNotFound = fmt.Sprintf(htmlBaseTemplate, `

User Not Found.

`) -const notallowedtosignup = ` - -

Your email is not allowed. Please contact your administrator.

- -` +var somethingwentwrong = fmt.Sprintf(htmlBaseTemplate, `

Something went wrong. Contact Admin.

`) + +var notallowedtosignup = fmt.Sprintf(htmlBaseTemplate, `

Your email is not allowed. Please contact your administrator.

`) func handleOauthUserNotFound(response http.ResponseWriter) { response.Header().Set("Content-Type", "text/html; charset=utf-8") From 6b208a7c59a274d9213682e5f147cf71a93b8ae8 Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Mon, 5 Aug 2024 09:24:47 +0530 Subject: [PATCH 113/139] fetch route temaplate --- controllers/middleware.go | 33 +++++++++++++++++++-------------- logic/security.go | 2 +- 2 files changed, 20 insertions(+), 15 deletions(-) diff --git a/controllers/middleware.go b/controllers/middleware.go index a057fb28d..733db69d3 100644 --- a/controllers/middleware.go +++ b/controllers/middleware.go @@ -14,43 +14,48 @@ import ( func userMiddleWare(handler http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { var params = mux.Vars(r) + route, err := mux.CurrentRoute(r).GetPathTemplate() + if err != nil { + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) + return + } r.Header.Set("IS_GLOBAL_ACCESS", "no") r.Header.Set("TARGET_RSRC", "") r.Header.Set("RSRC_TYPE", "") r.Header.Set("TARGET_RSRC_ID", "") r.Header.Set("NET_ID", params["network"]) - if strings.Contains(r.URL.Path, "hosts") || strings.Contains(r.URL.Path, "nodes") { + if strings.Contains(route, "hosts") || strings.Contains(route, "nodes") { r.Header.Set("TARGET_RSRC", models.HostRsrc.String()) } - if strings.Contains(r.URL.Path, "dns") { + if strings.Contains(route, "dns") { r.Header.Set("TARGET_RSRC", models.DnsRsrc.String()) } - if strings.Contains(r.URL.Path, "users") { + if strings.Contains(route, "users") { r.Header.Set("TARGET_RSRC", models.UserRsrc.String()) } - if strings.Contains(r.URL.Path, "ingress") { + if strings.Contains(route, "ingress") { r.Header.Set("TARGET_RSRC", models.RemoteAccessGwRsrc.String()) } - if strings.Contains(r.URL.Path, "createrelay") || strings.Contains(r.URL.Path, "deleterelay") { + if strings.Contains(route, "createrelay") || strings.Contains(route, "deleterelay") { r.Header.Set("TARGET_RSRC", models.RelayRsrc.String()) } - if strings.Contains(r.URL.Path, "gateway") { + if strings.Contains(route, "gateway") { r.Header.Set("TARGET_RSRC", models.EgressGwRsrc.String()) } - if strings.Contains(r.URL.Path, "networks") { + if strings.Contains(route, "networks") { r.Header.Set("TARGET_RSRC", models.NetworkRsrc.String()) } - if strings.Contains(r.URL.Path, "acls") { + if strings.Contains(route, "acls") { r.Header.Set("TARGET_RSRC", models.AclRsrc.String()) } - if strings.Contains(r.URL.Path, "extclients") { + if strings.Contains(route, "extclients") { r.Header.Set("TARGET_RSRC", models.ExtClientsRsrc.String()) } - if strings.Contains(r.URL.Path, "enrollment-keys") { + if strings.Contains(route, "enrollment-keys") { r.Header.Set("TARGET_RSRC", models.EnrollmentKeysRsrc.String()) } - if strings.Contains(r.URL.Path, "metrics") { + if strings.Contains(route, "metrics") { r.Header.Set("TARGET_RSRC", models.MetricRsrc.String()) } if keyID, ok := params["keyID"]; ok { @@ -59,7 +64,7 @@ func userMiddleWare(handler http.Handler) http.Handler { if nodeID, ok := params["nodeid"]; ok && r.Header.Get("TARGET_RSRC") != models.ExtClientsRsrc.String() { r.Header.Set("TARGET_RSRC_ID", nodeID) } - if strings.Contains(r.URL.Path, "failover") { + if strings.Contains(route, "failover") { r.Header.Set("TARGET_RSRC", models.FailOverRsrc.String()) nodeID := r.Header.Get("TARGET_RSRC_ID") node, _ := logic.GetNodeByID(nodeID) @@ -73,7 +78,7 @@ func userMiddleWare(handler http.Handler) http.Handler { r.Header.Set("TARGET_RSRC_ID", clientID) } if netID, ok := params["networkname"]; ok { - if !strings.Contains(r.URL.Path, "acls") { + if !strings.Contains(route, "acls") { r.Header.Set("TARGET_RSRC_ID", netID) } r.Header.Set("NET_ID", params["networkname"]) @@ -94,7 +99,7 @@ func userMiddleWare(handler http.Handler) http.Handler { } r.Header.Set("RSRC_TYPE", r.Header.Get("TARGET_RSRC")) - logger.Log(0, "URL ------> ", r.URL.String()) + logger.Log(0, "URL ------> ", route) handler.ServeHTTP(w, r) }) } diff --git a/logic/security.go b/logic/security.go index d017e1484..aa6922360 100644 --- a/logic/security.go +++ b/logic/security.go @@ -33,7 +33,7 @@ func SecurityCheck(reqAdmin bool, next http.Handler) http.HandlerFunc { username, err := GetUserNameFromToken(bearerToken) if err != nil { logger.Log(0, "next 1", r.URL.String(), err.Error()) - ReturnErrorResponse(w, r, FormatError(err, err.Error())) + ReturnErrorResponse(w, r, FormatError(err, "unauthorized")) return } // detect masteradmin From 42c0fbc32fb950d3b1f9d10f45ea97e760db61ee Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Tue, 6 Aug 2024 15:50:51 +0530 Subject: [PATCH 114/139] return err if user wrong login type --- controllers/user.go | 9 +++++++++ logic/auth.go | 10 +++++----- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/controllers/user.go b/controllers/user.go index 2d2fcdcdb..215a31545 100644 --- a/controllers/user.go +++ b/controllers/user.go @@ -90,6 +90,15 @@ func authenticateUser(response http.ResponseWriter, request *http.Request) { return } } + user, err := logic.GetUser(authRequest.UserName) + if err != nil { + logic.ReturnErrorResponse(response, request, logic.FormatError(err, "unauthorized")) + return + } + if logic.IsOauthUser(user) == nil { + logic.ReturnErrorResponse(response, request, logic.FormatError(errors.New("user is registered via SSO"), "badrequest")) + return + } username := authRequest.UserName jwt, err := logic.VerifyAuthRequest(authRequest) if err != nil { diff --git a/logic/auth.go b/logic/auth.go index 49130d272..5a92b0694 100644 --- a/logic/auth.go +++ b/logic/auth.go @@ -139,10 +139,7 @@ func CreateUser(user *models.User) error { if err := IsNetworkRolesValid(user.NetworkRoles); err != nil { return errors.New("invalid network roles: " + err.Error()) } - user.AuthType = models.BasicAuth - if IsOauthUser(user) == nil { - user.AuthType = models.OAuth - } + var err = ValidateUser(user) if err != nil { logger.Log(0, "failed to validate user", err.Error()) @@ -156,7 +153,10 @@ func CreateUser(user *models.User) error { } // set password to encrypted password user.Password = string(hash) - + user.AuthType = models.BasicAuth + if IsOauthUser(user) == nil { + user.AuthType = models.OAuth + } _, err = CreateUserJWT(user.UserName, user.PlatformRoleID) if err != nil { logger.Log(0, "failed to generate token", err.Error()) From 9eaffd131410a2496544c9074ef62353e65893aa Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Tue, 6 Aug 2024 23:09:42 +0530 Subject: [PATCH 115/139] check user groups on rac apis --- pro/logic/user_mgmt.go | 56 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 55 insertions(+), 1 deletion(-) diff --git a/pro/logic/user_mgmt.go b/pro/logic/user_mgmt.go index e798decc1..98abf67f5 100644 --- a/pro/logic/user_mgmt.go +++ b/pro/logic/user_mgmt.go @@ -509,7 +509,7 @@ func GetUserRAGNodes(user models.User) (gws map[string]models.Node) { if err != nil { return } - logger.Log(0, "------------> 8. getUserRemoteAccessGwsV1") + logger.Log(0, fmt.Sprintf("------------> 8. getUserRemoteAccessGwsV1 %+v", allNetAccess)) for _, node := range nodes { if node.IsIngressGateway && !node.PendingDelete { if allNetAccess { @@ -549,6 +549,59 @@ func GetUserNetworkRolesWithRemoteVPNAccess(user models.User) (gwAccess map[mode gwAccess[models.NetworkID("*")] = make(map[models.RsrcID]models.RsrcPermissionScope) } logger.Log(0, "------------> 7.2 getUserRemoteAccessGwsV1") + if len(user.UserGroups) > 0 { + for gID := range user.UserGroups { + userG, err := GetUserGroup(gID) + if err != nil { + continue + } + for netID, roleMap := range userG.NetworkRoles { + for roleID := range roleMap { + role, err := logic.GetRole(roleID) + if err == nil { + if role.FullAccess { + gwAccess[netID] = map[models.RsrcID]models.RsrcPermissionScope{ + models.AllRemoteAccessGwRsrcID: { + Create: true, + Read: true, + Update: true, + VPNaccess: true, + Delete: true, + }, + models.AllExtClientsRsrcID: { + Create: true, + Read: true, + Update: true, + Delete: true, + }, + } + break + } + if rsrcsMap, ok := role.NetworkLevelAccess[models.RemoteAccessGwRsrc]; ok { + if permissions, ok := rsrcsMap[models.AllRemoteAccessGwRsrcID]; ok && permissions.VPNaccess { + if len(gwAccess[netID]) == 0 { + gwAccess[netID] = make(map[models.RsrcID]models.RsrcPermissionScope) + } + gwAccess[netID][models.AllRemoteAccessGwRsrcID] = permissions + break + } else { + for gwID, scope := range rsrcsMap { + if scope.VPNaccess { + if len(gwAccess[netID]) == 0 { + gwAccess[netID] = make(map[models.RsrcID]models.RsrcPermissionScope) + } + gwAccess[netID][gwID] = scope + } + } + } + + } + + } + } + } + } + } for netID, roleMap := range user.NetworkRoles { for roleID := range roleMap { role, err := logic.GetRole(roleID) @@ -594,6 +647,7 @@ func GetUserNetworkRolesWithRemoteVPNAccess(user models.User) (gwAccess map[mode } } } + logger.Log(0, "------------> 7.3 getUserRemoteAccessGwsV1") return } From 30e1272ae9cef13f3ad29d9c622b5c4e8ff387e1 Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Wed, 7 Aug 2024 14:07:01 +0530 Subject: [PATCH 116/139] fix rac apis --- controllers/node.go | 3 ++- logic/user_mgmt.go | 5 ++++- pro/controllers/users.go | 2 +- pro/initialize.go | 2 +- pro/logic/user_mgmt.go | 30 +++--------------------------- 5 files changed, 11 insertions(+), 31 deletions(-) diff --git a/controllers/node.go b/controllers/node.go index 400eb85f5..a30588384 100644 --- a/controllers/node.go +++ b/controllers/node.go @@ -586,6 +586,7 @@ func createIngressGateway(w http.ResponseWriter, r *http.Request) { logic.CreateRole(models.UserRolePermissionTemplate{ ID: models.GetRAGRoleName(node.Network, host.Name), NetworkID: models.NetworkID(node.Network), + Default: true, NetworkLevelAccess: map[models.RsrcType]map[models.RsrcID]models.RsrcPermissionScope{ models.RemoteAccessGwRsrc: { models.RsrcID(node.ID.String()): models.RsrcPermissionScope{ @@ -651,7 +652,7 @@ func deleteIngressGateway(w http.ResponseWriter, r *http.Request) { return } - go logic.RemoveNetworkRoleFromUsers(*host, node) + go logic.DeleteRole(models.GetRAGRoleName(node.Network, host.Name), true) apiNode := node.ConvertToAPINode() logger.Log(1, r.Header.Get("user"), "deleted ingress gateway", nodeid) diff --git a/logic/user_mgmt.go b/logic/user_mgmt.go index 9fa5a10ea..ccb9d2b26 100644 --- a/logic/user_mgmt.go +++ b/logic/user_mgmt.go @@ -28,6 +28,10 @@ var CreateRole = func(r models.UserRolePermissionTemplate) error { return nil } +var DeleteRole = func(r models.UserRoleID, force bool) error { + return nil +} + var FilterNetworksByRole = func(allnetworks []models.Network, user models.User) []models.Network { return allnetworks } @@ -38,7 +42,6 @@ var IsGroupsValid = func(groups map[models.UserGroupID]struct{}) error { var IsNetworkRolesValid = func(networkRoles map[models.NetworkID]map[models.UserRoleID]struct{}) error { return nil } -var RemoveNetworkRoleFromUsers = func(host models.Host, node models.Node) {} var InitialiseRoles = userRolesInit var DeleteNetworkRoles = func(netID string) {} diff --git a/pro/controllers/users.go b/pro/controllers/users.go index bb893aaa9..344bf45b7 100644 --- a/pro/controllers/users.go +++ b/pro/controllers/users.go @@ -591,7 +591,7 @@ func deleteRole(w http.ResponseWriter, r *http.Request) { logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("role is required"), "badrequest")) return } - err := proLogic.DeleteRole(models.UserRoleID(rid)) + err := proLogic.DeleteRole(models.UserRoleID(rid), false) if err != nil { logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return diff --git a/pro/initialize.go b/pro/initialize.go index 689526764..c888e6b3e 100644 --- a/pro/initialize.go +++ b/pro/initialize.go @@ -121,6 +121,7 @@ func InitPro() { mq.UpdateMetricsFallBack = proLogic.MQUpdateMetricsFallBack logic.GetFilteredNodesByUserAccess = proLogic.GetFilteredNodesByUserAccess logic.CreateRole = proLogic.CreateRole + logic.DeleteRole = proLogic.DeleteRole logic.NetworkPermissionsCheck = proLogic.NetworkPermissionsCheck logic.GlobalPermissionsCheck = proLogic.GlobalPermissionsCheck logic.DeleteNetworkRoles = proLogic.DeleteNetworkRoles @@ -128,7 +129,6 @@ func InitPro() { logic.FilterNetworksByRole = proLogic.FilterNetworksByRole logic.IsGroupsValid = proLogic.IsGroupsValid logic.IsNetworkRolesValid = proLogic.IsNetworkRolesValid - logic.RemoveNetworkRoleFromUsers = proLogic.RemoveNetworkRoleFromUsers logic.InitialiseRoles = proLogic.UserRolesInit } diff --git a/pro/logic/user_mgmt.go b/pro/logic/user_mgmt.go index 98abf67f5..e085adedc 100644 --- a/pro/logic/user_mgmt.go +++ b/pro/logic/user_mgmt.go @@ -9,7 +9,6 @@ import ( "github.com/gravitl/netmaker/logger" "github.com/gravitl/netmaker/logic" "github.com/gravitl/netmaker/models" - "golang.org/x/exp/slog" ) var ServiceUserPermissionTemplate = models.UserRolePermissionTemplate{ @@ -158,7 +157,7 @@ func DeleteNetworkRoles(netID string) { roles, _ := ListNetworkRoles() for _, role := range roles { if role.NetworkID.String() == netID { - DeleteRole(role.ID) + DeleteRole(role.ID, true) } } } @@ -325,7 +324,7 @@ func UpdateRole(r models.UserRolePermissionTemplate) error { } // DeleteRole - deletes user role -func DeleteRole(rid models.UserRoleID) error { +func DeleteRole(rid models.UserRoleID, force bool) error { if rid.String() == "" { return errors.New("role id cannot be empty") } @@ -337,7 +336,7 @@ func DeleteRole(rid models.UserRoleID) error { if err != nil { return err } - if role.Default { + if !force && role.Default { return errors.New("cannot delete default role") } for _, user := range users { @@ -806,29 +805,6 @@ func IsNetworkRolesValid(networkRoles map[models.NetworkID]map[models.UserRoleID return nil } -func RemoveNetworkRoleFromUsers(host models.Host, node models.Node) { - users, err := logic.GetUsersDB() - if err == nil { - for _, user := range users { - // delete role from user - if netRoles, ok := user.NetworkRoles[models.NetworkID(node.Network)]; ok { - delete(netRoles, models.GetRAGRoleName(node.Network, host.Name)) - user.NetworkRoles[models.NetworkID(node.Network)] = netRoles - err = logic.UpsertUser(user) - if err != nil { - slog.Error("failed to get user", "user", user.UserName, "error", err) - } - } - } - } else { - slog.Error("failed to get users", "error", err) - } - err = DeleteRole(models.GetRAGRoleName(node.Network, host.Name)) - if err != nil { - slog.Error("failed to delete role: ", models.GetRAGRoleName(node.Network, host.Name), err) - } -} - // PrepareOauthUserFromInvite - init oauth user before create func PrepareOauthUserFromInvite(in models.UserInvite) (models.User, error) { var newPass, fetchErr = logic.FetchPassValue("") From 088c108b9356308e15d7667667ef69c310e2bb6f Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Fri, 9 Aug 2024 08:59:35 +0530 Subject: [PATCH 117/139] fix resp msg --- pro/logic/security.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pro/logic/security.go b/pro/logic/security.go index 4d328bcd8..3225c269e 100644 --- a/pro/logic/security.go +++ b/pro/logic/security.go @@ -163,7 +163,7 @@ func GlobalPermissionsCheck(username string, r *http.Request) error { } rsrcPermissionScope, ok := userRole.GlobalLevelAccess[models.RsrcType(targetRsrc)] if !ok { - return fmt.Errorf("access denied to %s rsrc", targetRsrc) + return fmt.Errorf("access denied to %s", targetRsrc) } if allRsrcsTypePermissionScope, ok := rsrcPermissionScope[models.RsrcID(fmt.Sprintf("all_%s", targetRsrc))]; ok { return checkPermissionScopeWithReqMethod(allRsrcsTypePermissionScope, r.Method) From b160445a18468372d068f790ef29351f5a10ad78 Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Fri, 9 Aug 2024 17:06:26 +0530 Subject: [PATCH 118/139] add validation checks for admin invite --- pro/controllers/users.go | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/pro/controllers/users.go b/pro/controllers/users.go index 344bf45b7..bac22befb 100644 --- a/pro/controllers/users.go +++ b/pro/controllers/users.go @@ -164,6 +164,21 @@ func inviteUsers(w http.ResponseWriter, r *http.Request) { logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) return } + callerUserName := r.Header.Get("user") + caller, err := logic.GetUser(callerUserName) + if err != nil { + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "notfound")) + return + } + if inviteReq.PlatformRoleID == models.SuperAdminRole.String() { + logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("super admin cannot be invited"), "badrequest")) + return + } + if (inviteReq.PlatformRoleID == models.AdminRole.String() || + inviteReq.PlatformRoleID == models.SuperAdminRole.String()) && caller.PlatformRoleID != models.SuperAdminRole { + logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("only superadmin can invite admin users"), "forbidden")) + return + } //validate Req err = proLogic.IsGroupsValid(inviteReq.UserGroups) if err != nil { From 527c77b4a4c6b7a6e477663c021fbc589354c639 Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Fri, 9 Aug 2024 18:10:28 +0530 Subject: [PATCH 119/139] return oauth type --- logic/users.go | 1 + 1 file changed, 1 insertion(+) diff --git a/logic/users.go b/logic/users.go index 1b009fc72..60d773561 100644 --- a/logic/users.go +++ b/logic/users.go @@ -43,6 +43,7 @@ func ToReturnUser(user models.User) models.ReturnUser { return models.ReturnUser{ UserName: user.UserName, PlatformRoleID: user.PlatformRoleID, + AuthType: user.AuthType, UserGroups: user.UserGroups, NetworkRoles: user.NetworkRoles, RemoteGwIDs: user.RemoteGwIDs, From 6078639699dc995dc02b99c3c0b60da21fe5bb95 Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Mon, 12 Aug 2024 08:10:26 +0530 Subject: [PATCH 120/139] format group err msg --- pro/logic/user_mgmt.go | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/pro/logic/user_mgmt.go b/pro/logic/user_mgmt.go index e085adedc..140837530 100644 --- a/pro/logic/user_mgmt.go +++ b/pro/logic/user_mgmt.go @@ -773,7 +773,7 @@ func IsGroupsValid(groups map[models.UserGroupID]struct{}) error { for groupID := range groups { _, err := GetUserGroup(groupID) if err != nil { - return err + return fmt.Errorf("user group `%s` not found", groupID) } } return nil @@ -787,10 +787,6 @@ func IsNetworkRolesValid(networkRoles map[models.NetworkID]map[models.UserRoleID if err != nil { return fmt.Errorf("failed to fetch network %s ", netID) } - if len(netRoles) == 0 { - delete(networkRoles, netID) - continue - } } for netRoleID := range netRoles { role, err := logic.GetRole(netRoleID) From da5505f2984d08b7c47cc8eeca44eecb60ad6b03 Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Mon, 12 Aug 2024 08:44:39 +0530 Subject: [PATCH 121/139] fix html tag --- pro/auth/error.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pro/auth/error.go b/pro/auth/error.go index d1f151317..b2a5efcdd 100644 --- a/pro/auth/error.go +++ b/pro/auth/error.go @@ -97,7 +97,7 @@ var oauthNotConfigured = fmt.Sprintf(htmlBaseTemplate, `

Your Netmaker server var oauthStateInvalid = fmt.Sprintf(htmlBaseTemplate, `

Invalid OAuth Session. Please re-try again.

`) -var userNotAllowed = fmt.Sprintf(htmlBaseTemplate, `h2>Your account does not have access to the dashboard. Please contact your administrator for more information about your account. +var userNotAllowed = fmt.Sprintf(htmlBaseTemplate, `

Your account does not have access to the dashboard. Please contact your administrator for more information about your account.

Non-Admins can access the netmaker networks using RemoteAccessClient.

`) var userFirstTimeSignUp = fmt.Sprintf(htmlBaseTemplate, `

Thank you for signing up. Please contact your administrator for access.

`) From ac8cfdf7a4afc711223f96b095f357e3c31ad8de Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Mon, 12 Aug 2024 09:39:52 +0530 Subject: [PATCH 122/139] clean up default groups --- pro/logic/user_mgmt.go | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/pro/logic/user_mgmt.go b/pro/logic/user_mgmt.go index 140837530..3e7b99c0c 100644 --- a/pro/logic/user_mgmt.go +++ b/pro/logic/user_mgmt.go @@ -146,6 +146,8 @@ func DeleteNetworkRoles(netID string) { } } + database.DeleteRecord(database.USER_GROUPS_TABLE_NAME, fmt.Sprintf("%s-%s-grp", netID, models.NetworkUser)) + database.DeleteRecord(database.USER_GROUPS_TABLE_NAME, fmt.Sprintf("%s-%s-grp", netID, models.NetworkAdmin)) userGs, _ := ListUserGroups() for _, userGI := range userGs { if _, ok := userGI.NetworkRoles[models.NetworkID(netID)]; ok { @@ -157,7 +159,7 @@ func DeleteNetworkRoles(netID string) { roles, _ := ListNetworkRoles() for _, role := range roles { if role.NetworkID.String() == netID { - DeleteRole(role.ID, true) + database.DeleteRecord(database.USER_PERMISSIONS_TABLE_NAME, role.ID.String()) } } } @@ -361,13 +363,15 @@ func DeleteRole(rid models.UserRoleID, force bool) error { err = errors.New("active roles cannot be deleted.switch existing users to a new role before deleting") return err } - for netID, networkRoles := range user.NetworkRoles { - if _, ok := networkRoles[rid]; ok { - delete(networkRoles, rid) - user.NetworkRoles[netID] = networkRoles - logic.UpsertUser(user) - } + if role.NetworkID != "" { + for netID, networkRoles := range user.NetworkRoles { + if _, ok := networkRoles[rid]; ok { + delete(networkRoles, rid) + user.NetworkRoles[netID] = networkRoles + logic.UpsertUser(user) + } + } } } return database.DeleteRecord(database.USER_PERMISSIONS_TABLE_NAME, rid.String()) From bc1f2d0c7285ccda3141da6c20ecdc2700a64344 Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Mon, 12 Aug 2024 10:49:24 +0530 Subject: [PATCH 123/139] create default rag role --- controllers/hosts.go | 1 - controllers/node.go | 35 ----------------------------------- logic/gateway.go | 28 ++++++++++++++++++++++++++++ logic/nodes.go | 4 ++++ 4 files changed, 32 insertions(+), 36 deletions(-) diff --git a/controllers/hosts.go b/controllers/hosts.go index be44730ba..6f844fd6b 100644 --- a/controllers/hosts.go +++ b/controllers/hosts.go @@ -219,7 +219,6 @@ func updateHost(w http.ResponseWriter, r *http.Request) { } newHost := newHostData.ConvertAPIHostToNMHost(currHost) - logic.UpdateHost(newHost, currHost) // update the in memory struct values if err = logic.UpsertHost(newHost); err != nil { logger.Log(0, r.Header.Get("user"), "failed to update a host:", err.Error()) diff --git a/controllers/node.go b/controllers/node.go index a30588384..432769c60 100644 --- a/controllers/node.go +++ b/controllers/node.go @@ -577,34 +577,6 @@ func createIngressGateway(w http.ResponseWriter, r *http.Request) { logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } - host, err := logic.GetHost(node.HostID.String()) - if err != nil { - logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) - return - } - // create network role for this gateway - logic.CreateRole(models.UserRolePermissionTemplate{ - ID: models.GetRAGRoleName(node.Network, host.Name), - NetworkID: models.NetworkID(node.Network), - Default: true, - NetworkLevelAccess: map[models.RsrcType]map[models.RsrcID]models.RsrcPermissionScope{ - models.RemoteAccessGwRsrc: { - models.RsrcID(node.ID.String()): models.RsrcPermissionScope{ - Read: true, - VPNaccess: true, - }, - }, - models.ExtClientsRsrc: { - models.AllExtClientsRsrcID: models.RsrcPermissionScope{ - Read: true, - Create: true, - Update: true, - Delete: true, - SelfOnly: true, - }, - }, - }, - }) apiNode := node.ConvertToAPINode() logger.Log(1, r.Header.Get("user"), "created ingress gateway on node", nodeid, "on network", netid) @@ -646,13 +618,6 @@ func deleteIngressGateway(w http.ResponseWriter, r *http.Request) { logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } - host, err := logic.GetHost(node.HostID.String()) - if err != nil { - logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) - return - } - - go logic.DeleteRole(models.GetRAGRoleName(node.Network, host.Name), true) apiNode := node.ConvertToAPINode() logger.Log(1, r.Header.Get("user"), "deleted ingress gateway", nodeid) diff --git a/logic/gateway.go b/logic/gateway.go index 98fdac167..0fdffeca0 100644 --- a/logic/gateway.go +++ b/logic/gateway.go @@ -178,6 +178,29 @@ func CreateIngressGateway(netid string, nodeid string, ingress models.IngressReq if err != nil { return models.Node{}, err } + // create network role for this gateway + CreateRole(models.UserRolePermissionTemplate{ + ID: models.GetRAGRoleName(node.Network, host.Name), + NetworkID: models.NetworkID(node.Network), + Default: true, + NetworkLevelAccess: map[models.RsrcType]map[models.RsrcID]models.RsrcPermissionScope{ + models.RemoteAccessGwRsrc: { + models.RsrcID(node.ID.String()): models.RsrcPermissionScope{ + Read: true, + VPNaccess: true, + }, + }, + models.ExtClientsRsrc: { + models.AllExtClientsRsrcID: models.RsrcPermissionScope{ + Read: true, + Create: true, + Update: true, + Delete: true, + SelfOnly: true, + }, + }, + }, + }) err = SetNetworkNodesLastModified(netid) return node, err } @@ -231,6 +254,11 @@ func DeleteIngressGateway(nodeid string) (models.Node, []models.ExtClient, error if err != nil { return models.Node{}, removedClients, err } + host, err := GetHost(node.HostID.String()) + if err != nil { + return models.Node{}, removedClients, err + } + go DeleteRole(models.GetRAGRoleName(node.Network, host.Name), true) err = SetNetworkNodesLastModified(node.Network) return node, removedClients, err } diff --git a/logic/nodes.go b/logic/nodes.go index 62f49557c..45ce75491 100644 --- a/logic/nodes.go +++ b/logic/nodes.go @@ -195,6 +195,10 @@ func DeleteNode(node *models.Node, purge bool) error { if err := DeleteGatewayExtClients(node.ID.String(), node.Network); err != nil { slog.Error("failed to delete ext clients", "nodeid", node.ID.String(), "error", err.Error()) } + host, err := GetHost(node.HostID.String()) + if err == nil { + go DeleteRole(models.GetRAGRoleName(node.Network, host.Name), true) + } } if node.IsRelayed { // cleanup node from relayednodes on relay node From 40038484479c88e2da66e8c7504198c914e26bb7 Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Mon, 12 Aug 2024 11:48:03 +0530 Subject: [PATCH 124/139] add UI name to roles --- controllers/hosts.go | 14 ++++++++++++++ logic/gateway.go | 5 +++-- logic/hosts.go | 13 +++++++++++++ logic/nodes.go | 2 +- logic/user_mgmt.go | 2 ++ migrate/migrate.go | 5 +++-- models/user_mgmt.go | 9 +++++++-- pro/initialize.go | 1 + 8 files changed, 44 insertions(+), 7 deletions(-) diff --git a/controllers/hosts.go b/controllers/hosts.go index 6f844fd6b..77f887f64 100644 --- a/controllers/hosts.go +++ b/controllers/hosts.go @@ -219,6 +219,20 @@ func updateHost(w http.ResponseWriter, r *http.Request) { } newHost := newHostData.ConvertAPIHostToNMHost(currHost) + + if newHost.Name != currHost.Name { + // update any rag role ids + for _, nodeID := range newHost.Nodes { + node, err := logic.GetNodeByID(nodeID) + if err == nil && node.IsIngressGateway { + role, err := logic.GetRole(models.GetRAGRoleID(node.Network, currHost.ID.String())) + if err == nil { + role.UiName = models.GetRAGRoleName(node.Network, newHost.Name) + logic.UpdateRole(role) + } + } + } + } logic.UpdateHost(newHost, currHost) // update the in memory struct values if err = logic.UpsertHost(newHost); err != nil { logger.Log(0, r.Header.Get("user"), "failed to update a host:", err.Error()) diff --git a/logic/gateway.go b/logic/gateway.go index 0fdffeca0..87a41105b 100644 --- a/logic/gateway.go +++ b/logic/gateway.go @@ -180,7 +180,8 @@ func CreateIngressGateway(netid string, nodeid string, ingress models.IngressReq } // create network role for this gateway CreateRole(models.UserRolePermissionTemplate{ - ID: models.GetRAGRoleName(node.Network, host.Name), + ID: models.GetRAGRoleID(node.Network, host.ID.String()), + UiName: models.GetRAGRoleName(node.Network, host.Name), NetworkID: models.NetworkID(node.Network), Default: true, NetworkLevelAccess: map[models.RsrcType]map[models.RsrcID]models.RsrcPermissionScope{ @@ -258,7 +259,7 @@ func DeleteIngressGateway(nodeid string) (models.Node, []models.ExtClient, error if err != nil { return models.Node{}, removedClients, err } - go DeleteRole(models.GetRAGRoleName(node.Network, host.Name), true) + go DeleteRole(models.GetRAGRoleID(node.Network, host.ID.String()), true) err = SetNetworkNodesLastModified(node.Network) return node, removedClients, err } diff --git a/logic/hosts.go b/logic/hosts.go index d0282bb40..0fa8887e9 100644 --- a/logic/hosts.go +++ b/logic/hosts.go @@ -269,6 +269,19 @@ func UpdateHostFromClient(newHost, currHost *models.Host) (sendPeerUpdate bool) currHost.IsStaticPort = newHost.IsStaticPort currHost.IsStatic = newHost.IsStatic currHost.MTU = newHost.MTU + if newHost.Name != currHost.Name { + // update any rag role ids + for _, nodeID := range newHost.Nodes { + node, err := GetNodeByID(nodeID) + if err == nil && node.IsIngressGateway { + role, err := GetRole(models.GetRAGRoleID(node.Network, currHost.ID.String())) + if err == nil { + role.UiName = models.GetRAGRoleName(node.Network, newHost.Name) + UpdateRole(role) + } + } + } + } currHost.Name = newHost.Name if len(newHost.NatType) > 0 && newHost.NatType != currHost.NatType { currHost.NatType = newHost.NatType diff --git a/logic/nodes.go b/logic/nodes.go index 45ce75491..2b0f7bf6d 100644 --- a/logic/nodes.go +++ b/logic/nodes.go @@ -197,7 +197,7 @@ func DeleteNode(node *models.Node, purge bool) error { } host, err := GetHost(node.HostID.String()) if err == nil { - go DeleteRole(models.GetRAGRoleName(node.Network, host.Name), true) + go DeleteRole(models.GetRAGRoleID(node.Network, host.ID.String()), true) } } if node.IsRelayed { diff --git a/logic/user_mgmt.go b/logic/user_mgmt.go index ccb9d2b26..93d3b8e56 100644 --- a/logic/user_mgmt.go +++ b/logic/user_mgmt.go @@ -43,6 +43,8 @@ var IsNetworkRolesValid = func(networkRoles map[models.NetworkID]map[models.User return nil } +var UpdateRole = func(r models.UserRolePermissionTemplate) error { return nil } + var InitialiseRoles = userRolesInit var DeleteNetworkRoles = func(netID string) {} var CreateDefaultNetworkRolesAndGroups = func(netID models.NetworkID) {} diff --git a/migrate/migrate.go b/migrate/migrate.go index 1ee4aaf17..fdf8f9f73 100644 --- a/migrate/migrate.go +++ b/migrate/migrate.go @@ -323,7 +323,8 @@ func syncUsers() { h, err := logic.GetHost(networkNodeI.HostID.String()) if err == nil { logic.CreateRole(models.UserRolePermissionTemplate{ - ID: models.GetRAGRoleName(networkNodeI.Network, h.Name), + ID: models.GetRAGRoleID(networkNodeI.Network, h.ID.String()), + UiName: models.GetRAGRoleName(networkNodeI.Network, h.Name), NetworkID: models.NetworkID(netI.NetID), NetworkLevelAccess: map[models.RsrcType]map[models.RsrcID]models.RsrcPermissionScope{ models.RemoteAccessGwRsrc: { @@ -387,7 +388,7 @@ func syncUsers() { if err != nil { continue } - r, err := logic.GetRole(models.GetRAGRoleName(gwNode.Network, h.Name)) + r, err := logic.GetRole(models.GetRAGRoleID(gwNode.Network, h.ID.String())) if err != nil { continue } diff --git a/models/user_mgmt.go b/models/user_mgmt.go index b08f800b1..3efa81bf1 100644 --- a/models/user_mgmt.go +++ b/models/user_mgmt.go @@ -27,8 +27,12 @@ func (rid RsrcID) String() string { return string(rid) } -func GetRAGRoleName(netID, hostName string) UserRoleID { - return UserRoleID(fmt.Sprintf("netID-%s-rag-%s", netID, hostName)) +func GetRAGRoleName(netID, hostName string) string { + return fmt.Sprintf("netID-%s-rag-%s", netID, hostName) +} + +func GetRAGRoleID(netID, hostID string) UserRoleID { + return UserRoleID(fmt.Sprintf("netID-%s-rag-%s", netID, hostID)) } var RsrcTypeMap = map[RsrcType]struct{}{ @@ -112,6 +116,7 @@ type RsrcPermissionScope struct { type UserRolePermissionTemplate struct { ID UserRoleID `json:"id"` + UiName string `json:"ui_name"` Default bool `json:"default"` DenyDashboardAccess bool `json:"deny_dashboard_access"` FullAccess bool `json:"full_access"` diff --git a/pro/initialize.go b/pro/initialize.go index c888e6b3e..ff5653fe4 100644 --- a/pro/initialize.go +++ b/pro/initialize.go @@ -121,6 +121,7 @@ func InitPro() { mq.UpdateMetricsFallBack = proLogic.MQUpdateMetricsFallBack logic.GetFilteredNodesByUserAccess = proLogic.GetFilteredNodesByUserAccess logic.CreateRole = proLogic.CreateRole + logic.UpdateRole = proLogic.UpdateRole logic.DeleteRole = proLogic.DeleteRole logic.NetworkPermissionsCheck = proLogic.NetworkPermissionsCheck logic.GlobalPermissionsCheck = proLogic.GlobalPermissionsCheck From ecf00dcd25782ca53e80fd0e9685260dfba0fab1 Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Mon, 12 Aug 2024 14:58:53 +0530 Subject: [PATCH 125/139] remove default net group from user when deleted --- controllers/user.go | 29 ++++++++++++++++------------- pro/logic/user_mgmt.go | 19 ++++++++++++++++--- 2 files changed, 32 insertions(+), 16 deletions(-) diff --git a/controllers/user.go b/controllers/user.go index 215a31545..d4f767ee0 100644 --- a/controllers/user.go +++ b/controllers/user.go @@ -529,20 +529,23 @@ func updateUser(w http.ResponseWriter, r *http.Request) { return } - // user cannot update his own roles and groups - if len(user.NetworkRoles) != len(userchange.NetworkRoles) || !reflect.DeepEqual(user.NetworkRoles, userchange.NetworkRoles) { - err = errors.New("user cannot update self update their network roles") - slog.Error("failed to update user", "caller", caller.UserName, "attempted to update user", username, "error", err) - logic.ReturnErrorResponse(w, r, logic.FormatError(err, "forbidden")) - return - } - // user cannot update his own roles and groups - if len(user.UserGroups) != len(userchange.UserGroups) || !reflect.DeepEqual(user.UserGroups, userchange.UserGroups) { - err = errors.New("user cannot update self update their groups") - slog.Error("failed to update user", "caller", caller.UserName, "attempted to update user", username, "error", err) - logic.ReturnErrorResponse(w, r, logic.FormatError(err, "forbidden")) - return + if servercfg.IsPro { + // user cannot update his own roles and groups + if len(user.NetworkRoles) != len(userchange.NetworkRoles) || !reflect.DeepEqual(user.NetworkRoles, userchange.NetworkRoles) { + err = errors.New("user cannot update self update their network roles") + slog.Error("failed to update user", "caller", caller.UserName, "attempted to update user", username, "error", err) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "forbidden")) + return + } + // user cannot update his own roles and groups + if len(user.UserGroups) != len(userchange.UserGroups) || !reflect.DeepEqual(user.UserGroups, userchange.UserGroups) { + err = errors.New("user cannot update self update their groups") + slog.Error("failed to update user", "caller", caller.UserName, "attempted to update user", username, "error", err) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "forbidden")) + return + } } + } if ismaster { if user.PlatformRoleID != models.SuperAdminRole && userchange.PlatformRoleID == models.SuperAdminRole { diff --git a/pro/logic/user_mgmt.go b/pro/logic/user_mgmt.go index 3e7b99c0c..1bbd004da 100644 --- a/pro/logic/user_mgmt.go +++ b/pro/logic/user_mgmt.go @@ -139,15 +139,28 @@ func DeleteNetworkRoles(netID string) { if err != nil { return } + defaultUserGrp := fmt.Sprintf("%s-%s-grp", netID, models.NetworkUser) + defaultAdminGrp := fmt.Sprintf("%s-%s-grp", netID, models.NetworkAdmin) for _, user := range users { + var upsert bool if _, ok := user.NetworkRoles[models.NetworkID(netID)]; ok { delete(user.NetworkRoles, models.NetworkID(netID)) + upsert = true + } + if _, ok := user.UserGroups[models.UserGroupID(defaultUserGrp)]; ok { + delete(user.UserGroups, models.UserGroupID(defaultUserGrp)) + upsert = true + } + if _, ok := user.UserGroups[models.UserGroupID(defaultAdminGrp)]; ok { + delete(user.UserGroups, models.UserGroupID(defaultAdminGrp)) + upsert = true + } + if upsert { logic.UpsertUser(user) } - } - database.DeleteRecord(database.USER_GROUPS_TABLE_NAME, fmt.Sprintf("%s-%s-grp", netID, models.NetworkUser)) - database.DeleteRecord(database.USER_GROUPS_TABLE_NAME, fmt.Sprintf("%s-%s-grp", netID, models.NetworkAdmin)) + database.DeleteRecord(database.USER_GROUPS_TABLE_NAME, defaultUserGrp) + database.DeleteRecord(database.USER_GROUPS_TABLE_NAME, defaultAdminGrp) userGs, _ := ListUserGroups() for _, userGI := range userGs { if _, ok := userGI.NetworkRoles[models.NetworkID(netID)]; ok { From 7d847642da72092e4487120e56f91cb8ac2a34c1 Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Tue, 13 Aug 2024 16:57:20 +0530 Subject: [PATCH 126/139] reorder migration funcs --- migrate/migrate.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/migrate/migrate.go b/migrate/migrate.go index fdf8f9f73..665fdc3a6 100644 --- a/migrate/migrate.go +++ b/migrate/migrate.go @@ -20,8 +20,8 @@ import ( // Run - runs all migrations func Run() { updateEnrollmentKeys() - syncUsers() assignSuperAdmin() + syncUsers() updateHosts() updateNodes() updateAcls() @@ -354,6 +354,7 @@ func syncUsers() { users, err := logic.GetUsersDB() if err == nil { for _, user := range users { + user := user if user.PlatformRoleID.String() != "" { continue } From 63e4bf7105e275af1bb75234e462e5191f2374bb Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Wed, 14 Aug 2024 12:34:52 +0530 Subject: [PATCH 127/139] fix duplicacy of hosts --- controllers/hosts.go | 1 + 1 file changed, 1 insertion(+) diff --git a/controllers/hosts.go b/controllers/hosts.go index 77f887f64..fd0d716ef 100644 --- a/controllers/hosts.go +++ b/controllers/hosts.go @@ -94,6 +94,7 @@ func getHosts(w http.ResponseWriter, r *http.Request) { for _, node := range filteredNodes { if host, ok := currentHostsMap[node.HostID.String()]; ok { currentHosts = append(currentHosts, host) + delete(currentHostsMap, host.ID.String()) } } From 4de7a45ce6ce333b2620dc000fd5afef0f0ad726 Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Wed, 14 Aug 2024 16:15:30 +0530 Subject: [PATCH 128/139] check old field for migration --- logic/auth.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/logic/auth.go b/logic/auth.go index 5a92b0694..6a3f7991d 100644 --- a/logic/auth.go +++ b/logic/auth.go @@ -37,7 +37,7 @@ func HasSuperAdmin() (bool, error) { if err != nil { continue } - if user.PlatformRoleID == models.SuperAdminRole { + if user.PlatformRoleID == models.SuperAdminRole || user.IsSuperAdmin { return true, nil } } From e10d9c349f86127b7a235c677cb584605575961f Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Wed, 14 Aug 2024 23:58:07 +0530 Subject: [PATCH 129/139] from pro to ce make all secondary users admins --- migrate/migrate.go | 66 ++++++++++++++++++++++++---------------------- 1 file changed, 34 insertions(+), 32 deletions(-) diff --git a/migrate/migrate.go b/migrate/migrate.go index 665fdc3a6..1ba2bced0 100644 --- a/migrate/migrate.go +++ b/migrate/migrate.go @@ -313,39 +313,41 @@ func MigrateEmqx() { func syncUsers() { // create default network user roles for existing networks - networks, _ := logic.GetNetworks() - nodes, err := logic.GetAllNodes() - if err == nil { - for _, netI := range networks { - networkNodes := logic.GetNetworkNodesMemory(nodes, netI.NetID) - for _, networkNodeI := range networkNodes { - if networkNodeI.IsIngressGateway { - h, err := logic.GetHost(networkNodeI.HostID.String()) - if err == nil { - logic.CreateRole(models.UserRolePermissionTemplate{ - ID: models.GetRAGRoleID(networkNodeI.Network, h.ID.String()), - UiName: models.GetRAGRoleName(networkNodeI.Network, h.Name), - NetworkID: models.NetworkID(netI.NetID), - NetworkLevelAccess: map[models.RsrcType]map[models.RsrcID]models.RsrcPermissionScope{ - models.RemoteAccessGwRsrc: { - models.RsrcID(networkNodeI.ID.String()): models.RsrcPermissionScope{ - Read: true, - VPNaccess: true, + if servercfg.IsPro { + networks, _ := logic.GetNetworks() + nodes, err := logic.GetAllNodes() + if err == nil { + for _, netI := range networks { + networkNodes := logic.GetNetworkNodesMemory(nodes, netI.NetID) + for _, networkNodeI := range networkNodes { + if networkNodeI.IsIngressGateway { + h, err := logic.GetHost(networkNodeI.HostID.String()) + if err == nil { + logic.CreateRole(models.UserRolePermissionTemplate{ + ID: models.GetRAGRoleID(networkNodeI.Network, h.ID.String()), + UiName: models.GetRAGRoleName(networkNodeI.Network, h.Name), + NetworkID: models.NetworkID(netI.NetID), + NetworkLevelAccess: map[models.RsrcType]map[models.RsrcID]models.RsrcPermissionScope{ + models.RemoteAccessGwRsrc: { + models.RsrcID(networkNodeI.ID.String()): models.RsrcPermissionScope{ + Read: true, + VPNaccess: true, + }, }, - }, - models.ExtClientsRsrc: { - models.AllExtClientsRsrcID: models.RsrcPermissionScope{ - Read: true, - Create: true, - Update: true, - Delete: true, - SelfOnly: true, + models.ExtClientsRsrc: { + models.AllExtClientsRsrcID: models.RsrcPermissionScope{ + Read: true, + Create: true, + Update: true, + Delete: true, + SelfOnly: true, + }, }, }, - }, - }) - } + }) + } + } } } } @@ -355,9 +357,6 @@ func syncUsers() { if err == nil { for _, user := range users { user := user - if user.PlatformRoleID.String() != "" { - continue - } user.AuthType = models.BasicAuth if logic.IsOauthUser(&user) == nil { user.AuthType = models.OAuth @@ -376,6 +375,9 @@ func syncUsers() { } else { user.PlatformRoleID = models.ServiceUser } + if !servercfg.IsPro && !user.IsSuperAdmin { + user.PlatformRoleID = models.AdminRole + } logic.UpsertUser(user) if len(user.RemoteGwIDs) > 0 { // define user roles for network From 988c3cf95dd0b3afb9abe50e9d6c3db7a25ba308 Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Thu, 15 Aug 2024 00:01:46 +0530 Subject: [PATCH 130/139] from pro to ce make all secondary users admins --- migrate/migrate.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/migrate/migrate.go b/migrate/migrate.go index 1ba2bced0..bcd437573 100644 --- a/migrate/migrate.go +++ b/migrate/migrate.go @@ -375,9 +375,6 @@ func syncUsers() { } else { user.PlatformRoleID = models.ServiceUser } - if !servercfg.IsPro && !user.IsSuperAdmin { - user.PlatformRoleID = models.AdminRole - } logic.UpsertUser(user) if len(user.RemoteGwIDs) > 0 { // define user roles for network From 3332d4da9c0ccb977a1b7c84f96ff3233eeae1eb Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Thu, 15 Aug 2024 00:02:48 +0530 Subject: [PATCH 131/139] revert: from pro to ce make all secondary users admins --- migrate/migrate.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/migrate/migrate.go b/migrate/migrate.go index bcd437573..423412c11 100644 --- a/migrate/migrate.go +++ b/migrate/migrate.go @@ -357,6 +357,9 @@ func syncUsers() { if err == nil { for _, user := range users { user := user + if user.PlatformRoleID.String() != "" { + continue + } user.AuthType = models.BasicAuth if logic.IsOauthUser(&user) == nil { user.AuthType = models.OAuth From 17cf210f92880f23453fd65734a8d930e606afca Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Thu, 15 Aug 2024 13:37:46 +0530 Subject: [PATCH 132/139] make sure downgrades work --- migrate/migrate.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/migrate/migrate.go b/migrate/migrate.go index 423412c11..1675f4ee1 100644 --- a/migrate/migrate.go +++ b/migrate/migrate.go @@ -65,6 +65,7 @@ func assignSuperAdmin() { continue } user.PlatformRoleID = models.SuperAdminRole + user.IsSuperAdmin = true err = logic.UpsertUser(*user) if err != nil { slog.Error( @@ -357,6 +358,14 @@ func syncUsers() { if err == nil { for _, user := range users { user := user + if user.PlatformRoleID == models.AdminRole && !user.IsAdmin { + user.IsAdmin = true + logic.UpsertUser(user) + } + if user.PlatformRoleID == models.SuperAdminRole && !user.IsSuperAdmin { + user.IsSuperAdmin = true + logic.UpsertUser(user) + } if user.PlatformRoleID.String() != "" { continue } From f4a293ab27a356c80ac7517d01eecec5b911c219 Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Fri, 16 Aug 2024 13:31:46 +0530 Subject: [PATCH 133/139] fix pending users approval --- logic/users.go | 2 -- pro/auth/azure-ad.go | 9 +++++---- pro/auth/github.go | 9 +++++---- pro/auth/google.go | 9 +++++---- pro/auth/oidc.go | 9 +++++---- pro/controllers/users.go | 5 +++-- 6 files changed, 23 insertions(+), 20 deletions(-) diff --git a/logic/users.go b/logic/users.go index 60d773561..75a45ee67 100644 --- a/logic/users.go +++ b/logic/users.go @@ -137,8 +137,6 @@ func InsertUserInvite(invite models.UserInvite) error { return database.Insert(invite.Email, string(data), database.USER_INVITES_TABLE_NAME) } -func ImportGroupsFromInvite() {} - func GetUserInvite(email string) (in models.UserInvite, err error) { d, err := database.FetchRecord(database.USER_INVITES_TABLE_NAME, email) if err != nil { diff --git a/pro/auth/azure-ad.go b/pro/auth/azure-ad.go index 051f5f528..b0981aaeb 100644 --- a/pro/auth/azure-ad.go +++ b/pro/auth/azure-ad.go @@ -67,10 +67,7 @@ func handleAzureCallback(w http.ResponseWriter, r *http.Request) { handleOauthNotConfigured(w) return } - if !isEmailAllowed(content.UserPrincipalName) { - handleOauthUserNotAllowedToSignUp(w) - return - } + var inviteExists bool // check if invite exists for User in, err := logic.GetUserInvite(content.UserPrincipalName) @@ -100,6 +97,10 @@ func handleAzureCallback(w http.ResponseWriter, r *http.Request) { logic.DeleteUserInvite(user.UserName) logic.DeletePendingUser(content.UserPrincipalName) } else { + if !isEmailAllowed(content.UserPrincipalName) { + handleOauthUserNotAllowedToSignUp(w) + return + } err = logic.InsertPendingUser(&models.User{ UserName: content.UserPrincipalName, }) diff --git a/pro/auth/github.go b/pro/auth/github.go index 2c40837e4..c62fd1b73 100644 --- a/pro/auth/github.go +++ b/pro/auth/github.go @@ -67,10 +67,7 @@ func handleGithubCallback(w http.ResponseWriter, r *http.Request) { handleOauthNotConfigured(w) return } - if !isEmailAllowed(content.Login) { - handleOauthUserNotAllowedToSignUp(w) - return - } + var inviteExists bool // check if invite exists for User in, err := logic.GetUserInvite(content.Login) @@ -99,6 +96,10 @@ func handleGithubCallback(w http.ResponseWriter, r *http.Request) { logic.DeleteUserInvite(user.UserName) logic.DeletePendingUser(content.Login) } else { + if !isEmailAllowed(content.Login) { + handleOauthUserNotAllowedToSignUp(w) + return + } err = logic.InsertPendingUser(&models.User{ UserName: content.Login, }) diff --git a/pro/auth/google.go b/pro/auth/google.go index b5f6197c4..e4dbc2ae6 100644 --- a/pro/auth/google.go +++ b/pro/auth/google.go @@ -70,10 +70,7 @@ func handleGoogleCallback(w http.ResponseWriter, r *http.Request) { return } logger.Log(0, "CALLBACK ----> 1") - if !isEmailAllowed(content.Email) { - handleOauthUserNotAllowedToSignUp(w) - return - } + logger.Log(0, "CALLBACK ----> 2") var inviteExists bool // check if invite exists for User @@ -107,6 +104,10 @@ func handleGoogleCallback(w http.ResponseWriter, r *http.Request) { logic.DeleteUserInvite(user.UserName) logic.DeletePendingUser(content.Email) } else { + if !isEmailAllowed(content.Email) { + handleOauthUserNotAllowedToSignUp(w) + return + } err = logic.InsertPendingUser(&models.User{ UserName: content.Email, }) diff --git a/pro/auth/oidc.go b/pro/auth/oidc.go index 355b41103..72dc2b957 100644 --- a/pro/auth/oidc.go +++ b/pro/auth/oidc.go @@ -80,10 +80,7 @@ func handleOIDCCallback(w http.ResponseWriter, r *http.Request) { handleOauthNotConfigured(w) return } - if !isEmailAllowed(content.Email) { - handleOauthUserNotAllowedToSignUp(w) - return - } + var inviteExists bool // check if invite exists for User in, err := logic.GetUserInvite(content.Login) @@ -112,6 +109,10 @@ func handleOIDCCallback(w http.ResponseWriter, r *http.Request) { logic.DeleteUserInvite(user.UserName) logic.DeletePendingUser(content.Email) } else { + if !isEmailAllowed(content.Email) { + handleOauthUserNotAllowedToSignUp(w) + return + } err = logic.InsertPendingUser(&models.User{ UserName: content.Email, }) diff --git a/pro/controllers/users.go b/pro/controllers/users.go index 0c0fe4bda..d061aa72c 100644 --- a/pro/controllers/users.go +++ b/pro/controllers/users.go @@ -1237,8 +1237,9 @@ func approvePendingUser(w http.ResponseWriter, r *http.Request) { return } if err = logic.CreateUser(&models.User{ - UserName: user.UserName, - Password: newPass, + UserName: user.UserName, + Password: newPass, + PlatformRoleID: models.ServiceUser, }); err != nil { logic.ReturnErrorResponse(w, r, logic.FormatError(fmt.Errorf("failed to create user: %s", err), "internal")) return From e8e795ce3c83a864d188b01b5436f365e17a9eda Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Fri, 16 Aug 2024 15:12:20 +0530 Subject: [PATCH 134/139] fix duplicate hosts --- pro/logic/user_mgmt.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pro/logic/user_mgmt.go b/pro/logic/user_mgmt.go index 1bbd004da..ab951dc1b 100644 --- a/pro/logic/user_mgmt.go +++ b/pro/logic/user_mgmt.go @@ -707,9 +707,13 @@ func GetFilteredNodesByUserAccess(user models.User, nodes []models.Node) (filter networkNodes := logic.GetNetworkNodesMemory(nodes, userPermTemplate.NetworkID.String()) if userPermTemplate.FullAccess { for _, node := range networkNodes { + if _, ok := nodesMap[node.ID.String()]; ok { + continue + } nodesMap[node.ID.String()] = struct{}{} + filteredNodes = append(filteredNodes, node) } - filteredNodes = append(filteredNodes, networkNodes...) + continue } if rsrcPerms, ok := userPermTemplate.NetworkLevelAccess[models.RemoteAccessGwRsrc]; ok { From d4af26b289e0c93059099122a25e94ca2bfdd0aa Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Fri, 16 Aug 2024 15:32:23 +0530 Subject: [PATCH 135/139] handle invite link for saas tenants --- pro/controllers/users.go | 8 ++++++++ pro/license.go | 14 ++------------ pro/license_test.go | 10 ++++++---- pro/logic/security.go | 22 ++++++++++++++++++++++ pro/types.go | 10 ---------- 5 files changed, 38 insertions(+), 26 deletions(-) diff --git a/pro/controllers/users.go b/pro/controllers/users.go index d061aa72c..fd7e91740 100644 --- a/pro/controllers/users.go +++ b/pro/controllers/users.go @@ -217,6 +217,14 @@ func inviteUsers(w http.ResponseWriter, r *http.Request) { slog.Error("failed to parse to invite url", "error", err) return } + if servercfg.DeployedByOperator() { + u, err = url.Parse(fmt.Sprintf("%s/invite?tenant_id=%s&email=%s&invite_code=%s", + proLogic.GetAccountsHost(), url.QueryEscape(servercfg.GetNetmakerTenantID()), url.QueryEscape(invite.Email), url.QueryEscape(invite.InviteCode))) + if err != nil { + slog.Error("failed to parse to invite url", "error", err) + return + } + } invite.InviteURL = u.String() err = logic.InsertUserInvite(invite) if err != nil { diff --git a/pro/license.go b/pro/license.go index 633728d14..50825d8a3 100644 --- a/pro/license.go +++ b/pro/license.go @@ -20,6 +20,7 @@ import ( "github.com/gravitl/netmaker/logic" "github.com/gravitl/netmaker/models" "github.com/gravitl/netmaker/netclient/ncutils" + proLogic "github.com/gravitl/netmaker/pro/logic" "github.com/gravitl/netmaker/servercfg" ) @@ -206,7 +207,7 @@ func validateLicenseKey(encryptedData []byte, publicKey *[32]byte) ([]byte, bool req, err := http.NewRequest( http.MethodPost, - getAccountsHost()+"/api/v1/license/validate", + proLogic.GetAccountsHost()+"/api/v1/license/validate", bytes.NewReader(requestBody), ) if err != nil { @@ -255,17 +256,6 @@ func validateLicenseKey(encryptedData []byte, publicKey *[32]byte) ([]byte, bool return nil, false, err } -func getAccountsHost() string { - switch servercfg.GetEnvironment() { - case "dev": - return accountsHostDevelopment - case "staging": - return accountsHostStaging - default: - return accountsHostProduction - } -} - func cacheResponse(response []byte) error { lrc := licenseResponseCache{ Body: response, diff --git a/pro/license_test.go b/pro/license_test.go index 12d15d3f8..549bac0cd 100644 --- a/pro/license_test.go +++ b/pro/license_test.go @@ -4,11 +4,13 @@ package pro import ( - "github.com/gravitl/netmaker/config" "testing" + + "github.com/gravitl/netmaker/config" + proLogic "github.com/gravitl/netmaker/pro/logic" ) -func Test_getAccountsHost(t *testing.T) { +func Test_GetAccountsHost(t *testing.T) { tests := []struct { name string envK string @@ -69,8 +71,8 @@ func Test_getAccountsHost(t *testing.T) { if tt.envK != "" { t.Setenv(tt.envK, tt.envV) } - if got := getAccountsHost(); got != tt.want { - t.Errorf("getAccountsHost() = %v, want %v", got, tt.want) + if got := proLogic.GetAccountsHost(); got != tt.want { + t.Errorf("GetAccountsHost() = %v, want %v", got, tt.want) } }) } diff --git a/pro/logic/security.go b/pro/logic/security.go index 3225c269e..26d3d2fff 100644 --- a/pro/logic/security.go +++ b/pro/logic/security.go @@ -8,6 +8,17 @@ import ( "github.com/gravitl/netmaker/logger" "github.com/gravitl/netmaker/logic" "github.com/gravitl/netmaker/models" + "github.com/gravitl/netmaker/servercfg" +) + +// constants for accounts api hosts +const ( + // accountsHostDevelopment is the accounts api host for development environment + accountsHostDevelopment = "https://api.dev.accounts.netmaker.io" + // accountsHostStaging is the accounts api host for staging environment + accountsHostStaging = "https://api.staging.accounts.netmaker.io" + // accountsHostProduction is the accounts api host for production environment + accountsHostProduction = "https://api.accounts.netmaker.io" ) func NetworkPermissionsCheck(username string, r *http.Request) error { @@ -193,3 +204,14 @@ func checkPermissionScopeWithReqMethod(scope models.RsrcPermissionScope, reqmeth } return errors.New("operation not permitted") } + +func GetAccountsHost() string { + switch servercfg.GetEnvironment() { + case "dev": + return accountsHostDevelopment + case "staging": + return accountsHostStaging + default: + return accountsHostProduction + } +} diff --git a/pro/types.go b/pro/types.go index b4f1fe15b..ae31cafe0 100644 --- a/pro/types.go +++ b/pro/types.go @@ -7,16 +7,6 @@ import ( "fmt" ) -// constants for accounts api hosts -const ( - // accountsHostDevelopment is the accounts api host for development environment - accountsHostDevelopment = "https://api.dev.accounts.netmaker.io" - // accountsHostStaging is the accounts api host for staging environment - accountsHostStaging = "https://api.staging.accounts.netmaker.io" - // accountsHostProduction is the accounts api host for production environment - accountsHostProduction = "https://api.accounts.netmaker.io" -) - const ( license_cache_key = "license_response_cache" license_validation_err_msg = "invalid license" From bc42f1ebf068f09180e72bffa1f4579f0cfb1451 Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Tue, 20 Aug 2024 17:53:56 +0530 Subject: [PATCH 136/139] fix saas frontend url --- pro/controllers/users.go | 2 +- pro/logic/security.go | 21 +++++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/pro/controllers/users.go b/pro/controllers/users.go index fd7e91740..90e4ee872 100644 --- a/pro/controllers/users.go +++ b/pro/controllers/users.go @@ -219,7 +219,7 @@ func inviteUsers(w http.ResponseWriter, r *http.Request) { } if servercfg.DeployedByOperator() { u, err = url.Parse(fmt.Sprintf("%s/invite?tenant_id=%s&email=%s&invite_code=%s", - proLogic.GetAccountsHost(), url.QueryEscape(servercfg.GetNetmakerTenantID()), url.QueryEscape(invite.Email), url.QueryEscape(invite.InviteCode))) + proLogic.GetAccountsUIHost(), url.QueryEscape(servercfg.GetNetmakerTenantID()), url.QueryEscape(invite.Email), url.QueryEscape(invite.InviteCode))) if err != nil { slog.Error("failed to parse to invite url", "error", err) return diff --git a/pro/logic/security.go b/pro/logic/security.go index 26d3d2fff..dc4832024 100644 --- a/pro/logic/security.go +++ b/pro/logic/security.go @@ -21,6 +21,16 @@ const ( accountsHostProduction = "https://api.accounts.netmaker.io" ) +// constants for accounts UI hosts +const ( + // accountsUIHostDevelopment is the accounts UI host for development environment + accountsUIHostDevelopment = "https://account.dev.netmaker.io" + // accountsUIHostStaging is the accounts UI host for staging environment + accountsUIHostStaging = "https://account.staging.netmaker.io" + // accountsUIHostProduction is the accounts UI host for production environment + accountsUIHostProduction = "https://account.netmaker.io" +) + func NetworkPermissionsCheck(username string, r *http.Request) error { // at this point global checks should be completed user, err := logic.GetUser(username) @@ -215,3 +225,14 @@ func GetAccountsHost() string { return accountsHostProduction } } + +func GetAccountsUIHost() string { + switch servercfg.GetEnvironment() { + case "dev": + return accountsUIHostDevelopment + case "staging": + return accountsUIHostStaging + default: + return accountsUIHostProduction + } +} From 021cebdd09d2755c06b78218f4f580bf6216786d Mon Sep 17 00:00:00 2001 From: Sayan Mallick Date: Mon, 2 Sep 2024 17:13:33 +0530 Subject: [PATCH 137/139] Updated DeleteDroplet workflow to use new token --- .github/workflows/deletedroplets.yml | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/.github/workflows/deletedroplets.yml b/.github/workflows/deletedroplets.yml index 63f258244..11577278a 100644 --- a/.github/workflows/deletedroplets.yml +++ b/.github/workflows/deletedroplets.yml @@ -37,7 +37,7 @@ jobs: - name: delete droplets if: success() || failure() run: | - sleep 5m + sleep 1m response=$(curl -X DELETE \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $DIGITALOCEAN_TOKEN" \ @@ -56,8 +56,9 @@ jobs: echo "Failed to delete droplets. Status code: $status_code" exit 1 fi + sleep 1m env: - DIGITALOCEAN_TOKEN: ${{ secrets.DIGITALOCEAN_TOKEN }} + DIGITALOCEAN_TOKEN: ${{ secrets.DO_TEST_TOKEN }} TAG: ${{ github.event.workflow_run.id }}-${{ github.event.workflow_run.run_attempt }} - name: mark server as available if: success() || failure() @@ -108,13 +109,28 @@ jobs: - name: delete droplets if: success() || failure() run: | - sleep 3h - curl -X DELETE \ + sleep 1m + response=$(curl -X DELETE \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $DIGITALOCEAN_TOKEN" \ - "https://api.digitalocean.com/v2/droplets?tag_name=$TAG" + -w "\n%{http_code}" \ + "https://api.digitalocean.com/v2/droplets?tag_name=$TAG") + + status_code=$(echo "$response" | tail -n1) + body=$(echo "$response" | sed '$d') + + echo "Response body: $body" + echo "Status code: $status_code" + + if [ "$status_code" -eq 204 ]; then + echo "Droplets deleted successfully" + else + echo "Failed to delete droplets. Status code: $status_code" + exit 1 + fi + sleep 1m env: - DIGITALOCEAN_TOKEN: ${{ secrets.DIGITALOCEAN_TOKEN }} + DIGITALOCEAN_TOKEN: ${{ secrets.DO_TEST_TOKEN }} TAG: ${{ github.event.workflow_run.id }}-${{ github.event.workflow_run.run_attempt }} - name: mark server as available if: success() || failure() From 1bb349206091e849acc2cee01aab4e5644f4aca9 Mon Sep 17 00:00:00 2001 From: Sayan Mallick Date: Mon, 2 Sep 2024 19:27:59 +0530 Subject: [PATCH 138/139] Updated Deploy and Test Branch to have a manual trigger --- .github/workflows/branchtest.yml | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/.github/workflows/branchtest.yml b/.github/workflows/branchtest.yml index d2b22a051..4dafd9d58 100644 --- a/.github/workflows/branchtest.yml +++ b/.github/workflows/branchtest.yml @@ -2,6 +2,11 @@ name: Deploy and Test Branch on: workflow_dispatch: + inputs: + branches: + description: 'Branch to deploy and test' + required: true + default: 'develop' pull_request: types: [opened, synchronize, reopened] branches: [develop] @@ -28,7 +33,7 @@ jobs: uses: actions/checkout@v4 with: repository: gravitl/netclient - ref: develop + ref: ${{ github.event_name == 'workflow_dispatch' && inputs.branch || 'develop' }} - name: check if branch exists id: getbranch run: | @@ -45,6 +50,6 @@ jobs: needs: [getbranch, skip-check] with: netclientbranch: ${{ needs.getbranch.outputs.netclientbranch }} - netmakerbranch: ${{ github.head_ref }} + netmakerbranch: ${{ github.event_name == 'workflow_dispatch' && inputs.branch || github.head_ref }} tag: ${{ github.run_id }}-${{ github.run_attempt }} secrets: inherit From 30309a4f9a35c54044faef0ca3d3d7736bac38e5 Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Wed, 11 Sep 2024 15:43:58 +0400 Subject: [PATCH 139/139] add email validation --- pro/controllers/users.go | 4 ++++ pro/email/email.go | 6 ++++++ 2 files changed, 10 insertions(+) diff --git a/pro/controllers/users.go b/pro/controllers/users.go index 80fab1b9f..c3bd9df40 100644 --- a/pro/controllers/users.go +++ b/pro/controllers/users.go @@ -206,6 +206,10 @@ func inviteUsers(w http.ResponseWriter, r *http.Request) { } for _, inviteeEmail := range inviteReq.UserEmails { // check if user with email exists, then ignore + if !email.IsValid(inviteeEmail) { + logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("invalid email "+inviteeEmail), "badrequest")) + return + } _, err := logic.GetUser(inviteeEmail) if err == nil { // user exists already, so ignore diff --git a/pro/email/email.go b/pro/email/email.go index f6a3e80a8..7411ae3e5 100644 --- a/pro/email/email.go +++ b/pro/email/email.go @@ -2,6 +2,7 @@ package email import ( "context" + "regexp" "github.com/gravitl/netmaker/servercfg" ) @@ -52,3 +53,8 @@ type Notification struct { func GetClient() (e EmailSender) { return client } + +func IsValid(email string) bool { + emailRegex := regexp.MustCompile(`^[a-z0-9._%+\-]+@[a-z0-9.\-]+\.[a-z]{2,4}$`) + return emailRegex.MatchString(email) +}