From a7a431b36b19314641cceec2bf77270f9f324dd3 Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Mon, 29 Jul 2024 16:41:57 +0530 Subject: [PATCH] move user roles and groups --- controllers/user.go | 721 +-------------------------------- logic/nodes.go | 68 ---- logic/security.go | 191 +-------- logic/user_mgmt.go | 483 +--------------------- 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, 1490 insertions(+), 1447 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 266608ef..86c2172b 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 05ba3989..62f49557 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 9600f298..b16e808c 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 70d93a6d..262275f6 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 GetFilteredNodesByUserAccess = func(user models.User, nodes []models.Node) (filteredNodes []models.Node) { + return } -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") - } +var CreateRole = func(r models.UserRolePermissionTemplate) error { 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") - } - 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) -} +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{}{} + } + if len(uniqueGroupsPlatformRole) > 1 { - } - } - return database.DeleteRecord(database.USER_PERMISSIONS_TABLE_NAME, rid.String()) -} - -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 d0a46851..96a78215 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 193a321b..c8dc851a 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 29565350..8caf130d 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 393e456c..4858ba18 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 d21a1446..e83a487b 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 ffd28c6c..71e748b3 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 00000000..e025580e --- /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 00000000..aca027b3 --- /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 +}