mirror of
https://github.com/gravitl/netmaker.git
synced 2025-09-03 19:54:22 +08:00
NET-1227: User Mgmt V2 (#3055)
* user mgmt models * define user roles * define models for new user mgmt and groups * oauth debug log * initialize user role after db conn * print oauth token in debug log * user roles CRUD apis * user groups CRUD Apis * additional api checks * add additional scopes * add additional scopes url * add additional scopes url * rm additional scopes url * setup middlleware permission checks * integrate permission check into middleware * integrate permission check into middleware * check for headers for subjects * refactor user role models * refactor user groups models * add new user to pending user via RAC login * untracked * allow multiple groups for an user * change json tag * add debug headers * refer network controls form roles, add debug headers * refer network controls form roles, add debug headers * replace auth checks, add network id to role model * nodes handler * migration funcs * invoke sync users migration func * add debug logs * comment middleware * fix get all nodes api * add debug logs * fix middleware error nil check * add new func to get username from jwt * fix jwt parsing * abort on error * allow multiple network roles * allow multiple network roles * add migration func * return err if jwt parsing fails * set global check to true when accessing user apis * set netid for acls api calls * set netid for acls api calls * update role and groups routes * add validation checks * add invite flow apis and magic links * add invited user via oauth signup automatically * create invited user on oauth signup, with groups in the invite * add group validation for user invite * update create user handler with new role mgmt * add validation checks * create user invites tables * add error logging for email invite * fix invite singup url * debug log * get query params from url * get query params from url * add query escape * debug log * debug log * fix user signup via invite api * set admin field for backward compatbility * use new role id for user apis * deprecate use of old admin fields * deprecate usage of old user fields * add user role as service user if empty * setup email sender * delete invite after user singup * add plaform user role * redirect on invite verification link * fix invite redirect * temporary redirect * fix invite redirect * point invite link to frontend * fix query params lookup * add resend support, configure email interface types * fix groups and user creation * validate user groups, add check for metrics api in middleware * add invite url to invite model * migrate rac apis to new user mgmt * handle network nodes * add platform user to default role * fix user role migration * add default on rag creation and cleanup after deletion * fix rac apis * change to invite code param * filter nodes and hosts based on user network access * extend create user group req to accomodate users * filter network based on user access * format oauth error * move user roles and groups * fix get user v1 api * move user mgmt func to pro * add user auth type to user model * fix roles init * remove platform role from group object * list only platform roles * add network roles to invite req * create default groups and roles * fix middleware for global access * create default role * fix nodes filter with global network roles * block selfupdate of groups and network roles * delete netID if net roles are empty * validate user roles nd groups on update * set extclient permission scope when rag vpn access is set * allow deletion of roles and groups * replace _ with - in role naming convention * fix failover middleware mgmt * format oauth templates * fetch route temaplate * return err if user wrong login type * check user groups on rac apis * fix rac apis * fix resp msg * add validation checks for admin invite * return oauth type * format group err msg * fix html tag * clean up default groups * create default rag role * add UI name to roles * remove default net group from user when deleted * reorder migration funcs * fix duplicacy of hosts * check old field for migration * from pro to ce make all secondary users admins * from pro to ce make all secondary users admins * revert: from pro to ce make all secondary users admins * make sure downgrades work * fix pending users approval * fix duplicate hosts * fix duplicate hosts entries * fix cache reference issue * feat: configure FRONTEND_URL during installation * disable user vpn access when network roles are modified * rm vpn acces when roles or groups are deleted * add http to frontend url * revert crypto version * downgrade crytpo version * add platform id check on user invites --------- Co-authored-by: the_aceix <aceixsmartx@gmail.com>
This commit is contained in:
parent
fb40cd7d56
commit
2e8d95e80e
49 changed files with 4279 additions and 860 deletions
90
auth/auth.go
90
auth/auth.go
|
@ -1,15 +1,8 @@
|
|||
package auth
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/gravitl/netmaker/logger"
|
||||
"github.com/gravitl/netmaker/logic"
|
||||
"github.com/gravitl/netmaker/models"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"golang.org/x/exp/slog"
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
|
@ -22,88 +15,11 @@ var (
|
|||
auth_provider *oauth2.Config
|
||||
)
|
||||
|
||||
// IsOauthUser - returns
|
||||
func IsOauthUser(user *models.User) error {
|
||||
var currentValue, err = FetchPassValue("")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var bCryptErr = bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(currentValue))
|
||||
return bCryptErr
|
||||
}
|
||||
|
||||
func FetchPassValue(newValue string) (string, error) {
|
||||
|
||||
type valueHolder struct {
|
||||
Value string `json:"value" bson:"value"`
|
||||
}
|
||||
newValueHolder := valueHolder{}
|
||||
var currentValue, err = logic.FetchAuthSecret()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
var unmarshErr = json.Unmarshal([]byte(currentValue), &newValueHolder)
|
||||
if unmarshErr != nil {
|
||||
return "", unmarshErr
|
||||
}
|
||||
|
||||
var b64CurrentValue, b64Err = base64.StdEncoding.DecodeString(newValueHolder.Value)
|
||||
if b64Err != nil {
|
||||
logger.Log(0, "could not decode pass")
|
||||
return "", nil
|
||||
}
|
||||
return string(b64CurrentValue), nil
|
||||
}
|
||||
|
||||
// == private ==
|
||||
|
||||
func addUser(email string) error {
|
||||
var hasSuperAdmin, err = logic.HasSuperAdmin()
|
||||
if err != nil {
|
||||
slog.Error("error checking for existence of admin user during OAuth login for", "email", email, "error", err)
|
||||
return err
|
||||
} // generate random password to adapt to current model
|
||||
var newPass, fetchErr = FetchPassValue("")
|
||||
if fetchErr != nil {
|
||||
slog.Error("failed to get password", "error", fetchErr.Error())
|
||||
return fetchErr
|
||||
}
|
||||
var newUser = models.User{
|
||||
UserName: email,
|
||||
Password: newPass,
|
||||
}
|
||||
if !hasSuperAdmin { // must be first attempt, create a superadmin
|
||||
logger.Log(0, "creating superadmin")
|
||||
if err = logic.CreateSuperAdmin(&newUser); err != nil {
|
||||
slog.Error("error creating super admin from user", "email", email, "error", err)
|
||||
} else {
|
||||
slog.Info("superadmin created from user", "email", email)
|
||||
}
|
||||
} else { // otherwise add to db as admin..?
|
||||
// TODO: add ability to add users with preemptive permissions
|
||||
newUser.IsAdmin = false
|
||||
if err = logic.CreateUser(&newUser); err != nil {
|
||||
logger.Log(0, "error creating user,", email, "; user not added", "error", err.Error())
|
||||
} else {
|
||||
logger.Log(0, "user created from ", email)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func isUserIsAllowed(username, network string, shouldAddUser bool) (*models.User, error) {
|
||||
func isUserIsAllowed(username, network string) (*models.User, error) {
|
||||
|
||||
user, err := logic.GetUser(username)
|
||||
if err != nil && shouldAddUser { // user must not exist, so try to make one
|
||||
if err = addUser(username); err != nil {
|
||||
logger.Log(0, "failed to add user", username, "during a node SSO network join on network", network)
|
||||
// response := returnErrTemplate(user.UserName, "failed to add user", state, reqKeyIf)
|
||||
// w.WriteHeader(http.StatusInternalServerError)
|
||||
// w.Write(response)
|
||||
return nil, fmt.Errorf("failed to add user to system")
|
||||
}
|
||||
logger.Log(0, "user", username, "was added during a node SSO network join on network", network)
|
||||
user, _ = logic.GetUser(username)
|
||||
if err != nil { // user must not exist, so try to make one
|
||||
return &models.User{}, err
|
||||
}
|
||||
|
||||
return user, nil
|
||||
|
|
|
@ -85,24 +85,24 @@ func SessionHandler(conn *websocket.Conn) {
|
|||
return
|
||||
}
|
||||
req.Pass = req.Host.ID.String()
|
||||
user, err := logic.GetUser(req.User)
|
||||
if err != nil {
|
||||
logger.Log(0, "failed to get user", req.User, "from database")
|
||||
err = conn.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""))
|
||||
if err != nil {
|
||||
logger.Log(0, "error during message writing:", err.Error())
|
||||
}
|
||||
return
|
||||
}
|
||||
if !user.IsAdmin && !user.IsSuperAdmin {
|
||||
logger.Log(0, "user", req.User, "is neither an admin or superadmin. denying registeration")
|
||||
conn.WriteMessage(messageType, []byte("cannot register with a non-admin or non-superadmin"))
|
||||
err = conn.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""))
|
||||
if err != nil {
|
||||
logger.Log(0, "error during message writing:", err.Error())
|
||||
}
|
||||
return
|
||||
}
|
||||
// user, err := logic.GetUser(req.User)
|
||||
// if err != nil {
|
||||
// logger.Log(0, "failed to get user", req.User, "from database")
|
||||
// err = conn.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""))
|
||||
// if err != nil {
|
||||
// logger.Log(0, "error during message writing:", err.Error())
|
||||
// }
|
||||
// return
|
||||
// }
|
||||
// if !user.IsAdmin && !user.IsSuperAdmin {
|
||||
// logger.Log(0, "user", req.User, "is neither an admin or superadmin. denying registeration")
|
||||
// conn.WriteMessage(messageType, []byte("cannot register with a non-admin or non-superadmin"))
|
||||
// err = conn.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""))
|
||||
// if err != nil {
|
||||
// logger.Log(0, "error during message writing:", err.Error())
|
||||
// }
|
||||
// return
|
||||
// }
|
||||
|
||||
if err = netcache.Set(stateStr, req); err != nil { // give the user's host access in the DB
|
||||
logger.Log(0, "machine failed to complete join on network,", registerMessage.Network, "-", err.Error())
|
||||
|
@ -197,7 +197,7 @@ func SessionHandler(conn *websocket.Conn) {
|
|||
for _, newNet := range currentNetworks {
|
||||
if !logic.StringSliceContains(hostNets, newNet) {
|
||||
if len(result.User) > 0 {
|
||||
_, err := isUserIsAllowed(result.User, newNet, false)
|
||||
_, err := isUserIsAllowed(result.User, newNet)
|
||||
if err != nil {
|
||||
logger.Log(0, "unauthorized user", result.User, "attempted to register to network", newNet)
|
||||
handleHostRegErr(conn, err)
|
||||
|
|
|
@ -94,6 +94,11 @@ type ServerConfig struct {
|
|||
CacheEnabled string `yaml:"caching_enabled"`
|
||||
EndpointDetection bool `json:"endpoint_detection"`
|
||||
AllowedEmailDomains string `yaml:"allowed_email_domains"`
|
||||
EmailSenderAddr string `json:"email_sender_addr"`
|
||||
EmailSenderAuth string `json:"email_sender_auth"`
|
||||
EmailSenderType string `json:"email_sender_type"`
|
||||
SmtpHost string `json:"smtp_host"`
|
||||
SmtpPort int `json:"smtp_port"`
|
||||
MetricInterval string `yaml:"metric_interval"`
|
||||
}
|
||||
|
||||
|
|
|
@ -17,7 +17,9 @@ import (
|
|||
)
|
||||
|
||||
// HttpMiddlewares - middleware functions for REST interactions
|
||||
var HttpMiddlewares []mux.MiddlewareFunc
|
||||
var HttpMiddlewares = []mux.MiddlewareFunc{
|
||||
userMiddleWare,
|
||||
}
|
||||
|
||||
// HttpHandlers - handler functions for REST interactions
|
||||
var HttpHandlers = []interface{}{
|
||||
|
@ -39,7 +41,6 @@ func HandleRESTRequests(wg *sync.WaitGroup, ctx context.Context) {
|
|||
defer wg.Done()
|
||||
|
||||
r := mux.NewRouter()
|
||||
|
||||
// Currently allowed dev origin is all. Should change in prod
|
||||
// should consider analyzing the allowed methods further
|
||||
headersOk := handlers.AllowedHeaders(
|
||||
|
|
|
@ -128,18 +128,6 @@ func getExtClient(w http.ResponseWriter, r *http.Request) {
|
|||
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
|
||||
return
|
||||
}
|
||||
if !logic.IsUserAllowedAccessToExtClient(r.Header.Get("user"), client) {
|
||||
// check if user has access to extclient
|
||||
slog.Error("failed to get extclient", "network", network, "clientID",
|
||||
clientid, "error", errors.New("access is denied"))
|
||||
logic.ReturnErrorResponse(
|
||||
w,
|
||||
r,
|
||||
logic.FormatError(errors.New("access is denied"), "forbidden"),
|
||||
)
|
||||
return
|
||||
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
json.NewEncoder(w).Encode(client)
|
||||
|
@ -170,16 +158,6 @@ func getExtClientConf(w http.ResponseWriter, r *http.Request) {
|
|||
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
|
||||
return
|
||||
}
|
||||
if !logic.IsUserAllowedAccessToExtClient(r.Header.Get("user"), client) {
|
||||
slog.Error("failed to get extclient", "network", networkid, "clientID",
|
||||
clientid, "error", errors.New("access is denied"))
|
||||
logic.ReturnErrorResponse(
|
||||
w,
|
||||
r,
|
||||
logic.FormatError(errors.New("access is denied"), "forbidden"),
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
gwnode, err := logic.GetNodeByID(client.IngressGatewayID)
|
||||
if err != nil {
|
||||
|
@ -445,12 +423,6 @@ func createExtClient(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
userName = caller.UserName
|
||||
if _, ok := caller.RemoteGwIDs[nodeid]; (!caller.IsAdmin && !caller.IsSuperAdmin) && !ok {
|
||||
err = errors.New("permission denied")
|
||||
slog.Error("failed to create extclient", "error", err)
|
||||
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "forbidden"))
|
||||
return
|
||||
}
|
||||
// check if user has a config already for remote access client
|
||||
extclients, err := logic.GetNetworkExtClients(node.Network)
|
||||
if err != nil {
|
||||
|
@ -567,7 +539,6 @@ func updateExtClient(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
clientid := params["clientid"]
|
||||
network := params["network"]
|
||||
oldExtClient, err := logic.GetExtClientByName(clientid)
|
||||
if err != nil {
|
||||
slog.Error(
|
||||
|
@ -582,18 +553,6 @@ func updateExtClient(w http.ResponseWriter, r *http.Request) {
|
|||
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
|
||||
return
|
||||
}
|
||||
if !logic.IsUserAllowedAccessToExtClient(r.Header.Get("user"), oldExtClient) {
|
||||
// check if user has access to extclient
|
||||
slog.Error("failed to get extclient", "network", network, "clientID",
|
||||
clientid, "error", errors.New("access is denied"))
|
||||
logic.ReturnErrorResponse(
|
||||
w,
|
||||
r,
|
||||
logic.FormatError(errors.New("access is denied"), "forbidden"),
|
||||
)
|
||||
return
|
||||
|
||||
}
|
||||
if oldExtClient.ClientID == update.ClientID {
|
||||
if err := validateCustomExtClient(&update, false); err != nil {
|
||||
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
|
||||
|
@ -729,16 +688,6 @@ func deleteExtClient(w http.ResponseWriter, r *http.Request) {
|
|||
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
|
||||
return
|
||||
}
|
||||
if !logic.IsUserAllowedAccessToExtClient(r.Header.Get("user"), extclient) {
|
||||
slog.Error("user not allowed to delete", "network", network, "clientID",
|
||||
clientid, "error", errors.New("access is denied"))
|
||||
logic.ReturnErrorResponse(
|
||||
w,
|
||||
r,
|
||||
logic.FormatError(errors.New("access is denied"), "forbidden"),
|
||||
)
|
||||
return
|
||||
}
|
||||
ingressnode, err := logic.GetNodeByID(extclient.IngressGatewayID)
|
||||
if err != nil {
|
||||
logger.Log(
|
||||
|
|
|
@ -79,12 +79,53 @@ func upgradeHost(w http.ResponseWriter, r *http.Request) {
|
|||
// @Success 200 {array} models.ApiHost
|
||||
// @Failure 500 {object} models.ErrorResponse
|
||||
func getHosts(w http.ResponseWriter, r *http.Request) {
|
||||
currentHosts, err := logic.GetAllHosts()
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
currentHosts := []models.Host{}
|
||||
username := r.Header.Get("user")
|
||||
user, err := logic.GetUser(username)
|
||||
if err != nil {
|
||||
logger.Log(0, r.Header.Get("user"), "failed to fetch hosts: ", err.Error())
|
||||
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
|
||||
return
|
||||
}
|
||||
userPlatformRole, err := logic.GetRole(user.PlatformRoleID)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
respHostsMap := make(map[string]struct{})
|
||||
if !userPlatformRole.FullAccess {
|
||||
nodes, err := logic.GetAllNodes()
|
||||
if err != nil {
|
||||
logger.Log(0, "error fetching all nodes info: ", err.Error())
|
||||
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
|
||||
return
|
||||
}
|
||||
filteredNodes := logic.GetFilteredNodesByUserAccess(*user, nodes)
|
||||
if len(filteredNodes) > 0 {
|
||||
currentHostsMap, err := logic.GetHostsMap()
|
||||
if err != nil {
|
||||
logger.Log(0, r.Header.Get("user"), "failed to fetch hosts: ", err.Error())
|
||||
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
|
||||
return
|
||||
}
|
||||
for _, node := range filteredNodes {
|
||||
if _, ok := respHostsMap[node.HostID.String()]; ok {
|
||||
continue
|
||||
}
|
||||
if host, ok := currentHostsMap[node.HostID.String()]; ok {
|
||||
currentHosts = append(currentHosts, host)
|
||||
respHostsMap[host.ID.String()] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
} else {
|
||||
currentHosts, err = logic.GetAllHosts()
|
||||
if err != nil {
|
||||
logger.Log(0, r.Header.Get("user"), "failed to fetch hosts: ", err.Error())
|
||||
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
apiHosts := logic.GetAllHostsAPI(currentHosts[:])
|
||||
logger.Log(2, r.Header.Get("user"), "fetched all hosts")
|
||||
logic.SortApiHosts(apiHosts[:])
|
||||
|
@ -194,6 +235,19 @@ func updateHost(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
newHost := newHostData.ConvertAPIHostToNMHost(currHost)
|
||||
|
||||
if newHost.Name != currHost.Name {
|
||||
// update any rag role ids
|
||||
for _, nodeID := range newHost.Nodes {
|
||||
node, err := logic.GetNodeByID(nodeID)
|
||||
if err == nil && node.IsIngressGateway {
|
||||
role, err := logic.GetRole(models.GetRAGRoleID(node.Network, currHost.ID.String()))
|
||||
if err == nil {
|
||||
role.UiName = models.GetRAGRoleName(node.Network, newHost.Name)
|
||||
logic.UpdateRole(role)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
logic.UpdateHost(newHost, currHost) // update the in memory struct values
|
||||
if err = logic.UpsertHost(newHost); err != nil {
|
||||
logger.Log(0, r.Header.Get("user"), "failed to update a host:", err.Error())
|
||||
|
|
105
controllers/middleware.go
Normal file
105
controllers/middleware.go
Normal file
|
@ -0,0 +1,105 @@
|
|||
package controller
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/gravitl/netmaker/logger"
|
||||
"github.com/gravitl/netmaker/logic"
|
||||
"github.com/gravitl/netmaker/models"
|
||||
)
|
||||
|
||||
func userMiddleWare(handler http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
var params = mux.Vars(r)
|
||||
route, err := mux.CurrentRoute(r).GetPathTemplate()
|
||||
if err != nil {
|
||||
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
|
||||
return
|
||||
}
|
||||
r.Header.Set("IS_GLOBAL_ACCESS", "no")
|
||||
r.Header.Set("TARGET_RSRC", "")
|
||||
r.Header.Set("RSRC_TYPE", "")
|
||||
r.Header.Set("TARGET_RSRC_ID", "")
|
||||
r.Header.Set("NET_ID", params["network"])
|
||||
if strings.Contains(route, "hosts") || strings.Contains(route, "nodes") {
|
||||
r.Header.Set("TARGET_RSRC", models.HostRsrc.String())
|
||||
}
|
||||
if strings.Contains(route, "dns") {
|
||||
r.Header.Set("TARGET_RSRC", models.DnsRsrc.String())
|
||||
}
|
||||
if strings.Contains(route, "users") {
|
||||
r.Header.Set("TARGET_RSRC", models.UserRsrc.String())
|
||||
}
|
||||
if strings.Contains(route, "ingress") {
|
||||
r.Header.Set("TARGET_RSRC", models.RemoteAccessGwRsrc.String())
|
||||
}
|
||||
if strings.Contains(route, "createrelay") || strings.Contains(route, "deleterelay") {
|
||||
r.Header.Set("TARGET_RSRC", models.RelayRsrc.String())
|
||||
}
|
||||
|
||||
if strings.Contains(route, "gateway") {
|
||||
r.Header.Set("TARGET_RSRC", models.EgressGwRsrc.String())
|
||||
}
|
||||
if strings.Contains(route, "networks") {
|
||||
r.Header.Set("TARGET_RSRC", models.NetworkRsrc.String())
|
||||
}
|
||||
if strings.Contains(route, "acls") {
|
||||
r.Header.Set("TARGET_RSRC", models.AclRsrc.String())
|
||||
}
|
||||
if strings.Contains(route, "extclients") {
|
||||
r.Header.Set("TARGET_RSRC", models.ExtClientsRsrc.String())
|
||||
}
|
||||
if strings.Contains(route, "enrollment-keys") {
|
||||
r.Header.Set("TARGET_RSRC", models.EnrollmentKeysRsrc.String())
|
||||
}
|
||||
if strings.Contains(route, "metrics") {
|
||||
r.Header.Set("TARGET_RSRC", models.MetricRsrc.String())
|
||||
}
|
||||
if keyID, ok := params["keyID"]; ok {
|
||||
r.Header.Set("TARGET_RSRC_ID", keyID)
|
||||
}
|
||||
if nodeID, ok := params["nodeid"]; ok && r.Header.Get("TARGET_RSRC") != models.ExtClientsRsrc.String() {
|
||||
r.Header.Set("TARGET_RSRC_ID", nodeID)
|
||||
}
|
||||
if strings.Contains(route, "failover") {
|
||||
r.Header.Set("TARGET_RSRC", models.FailOverRsrc.String())
|
||||
nodeID := r.Header.Get("TARGET_RSRC_ID")
|
||||
node, _ := logic.GetNodeByID(nodeID)
|
||||
r.Header.Set("NET_ID", node.Network)
|
||||
|
||||
}
|
||||
if hostID, ok := params["hostid"]; ok {
|
||||
r.Header.Set("TARGET_RSRC_ID", hostID)
|
||||
}
|
||||
if clientID, ok := params["clientid"]; ok {
|
||||
r.Header.Set("TARGET_RSRC_ID", clientID)
|
||||
}
|
||||
if netID, ok := params["networkname"]; ok {
|
||||
if !strings.Contains(route, "acls") {
|
||||
r.Header.Set("TARGET_RSRC_ID", netID)
|
||||
}
|
||||
r.Header.Set("NET_ID", params["networkname"])
|
||||
}
|
||||
|
||||
if userID, ok := params["username"]; ok {
|
||||
r.Header.Set("TARGET_RSRC_ID", userID)
|
||||
} else {
|
||||
username, _ := url.QueryUnescape(r.URL.Query().Get("username"))
|
||||
if username != "" {
|
||||
r.Header.Set("TARGET_RSRC_ID", username)
|
||||
}
|
||||
}
|
||||
if r.Header.Get("NET_ID") == "" && (r.Header.Get("TARGET_RSRC_ID") == "" ||
|
||||
r.Header.Get("TARGET_RSRC") == models.EnrollmentKeysRsrc.String() ||
|
||||
r.Header.Get("TARGET_RSRC") == models.UserRsrc.String()) {
|
||||
r.Header.Set("IS_GLOBAL_ACCESS", "yes")
|
||||
}
|
||||
|
||||
r.Header.Set("RSRC_TYPE", r.Header.Get("TARGET_RSRC"))
|
||||
logger.Log(0, "URL ------> ", route)
|
||||
handler.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
|
@ -58,7 +58,13 @@ func getNetworks(w http.ResponseWriter, r *http.Request) {
|
|||
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
|
||||
return
|
||||
}
|
||||
|
||||
username := r.Header.Get("user")
|
||||
user, err := logic.GetUser(username)
|
||||
if err != nil {
|
||||
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
|
||||
return
|
||||
}
|
||||
allnetworks = logic.FilterNetworksByRole(allnetworks, *user)
|
||||
logger.Log(2, r.Header.Get("user"), "fetched networks.")
|
||||
logic.SortNetworks(allnetworks[:])
|
||||
w.WriteHeader(http.StatusOK)
|
||||
|
@ -402,6 +408,7 @@ func deleteNetwork(w http.ResponseWriter, r *http.Request) {
|
|||
logic.ReturnErrorResponse(w, r, logic.FormatError(err, errtype))
|
||||
return
|
||||
}
|
||||
go logic.DeleteNetworkRoles(network)
|
||||
//delete network from allocated ip map
|
||||
go logic.RemoveNetworkFromAllocatedIpMap(network)
|
||||
|
||||
|
@ -476,6 +483,7 @@ func createNetwork(w http.ResponseWriter, r *http.Request) {
|
|||
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
|
||||
return
|
||||
}
|
||||
logic.CreateDefaultNetworkRolesAndGroups(models.NetworkID(network.NetID))
|
||||
|
||||
//add new network to allocated ip map
|
||||
go logic.AddNetworkToAllocatedIpMap(network.NetID)
|
||||
|
|
|
@ -26,9 +26,9 @@ func TestMain(m *testing.M) {
|
|||
database.InitializeDatabase()
|
||||
defer database.CloseDB()
|
||||
logic.CreateSuperAdmin(&models.User{
|
||||
UserName: "admin",
|
||||
Password: "password",
|
||||
IsAdmin: true,
|
||||
UserName: "admin",
|
||||
Password: "password",
|
||||
PlatformRoleID: models.SuperAdminRole,
|
||||
})
|
||||
peerUpdate := make(chan *models.Node)
|
||||
go logic.ManageZombies(context.Background(), peerUpdate)
|
||||
|
|
|
@ -21,24 +21,15 @@ var hostIDHeader = "host-id"
|
|||
|
||||
func nodeHandlers(r *mux.Router) {
|
||||
|
||||
r.HandleFunc("/api/nodes", Authorize(false, false, "user", http.HandlerFunc(getAllNodes))).
|
||||
Methods(http.MethodGet)
|
||||
r.HandleFunc("/api/nodes/{network}", Authorize(false, true, "network", http.HandlerFunc(getNetworkNodes))).
|
||||
Methods(http.MethodGet)
|
||||
r.HandleFunc("/api/nodes/{network}/{nodeid}", Authorize(true, true, "node", http.HandlerFunc(getNode))).
|
||||
Methods(http.MethodGet)
|
||||
r.HandleFunc("/api/nodes/{network}/{nodeid}", logic.SecurityCheck(true, http.HandlerFunc(updateNode))).
|
||||
Methods(http.MethodPut)
|
||||
r.HandleFunc("/api/nodes/{network}/{nodeid}", Authorize(true, true, "node", http.HandlerFunc(deleteNode))).
|
||||
Methods(http.MethodDelete)
|
||||
r.HandleFunc("/api/nodes/{network}/{nodeid}/creategateway", logic.SecurityCheck(true, checkFreeTierLimits(limitChoiceEgress, http.HandlerFunc(createEgressGateway)))).
|
||||
Methods(http.MethodPost)
|
||||
r.HandleFunc("/api/nodes/{network}/{nodeid}/deletegateway", logic.SecurityCheck(true, http.HandlerFunc(deleteEgressGateway))).
|
||||
Methods(http.MethodDelete)
|
||||
r.HandleFunc("/api/nodes/{network}/{nodeid}/createingress", logic.SecurityCheck(true, checkFreeTierLimits(limitChoiceIngress, http.HandlerFunc(createIngressGateway)))).
|
||||
Methods(http.MethodPost)
|
||||
r.HandleFunc("/api/nodes/{network}/{nodeid}/deleteingress", logic.SecurityCheck(true, http.HandlerFunc(deleteIngressGateway))).
|
||||
Methods(http.MethodDelete)
|
||||
r.HandleFunc("/api/nodes", logic.SecurityCheck(true, http.HandlerFunc(getAllNodes))).Methods(http.MethodGet)
|
||||
r.HandleFunc("/api/nodes/{network}", logic.SecurityCheck(true, http.HandlerFunc(getNetworkNodes))).Methods(http.MethodGet)
|
||||
r.HandleFunc("/api/nodes/{network}/{nodeid}", Authorize(true, true, "node", http.HandlerFunc(getNode))).Methods(http.MethodGet)
|
||||
r.HandleFunc("/api/nodes/{network}/{nodeid}", logic.SecurityCheck(true, http.HandlerFunc(updateNode))).Methods(http.MethodPut)
|
||||
r.HandleFunc("/api/nodes/{network}/{nodeid}", Authorize(true, true, "node", http.HandlerFunc(deleteNode))).Methods(http.MethodDelete)
|
||||
r.HandleFunc("/api/nodes/{network}/{nodeid}/creategateway", logic.SecurityCheck(true, checkFreeTierLimits(limitChoiceEgress, http.HandlerFunc(createEgressGateway)))).Methods(http.MethodPost)
|
||||
r.HandleFunc("/api/nodes/{network}/{nodeid}/deletegateway", logic.SecurityCheck(true, http.HandlerFunc(deleteEgressGateway))).Methods(http.MethodDelete)
|
||||
r.HandleFunc("/api/nodes/{network}/{nodeid}/createingress", logic.SecurityCheck(true, checkFreeTierLimits(limitChoiceIngress, http.HandlerFunc(createIngressGateway)))).Methods(http.MethodPost)
|
||||
r.HandleFunc("/api/nodes/{network}/{nodeid}/deleteingress", logic.SecurityCheck(true, http.HandlerFunc(deleteIngressGateway))).Methods(http.MethodDelete)
|
||||
r.HandleFunc("/api/nodes/adm/{network}/authenticate", authenticate).Methods(http.MethodPost)
|
||||
r.HandleFunc("/api/v1/nodes/migrate", migrate).Methods(http.MethodPost)
|
||||
}
|
||||
|
@ -277,6 +268,61 @@ func getNetworkNodes(w http.ResponseWriter, r *http.Request) {
|
|||
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
|
||||
return
|
||||
}
|
||||
username := r.Header.Get("user")
|
||||
user, err := logic.GetUser(username)
|
||||
if err != nil {
|
||||
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
|
||||
return
|
||||
}
|
||||
userPlatformRole, err := logic.GetRole(user.PlatformRoleID)
|
||||
if err != nil {
|
||||
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
|
||||
return
|
||||
}
|
||||
filteredNodes := []models.Node{}
|
||||
if !userPlatformRole.FullAccess {
|
||||
nodesMap := make(map[string]struct{})
|
||||
networkRoles := user.NetworkRoles[models.NetworkID(networkName)]
|
||||
for networkRoleID := range networkRoles {
|
||||
userPermTemplate, err := logic.GetRole(networkRoleID)
|
||||
if err != nil {
|
||||
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
|
||||
return
|
||||
}
|
||||
if userPermTemplate.FullAccess {
|
||||
break
|
||||
}
|
||||
if rsrcPerms, ok := userPermTemplate.NetworkLevelAccess[models.RemoteAccessGwRsrc]; ok {
|
||||
if _, ok := rsrcPerms[models.AllRemoteAccessGwRsrcID]; ok {
|
||||
for _, node := range nodes {
|
||||
if _, ok := nodesMap[node.ID.String()]; ok {
|
||||
continue
|
||||
}
|
||||
if node.IsIngressGateway {
|
||||
nodesMap[node.ID.String()] = struct{}{}
|
||||
filteredNodes = append(filteredNodes, node)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for gwID, scope := range rsrcPerms {
|
||||
if _, ok := nodesMap[gwID.String()]; ok {
|
||||
continue
|
||||
}
|
||||
if scope.Read {
|
||||
gwNode, err := logic.GetNodeByID(gwID.String())
|
||||
if err == nil && gwNode.IsIngressGateway {
|
||||
filteredNodes = append(filteredNodes, gwNode)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
if len(filteredNodes) > 0 {
|
||||
nodes = filteredNodes
|
||||
}
|
||||
|
||||
// returns all the nodes in JSON/API format
|
||||
apiNodes := logic.GetAllNodesAPI(nodes[:])
|
||||
|
@ -294,22 +340,26 @@ func getNetworkNodes(w http.ResponseWriter, r *http.Request) {
|
|||
// Not quite sure if this is necessary. Probably necessary based on front end but may want to review after iteration 1 if it's being used or not
|
||||
func getAllNodes(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
user, err := logic.GetUser(r.Header.Get("user"))
|
||||
if err != nil && r.Header.Get("ismasterkey") != "yes" {
|
||||
logger.Log(0, r.Header.Get("user"),
|
||||
"error fetching user info: ", err.Error())
|
||||
var nodes []models.Node
|
||||
nodes, err := logic.GetAllNodes()
|
||||
if err != nil {
|
||||
logger.Log(0, "error fetching all nodes info: ", err.Error())
|
||||
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
|
||||
return
|
||||
}
|
||||
var nodes []models.Node
|
||||
if user.IsAdmin || r.Header.Get("ismasterkey") == "yes" {
|
||||
nodes, err = logic.GetAllNodes()
|
||||
if err != nil {
|
||||
logger.Log(0, "error fetching all nodes info: ", err.Error())
|
||||
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
|
||||
return
|
||||
}
|
||||
username := r.Header.Get("user")
|
||||
user, err := logic.GetUser(username)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
userPlatformRole, err := logic.GetRole(user.PlatformRoleID)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if !userPlatformRole.FullAccess {
|
||||
nodes = logic.GetFilteredNodesByUserAccess(*user, nodes)
|
||||
}
|
||||
|
||||
// return all the nodes in JSON/API format
|
||||
apiNodes := logic.GetAllNodesAPI(nodes[:])
|
||||
logger.Log(3, r.Header.Get("user"), "fetched all nodes they have access to")
|
||||
|
@ -561,25 +611,6 @@ func deleteIngressGateway(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
if servercfg.IsPro {
|
||||
go func() {
|
||||
users, err := logic.GetUsersDB()
|
||||
if err == nil {
|
||||
for _, user := range users {
|
||||
if _, ok := user.RemoteGwIDs[nodeid]; ok {
|
||||
delete(user.RemoteGwIDs, nodeid)
|
||||
err = logic.UpsertUser(user)
|
||||
if err != nil {
|
||||
slog.Error("failed to get user", "user", user.UserName, "error", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
slog.Error("failed to get users", "error", err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
apiNode := node.ConvertToAPINode()
|
||||
logger.Log(1, r.Header.Get("user"), "deleted ingress gateway", nodeid)
|
||||
w.WriteHeader(http.StatusOK)
|
||||
|
|
|
@ -38,10 +38,10 @@ func serverHandlers(r *mux.Router) {
|
|||
).Methods(http.MethodPost)
|
||||
r.HandleFunc("/api/server/getconfig", allowUsers(http.HandlerFunc(getConfig))).
|
||||
Methods(http.MethodGet)
|
||||
r.HandleFunc("/api/server/getserverinfo", Authorize(true, false, "node", http.HandlerFunc(getServerInfo))).
|
||||
r.HandleFunc("/api/server/getserverinfo", logic.SecurityCheck(true, http.HandlerFunc(getServerInfo))).
|
||||
Methods(http.MethodGet)
|
||||
r.HandleFunc("/api/server/status", getStatus).Methods(http.MethodGet)
|
||||
r.HandleFunc("/api/server/usage", Authorize(true, false, "user", http.HandlerFunc(getUsage))).
|
||||
r.HandleFunc("/api/server/usage", logic.SecurityCheck(false, http.HandlerFunc(getUsage))).
|
||||
Methods(http.MethodGet)
|
||||
}
|
||||
|
||||
|
|
|
@ -5,11 +5,12 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"reflect"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/gorilla/websocket"
|
||||
"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"
|
||||
|
@ -28,24 +29,12 @@ func userHandlers(r *mux.Router) {
|
|||
r.HandleFunc("/api/users/adm/transfersuperadmin/{username}", logic.SecurityCheck(true, http.HandlerFunc(transferSuperAdmin))).
|
||||
Methods(http.MethodPost)
|
||||
r.HandleFunc("/api/users/adm/authenticate", authenticateUser).Methods(http.MethodPost)
|
||||
r.HandleFunc("/api/users/{username}", logic.SecurityCheck(true, http.HandlerFunc(updateUser))).
|
||||
Methods(http.MethodPut)
|
||||
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/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)
|
||||
r.HandleFunc("/api/users/{username}", logic.SecurityCheck(true, http.HandlerFunc(updateUser))).Methods(http.MethodPut)
|
||||
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", logic.SecurityCheck(false, logic.ContinueIfUserMatch(http.HandlerFunc(getUserV1)))).Methods(http.MethodGet)
|
||||
r.HandleFunc("/api/users", logic.SecurityCheck(true, http.HandlerFunc(getUsers))).Methods(http.MethodGet)
|
||||
|
||||
}
|
||||
|
||||
|
@ -94,14 +83,24 @@ func authenticateUser(response http.ResponseWriter, request *http.Request) {
|
|||
logic.ReturnErrorResponse(response, request, logic.FormatError(err, "unauthorized"))
|
||||
return
|
||||
}
|
||||
if !(user.IsAdmin || user.IsSuperAdmin) {
|
||||
logic.ReturnErrorResponse(
|
||||
response,
|
||||
request,
|
||||
logic.FormatError(errors.New("only admins can access dashboard"), "unauthorized"),
|
||||
)
|
||||
role, err := logic.GetRole(user.PlatformRoleID)
|
||||
if err != nil {
|
||||
logic.ReturnErrorResponse(response, request, logic.FormatError(errors.New("access denied to dashboard"), "unauthorized"))
|
||||
return
|
||||
}
|
||||
if role.DenyDashboardAccess {
|
||||
logic.ReturnErrorResponse(response, request, logic.FormatError(errors.New("access denied to dashboard"), "unauthorized"))
|
||||
return
|
||||
}
|
||||
}
|
||||
user, err := logic.GetUser(authRequest.UserName)
|
||||
if err != nil {
|
||||
logic.ReturnErrorResponse(response, request, logic.FormatError(err, "unauthorized"))
|
||||
return
|
||||
}
|
||||
if logic.IsOauthUser(user) == nil {
|
||||
logic.ReturnErrorResponse(response, request, logic.FormatError(errors.New("user is registered via SSO"), "badrequest"))
|
||||
return
|
||||
}
|
||||
username := authRequest.UserName
|
||||
jwt, err := logic.VerifyAuthRequest(authRequest)
|
||||
|
@ -224,11 +223,55 @@ func getUser(w http.ResponseWriter, r *http.Request) {
|
|||
json.NewEncoder(w).Encode(user)
|
||||
}
|
||||
|
||||
// @Summary Get all users
|
||||
// @Router /api/users [get]
|
||||
// @Tags Users
|
||||
// @Success 200 {array} models.User
|
||||
// @Failure 500 {object} models.ErrorResponse
|
||||
// swagger:route GET /api/v1/users user getUserV1
|
||||
//
|
||||
// Get an individual user with role info.
|
||||
//
|
||||
// Schemes: https
|
||||
//
|
||||
// Security:
|
||||
// oauth
|
||||
//
|
||||
// Responses:
|
||||
// 200: ReturnUserWithRolesAndGroups
|
||||
func getUserV1(w http.ResponseWriter, r *http.Request) {
|
||||
// set header.
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
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())
|
||||
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
|
||||
return
|
||||
}
|
||||
userRoleTemplate, err := logic.GetRole(user.PlatformRoleID)
|
||||
if err != nil {
|
||||
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
|
||||
return
|
||||
}
|
||||
resp := models.ReturnUserWithRolesAndGroups{
|
||||
ReturnUser: user,
|
||||
PlatformRole: userRoleTemplate,
|
||||
}
|
||||
logger.Log(2, r.Header.Get("user"), "fetched user", usernameFetched)
|
||||
logic.ReturnSuccessResponseWithJson(w, r, resp, "fetched user with role info")
|
||||
}
|
||||
|
||||
// swagger:route GET /api/users user getUsers
|
||||
//
|
||||
// Get all users.
|
||||
//
|
||||
// Schemes: https
|
||||
//
|
||||
// Security:
|
||||
// oauth
|
||||
//
|
||||
// Responses:
|
||||
// 200: userBodyResponse
|
||||
func getUsers(w http.ResponseWriter, r *http.Request) {
|
||||
// set header.
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
@ -297,15 +340,8 @@ func transferSuperAdmin(w http.ResponseWriter, r *http.Request) {
|
|||
if err != nil {
|
||||
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
|
||||
}
|
||||
if !caller.IsSuperAdmin {
|
||||
logic.ReturnErrorResponse(
|
||||
w,
|
||||
r,
|
||||
logic.FormatError(
|
||||
errors.New("only superadmin can assign the superadmin role to another user"),
|
||||
"forbidden",
|
||||
),
|
||||
)
|
||||
if caller.PlatformRoleID != models.SuperAdminRole {
|
||||
logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("only superadmin can assign the superadmin role to another user"), "forbidden"))
|
||||
return
|
||||
}
|
||||
var params = mux.Vars(r)
|
||||
|
@ -316,15 +352,8 @@ func transferSuperAdmin(w http.ResponseWriter, r *http.Request) {
|
|||
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
|
||||
return
|
||||
}
|
||||
if !u.IsAdmin {
|
||||
logic.ReturnErrorResponse(
|
||||
w,
|
||||
r,
|
||||
logic.FormatError(
|
||||
errors.New("only admins can be promoted to superadmin role"),
|
||||
"forbidden",
|
||||
),
|
||||
)
|
||||
if u.PlatformRoleID != models.AdminRole {
|
||||
logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("only admins can be promoted to superadmin role"), "forbidden"))
|
||||
return
|
||||
}
|
||||
if !servercfg.IsBasicAuthEnabled() {
|
||||
|
@ -336,16 +365,14 @@ func transferSuperAdmin(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
u.IsSuperAdmin = true
|
||||
u.IsAdmin = false
|
||||
u.PlatformRoleID = models.SuperAdminRole
|
||||
err = logic.UpsertUser(*u)
|
||||
if err != nil {
|
||||
slog.Error("error updating user to superadmin: ", "user", u.UserName, "error", err.Error())
|
||||
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
|
||||
return
|
||||
}
|
||||
caller.IsSuperAdmin = false
|
||||
caller.IsAdmin = true
|
||||
caller.PlatformRoleID = models.AdminRole
|
||||
err = logic.UpsertUser(*caller)
|
||||
if err != nil {
|
||||
slog.Error("error demoting user to admin: ", "user", caller.UserName, "error", err.Error())
|
||||
|
@ -369,7 +396,7 @@ func createUser(w http.ResponseWriter, r *http.Request) {
|
|||
w.Header().Set("Content-Type", "application/json")
|
||||
caller, err := logic.GetUser(r.Header.Get("user"))
|
||||
if err != nil {
|
||||
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
|
||||
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
|
||||
return
|
||||
}
|
||||
var user models.User
|
||||
|
@ -380,27 +407,34 @@ func createUser(w http.ResponseWriter, r *http.Request) {
|
|||
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
|
||||
return
|
||||
}
|
||||
if !caller.IsSuperAdmin && user.IsAdmin {
|
||||
err = errors.New("only superadmin can create admin users")
|
||||
slog.Error("error creating new user: ", "user", user.UserName, "error", err)
|
||||
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "forbidden"))
|
||||
|
||||
if user.PlatformRoleID == "" {
|
||||
logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("platform role is missing"), "badrequest"))
|
||||
return
|
||||
}
|
||||
if user.IsSuperAdmin {
|
||||
userRole, err := logic.GetRole(user.PlatformRoleID)
|
||||
if err != nil {
|
||||
err = errors.New("error fetching role " + user.PlatformRoleID.String() + " " + err.Error())
|
||||
slog.Error("error creating new user: ", "user", user.UserName, "error", err)
|
||||
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
|
||||
return
|
||||
}
|
||||
if userRole.ID == models.SuperAdminRole {
|
||||
err = errors.New("additional superadmins cannot be created")
|
||||
slog.Error("error creating new user: ", "user", user.UserName, "error", err)
|
||||
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "forbidden"))
|
||||
return
|
||||
}
|
||||
if !servercfg.IsPro && !user.IsAdmin {
|
||||
logic.ReturnErrorResponse(
|
||||
w,
|
||||
r,
|
||||
logic.FormatError(
|
||||
errors.New("non-admins users can only be created on Pro version"),
|
||||
"forbidden",
|
||||
),
|
||||
)
|
||||
|
||||
if caller.PlatformRoleID != models.SuperAdminRole && user.PlatformRoleID == models.AdminRole {
|
||||
err = errors.New("only superadmin can create admin users")
|
||||
slog.Error("error creating new user: ", "user", user.UserName, "error", err)
|
||||
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "forbidden"))
|
||||
return
|
||||
}
|
||||
|
||||
if !servercfg.IsPro && user.PlatformRoleID != models.AdminRole {
|
||||
logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("non-admins users can only be created on Pro version"), "forbidden"))
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -410,6 +444,8 @@ func createUser(w http.ResponseWriter, r *http.Request) {
|
|||
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
|
||||
return
|
||||
}
|
||||
logic.DeleteUserInvite(user.UserName)
|
||||
logic.DeletePendingUser(user.UserName)
|
||||
slog.Info("user was created", "username", user.UserName)
|
||||
json.NewEncoder(w).Encode(logic.ToReturnUser(user))
|
||||
}
|
||||
|
@ -472,55 +508,22 @@ func updateUser(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
if !ismaster && !selfUpdate {
|
||||
if caller.IsAdmin && user.IsSuperAdmin {
|
||||
slog.Error(
|
||||
"non-superadmin user",
|
||||
"caller",
|
||||
caller.UserName,
|
||||
"attempted to update superadmin user",
|
||||
username,
|
||||
)
|
||||
logic.ReturnErrorResponse(
|
||||
w,
|
||||
r,
|
||||
logic.FormatError(errors.New("cannot update superadmin user"), "forbidden"),
|
||||
)
|
||||
if caller.PlatformRoleID == models.AdminRole && user.PlatformRoleID == models.SuperAdminRole {
|
||||
slog.Error("non-superadmin user", "caller", caller.UserName, "attempted to update superadmin user", username)
|
||||
logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("cannot update superadmin user"), "forbidden"))
|
||||
return
|
||||
}
|
||||
if !caller.IsAdmin && !caller.IsSuperAdmin {
|
||||
slog.Error(
|
||||
"operation not allowed",
|
||||
"caller",
|
||||
caller.UserName,
|
||||
"attempted to update user",
|
||||
username,
|
||||
)
|
||||
logic.ReturnErrorResponse(
|
||||
w,
|
||||
r,
|
||||
logic.FormatError(errors.New("cannot update superadmin user"), "forbidden"),
|
||||
)
|
||||
if caller.PlatformRoleID != models.AdminRole && caller.PlatformRoleID != models.SuperAdminRole {
|
||||
slog.Error("operation not allowed", "caller", caller.UserName, "attempted to update user", username)
|
||||
logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("cannot update superadmin user"), "forbidden"))
|
||||
return
|
||||
}
|
||||
if caller.IsAdmin && user.IsAdmin {
|
||||
slog.Error(
|
||||
"admin user cannot update another admin",
|
||||
"caller",
|
||||
caller.UserName,
|
||||
"attempted to update admin user",
|
||||
username,
|
||||
)
|
||||
logic.ReturnErrorResponse(
|
||||
w,
|
||||
r,
|
||||
logic.FormatError(
|
||||
errors.New("admin user cannot update another admin"),
|
||||
"forbidden",
|
||||
),
|
||||
)
|
||||
if caller.PlatformRoleID == models.AdminRole && user.PlatformRoleID == models.AdminRole {
|
||||
slog.Error("admin user cannot update another admin", "caller", caller.UserName, "attempted to update admin user", username)
|
||||
logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("admin user cannot update another admin"), "forbidden"))
|
||||
return
|
||||
}
|
||||
if caller.IsAdmin && userchange.IsAdmin {
|
||||
if caller.PlatformRoleID == models.AdminRole && userchange.PlatformRoleID == models.AdminRole {
|
||||
err = errors.New("admin user cannot update role of an another user to admin")
|
||||
slog.Error(
|
||||
"failed to update user",
|
||||
|
@ -537,45 +540,39 @@ func updateUser(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
}
|
||||
if !ismaster && selfUpdate {
|
||||
if user.IsAdmin != userchange.IsAdmin || user.IsSuperAdmin != userchange.IsSuperAdmin {
|
||||
slog.Error(
|
||||
"user cannot change his own role",
|
||||
"caller",
|
||||
caller.UserName,
|
||||
"attempted to update user role",
|
||||
username,
|
||||
)
|
||||
logic.ReturnErrorResponse(
|
||||
w,
|
||||
r,
|
||||
logic.FormatError(errors.New("user not allowed to self assign role"), "forbidden"),
|
||||
)
|
||||
if user.PlatformRoleID != userchange.PlatformRoleID {
|
||||
slog.Error("user cannot change his own role", "caller", caller.UserName, "attempted to update user role", username)
|
||||
logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("user not allowed to self assign role"), "forbidden"))
|
||||
return
|
||||
|
||||
}
|
||||
if servercfg.IsPro {
|
||||
// user cannot update his own roles and groups
|
||||
if len(user.NetworkRoles) != len(userchange.NetworkRoles) || !reflect.DeepEqual(user.NetworkRoles, userchange.NetworkRoles) {
|
||||
err = errors.New("user cannot update self update their network roles")
|
||||
slog.Error("failed to update user", "caller", caller.UserName, "attempted to update user", username, "error", err)
|
||||
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "forbidden"))
|
||||
return
|
||||
}
|
||||
// user cannot update his own roles and groups
|
||||
if len(user.UserGroups) != len(userchange.UserGroups) || !reflect.DeepEqual(user.UserGroups, userchange.UserGroups) {
|
||||
err = errors.New("user cannot update self update their groups")
|
||||
slog.Error("failed to update user", "caller", caller.UserName, "attempted to update user", username, "error", err)
|
||||
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "forbidden"))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
if ismaster {
|
||||
if !user.IsSuperAdmin && userchange.IsSuperAdmin {
|
||||
slog.Error(
|
||||
"operation not allowed",
|
||||
"caller",
|
||||
logic.MasterUser,
|
||||
"attempted to update user role to superadmin",
|
||||
username,
|
||||
)
|
||||
logic.ReturnErrorResponse(
|
||||
w,
|
||||
r,
|
||||
logic.FormatError(
|
||||
errors.New("attempted to update user role to superadmin"),
|
||||
"forbidden",
|
||||
),
|
||||
)
|
||||
if user.PlatformRoleID != models.SuperAdminRole && userchange.PlatformRoleID == models.SuperAdminRole {
|
||||
slog.Error("operation not allowed", "caller", logic.MasterUser, "attempted to update user role to superadmin", username)
|
||||
logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("attempted to update user role to superadmin"), "forbidden"))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if auth.IsOauthUser(user) == nil && userchange.Password != "" {
|
||||
if logic.IsOauthUser(user) == nil && userchange.Password != "" {
|
||||
err := fmt.Errorf("cannot update user's password for an oauth user %s", username)
|
||||
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "forbidden"))
|
||||
return
|
||||
|
@ -608,6 +605,12 @@ func deleteUser(w http.ResponseWriter, r *http.Request) {
|
|||
if err != nil {
|
||||
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
|
||||
}
|
||||
callerUserRole, err := logic.GetRole(caller.PlatformRoleID)
|
||||
if err != nil {
|
||||
slog.Error("failed to get role ", "role", callerUserRole.ID, "error", err)
|
||||
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
|
||||
return
|
||||
}
|
||||
username := params["username"]
|
||||
user, err := logic.GetUser(username)
|
||||
if err != nil {
|
||||
|
@ -616,7 +619,13 @@ func deleteUser(w http.ResponseWriter, r *http.Request) {
|
|||
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
|
||||
return
|
||||
}
|
||||
if user.IsSuperAdmin {
|
||||
userRole, err := logic.GetRole(user.PlatformRoleID)
|
||||
if err != nil {
|
||||
slog.Error("failed to get role ", "role", userRole.ID, "error", err)
|
||||
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
|
||||
return
|
||||
}
|
||||
if userRole.ID == models.SuperAdminRole {
|
||||
slog.Error(
|
||||
"failed to delete user: ", "user", username, "error", "superadmin cannot be deleted")
|
||||
logic.ReturnErrorResponse(
|
||||
|
@ -626,8 +635,8 @@ func deleteUser(w http.ResponseWriter, r *http.Request) {
|
|||
)
|
||||
return
|
||||
}
|
||||
if !caller.IsSuperAdmin {
|
||||
if caller.IsAdmin && user.IsAdmin {
|
||||
if callerUserRole.ID != models.SuperAdminRole {
|
||||
if callerUserRole.ID == models.AdminRole && userRole.ID == models.AdminRole {
|
||||
slog.Error(
|
||||
"failed to delete user: ",
|
||||
"user",
|
||||
|
@ -667,10 +676,14 @@ func deleteUser(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
for _, extclient := range extclients {
|
||||
if extclient.OwnerID == user.UserName {
|
||||
err = logic.DeleteExtClient(extclient.Network, extclient.ClientID)
|
||||
err = logic.DeleteExtClientAndCleanup(extclient)
|
||||
if err != nil {
|
||||
slog.Error("failed to delete extclient",
|
||||
"id", extclient.ClientID, "owner", user.UserName, "error", err)
|
||||
"id", extclient.ClientID, "owner", username, "error", err)
|
||||
} else {
|
||||
if err := mq.PublishDeletedClientPeerUpdate(&extclient); err != nil {
|
||||
slog.Error("error setting ext peers: " + err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -697,139 +710,3 @@ func socketHandler(w http.ResponseWriter, r *http.Request) {
|
|||
// Start handling the session
|
||||
go auth.SessionHandler(conn)
|
||||
}
|
||||
|
||||
// @Summary Get all pending users
|
||||
// @Router /api/users_pending [get]
|
||||
// @Tags Users
|
||||
// @Success 200 {array} models.User
|
||||
// @Failure 500 {object} models.ErrorResponse
|
||||
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)
|
||||
}
|
||||
|
||||
// @Summary Approve a pending user
|
||||
// @Router /api/users_pending/user/{username} [post]
|
||||
// @Tags Users
|
||||
// @Param username path string true "Username of the pending user to approve"
|
||||
// @Success 200 {string} string
|
||||
// @Failure 500 {object} models.ErrorResponse
|
||||
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)
|
||||
}
|
||||
|
||||
// @Summary Delete a pending user
|
||||
// @Router /api/users_pending/user/{username} [delete]
|
||||
// @Tags Users
|
||||
// @Param username path string true "Username of the pending user to delete"
|
||||
// @Success 200 {string} string
|
||||
// @Failure 500 {object} models.ErrorResponse
|
||||
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)
|
||||
}
|
||||
|
||||
// @Summary Delete all pending users
|
||||
// @Router /api/users_pending [delete]
|
||||
// @Tags Users
|
||||
// @Success 200 {string} string
|
||||
// @Failure 500 {object} models.ErrorResponse
|
||||
func deleteAllPendingUsers(w http.ResponseWriter, r *http.Request) {
|
||||
// set header.
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
err := database.DeleteAllRecords(database.PENDING_USERS_TABLE_NAME)
|
||||
if err != nil {
|
||||
logic.ReturnErrorResponse(
|
||||
w,
|
||||
r,
|
||||
logic.FormatError(
|
||||
errors.New("failed to delete all pending users "+err.Error()),
|
||||
"internal",
|
||||
),
|
||||
)
|
||||
return
|
||||
}
|
||||
logic.ReturnSuccessResponse(w, r, "cleared all pending users")
|
||||
}
|
||||
|
|
|
@ -66,7 +66,7 @@ func prepareUserRequest(t *testing.T, userForBody models.User, userNameForParam
|
|||
func haveOnlyOneUser(t *testing.T, user models.User) {
|
||||
deleteAllUsers(t)
|
||||
var err error
|
||||
if user.IsSuperAdmin {
|
||||
if user.PlatformRoleID == models.SuperAdminRole {
|
||||
err = logic.CreateSuperAdmin(&user)
|
||||
} else {
|
||||
err = logic.CreateUser(&user)
|
||||
|
@ -104,7 +104,7 @@ func TestHasSuperAdmin(t *testing.T) {
|
|||
assert.False(t, found)
|
||||
})
|
||||
t.Run("superadmin user", func(t *testing.T) {
|
||||
var user = models.User{UserName: "superadmin", Password: "password", IsSuperAdmin: true}
|
||||
var user = models.User{UserName: "superadmin", Password: "password", PlatformRoleID: models.SuperAdminRole}
|
||||
err := logic.CreateUser(&user)
|
||||
assert.Nil(t, err)
|
||||
found, err := logic.HasSuperAdmin()
|
||||
|
@ -112,7 +112,7 @@ func TestHasSuperAdmin(t *testing.T) {
|
|||
assert.True(t, found)
|
||||
})
|
||||
t.Run("multiple superadmins", func(t *testing.T) {
|
||||
var user = models.User{UserName: "superadmin1", Password: "password", IsSuperAdmin: true}
|
||||
var user = models.User{UserName: "superadmin1", Password: "password", PlatformRoleID: models.SuperAdminRole}
|
||||
err := logic.CreateUser(&user)
|
||||
assert.Nil(t, err)
|
||||
found, err := logic.HasSuperAdmin()
|
||||
|
@ -123,7 +123,7 @@ func TestHasSuperAdmin(t *testing.T) {
|
|||
|
||||
func TestCreateUser(t *testing.T) {
|
||||
deleteAllUsers(t)
|
||||
user := models.User{UserName: "admin", Password: "password", IsAdmin: true}
|
||||
user := models.User{UserName: "admin", Password: "password", PlatformRoleID: models.AdminRole}
|
||||
t.Run("NoUser", func(t *testing.T) {
|
||||
err := logic.CreateUser(&user)
|
||||
assert.Nil(t, err)
|
||||
|
@ -161,7 +161,7 @@ func TestDeleteUser(t *testing.T) {
|
|||
assert.False(t, deleted)
|
||||
})
|
||||
t.Run("Existing User", func(t *testing.T) {
|
||||
user := models.User{UserName: "admin", Password: "password", IsAdmin: true}
|
||||
user := models.User{UserName: "admin", Password: "password", PlatformRoleID: models.AdminRole}
|
||||
if err := logic.CreateUser(&user); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -221,7 +221,7 @@ func TestValidateUser(t *testing.T) {
|
|||
func TestGetUser(t *testing.T) {
|
||||
deleteAllUsers(t)
|
||||
|
||||
user := models.User{UserName: "admin", Password: "password", IsAdmin: true}
|
||||
user := models.User{UserName: "admin", Password: "password", PlatformRoleID: models.AdminRole}
|
||||
|
||||
t.Run("NonExistantUser", func(t *testing.T) {
|
||||
admin, err := logic.GetUser("admin")
|
||||
|
@ -241,8 +241,8 @@ func TestGetUser(t *testing.T) {
|
|||
func TestGetUsers(t *testing.T) {
|
||||
deleteAllUsers(t)
|
||||
|
||||
adminUser := models.User{UserName: "admin", Password: "password", IsAdmin: true}
|
||||
user := models.User{UserName: "admin", Password: "password", IsAdmin: false}
|
||||
adminUser := models.User{UserName: "admin", Password: "password", PlatformRoleID: models.AdminRole}
|
||||
user := models.User{UserName: "admin", Password: "password", PlatformRoleID: models.AdminRole}
|
||||
|
||||
t.Run("NonExistantUser", func(t *testing.T) {
|
||||
admin, err := logic.GetUsers()
|
||||
|
@ -269,7 +269,7 @@ func TestGetUsers(t *testing.T) {
|
|||
assert.Equal(t, true, u.IsAdmin)
|
||||
} else {
|
||||
assert.Equal(t, user.UserName, u.UserName)
|
||||
assert.Equal(t, user.IsAdmin, u.IsAdmin)
|
||||
assert.Equal(t, user.PlatformRoleID, u.PlatformRoleID)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@ -278,8 +278,8 @@ func TestGetUsers(t *testing.T) {
|
|||
|
||||
func TestUpdateUser(t *testing.T) {
|
||||
deleteAllUsers(t)
|
||||
user := models.User{UserName: "admin", Password: "password", IsAdmin: true}
|
||||
newuser := models.User{UserName: "hello", Password: "world", IsAdmin: true}
|
||||
user := models.User{UserName: "admin", Password: "password", PlatformRoleID: models.AdminRole}
|
||||
newuser := models.User{UserName: "hello", Password: "world", PlatformRoleID: models.AdminRole}
|
||||
t.Run("NonExistantUser", func(t *testing.T) {
|
||||
admin, err := logic.UpdateUser(&newuser, &user)
|
||||
assert.EqualError(t, err, "could not find any records")
|
||||
|
@ -322,7 +322,7 @@ func TestUpdateUser(t *testing.T) {
|
|||
|
||||
func TestVerifyAuthRequest(t *testing.T) {
|
||||
deleteAllUsers(t)
|
||||
user := models.User{UserName: "admin", Password: "password", IsSuperAdmin: false, IsAdmin: true}
|
||||
user := models.User{UserName: "admin", Password: "password", PlatformRoleID: models.AdminRole}
|
||||
var authRequest models.UserAuthParams
|
||||
t.Run("EmptyUserName", func(t *testing.T) {
|
||||
authRequest.UserName = ""
|
||||
|
@ -346,7 +346,7 @@ func TestVerifyAuthRequest(t *testing.T) {
|
|||
assert.EqualError(t, err, "incorrect credentials")
|
||||
})
|
||||
t.Run("Non-Admin", func(t *testing.T) {
|
||||
user.IsAdmin = false
|
||||
user.PlatformRoleID = models.ServiceUser
|
||||
user.Password = "somepass"
|
||||
user.UserName = "nonadmin"
|
||||
if err := logic.CreateUser(&user); err != nil {
|
||||
|
|
|
@ -25,6 +25,8 @@ const (
|
|||
DELETED_NODES_TABLE_NAME = "deletednodes"
|
||||
// USERS_TABLE_NAME - users table
|
||||
USERS_TABLE_NAME = "users"
|
||||
// USER_PERMISSIONS_TABLE_NAME - user permissions table
|
||||
USER_PERMISSIONS_TABLE_NAME = "user_permissions"
|
||||
// CERTS_TABLE_NAME - certificates table
|
||||
CERTS_TABLE_NAME = "certs"
|
||||
// DNS_TABLE_NAME - dns table
|
||||
|
@ -63,6 +65,8 @@ const (
|
|||
HOST_ACTIONS_TABLE_NAME = "hostactions"
|
||||
// PENDING_USERS_TABLE_NAME - table name for pending users
|
||||
PENDING_USERS_TABLE_NAME = "pending_users"
|
||||
// USER_INVITES - table for user invites
|
||||
USER_INVITES_TABLE_NAME = "user_invites"
|
||||
// == ERROR CONSTS ==
|
||||
// NO_RECORD - no singular result found
|
||||
NO_RECORD = "no result found"
|
||||
|
@ -146,6 +150,8 @@ func createTables() {
|
|||
CreateTable(ENROLLMENT_KEYS_TABLE_NAME)
|
||||
CreateTable(HOST_ACTIONS_TABLE_NAME)
|
||||
CreateTable(PENDING_USERS_TABLE_NAME)
|
||||
CreateTable(USER_PERMISSIONS_TABLE_NAME)
|
||||
CreateTable(USER_INVITES_TABLE_NAME)
|
||||
}
|
||||
|
||||
func CreateTable(tableName string) error {
|
||||
|
|
|
@ -26,9 +26,9 @@ func TestMain(m *testing.M) {
|
|||
database.InitializeDatabase()
|
||||
defer database.CloseDB()
|
||||
logic.CreateSuperAdmin(&models.User{
|
||||
UserName: "superadmin",
|
||||
Password: "password",
|
||||
IsSuperAdmin: true,
|
||||
UserName: "superadmin",
|
||||
Password: "password",
|
||||
PlatformRoleID: models.SuperAdminRole,
|
||||
})
|
||||
peerUpdate := make(chan *models.Node)
|
||||
go logic.ManageZombies(context.Background(), peerUpdate)
|
||||
|
|
3
go.mod
3
go.mod
|
@ -42,7 +42,9 @@ require (
|
|||
github.com/guumaster/tablewriter v0.0.10
|
||||
github.com/matryer/is v1.4.1
|
||||
github.com/olekukonko/tablewriter v0.0.5
|
||||
github.com/resendlabs/resend-go v1.7.0
|
||||
github.com/spf13/cobra v1.8.1
|
||||
gopkg.in/mail.v2 v2.3.1
|
||||
)
|
||||
|
||||
require (
|
||||
|
@ -52,6 +54,7 @@ require (
|
|||
github.com/rivo/uniseg v0.2.0 // indirect
|
||||
github.com/seancfoley/bintree v1.3.1 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
|
||||
)
|
||||
|
||||
require (
|
||||
|
|
6
go.sum
6
go.sum
|
@ -61,6 +61,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
|
|||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/posthog/posthog-go v1.2.18 h1:2CBA0LOB0up+gon+xpeXuhFw69gZpjAYxQoBBGwiDWw=
|
||||
github.com/posthog/posthog-go v1.2.18/go.mod h1:QjlpryJtfYLrZF2GUkAhejH4E7WlDbdKkvOi5hLmkdg=
|
||||
github.com/resendlabs/resend-go v1.7.0 h1:DycOqSXtw2q7aB+Nt9DDJUDtaYcrNPGn1t5RFposas0=
|
||||
github.com/resendlabs/resend-go v1.7.0/go.mod h1:yip1STH7Bqfm4fD0So5HgyNbt5taG5Cplc4xXxETyLI=
|
||||
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
|
@ -137,8 +139,12 @@ golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
|||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20221104135756-97bc4ad4a1cb h1:9aqVcYEDHmSNb0uOWukxV5lHV09WqiSiCuhEgWNETLY=
|
||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20221104135756-97bc4ad4a1cb/go.mod h1:mQqgjkW8GQQcJQsbBvK890TKqUK1DfKWkuBGbOkuMHQ=
|
||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk=
|
||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/mail.v2 v2.3.1 h1:WYFn/oANrAGP2C0dcV6/pbkPzv8yGzqTjPmTeO7qoXk=
|
||||
gopkg.in/mail.v2 v2.3.1/go.mod h1:htwXN1Qh09vZJ1NVKxQqHPBaCBbzKhp5GzuJEA4VJWw=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
|
|
@ -49,8 +49,7 @@ func HasSuperAdmin() (bool, error) {
|
|||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if user.IsSuperAdmin {
|
||||
superUser = user
|
||||
if user.PlatformRoleID == models.SuperAdminRole || user.IsSuperAdmin {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
|
@ -106,18 +105,58 @@ func GetUsers() ([]models.ReturnUser, error) {
|
|||
return users, err
|
||||
}
|
||||
|
||||
// IsOauthUser - returns
|
||||
func IsOauthUser(user *models.User) error {
|
||||
var currentValue, err = FetchPassValue("")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var bCryptErr = bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(currentValue))
|
||||
return bCryptErr
|
||||
}
|
||||
|
||||
func FetchPassValue(newValue string) (string, error) {
|
||||
|
||||
type valueHolder struct {
|
||||
Value string `json:"value" bson:"value"`
|
||||
}
|
||||
newValueHolder := valueHolder{}
|
||||
var currentValue, err = FetchAuthSecret()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
var unmarshErr = json.Unmarshal([]byte(currentValue), &newValueHolder)
|
||||
if unmarshErr != nil {
|
||||
return "", unmarshErr
|
||||
}
|
||||
|
||||
var b64CurrentValue, b64Err = base64.StdEncoding.DecodeString(newValueHolder.Value)
|
||||
if b64Err != nil {
|
||||
logger.Log(0, "could not decode pass")
|
||||
return "", nil
|
||||
}
|
||||
return string(b64CurrentValue), nil
|
||||
}
|
||||
|
||||
// CreateUser - creates a user
|
||||
func CreateUser(user *models.User) error {
|
||||
// check if user exists
|
||||
if _, err := GetUser(user.UserName); err == nil {
|
||||
return errors.New("user exists")
|
||||
}
|
||||
SetUserDefaults(user)
|
||||
if err := IsGroupsValid(user.UserGroups); err != nil {
|
||||
return errors.New("invalid groups: " + err.Error())
|
||||
}
|
||||
if err := IsNetworkRolesValid(user.NetworkRoles); err != nil {
|
||||
return errors.New("invalid network roles: " + err.Error())
|
||||
}
|
||||
|
||||
var err = ValidateUser(user)
|
||||
if err != nil {
|
||||
logger.Log(0, "failed to validate user", err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
// encrypt that password so we never see it again
|
||||
hash, err := bcrypt.GenerateFromPassword([]byte(user.Password), 5)
|
||||
if err != nil {
|
||||
|
@ -126,15 +165,16 @@ func CreateUser(user *models.User) error {
|
|||
}
|
||||
// set password to encrypted password
|
||||
user.Password = string(hash)
|
||||
|
||||
tokenString, _ := CreateUserJWT(user.UserName, user.IsSuperAdmin, user.IsAdmin)
|
||||
if tokenString == "" {
|
||||
logger.Log(0, "failed to generate token")
|
||||
user.AuthType = models.BasicAuth
|
||||
if IsOauthUser(user) == nil {
|
||||
user.AuthType = models.OAuth
|
||||
}
|
||||
_, err = CreateUserJWT(user.UserName, user.PlatformRoleID)
|
||||
if err != nil {
|
||||
logger.Log(0, "failed to generate token", err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
SetUserDefaults(user)
|
||||
|
||||
// connect db
|
||||
data, err := json.Marshal(user)
|
||||
if err != nil {
|
||||
|
@ -159,8 +199,7 @@ func CreateSuperAdmin(u *models.User) error {
|
|||
if hassuperadmin {
|
||||
return errors.New("superadmin user already exists")
|
||||
}
|
||||
u.IsSuperAdmin = true
|
||||
u.IsAdmin = false
|
||||
u.PlatformRoleID = models.SuperAdminRole
|
||||
return CreateUser(u)
|
||||
}
|
||||
|
||||
|
@ -189,7 +228,7 @@ func VerifyAuthRequest(authRequest models.UserAuthParams) (string, error) {
|
|||
}
|
||||
|
||||
// Create a new JWT for the node
|
||||
tokenString, err := CreateUserJWT(authRequest.UserName, result.IsSuperAdmin, result.IsAdmin)
|
||||
tokenString, err := CreateUserJWT(authRequest.UserName, result.PlatformRoleID)
|
||||
if err != nil {
|
||||
slog.Error("error creating jwt", "error", err)
|
||||
return "", err
|
||||
|
@ -250,8 +289,17 @@ func UpdateUser(userchange, user *models.User) (*models.User, error) {
|
|||
|
||||
user.Password = userchange.Password
|
||||
}
|
||||
user.IsAdmin = userchange.IsAdmin
|
||||
|
||||
if err := IsGroupsValid(userchange.UserGroups); err != nil {
|
||||
return userchange, errors.New("invalid groups: " + err.Error())
|
||||
}
|
||||
if err := IsNetworkRolesValid(userchange.NetworkRoles); err != nil {
|
||||
return userchange, errors.New("invalid network roles: " + err.Error())
|
||||
}
|
||||
// Reset Gw Access for service users
|
||||
go UpdateUserGwAccess(*user, *userchange)
|
||||
user.PlatformRoleID = userchange.PlatformRoleID
|
||||
user.UserGroups = userchange.UserGroups
|
||||
user.NetworkRoles = userchange.NetworkRoles
|
||||
err := ValidateUser(user)
|
||||
if err != nil {
|
||||
return &models.User{}, err
|
||||
|
@ -274,12 +322,17 @@ func UpdateUser(userchange, user *models.User) (*models.User, error) {
|
|||
// ValidateUser - validates a user model
|
||||
func ValidateUser(user *models.User) error {
|
||||
|
||||
// check if role is valid
|
||||
_, err := GetRole(user.PlatformRoleID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
v := validator.New()
|
||||
_ = v.RegisterValidation("in_charset", func(fl validator.FieldLevel) bool {
|
||||
isgood := user.NameInCharSet()
|
||||
return isgood
|
||||
})
|
||||
err := v.Struct(user)
|
||||
err = v.Struct(user)
|
||||
|
||||
if err != nil {
|
||||
for _, e := range err.(validator.ValidationErrors) {
|
||||
|
|
|
@ -178,6 +178,30 @@ func CreateIngressGateway(netid string, nodeid string, ingress models.IngressReq
|
|||
if err != nil {
|
||||
return models.Node{}, err
|
||||
}
|
||||
// create network role for this gateway
|
||||
CreateRole(models.UserRolePermissionTemplate{
|
||||
ID: models.GetRAGRoleID(node.Network, host.ID.String()),
|
||||
UiName: models.GetRAGRoleName(node.Network, host.Name),
|
||||
NetworkID: models.NetworkID(node.Network),
|
||||
Default: true,
|
||||
NetworkLevelAccess: map[models.RsrcType]map[models.RsrcID]models.RsrcPermissionScope{
|
||||
models.RemoteAccessGwRsrc: {
|
||||
models.RsrcID(node.ID.String()): models.RsrcPermissionScope{
|
||||
Read: true,
|
||||
VPNaccess: true,
|
||||
},
|
||||
},
|
||||
models.ExtClientsRsrc: {
|
||||
models.AllExtClientsRsrcID: models.RsrcPermissionScope{
|
||||
Read: true,
|
||||
Create: true,
|
||||
Update: true,
|
||||
Delete: true,
|
||||
SelfOnly: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
err = SetNetworkNodesLastModified(netid)
|
||||
return node, err
|
||||
}
|
||||
|
@ -231,6 +255,11 @@ func DeleteIngressGateway(nodeid string) (models.Node, []models.ExtClient, error
|
|||
if err != nil {
|
||||
return models.Node{}, removedClients, err
|
||||
}
|
||||
host, err := GetHost(node.HostID.String())
|
||||
if err != nil {
|
||||
return models.Node{}, removedClients, err
|
||||
}
|
||||
go DeleteRole(models.GetRAGRoleID(node.Network, host.ID.String()), true)
|
||||
err = SetNetworkNodesLastModified(node.Network)
|
||||
return node, removedClients, err
|
||||
}
|
||||
|
@ -264,10 +293,8 @@ func IsUserAllowedAccessToExtClient(username string, client models.ExtClient) bo
|
|||
if err != nil {
|
||||
return false
|
||||
}
|
||||
if !user.IsAdmin && !user.IsSuperAdmin {
|
||||
if user.UserName != client.OwnerID {
|
||||
return false
|
||||
}
|
||||
if user.UserName != client.OwnerID {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
|
|
@ -269,6 +269,19 @@ func UpdateHostFromClient(newHost, currHost *models.Host) (sendPeerUpdate bool)
|
|||
currHost.IsStaticPort = newHost.IsStaticPort
|
||||
currHost.IsStatic = newHost.IsStatic
|
||||
currHost.MTU = newHost.MTU
|
||||
if newHost.Name != currHost.Name {
|
||||
// update any rag role ids
|
||||
for _, nodeID := range newHost.Nodes {
|
||||
node, err := GetNodeByID(nodeID)
|
||||
if err == nil && node.IsIngressGateway {
|
||||
role, err := GetRole(models.GetRAGRoleID(node.Network, currHost.ID.String()))
|
||||
if err == nil {
|
||||
role.UiName = models.GetRAGRoleName(node.Network, newHost.Name)
|
||||
UpdateRole(role)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
currHost.Name = newHost.Name
|
||||
if len(newHost.NatType) > 0 && newHost.NatType != currHost.NatType {
|
||||
currHost.NatType = newHost.NatType
|
||||
|
|
|
@ -53,12 +53,11 @@ func CreateJWT(uuid string, macAddress string, network string) (response string,
|
|||
}
|
||||
|
||||
// CreateUserJWT - creates a user jwt token
|
||||
func CreateUserJWT(username string, issuperadmin, isadmin bool) (response string, err error) {
|
||||
func CreateUserJWT(username string, role models.UserRoleID) (response string, err error) {
|
||||
expirationTime := time.Now().Add(servercfg.GetServerConfig().JwtValidityDuration)
|
||||
claims := &models.UserClaims{
|
||||
UserName: username,
|
||||
IsSuperAdmin: issuperadmin,
|
||||
IsAdmin: isadmin,
|
||||
UserName: username,
|
||||
Role: role,
|
||||
RegisteredClaims: jwt.RegisteredClaims{
|
||||
Issuer: "Netmaker",
|
||||
Subject: fmt.Sprintf("user|%s", username),
|
||||
|
@ -87,6 +86,47 @@ func VerifyJWT(bearerToken string) (username string, issuperadmin, isadmin bool,
|
|||
return VerifyUserToken(token)
|
||||
}
|
||||
|
||||
func GetUserNameFromToken(authtoken string) (username string, err error) {
|
||||
claims := &models.UserClaims{}
|
||||
var tokenSplit = strings.Split(authtoken, " ")
|
||||
var tokenString = ""
|
||||
|
||||
if len(tokenSplit) < 2 {
|
||||
return "", Unauthorized_Err
|
||||
} else {
|
||||
tokenString = tokenSplit[1]
|
||||
}
|
||||
if tokenString == servercfg.GetMasterKey() && servercfg.GetMasterKey() != "" {
|
||||
return MasterUser, nil
|
||||
}
|
||||
|
||||
token, err := jwt.ParseWithClaims(tokenString, claims, func(token *jwt.Token) (interface{}, error) {
|
||||
return jwtSecretKey, nil
|
||||
})
|
||||
if err != nil {
|
||||
return "", Unauthorized_Err
|
||||
}
|
||||
|
||||
if token != nil && token.Valid {
|
||||
var user *models.User
|
||||
// check that user exists
|
||||
user, err = GetUser(claims.UserName)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if user.UserName != "" {
|
||||
return user.UserName, nil
|
||||
}
|
||||
if user.PlatformRoleID != claims.Role {
|
||||
return "", Unauthorized_Err
|
||||
}
|
||||
err = errors.New("user does not exist")
|
||||
} else {
|
||||
err = Unauthorized_Err
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
|
||||
// VerifyUserToken func will used to Verify the JWT Token while using APIS
|
||||
func VerifyUserToken(tokenString string) (username string, issuperadmin, isadmin bool, err error) {
|
||||
claims := &models.UserClaims{}
|
||||
|
@ -107,7 +147,8 @@ func VerifyUserToken(tokenString string) (username string, issuperadmin, isadmin
|
|||
return "", false, false, err
|
||||
}
|
||||
if user.UserName != "" {
|
||||
return user.UserName, user.IsSuperAdmin, user.IsAdmin, nil
|
||||
return user.UserName, user.PlatformRoleID == models.SuperAdminRole,
|
||||
user.PlatformRoleID == models.AdminRole, nil
|
||||
}
|
||||
err = errors.New("user does not exist")
|
||||
}
|
||||
|
|
|
@ -196,6 +196,10 @@ func DeleteNode(node *models.Node, purge bool) error {
|
|||
if err := DeleteGatewayExtClients(node.ID.String(), node.Network); err != nil {
|
||||
slog.Error("failed to delete ext clients", "nodeid", node.ID.String(), "error", err.Error())
|
||||
}
|
||||
host, err := GetHost(node.HostID.String())
|
||||
if err == nil {
|
||||
go DeleteRole(models.GetRAGRoleID(node.Network, host.ID.String()), true)
|
||||
}
|
||||
}
|
||||
if node.IsRelayed {
|
||||
// cleanup node from relayednodes on relay node
|
||||
|
|
|
@ -2,9 +2,11 @@ package logic
|
|||
|
||||
import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/gravitl/netmaker/logger"
|
||||
"github.com/gravitl/netmaker/models"
|
||||
"github.com/gravitl/netmaker/servercfg"
|
||||
)
|
||||
|
@ -17,20 +19,42 @@ const (
|
|||
Unauthorized_Err = models.Error(Unauthorized_Msg)
|
||||
)
|
||||
|
||||
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 {
|
||||
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
r.Header.Set("ismaster", "no")
|
||||
logger.Log(0, "next", r.URL.String())
|
||||
isGlobalAccesss := r.Header.Get("IS_GLOBAL_ACCESS") == "yes"
|
||||
bearerToken := r.Header.Get("Authorization")
|
||||
username, err := UserPermissions(reqAdmin, bearerToken)
|
||||
username, err := GetUserNameFromToken(bearerToken)
|
||||
if err != nil {
|
||||
ReturnErrorResponse(w, r, FormatError(err, err.Error()))
|
||||
logger.Log(0, "next 1", r.URL.String(), err.Error())
|
||||
ReturnErrorResponse(w, r, FormatError(err, "unauthorized"))
|
||||
return
|
||||
}
|
||||
// detect masteradmin
|
||||
if username == MasterUser {
|
||||
r.Header.Set("ismaster", "yes")
|
||||
} else {
|
||||
if isGlobalAccesss {
|
||||
err = GlobalPermissionsCheck(username, r)
|
||||
} else {
|
||||
err = NetworkPermissionsCheck(username, r)
|
||||
}
|
||||
}
|
||||
w.Header().Set("TARGET_RSRC", r.Header.Get("TARGET_RSRC"))
|
||||
w.Header().Set("TARGET_RSRC_ID", r.Header.Get("TARGET_RSRC_ID"))
|
||||
w.Header().Set("RSRC_TYPE", r.Header.Get("RSRC_TYPE"))
|
||||
w.Header().Set("IS_GLOBAL_ACCESS", r.Header.Get("IS_GLOBAL_ACCESS"))
|
||||
w.Header().Set("Access-Control-Allow-Origin", "*")
|
||||
if err != nil {
|
||||
w.Header().Set("ACCESS_PERM", err.Error())
|
||||
ReturnErrorResponse(w, r, FormatError(err, "forbidden"))
|
||||
return
|
||||
}
|
||||
r.Header.Set("user", username)
|
||||
next.ServeHTTP(w, r)
|
||||
|
@ -75,7 +99,11 @@ func ContinueIfUserMatch(next http.Handler) http.HandlerFunc {
|
|||
}
|
||||
var params = mux.Vars(r)
|
||||
var requestedUser = params["username"]
|
||||
if requestedUser == "" {
|
||||
requestedUser, _ = url.QueryUnescape(r.URL.Query().Get("username"))
|
||||
}
|
||||
if requestedUser != r.Header.Get("user") {
|
||||
logger.Log(0, "next 2", r.URL.String(), errorResponse.Message)
|
||||
ReturnErrorResponse(w, r, errorResponse)
|
||||
return
|
||||
}
|
||||
|
|
75
logic/user_mgmt.go
Normal file
75
logic/user_mgmt.go
Normal file
|
@ -0,0 +1,75 @@
|
|||
package logic
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/gravitl/netmaker/database"
|
||||
"github.com/gravitl/netmaker/models"
|
||||
)
|
||||
|
||||
// Pre-Define Permission Templates for default Roles
|
||||
var SuperAdminPermissionTemplate = models.UserRolePermissionTemplate{
|
||||
ID: models.SuperAdminRole,
|
||||
Default: true,
|
||||
FullAccess: true,
|
||||
}
|
||||
|
||||
var AdminPermissionTemplate = models.UserRolePermissionTemplate{
|
||||
ID: models.AdminRole,
|
||||
Default: true,
|
||||
FullAccess: true,
|
||||
}
|
||||
|
||||
var GetFilteredNodesByUserAccess = func(user models.User, nodes []models.Node) (filteredNodes []models.Node) {
|
||||
return
|
||||
}
|
||||
|
||||
var CreateRole = func(r models.UserRolePermissionTemplate) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
var DeleteRole = func(r models.UserRoleID, force bool) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
var FilterNetworksByRole = func(allnetworks []models.Network, user models.User) []models.Network {
|
||||
return allnetworks
|
||||
}
|
||||
|
||||
var IsGroupsValid = func(groups map[models.UserGroupID]struct{}) error {
|
||||
return nil
|
||||
}
|
||||
var IsNetworkRolesValid = func(networkRoles map[models.NetworkID]map[models.UserRoleID]struct{}) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
var UpdateUserGwAccess = func(currentUser, changeUser models.User) {}
|
||||
|
||||
var UpdateRole = func(r models.UserRolePermissionTemplate) error { return nil }
|
||||
|
||||
var InitialiseRoles = userRolesInit
|
||||
var DeleteNetworkRoles = func(netID string) {}
|
||||
var CreateDefaultNetworkRolesAndGroups = func(netID models.NetworkID) {}
|
||||
|
||||
// GetRole - fetches role template by id
|
||||
func GetRole(roleID models.UserRoleID) (models.UserRolePermissionTemplate, error) {
|
||||
// check if role already exists
|
||||
data, err := database.FetchRecord(database.USER_PERMISSIONS_TABLE_NAME, roleID.String())
|
||||
if err != nil {
|
||||
return models.UserRolePermissionTemplate{}, err
|
||||
}
|
||||
ur := models.UserRolePermissionTemplate{}
|
||||
err = json.Unmarshal([]byte(data), &ur)
|
||||
if err != nil {
|
||||
return ur, err
|
||||
}
|
||||
return ur, nil
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
}
|
|
@ -41,10 +41,13 @@ func GetReturnUser(username string) (models.ReturnUser, error) {
|
|||
// ToReturnUser - gets a user as a return user
|
||||
func ToReturnUser(user models.User) models.ReturnUser {
|
||||
return models.ReturnUser{
|
||||
UserName: user.UserName,
|
||||
IsSuperAdmin: user.IsSuperAdmin,
|
||||
IsAdmin: user.IsAdmin,
|
||||
RemoteGwIDs: user.RemoteGwIDs,
|
||||
UserName: user.UserName,
|
||||
PlatformRoleID: user.PlatformRoleID,
|
||||
AuthType: user.AuthType,
|
||||
UserGroups: user.UserGroups,
|
||||
NetworkRoles: user.NetworkRoles,
|
||||
RemoteGwIDs: user.RemoteGwIDs,
|
||||
LastLoginTime: user.LastLoginTime,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -53,6 +56,12 @@ func SetUserDefaults(user *models.User) {
|
|||
if user.RemoteGwIDs == nil {
|
||||
user.RemoteGwIDs = make(map[string]struct{})
|
||||
}
|
||||
if len(user.NetworkRoles) == 0 {
|
||||
user.NetworkRoles = make(map[models.NetworkID]map[models.UserRoleID]struct{})
|
||||
}
|
||||
if len(user.UserGroups) == 0 {
|
||||
user.UserGroups = make(map[models.UserGroupID]struct{})
|
||||
}
|
||||
}
|
||||
|
||||
// SortUsers - Sorts slice of Users by username
|
||||
|
@ -119,3 +128,66 @@ func ListPendingUsers() ([]models.ReturnUser, error) {
|
|||
}
|
||||
return pendingUsers, nil
|
||||
}
|
||||
|
||||
func GetUserMap() (map[string]models.User, error) {
|
||||
userMap := make(map[string]models.User)
|
||||
records, err := database.FetchRecords(database.USERS_TABLE_NAME)
|
||||
if err != nil && !database.IsEmptyRecord(err) {
|
||||
return userMap, err
|
||||
}
|
||||
for _, record := range records {
|
||||
u := models.User{}
|
||||
err = json.Unmarshal([]byte(record), &u)
|
||||
if err == nil {
|
||||
userMap[u.UserName] = u
|
||||
}
|
||||
}
|
||||
return userMap, nil
|
||||
}
|
||||
|
||||
func InsertUserInvite(invite models.UserInvite) error {
|
||||
data, err := json.Marshal(invite)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return database.Insert(invite.Email, string(data), database.USER_INVITES_TABLE_NAME)
|
||||
}
|
||||
|
||||
func GetUserInvite(email string) (in models.UserInvite, err error) {
|
||||
d, err := database.FetchRecord(database.USER_INVITES_TABLE_NAME, email)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = json.Unmarshal([]byte(d), &in)
|
||||
return
|
||||
}
|
||||
|
||||
func ListUserInvites() ([]models.UserInvite, error) {
|
||||
invites := []models.UserInvite{}
|
||||
records, err := database.FetchRecords(database.USER_INVITES_TABLE_NAME)
|
||||
if err != nil && !database.IsEmptyRecord(err) {
|
||||
return invites, err
|
||||
}
|
||||
for _, record := range records {
|
||||
in := models.UserInvite{}
|
||||
err = json.Unmarshal([]byte(record), &in)
|
||||
if err == nil {
|
||||
invites = append(invites, in)
|
||||
}
|
||||
}
|
||||
return invites, nil
|
||||
}
|
||||
|
||||
func DeleteUserInvite(email string) error {
|
||||
return database.DeleteRecord(database.USER_INVITES_TABLE_NAME, email)
|
||||
}
|
||||
func ValidateAndApproveUserInvite(email, code string) error {
|
||||
in, err := GetUserInvite(email)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if code != in.InviteCode {
|
||||
return errors.New("invalid code")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
2
main.go
2
main.go
|
@ -102,7 +102,7 @@ func initialize() { // Client Mode Prereq Check
|
|||
migrate.Run()
|
||||
|
||||
logic.SetJWTSecret()
|
||||
|
||||
logic.InitialiseRoles()
|
||||
err = serverctl.SetDefaults()
|
||||
if err != nil {
|
||||
logger.FatalLog("error setting defaults: ", err.Error())
|
||||
|
|
|
@ -21,6 +21,7 @@ import (
|
|||
func Run() {
|
||||
updateEnrollmentKeys()
|
||||
assignSuperAdmin()
|
||||
syncUsers()
|
||||
updateHosts()
|
||||
updateNodes()
|
||||
updateAcls()
|
||||
|
@ -43,8 +44,7 @@ func assignSuperAdmin() {
|
|||
if err != nil {
|
||||
log.Fatal("error getting user", "user", owner, "error", err.Error())
|
||||
}
|
||||
user.IsSuperAdmin = true
|
||||
user.IsAdmin = false
|
||||
user.PlatformRoleID = models.SuperAdminRole
|
||||
err = logic.UpsertUser(*user)
|
||||
if err != nil {
|
||||
log.Fatal(
|
||||
|
@ -64,8 +64,8 @@ func assignSuperAdmin() {
|
|||
slog.Error("error getting user", "user", u.UserName, "error", err.Error())
|
||||
continue
|
||||
}
|
||||
user.PlatformRoleID = models.SuperAdminRole
|
||||
user.IsSuperAdmin = true
|
||||
user.IsAdmin = false
|
||||
err = logic.UpsertUser(*user)
|
||||
if err != nil {
|
||||
slog.Error(
|
||||
|
@ -311,3 +311,109 @@ func MigrateEmqx() {
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
func syncUsers() {
|
||||
// create default network user roles for existing networks
|
||||
if servercfg.IsPro {
|
||||
networks, _ := logic.GetNetworks()
|
||||
nodes, err := logic.GetAllNodes()
|
||||
if err == nil {
|
||||
for _, netI := range networks {
|
||||
networkNodes := logic.GetNetworkNodesMemory(nodes, netI.NetID)
|
||||
for _, networkNodeI := range networkNodes {
|
||||
if networkNodeI.IsIngressGateway {
|
||||
h, err := logic.GetHost(networkNodeI.HostID.String())
|
||||
if err == nil {
|
||||
logic.CreateRole(models.UserRolePermissionTemplate{
|
||||
ID: models.GetRAGRoleID(networkNodeI.Network, h.ID.String()),
|
||||
UiName: models.GetRAGRoleName(networkNodeI.Network, h.Name),
|
||||
NetworkID: models.NetworkID(netI.NetID),
|
||||
NetworkLevelAccess: map[models.RsrcType]map[models.RsrcID]models.RsrcPermissionScope{
|
||||
models.RemoteAccessGwRsrc: {
|
||||
models.RsrcID(networkNodeI.ID.String()): models.RsrcPermissionScope{
|
||||
Read: true,
|
||||
VPNaccess: true,
|
||||
},
|
||||
},
|
||||
models.ExtClientsRsrc: {
|
||||
models.AllExtClientsRsrcID: models.RsrcPermissionScope{
|
||||
Read: true,
|
||||
Create: true,
|
||||
Update: true,
|
||||
Delete: true,
|
||||
SelfOnly: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
users, err := logic.GetUsersDB()
|
||||
if err == nil {
|
||||
for _, user := range users {
|
||||
user := user
|
||||
if user.PlatformRoleID == models.AdminRole && !user.IsAdmin {
|
||||
user.IsAdmin = true
|
||||
logic.UpsertUser(user)
|
||||
}
|
||||
if user.PlatformRoleID == models.SuperAdminRole && !user.IsSuperAdmin {
|
||||
user.IsSuperAdmin = true
|
||||
logic.UpsertUser(user)
|
||||
}
|
||||
if user.PlatformRoleID.String() != "" {
|
||||
continue
|
||||
}
|
||||
user.AuthType = models.BasicAuth
|
||||
if logic.IsOauthUser(&user) == nil {
|
||||
user.AuthType = models.OAuth
|
||||
}
|
||||
if len(user.NetworkRoles) == 0 {
|
||||
user.NetworkRoles = make(map[models.NetworkID]map[models.UserRoleID]struct{})
|
||||
}
|
||||
if len(user.UserGroups) == 0 {
|
||||
user.UserGroups = make(map[models.UserGroupID]struct{})
|
||||
}
|
||||
if user.IsSuperAdmin {
|
||||
user.PlatformRoleID = models.SuperAdminRole
|
||||
|
||||
} else if user.IsAdmin {
|
||||
user.PlatformRoleID = models.AdminRole
|
||||
} else {
|
||||
user.PlatformRoleID = models.ServiceUser
|
||||
}
|
||||
logic.UpsertUser(user)
|
||||
if len(user.RemoteGwIDs) > 0 {
|
||||
// define user roles for network
|
||||
// assign relevant network role to user
|
||||
for remoteGwID := range user.RemoteGwIDs {
|
||||
gwNode, err := logic.GetNodeByID(remoteGwID)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
h, err := logic.GetHost(gwNode.HostID.String())
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
r, err := logic.GetRole(models.GetRAGRoleID(gwNode.Network, h.ID.String()))
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if netRoles, ok := user.NetworkRoles[models.NetworkID(gwNode.Network)]; ok {
|
||||
netRoles[r.ID] = struct{}{}
|
||||
} else {
|
||||
user.NetworkRoles[models.NetworkID(gwNode.Network)] = map[models.UserRoleID]struct{}{
|
||||
r.ID: {},
|
||||
}
|
||||
}
|
||||
}
|
||||
logic.UpsertUser(user)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,39 +23,6 @@ type AuthParams struct {
|
|||
Password string `json:"password"`
|
||||
}
|
||||
|
||||
// User struct - struct for Users
|
||||
type User struct {
|
||||
UserName string `json:"username" bson:"username" validate:"min=3,max=40,in_charset|email"`
|
||||
Password string `json:"password" bson:"password" validate:"required,min=5"`
|
||||
IsAdmin bool `json:"isadmin" bson:"isadmin"`
|
||||
IsSuperAdmin bool `json:"issuperadmin"`
|
||||
RemoteGwIDs map[string]struct{} `json:"remote_gw_ids"`
|
||||
LastLoginTime time.Time `json:"last_login_time"`
|
||||
}
|
||||
|
||||
// ReturnUser - return user struct
|
||||
type ReturnUser struct {
|
||||
UserName string `json:"username"`
|
||||
IsAdmin bool `json:"isadmin"`
|
||||
IsSuperAdmin bool `json:"issuperadmin"`
|
||||
RemoteGwIDs map[string]struct{} `json:"remote_gw_ids"`
|
||||
LastLoginTime time.Time `json:"last_login_time"`
|
||||
}
|
||||
|
||||
// UserAuthParams - user auth params struct
|
||||
type UserAuthParams struct {
|
||||
UserName string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
}
|
||||
|
||||
// UserClaims - user claims struct
|
||||
type UserClaims struct {
|
||||
IsAdmin bool
|
||||
IsSuperAdmin bool
|
||||
UserName string
|
||||
jwt.RegisteredClaims
|
||||
}
|
||||
|
||||
// IngressGwUsers - struct to hold users on a ingress gw
|
||||
type IngressGwUsers struct {
|
||||
NodeID string `json:"node_id"`
|
||||
|
@ -381,3 +348,8 @@ const (
|
|||
type GetClientConfReqDto struct {
|
||||
PreferredIp string `json:"preferred_ip"`
|
||||
}
|
||||
|
||||
type RsrcURLInfo struct {
|
||||
Method string
|
||||
Path string
|
||||
}
|
||||
|
|
199
models/user_mgmt.go
Normal file
199
models/user_mgmt.go
Normal file
|
@ -0,0 +1,199 @@
|
|||
package models
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
jwt "github.com/golang-jwt/jwt/v4"
|
||||
)
|
||||
|
||||
type NetworkID string
|
||||
type RsrcType string
|
||||
type RsrcID string
|
||||
type UserRoleID string
|
||||
type UserGroupID string
|
||||
type AuthType string
|
||||
|
||||
var (
|
||||
BasicAuth AuthType = "basic_auth"
|
||||
OAuth AuthType = "oauth"
|
||||
)
|
||||
|
||||
func (r RsrcType) String() string {
|
||||
return string(r)
|
||||
}
|
||||
|
||||
func (rid RsrcID) String() string {
|
||||
return string(rid)
|
||||
}
|
||||
|
||||
func GetRAGRoleName(netID, hostName string) string {
|
||||
return fmt.Sprintf("netID-%s-rag-%s", netID, hostName)
|
||||
}
|
||||
|
||||
func GetRAGRoleID(netID, hostID string) UserRoleID {
|
||||
return UserRoleID(fmt.Sprintf("netID-%s-rag-%s", netID, hostID))
|
||||
}
|
||||
|
||||
var RsrcTypeMap = map[RsrcType]struct{}{
|
||||
HostRsrc: {},
|
||||
RelayRsrc: {},
|
||||
RemoteAccessGwRsrc: {},
|
||||
ExtClientsRsrc: {},
|
||||
InetGwRsrc: {},
|
||||
EgressGwRsrc: {},
|
||||
NetworkRsrc: {},
|
||||
EnrollmentKeysRsrc: {},
|
||||
UserRsrc: {},
|
||||
AclRsrc: {},
|
||||
DnsRsrc: {},
|
||||
FailOverRsrc: {},
|
||||
}
|
||||
|
||||
const AllNetworks NetworkID = "all_networks"
|
||||
const (
|
||||
HostRsrc RsrcType = "hosts"
|
||||
RelayRsrc RsrcType = "relays"
|
||||
RemoteAccessGwRsrc RsrcType = "remote_access_gw"
|
||||
ExtClientsRsrc RsrcType = "extclients"
|
||||
InetGwRsrc RsrcType = "inet_gw"
|
||||
EgressGwRsrc RsrcType = "egress"
|
||||
NetworkRsrc RsrcType = "networks"
|
||||
EnrollmentKeysRsrc RsrcType = "enrollment_key"
|
||||
UserRsrc RsrcType = "users"
|
||||
AclRsrc RsrcType = "acl"
|
||||
DnsRsrc RsrcType = "dns"
|
||||
FailOverRsrc RsrcType = "fail_over"
|
||||
MetricRsrc RsrcType = "metrics"
|
||||
)
|
||||
|
||||
const (
|
||||
AllHostRsrcID RsrcID = "all_host"
|
||||
AllRelayRsrcID RsrcID = "all_relay"
|
||||
AllRemoteAccessGwRsrcID RsrcID = "all_remote_access_gw"
|
||||
AllExtClientsRsrcID RsrcID = "all_extclients"
|
||||
AllInetGwRsrcID RsrcID = "all_inet_gw"
|
||||
AllEgressGwRsrcID RsrcID = "all_egress"
|
||||
AllNetworkRsrcID RsrcID = "all_network"
|
||||
AllEnrollmentKeysRsrcID RsrcID = "all_enrollment_key"
|
||||
AllUserRsrcID RsrcID = "all_user"
|
||||
AllDnsRsrcID RsrcID = "all_dns"
|
||||
AllFailOverRsrcID RsrcID = "all_fail_over"
|
||||
AllAclsRsrcID RsrcID = "all_acls"
|
||||
)
|
||||
|
||||
// Pre-Defined User Roles
|
||||
|
||||
const (
|
||||
SuperAdminRole UserRoleID = "super-admin"
|
||||
AdminRole UserRoleID = "admin"
|
||||
ServiceUser UserRoleID = "service-user"
|
||||
PlatformUser UserRoleID = "platform-user"
|
||||
NetworkAdmin UserRoleID = "network-admin"
|
||||
NetworkUser UserRoleID = "network-user"
|
||||
)
|
||||
|
||||
func (r UserRoleID) String() string {
|
||||
return string(r)
|
||||
}
|
||||
|
||||
func (g UserGroupID) String() string {
|
||||
return string(g)
|
||||
}
|
||||
|
||||
func (n NetworkID) String() string {
|
||||
return string(n)
|
||||
}
|
||||
|
||||
type RsrcPermissionScope struct {
|
||||
Create bool `json:"create"`
|
||||
Read bool `json:"read"`
|
||||
Update bool `json:"update"`
|
||||
Delete bool `json:"delete"`
|
||||
VPNaccess bool `json:"vpn_access"`
|
||||
SelfOnly bool `json:"self_only"`
|
||||
}
|
||||
|
||||
type UserRolePermissionTemplate struct {
|
||||
ID UserRoleID `json:"id"`
|
||||
UiName string `json:"ui_name"`
|
||||
Default bool `json:"default"`
|
||||
DenyDashboardAccess bool `json:"deny_dashboard_access"`
|
||||
FullAccess bool `json:"full_access"`
|
||||
NetworkID NetworkID `json:"network_id"`
|
||||
NetworkLevelAccess map[RsrcType]map[RsrcID]RsrcPermissionScope `json:"network_level_access"`
|
||||
GlobalLevelAccess map[RsrcType]map[RsrcID]RsrcPermissionScope `json:"global_level_access"`
|
||||
}
|
||||
|
||||
type CreateGroupReq struct {
|
||||
Group UserGroup `json:"user_group"`
|
||||
Members []string `json:"members"`
|
||||
}
|
||||
|
||||
type UserGroup struct {
|
||||
ID UserGroupID `json:"id"`
|
||||
NetworkRoles map[NetworkID]map[UserRoleID]struct{} `json:"network_roles"`
|
||||
MetaData string `json:"meta_data"`
|
||||
}
|
||||
|
||||
// User struct - struct for Users
|
||||
type User struct {
|
||||
UserName string `json:"username" bson:"username" validate:"min=3,max=40,in_charset|email"`
|
||||
Password string `json:"password" bson:"password" validate:"required,min=5"`
|
||||
IsAdmin bool `json:"isadmin" bson:"isadmin"` // deprecated
|
||||
IsSuperAdmin bool `json:"issuperadmin"` // deprecated
|
||||
RemoteGwIDs map[string]struct{} `json:"remote_gw_ids"` // deprecated
|
||||
AuthType AuthType `json:"auth_type"`
|
||||
UserGroups map[UserGroupID]struct{} `json:"user_group_ids"`
|
||||
PlatformRoleID UserRoleID `json:"platform_role_id"`
|
||||
NetworkRoles map[NetworkID]map[UserRoleID]struct{} `json:"network_roles"`
|
||||
LastLoginTime time.Time `json:"last_login_time"`
|
||||
}
|
||||
|
||||
type ReturnUserWithRolesAndGroups struct {
|
||||
ReturnUser
|
||||
PlatformRole UserRolePermissionTemplate `json:"platform_role"`
|
||||
}
|
||||
|
||||
// ReturnUser - return user struct
|
||||
type ReturnUser struct {
|
||||
UserName string `json:"username"`
|
||||
IsAdmin bool `json:"isadmin"`
|
||||
IsSuperAdmin bool `json:"issuperadmin"`
|
||||
AuthType AuthType `json:"auth_type"`
|
||||
RemoteGwIDs map[string]struct{} `json:"remote_gw_ids"` // deprecated
|
||||
UserGroups map[UserGroupID]struct{} `json:"user_group_ids"`
|
||||
PlatformRoleID UserRoleID `json:"platform_role_id"`
|
||||
NetworkRoles map[NetworkID]map[UserRoleID]struct{} `json:"network_roles"`
|
||||
LastLoginTime time.Time `json:"last_login_time"`
|
||||
}
|
||||
|
||||
// UserAuthParams - user auth params struct
|
||||
type UserAuthParams struct {
|
||||
UserName string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
}
|
||||
|
||||
// UserClaims - user claims struct
|
||||
type UserClaims struct {
|
||||
Role UserRoleID
|
||||
UserName string
|
||||
jwt.RegisteredClaims
|
||||
}
|
||||
|
||||
type InviteUsersReq struct {
|
||||
UserEmails []string `json:"user_emails"`
|
||||
PlatformRoleID string `json:"platform_role_id"`
|
||||
UserGroups map[UserGroupID]struct{} `json:"user_group_ids"`
|
||||
NetworkRoles map[NetworkID]map[UserRoleID]struct{} `json:"network_roles"`
|
||||
}
|
||||
|
||||
// UserInvite - model for user invite
|
||||
type UserInvite struct {
|
||||
Email string `json:"email"`
|
||||
PlatformRoleID string `json:"platform_role_id"`
|
||||
UserGroups map[UserGroupID]struct{} `json:"user_group_ids"`
|
||||
NetworkRoles map[NetworkID]map[UserRoleID]struct{} `json:"network_roles"`
|
||||
InviteCode string `json:"invite_code"`
|
||||
InviteURL string `json:"invite_url"`
|
||||
}
|
|
@ -8,11 +8,11 @@ import (
|
|||
"net/http"
|
||||
"strings"
|
||||
|
||||
"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"
|
||||
proLogic "github.com/gravitl/netmaker/pro/logic"
|
||||
"github.com/gravitl/netmaker/servercfg"
|
||||
"golang.org/x/oauth2"
|
||||
"golang.org/x/oauth2/microsoft"
|
||||
|
@ -67,27 +67,50 @@ func handleAzureCallback(w http.ResponseWriter, r *http.Request) {
|
|||
handleOauthNotConfigured(w)
|
||||
return
|
||||
}
|
||||
if !isEmailAllowed(content.UserPrincipalName) {
|
||||
handleOauthUserNotAllowedToSignUp(w)
|
||||
return
|
||||
|
||||
var inviteExists bool
|
||||
// check if invite exists for User
|
||||
in, err := logic.GetUserInvite(content.UserPrincipalName)
|
||||
if err == nil {
|
||||
inviteExists = true
|
||||
}
|
||||
// check if user approval is already pending
|
||||
if logic.IsPendingUser(content.UserPrincipalName) {
|
||||
if !inviteExists && logic.IsPendingUser(content.UserPrincipalName) {
|
||||
handleOauthUserSignUpApprovalPending(w)
|
||||
return
|
||||
}
|
||||
|
||||
_, err = logic.GetUser(content.UserPrincipalName)
|
||||
if err != nil {
|
||||
if database.IsEmptyRecord(err) { // user must not exist, so try to make one
|
||||
err = logic.InsertPendingUser(&models.User{
|
||||
UserName: content.UserPrincipalName,
|
||||
})
|
||||
if err != nil {
|
||||
handleSomethingWentWrong(w)
|
||||
if inviteExists {
|
||||
// create user
|
||||
user, err := proLogic.PrepareOauthUserFromInvite(in)
|
||||
if err != nil {
|
||||
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
|
||||
return
|
||||
}
|
||||
if err = logic.CreateUser(&user); err != nil {
|
||||
handleSomethingWentWrong(w)
|
||||
return
|
||||
}
|
||||
logic.DeleteUserInvite(user.UserName)
|
||||
logic.DeletePendingUser(content.UserPrincipalName)
|
||||
} else {
|
||||
if !isEmailAllowed(content.UserPrincipalName) {
|
||||
handleOauthUserNotAllowedToSignUp(w)
|
||||
return
|
||||
}
|
||||
err = logic.InsertPendingUser(&models.User{
|
||||
UserName: content.UserPrincipalName,
|
||||
})
|
||||
if err != nil {
|
||||
handleSomethingWentWrong(w)
|
||||
return
|
||||
}
|
||||
handleFirstTimeOauthUserSignUp(w)
|
||||
return
|
||||
}
|
||||
handleFirstTimeOauthUserSignUp(w)
|
||||
return
|
||||
} else {
|
||||
handleSomethingWentWrong(w)
|
||||
return
|
||||
|
@ -98,11 +121,16 @@ func handleAzureCallback(w http.ResponseWriter, r *http.Request) {
|
|||
handleOauthUserNotFound(w)
|
||||
return
|
||||
}
|
||||
if !(user.IsSuperAdmin || user.IsAdmin) {
|
||||
userRole, err := logic.GetRole(user.PlatformRoleID)
|
||||
if err != nil {
|
||||
handleSomethingWentWrong(w)
|
||||
return
|
||||
}
|
||||
if userRole.DenyDashboardAccess {
|
||||
handleOauthUserNotAllowed(w)
|
||||
return
|
||||
}
|
||||
var newPass, fetchErr = auth.FetchPassValue("")
|
||||
var newPass, fetchErr = logic.FetchPassValue("")
|
||||
if fetchErr != nil {
|
||||
return
|
||||
}
|
||||
|
|
|
@ -1,60 +1,114 @@
|
|||
package auth
|
||||
|
||||
import "net/http"
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/gravitl/netmaker/servercfg"
|
||||
)
|
||||
|
||||
var htmlBaseTemplate = `<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes">
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||
<title>Netmaker :: SSO</title>
|
||||
<script type="text/javascript">
|
||||
function redirect()
|
||||
{
|
||||
window.location.href="` + servercfg.GetFrontendURL() + `";
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
html,
|
||||
body {
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
body {
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
#logo {
|
||||
width: 150px;
|
||||
}
|
||||
|
||||
h3 {
|
||||
margin-bottom: 3rem;
|
||||
color: rgb(25, 135, 84);
|
||||
font-size: xx-large;
|
||||
}
|
||||
|
||||
h4 {
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-top: 0px;
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
.back-to-login-btn {
|
||||
background: #5E5DF0;
|
||||
border-radius: 999px;
|
||||
box-shadow: #5E5DF0 0 10px 20px -10px;
|
||||
box-sizing: border-box;
|
||||
color: #FFFFFF;
|
||||
cursor: pointer;
|
||||
font-family: Inter,Helvetica,"Apple Color Emoji","Segoe UI Emoji",NotoColorEmoji,"Noto Color Emoji","Segoe UI Symbol","Android Emoji",EmojiSymbols,-apple-system,system-ui,"Segoe UI",Roboto,"Helvetica Neue","Noto Sans",sans-serif;
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
line-height: 24px;
|
||||
opacity: 1;
|
||||
outline: 0 solid transparent;
|
||||
padding: 8px 18px;
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
touch-action: manipulation;
|
||||
width: fit-content;
|
||||
word-break: break-word;
|
||||
border: 0;
|
||||
margin: 20px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
// == define error HTML here ==
|
||||
const oauthNotConfigured = `<!DOCTYPE html><html>
|
||||
<body>
|
||||
<h3>Your Netmaker server does not have OAuth configured.</h3>
|
||||
<p>Please visit the docs <a href="https://docs.netmaker.org/oauth.html" target="_blank" rel="noopener">here</a> to learn how to.</p>
|
||||
<img
|
||||
src="https://raw.githubusercontent.com/gravitl/netmaker-docs/master/images/netmaker-github/netmaker-teal.png"
|
||||
alt="netmaker logo"
|
||||
id="logo"
|
||||
>
|
||||
%s
|
||||
<button class="back-to-login-btn" onClick="redirect()" role="button">Back To Login</button>
|
||||
|
||||
</body>
|
||||
</html>`
|
||||
|
||||
const oauthStateInvalid = `<!DOCTYPE html><html>
|
||||
<body>
|
||||
<h3>Invalid OAuth Session. Please re-try again.</h3>
|
||||
</body>
|
||||
</html>`
|
||||
var oauthNotConfigured = fmt.Sprintf(htmlBaseTemplate, `<h2>Your Netmaker server does not have OAuth configured.</h2>
|
||||
<p>Please visit the docs <a href="https://docs.netmaker.org/oauth.html" target="_blank" rel="noopener">here</a> to learn how to.</p>`)
|
||||
|
||||
const userNotAllowed = `<!DOCTYPE html><html>
|
||||
<body>
|
||||
<h3>Only administrators can access the Dashboard. Please contact your administrator to elevate your account.</h3>
|
||||
<p>Non-Admins can access the netmaker networks using <a href="https://docs.netmaker.io/pro/rac.html" target="_blank" rel="noopener">RemoteAccessClient.</a></p>
|
||||
</body>
|
||||
</html>
|
||||
`
|
||||
var oauthStateInvalid = fmt.Sprintf(htmlBaseTemplate, `<h2>Invalid OAuth Session. Please re-try again.</h2>`)
|
||||
|
||||
const userFirstTimeSignUp = `<!DOCTYPE html><html>
|
||||
<body>
|
||||
<h3>Thank you for signing up. Please contact your administrator for access.</h3>
|
||||
</body>
|
||||
</html>
|
||||
`
|
||||
var userNotAllowed = fmt.Sprintf(htmlBaseTemplate, `<h2>Your account does not have access to the dashboard. Please contact your administrator for more information about your account.</h2>
|
||||
<p>Non-Admins can access the netmaker networks using <a href="https://docs.netmaker.io/pro/rac.html" target="_blank" rel="noopener">RemoteAccessClient.</a></p>`)
|
||||
|
||||
const userSignUpApprovalPending = `<!DOCTYPE html><html>
|
||||
<body>
|
||||
<h3>Your account is yet to be approved. Please contact your administrator for access.</h3>
|
||||
</body>
|
||||
</html>
|
||||
`
|
||||
var userFirstTimeSignUp = fmt.Sprintf(htmlBaseTemplate, `<h2>Thank you for signing up. Please contact your administrator for access.</h2>`)
|
||||
|
||||
const userNotFound = `<!DOCTYPE html><html>
|
||||
<body>
|
||||
<h3>User Not Found.</h3>
|
||||
</body>
|
||||
</html>`
|
||||
var userSignUpApprovalPending = fmt.Sprintf(htmlBaseTemplate, `<h2>Your account is yet to be approved. Please contact your administrator for access.</h2>`)
|
||||
|
||||
const somethingwentwrong = `<!DOCTYPE html><html>
|
||||
<body>
|
||||
<h3>Something went wrong. Contact Admin.</h3>
|
||||
</body>
|
||||
</html>`
|
||||
var userNotFound = fmt.Sprintf(htmlBaseTemplate, `<h2>User Not Found.</h2>`)
|
||||
|
||||
const notallowedtosignup = `<!DOCTYPE html><html>
|
||||
<body>
|
||||
<h3>Your email is not allowed. Please contact your administrator.</h3>
|
||||
</body>
|
||||
</html>`
|
||||
var somethingwentwrong = fmt.Sprintf(htmlBaseTemplate, `<h2>Something went wrong. Contact Admin.</h2>`)
|
||||
|
||||
var notallowedtosignup = fmt.Sprintf(htmlBaseTemplate, `<h2>Your email is not allowed. Please contact your administrator.</h2>`)
|
||||
|
||||
func handleOauthUserNotFound(response http.ResponseWriter) {
|
||||
response.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
|
|
|
@ -8,11 +8,11 @@ import (
|
|||
"net/http"
|
||||
"strings"
|
||||
|
||||
"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"
|
||||
proLogic "github.com/gravitl/netmaker/pro/logic"
|
||||
"github.com/gravitl/netmaker/servercfg"
|
||||
"golang.org/x/oauth2"
|
||||
"golang.org/x/oauth2/github"
|
||||
|
@ -67,27 +67,49 @@ func handleGithubCallback(w http.ResponseWriter, r *http.Request) {
|
|||
handleOauthNotConfigured(w)
|
||||
return
|
||||
}
|
||||
if !isEmailAllowed(content.Login) {
|
||||
handleOauthUserNotAllowedToSignUp(w)
|
||||
return
|
||||
|
||||
var inviteExists bool
|
||||
// check if invite exists for User
|
||||
in, err := logic.GetUserInvite(content.Login)
|
||||
if err == nil {
|
||||
inviteExists = true
|
||||
}
|
||||
// check if user approval is already pending
|
||||
if logic.IsPendingUser(content.Login) {
|
||||
if !inviteExists && logic.IsPendingUser(content.Login) {
|
||||
handleOauthUserSignUpApprovalPending(w)
|
||||
return
|
||||
}
|
||||
_, err = logic.GetUser(content.Login)
|
||||
if err != nil {
|
||||
if database.IsEmptyRecord(err) { // user must not exist, so try to make one
|
||||
err = logic.InsertPendingUser(&models.User{
|
||||
UserName: content.Login,
|
||||
})
|
||||
if err != nil {
|
||||
handleSomethingWentWrong(w)
|
||||
if inviteExists {
|
||||
// create user
|
||||
user, err := proLogic.PrepareOauthUserFromInvite(in)
|
||||
if err != nil {
|
||||
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
|
||||
return
|
||||
}
|
||||
if err = logic.CreateUser(&user); err != nil {
|
||||
handleSomethingWentWrong(w)
|
||||
return
|
||||
}
|
||||
logic.DeleteUserInvite(user.UserName)
|
||||
logic.DeletePendingUser(content.Login)
|
||||
} else {
|
||||
if !isEmailAllowed(content.Login) {
|
||||
handleOauthUserNotAllowedToSignUp(w)
|
||||
return
|
||||
}
|
||||
err = logic.InsertPendingUser(&models.User{
|
||||
UserName: content.Login,
|
||||
})
|
||||
if err != nil {
|
||||
handleSomethingWentWrong(w)
|
||||
return
|
||||
}
|
||||
handleFirstTimeOauthUserSignUp(w)
|
||||
return
|
||||
}
|
||||
handleFirstTimeOauthUserSignUp(w)
|
||||
return
|
||||
} else {
|
||||
handleSomethingWentWrong(w)
|
||||
return
|
||||
|
@ -98,11 +120,16 @@ func handleGithubCallback(w http.ResponseWriter, r *http.Request) {
|
|||
handleOauthUserNotFound(w)
|
||||
return
|
||||
}
|
||||
if !(user.IsSuperAdmin || user.IsAdmin) {
|
||||
userRole, err := logic.GetRole(user.PlatformRoleID)
|
||||
if err != nil {
|
||||
handleSomethingWentWrong(w)
|
||||
return
|
||||
}
|
||||
if userRole.DenyDashboardAccess {
|
||||
handleOauthUserNotAllowed(w)
|
||||
return
|
||||
}
|
||||
var newPass, fetchErr = auth.FetchPassValue("")
|
||||
var newPass, fetchErr = logic.FetchPassValue("")
|
||||
if fetchErr != nil {
|
||||
return
|
||||
}
|
||||
|
|
|
@ -9,11 +9,11 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"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"
|
||||
proLogic "github.com/gravitl/netmaker/pro/logic"
|
||||
"github.com/gravitl/netmaker/servercfg"
|
||||
"golang.org/x/oauth2"
|
||||
"golang.org/x/oauth2/google"
|
||||
|
@ -45,7 +45,7 @@ func handleGoogleLogin(w http.ResponseWriter, r *http.Request) {
|
|||
handleOauthNotConfigured(w)
|
||||
return
|
||||
}
|
||||
|
||||
logger.Log(0, "Setting OAuth State ", oauth_state_string)
|
||||
if err := logic.SetState(oauth_state_string); err != nil {
|
||||
handleOauthNotConfigured(w)
|
||||
return
|
||||
|
@ -58,7 +58,7 @@ func handleGoogleLogin(w http.ResponseWriter, r *http.Request) {
|
|||
func handleGoogleCallback(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
var rState, rCode = getStateAndCode(r)
|
||||
|
||||
logger.Log(0, "Fetched OAuth State ", rState)
|
||||
var content, err = getGoogleUserInfo(rState, rCode)
|
||||
if err != nil {
|
||||
logger.Log(1, "error when getting user info from google:", err.Error())
|
||||
|
@ -69,43 +69,78 @@ func handleGoogleCallback(w http.ResponseWriter, r *http.Request) {
|
|||
handleOauthNotConfigured(w)
|
||||
return
|
||||
}
|
||||
if !isEmailAllowed(content.Email) {
|
||||
handleOauthUserNotAllowedToSignUp(w)
|
||||
return
|
||||
logger.Log(0, "CALLBACK ----> 1")
|
||||
|
||||
logger.Log(0, "CALLBACK ----> 2")
|
||||
var inviteExists bool
|
||||
// check if invite exists for User
|
||||
in, err := logic.GetUserInvite(content.Email)
|
||||
if err == nil {
|
||||
inviteExists = true
|
||||
}
|
||||
logger.Log(0, fmt.Sprintf("CALLBACK ----> 3 %v", inviteExists))
|
||||
// check if user approval is already pending
|
||||
if logic.IsPendingUser(content.Email) {
|
||||
if !inviteExists && logic.IsPendingUser(content.Email) {
|
||||
handleOauthUserSignUpApprovalPending(w)
|
||||
return
|
||||
}
|
||||
logger.Log(0, "CALLBACK ----> 4")
|
||||
_, err = logic.GetUser(content.Email)
|
||||
if err != nil {
|
||||
if database.IsEmptyRecord(err) { // user must not exist, so try to make one
|
||||
err = logic.InsertPendingUser(&models.User{
|
||||
UserName: content.Email,
|
||||
})
|
||||
if err != nil {
|
||||
handleSomethingWentWrong(w)
|
||||
if inviteExists {
|
||||
// create user
|
||||
user, err := proLogic.PrepareOauthUserFromInvite(in)
|
||||
if err != nil {
|
||||
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
|
||||
return
|
||||
}
|
||||
logger.Log(0, "CALLBACK ----> 4.0")
|
||||
|
||||
if err = logic.CreateUser(&user); err != nil {
|
||||
handleSomethingWentWrong(w)
|
||||
return
|
||||
}
|
||||
logic.DeleteUserInvite(user.UserName)
|
||||
logic.DeletePendingUser(content.Email)
|
||||
} else {
|
||||
if !isEmailAllowed(content.Email) {
|
||||
handleOauthUserNotAllowedToSignUp(w)
|
||||
return
|
||||
}
|
||||
err = logic.InsertPendingUser(&models.User{
|
||||
UserName: content.Email,
|
||||
})
|
||||
if err != nil {
|
||||
handleSomethingWentWrong(w)
|
||||
return
|
||||
}
|
||||
handleFirstTimeOauthUserSignUp(w)
|
||||
return
|
||||
}
|
||||
handleFirstTimeOauthUserSignUp(w)
|
||||
return
|
||||
|
||||
} else {
|
||||
handleSomethingWentWrong(w)
|
||||
return
|
||||
}
|
||||
}
|
||||
logger.Log(0, "CALLBACK ----> 6")
|
||||
user, err := logic.GetUser(content.Email)
|
||||
if err != nil {
|
||||
logger.Log(0, "error fetching user: ", err.Error())
|
||||
handleOauthUserNotFound(w)
|
||||
return
|
||||
}
|
||||
if !(user.IsSuperAdmin || user.IsAdmin) {
|
||||
userRole, err := logic.GetRole(user.PlatformRoleID)
|
||||
if err != nil {
|
||||
handleSomethingWentWrong(w)
|
||||
return
|
||||
}
|
||||
if userRole.DenyDashboardAccess {
|
||||
handleOauthUserNotAllowed(w)
|
||||
return
|
||||
}
|
||||
var newPass, fetchErr = auth.FetchPassValue("")
|
||||
var newPass, fetchErr = logic.FetchPassValue("")
|
||||
if fetchErr != nil {
|
||||
return
|
||||
}
|
||||
|
@ -151,6 +186,7 @@ func getGoogleUserInfo(state string, code string) (*OAuthUser, error) {
|
|||
if err != nil {
|
||||
return nil, fmt.Errorf("failed reading response body: %s", err.Error())
|
||||
}
|
||||
logger.Log(0, fmt.Sprintf("---------------> USERINFO: %v, token: %s", string(contents), token.AccessToken))
|
||||
var userInfo = &OAuthUser{}
|
||||
if err = json.Unmarshal(contents, userInfo); err != nil {
|
||||
return nil, fmt.Errorf("failed parsing email from response data: %s", err.Error())
|
||||
|
|
|
@ -5,7 +5,6 @@ import (
|
|||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/gravitl/netmaker/auth"
|
||||
"github.com/gravitl/netmaker/database"
|
||||
"github.com/gravitl/netmaker/logger"
|
||||
"github.com/gravitl/netmaker/logic"
|
||||
|
@ -78,7 +77,7 @@ func HandleHeadlessSSOCallback(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
}
|
||||
newPass, fetchErr := auth.FetchPassValue("")
|
||||
newPass, fetchErr := logic.FetchPassValue("")
|
||||
if fetchErr != nil {
|
||||
return
|
||||
}
|
||||
|
|
|
@ -8,11 +8,11 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/coreos/go-oidc/v3/oidc"
|
||||
"github.com/gravitl/netmaker/auth"
|
||||
"github.com/gravitl/netmaker/database"
|
||||
"github.com/gravitl/netmaker/logger"
|
||||
"github.com/gravitl/netmaker/logic"
|
||||
"github.com/gravitl/netmaker/models"
|
||||
proLogic "github.com/gravitl/netmaker/pro/logic"
|
||||
"github.com/gravitl/netmaker/servercfg"
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
@ -80,27 +80,49 @@ func handleOIDCCallback(w http.ResponseWriter, r *http.Request) {
|
|||
handleOauthNotConfigured(w)
|
||||
return
|
||||
}
|
||||
if !isEmailAllowed(content.Email) {
|
||||
handleOauthUserNotAllowedToSignUp(w)
|
||||
return
|
||||
|
||||
var inviteExists bool
|
||||
// check if invite exists for User
|
||||
in, err := logic.GetUserInvite(content.Login)
|
||||
if err == nil {
|
||||
inviteExists = true
|
||||
}
|
||||
// check if user approval is already pending
|
||||
if logic.IsPendingUser(content.Email) {
|
||||
if !inviteExists && logic.IsPendingUser(content.Email) {
|
||||
handleOauthUserSignUpApprovalPending(w)
|
||||
return
|
||||
}
|
||||
_, err = logic.GetUser(content.Email)
|
||||
if err != nil {
|
||||
if database.IsEmptyRecord(err) { // user must not exist, so try to make one
|
||||
err = logic.InsertPendingUser(&models.User{
|
||||
UserName: content.Email,
|
||||
})
|
||||
if err != nil {
|
||||
handleSomethingWentWrong(w)
|
||||
if inviteExists {
|
||||
// create user
|
||||
user, err := proLogic.PrepareOauthUserFromInvite(in)
|
||||
if err != nil {
|
||||
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
|
||||
return
|
||||
}
|
||||
if err = logic.CreateUser(&user); err != nil {
|
||||
handleSomethingWentWrong(w)
|
||||
return
|
||||
}
|
||||
logic.DeleteUserInvite(user.UserName)
|
||||
logic.DeletePendingUser(content.Email)
|
||||
} else {
|
||||
if !isEmailAllowed(content.Email) {
|
||||
handleOauthUserNotAllowedToSignUp(w)
|
||||
return
|
||||
}
|
||||
err = logic.InsertPendingUser(&models.User{
|
||||
UserName: content.Email,
|
||||
})
|
||||
if err != nil {
|
||||
handleSomethingWentWrong(w)
|
||||
return
|
||||
}
|
||||
handleFirstTimeOauthUserSignUp(w)
|
||||
return
|
||||
}
|
||||
handleFirstTimeOauthUserSignUp(w)
|
||||
return
|
||||
} else {
|
||||
handleSomethingWentWrong(w)
|
||||
return
|
||||
|
@ -111,11 +133,16 @@ func handleOIDCCallback(w http.ResponseWriter, r *http.Request) {
|
|||
handleOauthUserNotFound(w)
|
||||
return
|
||||
}
|
||||
if !(user.IsSuperAdmin || user.IsAdmin) {
|
||||
userRole, err := logic.GetRole(user.PlatformRoleID)
|
||||
if err != nil {
|
||||
handleSomethingWentWrong(w)
|
||||
return
|
||||
}
|
||||
if userRole.DenyDashboardAccess {
|
||||
handleOauthUserNotAllowed(w)
|
||||
return
|
||||
}
|
||||
var newPass, fetchErr = auth.FetchPassValue("")
|
||||
var newPass, fetchErr = logic.FetchPassValue("")
|
||||
if fetchErr != nil {
|
||||
return
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ import (
|
|||
"github.com/gravitl/netmaker/logger"
|
||||
"github.com/gravitl/netmaker/logic"
|
||||
"github.com/gravitl/netmaker/logic/pro/netcache"
|
||||
"github.com/gravitl/netmaker/models"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -73,7 +74,7 @@ func HandleHostSSOCallback(w http.ResponseWriter, r *http.Request) {
|
|||
handleOauthUserNotFound(w)
|
||||
return
|
||||
}
|
||||
if !user.IsAdmin && !user.IsSuperAdmin {
|
||||
if user.PlatformRoleID != models.AdminRole && user.PlatformRoleID != models.SuperAdminRole {
|
||||
response := returnErrTemplate(userClaims.getUserName(), "only admin users can register using SSO", state, reqKeyIf)
|
||||
w.WriteHeader(http.StatusForbidden)
|
||||
w.Write(response)
|
||||
|
|
|
@ -19,12 +19,9 @@ import (
|
|||
// RelayHandlers - handle Pro Relays
|
||||
func RelayHandlers(r *mux.Router) {
|
||||
|
||||
r.HandleFunc("/api/nodes/{network}/{nodeid}/createrelay", controller.Authorize(false, true, "user", http.HandlerFunc(createRelay))).
|
||||
Methods(http.MethodPost)
|
||||
r.HandleFunc("/api/nodes/{network}/{nodeid}/deleterelay", controller.Authorize(false, true, "user", http.HandlerFunc(deleteRelay))).
|
||||
Methods(http.MethodDelete)
|
||||
r.HandleFunc("/api/v1/host/{hostid}/failoverme", controller.Authorize(true, false, "host", http.HandlerFunc(failOverME))).
|
||||
Methods(http.MethodPost)
|
||||
r.HandleFunc("/api/nodes/{network}/{nodeid}/createrelay", logic.SecurityCheck(true, http.HandlerFunc(createRelay))).Methods(http.MethodPost)
|
||||
r.HandleFunc("/api/nodes/{network}/{nodeid}/deleterelay", logic.SecurityCheck(true, http.HandlerFunc(deleteRelay))).Methods(http.MethodDelete)
|
||||
r.HandleFunc("/api/v1/host/{hostid}/failoverme", controller.Authorize(true, false, "host", http.HandlerFunc(failOverME))).Methods(http.MethodPost)
|
||||
}
|
||||
|
||||
// @Summary Create a relay
|
||||
|
|
File diff suppressed because it is too large
Load diff
53
pro/email/email.go
Normal file
53
pro/email/email.go
Normal file
|
@ -0,0 +1,53 @@
|
|||
package email
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/gravitl/netmaker/servercfg"
|
||||
)
|
||||
|
||||
type EmailSenderType string
|
||||
|
||||
var client EmailSender
|
||||
|
||||
const (
|
||||
Smtp EmailSenderType = "smtp"
|
||||
Resend EmailSenderType = "resend"
|
||||
)
|
||||
|
||||
func init() {
|
||||
switch EmailSenderType(servercfg.EmailSenderType()) {
|
||||
case Smtp:
|
||||
client = &SmtpSender{
|
||||
SmtpHost: servercfg.GetSmtpHost(),
|
||||
SmtpPort: servercfg.GetSmtpPort(),
|
||||
SenderEmail: servercfg.GetSenderEmail(),
|
||||
SenderPass: servercfg.GetEmaiSenderAuth(),
|
||||
}
|
||||
case Resend:
|
||||
client = NewResendEmailSenderFromConfig()
|
||||
}
|
||||
client = GetClient()
|
||||
}
|
||||
|
||||
// EmailSender - an interface for sending emails based on notifications and mail templates
|
||||
type EmailSender interface {
|
||||
// SendEmail - sends an email based on a context, notification and mail template
|
||||
SendEmail(ctx context.Context, notification Notification, email Mail) error
|
||||
}
|
||||
|
||||
type Mail interface {
|
||||
GetBody(info Notification) string
|
||||
GetSubject(info Notification) string
|
||||
}
|
||||
|
||||
// Notification - struct for notification details
|
||||
type Notification struct {
|
||||
RecipientMail string
|
||||
RecipientName string
|
||||
ProductName string
|
||||
}
|
||||
|
||||
func GetClient() (e EmailSender) {
|
||||
return client
|
||||
}
|
27
pro/email/invite.go
Normal file
27
pro/email/invite.go
Normal file
|
@ -0,0 +1,27 @@
|
|||
package email
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// UserInvitedMail - mail for users that are invited to a tenant
|
||||
type UserInvitedMail struct {
|
||||
BodyBuilder EmailBodyBuilder
|
||||
InviteURL string
|
||||
}
|
||||
|
||||
// GetSubject - gets the subject of the email
|
||||
func (UserInvitedMail) GetSubject(info Notification) string {
|
||||
return "Netmaker: Pending Invitation"
|
||||
}
|
||||
|
||||
// GetBody - gets the body of the email
|
||||
func (invite UserInvitedMail) GetBody(info Notification) string {
|
||||
|
||||
return invite.BodyBuilder.
|
||||
WithHeadline("Join Netmaker from this invite!").
|
||||
WithParagraph("Hello from Netmaker,").
|
||||
WithParagraph("You have been invited to join Netmaker.").
|
||||
WithParagraph(fmt.Sprintf("Join Using This Invite Link <a href=\"%s\">Netmaker</a>", invite.InviteURL)).
|
||||
Build()
|
||||
}
|
55
pro/email/resend.go
Normal file
55
pro/email/resend.go
Normal file
|
@ -0,0 +1,55 @@
|
|||
package email
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/gravitl/netmaker/servercfg"
|
||||
"github.com/resendlabs/resend-go"
|
||||
)
|
||||
|
||||
// ResendEmailSender - implementation of EmailSender using Resend (https://resend.com)
|
||||
type ResendEmailSender struct {
|
||||
client ResendClient
|
||||
from string
|
||||
}
|
||||
|
||||
// ResendClient - dependency interface for resend client
|
||||
type ResendClient interface {
|
||||
Send(*resend.SendEmailRequest) (resend.SendEmailResponse, error)
|
||||
}
|
||||
|
||||
// NewResendEmailSender - constructs a ResendEmailSender
|
||||
func NewResendEmailSender(client ResendClient, from string) ResendEmailSender {
|
||||
return ResendEmailSender{client: client, from: from}
|
||||
}
|
||||
|
||||
// NewResendEmailSender - constructs a ResendEmailSender from config
|
||||
// TODO let main.go handle this and use dependency injection instead of calling this function
|
||||
func NewResendEmailSenderFromConfig() ResendEmailSender {
|
||||
key, from := servercfg.GetEmaiSenderAuth(), servercfg.GetSenderEmail()
|
||||
resender := resend.NewClient(key)
|
||||
return NewResendEmailSender(resender.Emails, from)
|
||||
}
|
||||
|
||||
// SendEmail - sends an email using resend-go (https://github.com/resendlabs/resend-go)
|
||||
func (es ResendEmailSender) SendEmail(ctx context.Context, notification Notification, email Mail) error {
|
||||
var (
|
||||
from = es.from
|
||||
to = notification.RecipientMail
|
||||
subject = email.GetSubject(notification)
|
||||
body = email.GetBody(notification)
|
||||
)
|
||||
params := resend.SendEmailRequest{
|
||||
From: from,
|
||||
To: []string{to},
|
||||
Subject: subject,
|
||||
Html: body,
|
||||
}
|
||||
_, err := es.client.Send(¶ms)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed sending mail via resend: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
42
pro/email/smtp.go
Normal file
42
pro/email/smtp.go
Normal file
|
@ -0,0 +1,42 @@
|
|||
package email
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
|
||||
gomail "gopkg.in/mail.v2"
|
||||
)
|
||||
|
||||
type SmtpSender struct {
|
||||
SmtpHost string
|
||||
SmtpPort int
|
||||
SenderEmail string
|
||||
SenderPass string
|
||||
}
|
||||
|
||||
func (s *SmtpSender) SendEmail(ctx context.Context, n Notification, e Mail) error {
|
||||
m := gomail.NewMessage()
|
||||
|
||||
// Set E-Mail sender
|
||||
m.SetHeader("From", s.SenderEmail)
|
||||
|
||||
// Set E-Mail receivers
|
||||
m.SetHeader("To", n.RecipientMail)
|
||||
// Set E-Mail subject
|
||||
m.SetHeader("Subject", e.GetSubject(n))
|
||||
// Set E-Mail body. You can set plain text or html with text/html
|
||||
m.SetBody("text/html", e.GetBody(n))
|
||||
// Settings for SMTP server
|
||||
d := gomail.NewDialer(s.SmtpHost, s.SmtpPort, s.SenderEmail, s.SenderPass)
|
||||
|
||||
// This is only needed when SSL/TLS certificate is not valid on server.
|
||||
// In production this should be set to false.
|
||||
d.TLSConfig = &tls.Config{InsecureSkipVerify: true}
|
||||
|
||||
// Now send E-Mail
|
||||
if err := d.DialAndSend(m); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
567
pro/email/utils.go
Normal file
567
pro/email/utils.go
Normal file
|
@ -0,0 +1,567 @@
|
|||
package email
|
||||
|
||||
import "strings"
|
||||
|
||||
// mail related images hosted on github
|
||||
var (
|
||||
nLogoTeal = "https://raw.githubusercontent.com/gravitl/netmaker/netmaker_logos/img/logos/N_Teal.png"
|
||||
netmakerLogoTeal = "https://raw.githubusercontent.com/gravitl/netmaker/netmaker_logos/img/logos/netmaker-logo-2.png"
|
||||
netmakerMeshLogo = "https://raw.githubusercontent.com/gravitl/netmaker/netmaker_logos/img/logos/netmaker-mesh.png"
|
||||
linkedinIcon = "https://raw.githubusercontent.com/gravitl/netmaker/netmaker_logos/img/logos/linkedin2x.png"
|
||||
discordIcon = "https://raw.githubusercontent.com/gravitl/netmaker/netmaker_logos/img/logos/discord-logo-png-7617.png"
|
||||
githubIcon = "https://raw.githubusercontent.com/gravitl/netmaker/netmaker_logos/img/logos/Octocat.png"
|
||||
mailIcon = "https://raw.githubusercontent.com/gravitl/netmaker/netmaker_logos/img/logos/icons8-mail-24.png"
|
||||
addressIcon = "https://raw.githubusercontent.com/gravitl/netmaker/netmaker_logos/img/logos/icons8-address-16.png"
|
||||
linkIcon = "https://raw.githubusercontent.com/gravitl/netmaker/netmaker_logos/img/logos/icons8-hyperlink-64.png"
|
||||
)
|
||||
|
||||
type EmailBodyBuilder interface {
|
||||
WithHeadline(text string) EmailBodyBuilder
|
||||
WithParagraph(text string) EmailBodyBuilder
|
||||
WithSignature() EmailBodyBuilder
|
||||
Build() string
|
||||
}
|
||||
|
||||
type EmailBodyBuilderWithH1HeadlineAndImage struct {
|
||||
headline string
|
||||
paragraphs []string
|
||||
hasSignature bool
|
||||
}
|
||||
|
||||
func (b *EmailBodyBuilderWithH1HeadlineAndImage) WithHeadline(text string) EmailBodyBuilder {
|
||||
b.headline = text
|
||||
return b
|
||||
}
|
||||
|
||||
func (b *EmailBodyBuilderWithH1HeadlineAndImage) WithParagraph(text string) EmailBodyBuilder {
|
||||
b.paragraphs = append(b.paragraphs, text)
|
||||
return b
|
||||
}
|
||||
|
||||
func (b *EmailBodyBuilderWithH1HeadlineAndImage) WithSignature() EmailBodyBuilder {
|
||||
b.hasSignature = true
|
||||
return b
|
||||
}
|
||||
|
||||
func (b *EmailBodyBuilderWithH1HeadlineAndImage) Build() string {
|
||||
// map paragraphs to styled paragraphs
|
||||
styledParagraphsSlice := make([]string, len(b.paragraphs))
|
||||
for i, paragraph := range b.paragraphs {
|
||||
styledParagraphsSlice[i] = styledParagraph(paragraph)
|
||||
}
|
||||
// join styled paragraphs
|
||||
styledParagraphsString := strings.Join(styledParagraphsSlice, "")
|
||||
|
||||
signature := ""
|
||||
if b.hasSignature {
|
||||
signature = styledSignature()
|
||||
}
|
||||
|
||||
return `
|
||||
<!DOCTYPE html>
|
||||
<html xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office" lang="en">
|
||||
<head>
|
||||
<title></title>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<!--[if mso]>
|
||||
<xml>
|
||||
<o:OfficeDocumentSettings>
|
||||
<o:PixelsPerInch>96</o:PixelsPerInch>
|
||||
<o:AllowPNG/>
|
||||
</o:OfficeDocumentSettings>
|
||||
</xml>
|
||||
<![endif]-->
|
||||
<style>
|
||||
*{box-sizing:border-box}body{margin:0;padding:0}a[x-apple-data-detectors]{color:inherit!important;text-decoration:inherit!important}#MessageViewBody a{color:inherit;text-decoration:none}p{line-height:inherit}.desktop_hide,.desktop_hide table{mso-hide:all;display:none;max-height:0;overflow:hidden}@media (max-width:720px){.desktop_hide table.icons-inner{display:inline-block!important}.icons-inner{text-align:center}.icons-inner td{margin:0 auto}.image_block img.big,.row-content{width:100%!important}.mobile_hide{display:none}.stack .column{width:100%;display:block}.mobile_hide{min-height:0;max-height:0;max-width:0;overflow:hidden;font-size:0}.desktop_hide,.desktop_hide table{display:table!important;max-height:none!important}}
|
||||
</style>
|
||||
</head>
|
||||
<body style="background-color:transparent;margin:0;padding:0;-webkit-text-size-adjust:none;text-size-adjust:none">
|
||||
<table class="nl-container" width="100%" border="0" cellpadding="0" cellspacing="0" role="presentation" style="mso-table-lspace:0;mso-table-rspace:0;background-color:transparent">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<table class="row row-1" align="center" width="100%" border="0" cellpadding="0" cellspacing="0" role="presentation" style="mso-table-lspace:0;mso-table-rspace:0">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<table class="row-content" align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="mso-table-lspace:0;mso-table-rspace:0;color:#000;width:700px" width="700">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="column column-1" width="50%" style="mso-table-lspace:0;mso-table-rspace:0;font-weight:400;text-align:left;vertical-align:top;border-top:0;border-right:0;border-bottom:0;border-left:0">
|
||||
<table class="image_block block-2" width="100%" border="0" cellpadding="0" cellspacing="0"
|
||||
role="presentation" style="mso-table-lspace:0;mso-table-rspace:0">
|
||||
<tr>
|
||||
<td class="pad" style="padding-left:15px;padding-right:15px;width:100%;padding-top:5px">
|
||||
<div class="alignment" align="left" style="line-height:10px"><a href="https://www.netmaker.io/" target="_blank" style="outline:none" tabindex="-1"><img class="big" src="` + netmakerLogoTeal + `"
|
||||
style="display:block;height:auto;border:0;width:333px;max-width:100%" width="333" alt="Netmaker" title="Netmaker"></a></div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<table class="divider_block block-3" width="100%" border="0" cellpadding="0" cellspacing="0" role="presentation" style="mso-table-lspace:0;mso-table-rspace:0">
|
||||
<tr>
|
||||
<td class="pad" style="padding-bottom:10px;padding-left:5px;padding-right:5px;padding-top:10px">
|
||||
<div class="alignment" align="center">
|
||||
<table border="0" cellpadding="0" cellspacing="0"
|
||||
role="presentation" width="100%" style="mso-table-lspace:0;mso-table-rspace:0">
|
||||
<tr>
|
||||
<td class="divider_inner" style="font-size:1px;line-height:1px;border-top:0 solid #bbb"><span> </span></td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
<td class="column column-2" width="50%" style="mso-table-lspace:0;mso-table-rspace:0;font-weight:400;text-align:left;vertical-align:top;border-top:0;border-right:0;border-bottom:0;border-left:0">
|
||||
<table class="empty_block block-2" width="100%" border="0"
|
||||
cellpadding="0" cellspacing="0" role="presentation" style="mso-table-lspace:0;mso-table-rspace:0">
|
||||
<tr>
|
||||
<td class="pad" style="padding-right:0;padding-bottom:5px;padding-left:0;padding-top:5px">
|
||||
<div></div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<table class="row row-2" align="center" width="100%" border="0" cellpadding="0" cellspacing="0" role="presentation" style="mso-table-lspace:0;mso-table-rspace:0">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<table class="row-content stack" align="center"
|
||||
border="0" cellpadding="0" cellspacing="0" role="presentation" style="mso-table-lspace:0;mso-table-rspace:0;color:#000;width:700px" width="700">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="column column-1" width="100%" style="mso-table-lspace:0;mso-table-rspace:0;font-weight:400;text-align:left;padding-left:10px;padding-right:10px;vertical-align:top;padding-top:10px;padding-bottom:10px;border-top:0;border-right:0;border-bottom:0;border-left:0">
|
||||
<table class="divider_block block-1" width="100%" border="0"
|
||||
cellpadding="10" cellspacing="0" role="presentation" style="mso-table-lspace:0;mso-table-rspace:0">
|
||||
<tr>
|
||||
<td class="pad">
|
||||
<div class="alignment" align="center">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" width="100%" style="mso-table-lspace:0;mso-table-rspace:0">
|
||||
<tr>
|
||||
<td class="divider_inner" style="font-size:1px;line-height:1px;border-top:0 solid #bbb"><span> </span></td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<table
|
||||
class="row row-3" align="center" width="100%" border="0" cellpadding="0" cellspacing="0" role="presentation" style="mso-table-lspace:0;mso-table-rspace:0">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<table class="row-content stack" align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="mso-table-lspace:0;mso-table-rspace:0;color:#000;width:700px" width="700">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="column column-1" width="50%"
|
||||
style="mso-table-lspace:0;mso-table-rspace:0;font-weight:400;text-align:left;vertical-align:top;border-top:0;border-right:0;border-bottom:0;border-left:0">
|
||||
<table class="divider_block block-2" width="100%" border="0" cellpadding="0" cellspacing="0" role="presentation" style="mso-table-lspace:0;mso-table-rspace:0">
|
||||
<tr>
|
||||
<td class="pad" style="padding-bottom:20px;padding-left:20px;padding-right:20px;padding-top:25px">
|
||||
<div class="alignment" align="center">
|
||||
<table border="0" cellpadding="0"
|
||||
cellspacing="0" role="presentation" width="100%" style="mso-table-lspace:0;mso-table-rspace:0">
|
||||
<tr>
|
||||
<td class="divider_inner" style="font-size:1px;line-height:1px;border-top:0 solid #bbb"><span> </span></td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<table class="heading_block block-3" width="100%" border="0" cellpadding="0" cellspacing="0" role="presentation" style="mso-table-lspace:0;mso-table-rspace:0">
|
||||
<tr>
|
||||
<td class="pad"
|
||||
style="padding-bottom:15px;padding-left:10px;padding-right:10px;padding-top:10px;text-align:center;width:100%">
|
||||
<h1 style="margin:0;color:#2b2d2d;direction:ltr;font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:28px;font-weight:400;letter-spacing:normal;line-height:120%;text-align:left;margin-top:0;margin-bottom:0"><strong>` + b.headline + `</strong></h1>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
<td class="column column-2" width="50%"
|
||||
style="mso-table-lspace:0;mso-table-rspace:0;font-weight:400;text-align:left;vertical-align:top;border-top:0;border-right:0;border-bottom:0;border-left:0">
|
||||
<table class="image_block block-2" width="100%" border="0" cellpadding="0" cellspacing="0" role="presentation" style="mso-table-lspace:0;mso-table-rspace:0">
|
||||
<tr>
|
||||
<td class="pad" style="width:100%;padding-right:0;padding-left:0;padding-top:5px;padding-bottom:5px">
|
||||
<div class="alignment" align="center" style="line-height:10px"><img
|
||||
src="` + netmakerMeshLogo + `" style="display:block;height:auto;border:0;width:350px;max-width:100%" width="350" alt="Netmaker Mesh"></div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<table class="row row-4" align="center" width="100%" border="0" cellpadding="0" cellspacing="0" role="presentation"
|
||||
style="mso-table-lspace:0;mso-table-rspace:0">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<table class="row-content stack" align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="mso-table-lspace:0;mso-table-rspace:0;color:#000;width:700px" width="700">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="column column-1" width="100%" style="mso-table-lspace:0;mso-table-rspace:0;font-weight:400;text-align:left;vertical-align:top;padding-top:5px;padding-bottom:5px;border-top:0;border-right:0;border-bottom:0;border-left:0">
|
||||
<table class="divider_block block-1" width="100%" border="0" cellpadding="10" cellspacing="0" role="presentation" style="mso-table-lspace:0;mso-table-rspace:0">
|
||||
<tr>
|
||||
<td class="pad">
|
||||
<div class="alignment" align="center">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" width="100%" style="mso-table-lspace:0;mso-table-rspace:0">
|
||||
<tr>
|
||||
<td class="divider_inner" style="font-size:1px;line-height:1px;border-top:0 solid #bbb"><span> </span></td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<table class="row row-5" align="center" width="100%" border="0" cellpadding="0" cellspacing="0" role="presentation" style="mso-table-lspace:0;mso-table-rspace:0">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<table class="row-content stack" align="center" border="0" cellpadding="0" cellspacing="0" role="presentation"
|
||||
style="mso-table-lspace:0;mso-table-rspace:0;background-color:#0098a5;color:#000;border-top:2px solid transparent;border-right:2px solid transparent;border-left:2px solid transparent;border-bottom:2px solid transparent;border-radius:0;width:700px" width="700">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="column column-1" width="100%"
|
||||
style="mso-table-lspace:0;mso-table-rspace:0;font-weight:400;text-align:left;border-bottom:0 solid #000;border-left:0 solid #000;border-right:0 solid #000;border-top:0 solid #000;vertical-align:top;padding-top:25px;padding-bottom:25px">
|
||||
<table class="text_block block-3" width="100%" border="0"
|
||||
cellpadding="0" cellspacing="0" role="presentation" style="mso-table-lspace:0;mso-table-rspace:0;word-break:break-word">
|
||||
<tr>
|
||||
<td class="pad" style="padding-bottom:10px;padding-left:50px;padding-right:50px;padding-top:10px">
|
||||
<div style="font-family:Verdana,sans-serif">
|
||||
<div class="txtTinyMce-wrapper" style="font-size:12px;mso-line-height-alt:18px;color:#393d47;line-height:1.5;font-family:Verdana,Geneva,sans-serif">
|
||||
|
||||
<p style="margin:0;font-size:12px;mso-line-height-alt:18px"> </p>
|
||||
` + styledParagraphsString + `
|
||||
<p style="margin:0;mso-line-height-alt:18px"> </p>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<table class="row row-6" align="center" width="100%" border="0" cellpadding="0" cellspacing="0" role="presentation" style="mso-table-lspace:0;mso-table-rspace:0">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<table
|
||||
class="row-content stack" align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="mso-table-lspace:0;mso-table-rspace:0;color:#000;width:700px" width="700">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="column column-1" width="100%" style="mso-table-lspace:0;mso-table-rspace:0;font-weight:400;text-align:left;vertical-align:top;padding-top:5px;padding-bottom:5px;border-top:0;border-right:0;border-bottom:0;border-left:0">
|
||||
<table class="divider_block block-1" width="100%" border="0"
|
||||
cellpadding="10" cellspacing="0" role="presentation" style="mso-table-lspace:0;mso-table-rspace:0">
|
||||
<tr>
|
||||
<td class="pad">
|
||||
<div class="alignment" align="center">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" width="100%" style="mso-table-lspace:0;mso-table-rspace:0">
|
||||
<tr>
|
||||
<td class="divider_inner" style="font-size:1px;line-height:1px;border-top:0 solid #bbb"><span> </span></td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<table
|
||||
class="row row-7" align="center" width="100%" border="0" cellpadding="0" cellspacing="0" role="presentation" style="mso-table-lspace:0;mso-table-rspace:0;background-color:#f7fafe">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<table class="row-content stack" align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="mso-table-lspace:0;mso-table-rspace:0;color:#000;width:700px" width="700">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="column column-1" width="100%"
|
||||
style="mso-table-lspace:0;mso-table-rspace:0;font-weight:400;text-align:left;vertical-align:top;padding-top:25px;padding-bottom:5px;border-top:0;border-right:0;border-bottom:0;border-left:0">
|
||||
<table class="divider_block block-1" width="100%" border="0" cellpadding="10" cellspacing="0" role="presentation" style="mso-table-lspace:0;mso-table-rspace:0">
|
||||
<tr>
|
||||
<td class="pad">
|
||||
<div class="alignment" align="center">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" width="100%"
|
||||
style="mso-table-lspace:0;mso-table-rspace:0">
|
||||
<tr>
|
||||
<td class="divider_inner" style="font-size:1px;line-height:1px;border-top:0 solid #bbb"><span> </span></td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<table class="row row-8" align="center" width="100%" border="0" cellpadding="0" cellspacing="0" role="presentation" style="mso-table-lspace:0;mso-table-rspace:0;background-color:#090660">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<table class="row-content stack"
|
||||
align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="mso-table-lspace:0;mso-table-rspace:0;color:#000;width:700px" width="700">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="column column-1" width="100%" style="mso-table-lspace:0;mso-table-rspace:0;font-weight:400;text-align:left;vertical-align:top;padding-top:5px;padding-bottom:5px;border-top:0;border-right:0;border-bottom:0;border-left:0">
|
||||
<table class="text_block block-1" width="100%" border="0" cellpadding="0" cellspacing="0"
|
||||
role="presentation" style="mso-table-lspace:0;mso-table-rspace:0;word-break:break-word">
|
||||
<tr>
|
||||
<td class="pad" style="padding-bottom:10px;padding-left:50px;padding-right:50px;padding-top:10px">
|
||||
<div style="font-family:sans-serif">
|
||||
<div class="txtTinyMce-wrapper" style="font-size:12px;mso-line-height-alt:18px;color:#6f7077;line-height:1.5;font-family:Arial,Helvetica Neue,Helvetica,sans-serif">
|
||||
<p style="margin:0;font-size:12px;mso-line-height-alt:33px">
|
||||
<span style="color:#ffffff;font-size:22px;"> Get In Touch With Us</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<table class="social_block block-2" width="100%" border="0" cellpadding="10" cellspacing="0" role="presentation" style="mso-table-lspace:0;mso-table-rspace:0">
|
||||
<tr>
|
||||
<td class="pad">
|
||||
<div class="alignment" style="text-align:center">
|
||||
<table class="social-table"
|
||||
width="114.49624060150376px" border="0" cellpadding="0" cellspacing="0" role="presentation" style="mso-table-lspace:0;mso-table-rspace:0;display:inline-block">
|
||||
<tr>
|
||||
<td style="padding:0 2px 0 2px"><a href="https://www.linkedin.com/company/netmaker-inc/" target="_blank"><img src="` + linkedinIcon + `" width="32" height="32" alt="Linkedin" title="linkedin" style="display:block;height:auto;border:0"></a></td>
|
||||
<td
|
||||
style="padding:0 2px 0 2px"><a href="https://discord.gg/zRb9Vfhk8A" target="_blank"><img src="` + discordIcon + `" width="32" height="32" alt="Discord" title="Discord" style="display:block;height:auto;border:0"></a></td>
|
||||
<td style="padding:0 2px 0 2px"><a href="https://github.com/gravitl/netmaker" target="_blank"><img
|
||||
src="` + githubIcon + `" width="38.49624060150376" height="32" alt="Github" title="Github" style="display:block;height:auto;border:0"></a></td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<table class="row row-9" align="center" width="100%" border="0" cellpadding="0" cellspacing="0" role="presentation" style="mso-table-lspace:0;mso-table-rspace:0">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<table
|
||||
class="row-content stack" align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="mso-table-lspace:0;mso-table-rspace:0;color:#000;width:700px" width="700">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="column column-1" width="100%" style="mso-table-lspace:0;mso-table-rspace:0;font-weight:400;text-align:left;vertical-align:top;padding-top:5px;padding-bottom:5px;border-top:0;border-right:0;border-bottom:0;border-left:0">
|
||||
<table class="icons_block block-1" width="100%" border="0"
|
||||
cellpadding="0" cellspacing="0" role="presentation" style="mso-table-lspace:0;mso-table-rspace:0">
|
||||
<tr>
|
||||
<td class="pad" style="vertical-align:middle;padding-bottom:5px;padding-top:5px;text-align:center;color:#9d9d9d;font-family:inherit;font-size:15px">
|
||||
<table width="100%" cellpadding="0" cellspacing="0" role="presentation" style="mso-table-lspace:0;mso-table-rspace:0">
|
||||
<tr>
|
||||
<td class="alignment" style="vertical-align:middle;text-align:center">
|
||||
<!--[if vml]>
|
||||
<table align="left" cellpadding="0" cellspacing="0" role="presentation" style="display:inline-block;padding-left:0px;padding-right:0px;mso-table-lspace: 0pt;mso-table-rspace: 0pt;">
|
||||
<![endif]--><!--[if !vml]><!-->
|
||||
<table class="icons-inner" style="mso-table-lspace:0;mso-table-rspace:0;display:inline-block;margin-right:-4px;padding-left:0;padding-right:0" cellpadding="0" cellspacing="0" role="presentation">
|
||||
<!--<![endif]-->
|
||||
</table>
|
||||
</td></tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<!-- End -->
|
||||
</body>
|
||||
` + signature + `
|
||||
</html>`
|
||||
}
|
||||
|
||||
func styledSignature() string {
|
||||
return `
|
||||
<footer style="display:block">
|
||||
<table cellpadding="0" cellspacing="0" class="sc-gPEVay eQYmiW" style="vertical-align: -webkit-baseline-middle; font-size: medium; font-family: Arial;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<table cellpadding="0" cellspacing="0" class="sc-gPEVay eQYmiW" style="vertical-align: -webkit-baseline-middle; font-size: medium; font-family: Arial;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="vertical-align: top;">
|
||||
<table cellpadding="0" cellspacing="0" class="sc-gPEVay eQYmiW" style="vertical-align: -webkit-baseline-middle; font-size: medium; font-family: Arial;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="sc-TOsTZ kjYrri" style="text-align: center;"><img src="` + nLogoTeal + `" role="presentation" width="130" class="sc-cHGsZl bHiaRe" style="max-width: 130px; display: block;"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td height="30"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="text-align: center;">
|
||||
<table cellpadding="0" cellspacing="0" class="sc-gPEVay eQYmiW" style="vertical-align: -webkit-baseline-middle; font-size: medium; font-family: Arial; display: inline-block;">
|
||||
<tbody>
|
||||
<tr style="text-align: center;">
|
||||
<td><a href="https://www.linkedin.com/company/netmaker-inc/" color="#6a78d1" class="sc-hzDkRC kpsoyz" style="display: inline-block; padding: 0px; background-color: rgb(106, 120, 209);"><img src="` + linkedinIcon + `" alt="Linkedin" color="#6a78d1" height="24" class="sc-bRBYWo ccSRck" style="background-color: rgb(106, 120, 209); max-width: 135px; display: block;"></a></td>
|
||||
<td width="5">
|
||||
<div></div>
|
||||
</td>
|
||||
|
||||
<td><a href="https://discord.gg/zRb9Vfhk8A" class="sc-hzDkRC kpsoyz" style="display: inline-block; padding: 0px;"><img src="` + discordIcon + `" alt="Discord" height="24" class="sc-bRBYWo ccSRck" style="max-width: 135px; display: block;"></a></td>
|
||||
<td width="5">
|
||||
<div></div>
|
||||
</td>
|
||||
|
||||
<td><a href="https://github.com/gravitl/netmaker" class="sc-hzDkRC kpsoyz" style="display: inline-block; padding: 0px;"><img src="` + githubIcon + `" alt="Github" height="24" class="sc-bRBYWo ccSRck" style="max-width: 135px; display: block;"></a></td>
|
||||
<td width="5">
|
||||
<div></div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
<td width="46">
|
||||
<div></div>
|
||||
</td>
|
||||
<td style="padding: 0px; vertical-align: middle;">
|
||||
<h3 color="#000000" class="sc-fBuWsC eeihxG" style="margin: 0px; font-size: 18px; color: rgb(0, 0, 0);"><span>Alex</span><span> </span><span>Feiszli</span></h3>
|
||||
<p color="#000000" font-size="medium" class="sc-fMiknA bxZCMx" style="margin: 0px; color: rgb(0, 0, 0); font-size: 14px; line-height: 22px;"><span>Co-Founder & CEO</span></p>
|
||||
<p color="#000000" font-size="medium" class="sc-dVhcbM fghLuF" style="margin: 0px; font-weight: 500; color: rgb(0, 0, 0); font-size: 14px; line-height: 22px;"><span>Netmaker</span></p>
|
||||
<table cellpadding="0" cellspacing="0" class="sc-gPEVay eQYmiW" style="vertical-align: -webkit-baseline-middle; font-size: medium; font-family: Arial; width: 100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td height="30"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td color="#545af2" direction="horizontal" height="1" class="sc-jhAzac hmXDXQ" style="width: 100%; border-bottom: 1px solid rgb(84, 90, 242); border-left: none; display: block;"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td height="30"></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<table cellpadding="0" cellspacing="0" class="sc-gPEVay eQYmiW" style="vertical-align: -webkit-baseline-middle; font-size: medium; font-family: Arial;">
|
||||
<tbody>
|
||||
<tr height="25" style="vertical-align: middle;">
|
||||
<td width="30" style="vertical-align: middle;">
|
||||
<table cellpadding="0" cellspacing="0" class="sc-gPEVay eQYmiW" style="vertical-align: -webkit-baseline-middle; font-size: medium; font-family: Arial;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="vertical-align: bottom;"><span width="11" class="sc-jlyJG bbyJzT" style="display: block"><img src="` + mailIcon + `" width="13" class="sc-iRbamj blSEcj" style="display: block;"></span></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
<td style="padding: 0px;"><a href="mailto:alex@netmaker.io" color="#000000" class="sc-gipzik iyhjGb" style="text-decoration: none; color: rgb(0, 0, 0); font-size: 12px;"><span>alex@netmaker.io</span></a></td>
|
||||
</tr>
|
||||
<tr height="25" style="vertical-align: middle;">
|
||||
<td width="30" style="vertical-align: middle;">
|
||||
<table cellpadding="0" cellspacing="0" class="sc-gPEVay eQYmiW" style="vertical-align: -webkit-baseline-middle; font-size: medium; font-family: Arial;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="vertical-align: bottom;"><span width="11" class="sc-jlyJG bbyJzT" style="display: block;"><img src="` + linkIcon + `" color="#545af2" width="13" class="sc-iRbamj blSEcj" style="display: block;"></span></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
<td style="padding: 0px;"><a href="https://www.netmaker.io/" color="#000000" class="sc-gipzik iyhjGb" style="text-decoration: none; color: rgb(0, 0, 0); font-size: 12px;"><span>https://www.netmaker.io/</span></a></td>
|
||||
</tr>
|
||||
<tr height="25" style="vertical-align: middle;">
|
||||
<td width="30" style="vertical-align: middle;">
|
||||
<table cellpadding="0" cellspacing="0" class="sc-gPEVay eQYmiW" style="vertical-align: -webkit-baseline-middle; font-size: medium; font-family: Arial;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="vertical-align: bottom;"><span width="11" class="sc-jlyJG bbyJzT" style="display: block;"><img src="` + addressIcon + `" width="13" class="sc-iRbamj blSEcj" style="display: block;"></span></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
<td style="padding: 0px;"><span color="#000000" class="sc-csuQGl CQhxV" style="font-size: 12px; color: rgb(0, 0, 0);"><span>1465 Sand Hill Rd.Suite 2014, Candler, NC 28715</span></span></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<table cellpadding="0" cellspacing="0" class="sc-gPEVay eQYmiW" style="vertical-align: -webkit-baseline-middle; font-size: medium; font-family: Arial;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td height="30"></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</footer>`
|
||||
}
|
||||
|
||||
func styledParagraph(text string) string {
|
||||
return `<p style="margin:0;mso-line-height-alt:22.5px">
|
||||
<span style="color:#ffffff;font-size:15px;">` + text + `</span>
|
||||
</p>`
|
||||
}
|
||||
|
||||
func GetMailSignature() string {
|
||||
return styledSignature()
|
||||
}
|
|
@ -119,6 +119,19 @@ func InitPro() {
|
|||
logic.GetAllowedIpForInetNodeClient = proLogic.GetAllowedIpForInetNodeClient
|
||||
mq.UpdateMetrics = proLogic.MQUpdateMetrics
|
||||
mq.UpdateMetricsFallBack = proLogic.MQUpdateMetricsFallBack
|
||||
logic.GetFilteredNodesByUserAccess = proLogic.GetFilteredNodesByUserAccess
|
||||
logic.CreateRole = proLogic.CreateRole
|
||||
logic.UpdateRole = proLogic.UpdateRole
|
||||
logic.DeleteRole = proLogic.DeleteRole
|
||||
logic.NetworkPermissionsCheck = proLogic.NetworkPermissionsCheck
|
||||
logic.GlobalPermissionsCheck = proLogic.GlobalPermissionsCheck
|
||||
logic.DeleteNetworkRoles = proLogic.DeleteNetworkRoles
|
||||
logic.CreateDefaultNetworkRolesAndGroups = proLogic.CreateDefaultNetworkRolesAndGroups
|
||||
logic.FilterNetworksByRole = proLogic.FilterNetworksByRole
|
||||
logic.IsGroupsValid = proLogic.IsGroupsValid
|
||||
logic.IsNetworkRolesValid = proLogic.IsNetworkRolesValid
|
||||
logic.InitialiseRoles = proLogic.UserRolesInit
|
||||
logic.UpdateUserGwAccess = proLogic.UpdateUserGwAccess
|
||||
}
|
||||
|
||||
func retrieveProLogo() string {
|
||||
|
|
195
pro/logic/security.go
Normal file
195
pro/logic/security.go
Normal file
|
@ -0,0 +1,195 @@
|
|||
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
|
||||
// check for global network role
|
||||
if netRoles, ok := user.NetworkRoles[models.AllNetworks]; ok {
|
||||
for netRoleID := range netRoles {
|
||||
err = checkNetworkAccessPermissions(netRoleID, username, r.Method, targetRsrc, targetRsrcID, netID)
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
netRoles := user.NetworkRoles[models.NetworkID(netID)]
|
||||
for netRoleID := range netRoles {
|
||||
err = checkNetworkAccessPermissions(netRoleID, username, r.Method, targetRsrc, targetRsrcID, netID)
|
||||
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, netID)
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return errors.New("access denied")
|
||||
}
|
||||
|
||||
func checkNetworkAccessPermissions(netRoleID models.UserRoleID, username, reqScope, targetRsrc, targetRsrcID, netID 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, netID)
|
||||
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", 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")
|
||||
}
|
1057
pro/logic/user_mgmt.go
Normal file
1057
pro/logic/user_mgmt.go
Normal file
File diff suppressed because it is too large
Load diff
|
@ -75,3 +75,14 @@ RAC_AUTO_DISABLE=false
|
|||
CACHING_ENABLED=true
|
||||
# if turned on netclient checks if peers are reachable over private/LAN address, and choose that as peer endpoint
|
||||
ENDPOINT_DETECTION=true
|
||||
# config for sending emails
|
||||
# mail server host
|
||||
SMTP_HOST=smtp.gmail.com
|
||||
# mail server port
|
||||
SMTP_PORT=587
|
||||
# sender email
|
||||
EMAIL_SENDER_ADDR=
|
||||
# sender email auth
|
||||
EMAIL_SENDER_AUTH=
|
||||
# mail sender type (smtp or resend)
|
||||
EMAIL_SENDER_TYPE=smtp
|
|
@ -231,6 +231,7 @@ save_config() { (
|
|||
fi
|
||||
if [ -n "$NETMAKER_BASE_DOMAIN" ]; then
|
||||
save_config_item NM_DOMAIN "$NETMAKER_BASE_DOMAIN"
|
||||
save_config_item FRONTEND_URL "https://dashboard.$NETMAKER_BASE_DOMAIN"
|
||||
fi
|
||||
save_config_item UI_IMAGE_TAG "$IMAGE_TAG"
|
||||
# version-specific entries
|
||||
|
|
|
@ -242,6 +242,59 @@ func GetPublicBrokerEndpoint() string {
|
|||
}
|
||||
}
|
||||
|
||||
func GetSmtpHost() string {
|
||||
v := ""
|
||||
if fromEnv := os.Getenv("SMTP_HOST"); fromEnv != "" {
|
||||
v = fromEnv
|
||||
} else if fromCfg := config.Config.Server.SmtpHost; fromCfg != "" {
|
||||
v = fromCfg
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
func GetSmtpPort() int {
|
||||
v := 587
|
||||
if fromEnv := os.Getenv("SMTP_PORT"); fromEnv != "" {
|
||||
port, err := strconv.Atoi(fromEnv)
|
||||
if err == nil {
|
||||
v = port
|
||||
}
|
||||
} else if fromCfg := config.Config.Server.SmtpPort; fromCfg != 0 {
|
||||
v = fromCfg
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
func GetSenderEmail() string {
|
||||
v := ""
|
||||
if fromEnv := os.Getenv("EMAIL_SENDER_ADDR"); fromEnv != "" {
|
||||
v = fromEnv
|
||||
} else if fromCfg := config.Config.Server.EmailSenderAddr; fromCfg != "" {
|
||||
v = fromCfg
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
func GetEmaiSenderAuth() string {
|
||||
v := ""
|
||||
if fromEnv := os.Getenv("EMAIL_SENDER_AUTH"); fromEnv != "" {
|
||||
v = fromEnv
|
||||
} else if fromCfg := config.Config.Server.EmailSenderAddr; fromCfg != "" {
|
||||
v = fromCfg
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
func EmailSenderType() string {
|
||||
s := ""
|
||||
if fromEnv := os.Getenv("EMAIL_SENDER_TYPE"); fromEnv != "" {
|
||||
s = fromEnv
|
||||
} else if fromCfg := config.Config.Server.EmailSenderType; fromCfg != "" {
|
||||
s = fromCfg
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// GetOwnerEmail - gets the owner email (saas)
|
||||
func GetOwnerEmail() string {
|
||||
return os.Getenv("SAAS_OWNER_EMAIL")
|
||||
|
@ -472,7 +525,7 @@ func GetPublicIP() (string, error) {
|
|||
break
|
||||
}
|
||||
}
|
||||
if err == nil && endpoint == "" {
|
||||
if endpoint == "" {
|
||||
err = errors.New("public address not found")
|
||||
}
|
||||
return endpoint, err
|
||||
|
|
Loading…
Add table
Reference in a new issue