| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162 | package authimport (	"encoding/json"	"fmt"	"strings"	"time"	"github.com/gorilla/websocket"	"github.com/gravitl/netmaker/logger"	"github.com/gravitl/netmaker/logic"	"github.com/gravitl/netmaker/logic/pro/netcache"	"github.com/gravitl/netmaker/models"	"github.com/gravitl/netmaker/models/promodels"	"github.com/gravitl/netmaker/servercfg")// SessionHandler - called by the HTTP router when user// is calling netclient with --login-server parameter in order to authenticate// via SSO mechanism by OAuth2 protocol flow.// This triggers a session start and it is managed by the flow implemented here and callback// When this method finishes - the auth flow has finished either OK or by timeout or any other error occuredfunc SessionHandler(conn *websocket.Conn) {	defer conn.Close()	// If reached here we have a session from user to handle...	messageType, message, err := conn.ReadMessage()	if err != nil {		logger.Log(0, "Error during message reading:", err.Error())		return	}	var loginMessage promodels.LoginMsg	err = json.Unmarshal(message, &loginMessage)	if err != nil {		logger.Log(0, "Failed to unmarshall data err=", err.Error())		return	}	logger.Log(1, "SSO node join attempted with info network:", loginMessage.Network, "node identifier:", loginMessage.Mac, "user:", loginMessage.User)	req := new(netcache.CValue)	req.Value = string(loginMessage.Mac)	req.Network = loginMessage.Network	req.Pass = ""	req.User = ""	// Add any extra parameter provided in the configuration to the Authorize Endpoint request??	stateStr := logic.RandomString(node_signin_length)	if err := netcache.Set(stateStr, req); err != nil {		logger.Log(0, "Failed to process sso request -", err.Error())		return	}	// Wait for the user to finish his auth flow...	// TBD: what should be the timeout here ?	timeout := make(chan bool, 1)	answer := make(chan string, 1)	defer close(answer)	defer close(timeout)	if _, err = logic.GetNetwork(loginMessage.Network); err != nil {		err = conn.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""))		if err != nil {			logger.Log(0, "error during message writing:", err.Error())		}		return	}	if loginMessage.User != "" { // handle basic auth		// verify that server supports basic auth, then authorize the request with given credentials		// check if user is allowed to join via node sso		// i.e. user is admin or user has network permissions		if !servercfg.IsBasicAuthEnabled() {			err = conn.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""))			if err != nil {				logger.Log(0, "error during message writing:", err.Error())			}		}		_, err := logic.VerifyAuthRequest(models.UserAuthParams{			UserName: loginMessage.User,			Password: loginMessage.Password,		})		if err != nil {			err = conn.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""))			if err != nil {				logger.Log(0, "error during message writing:", err.Error())			}			return		}		_, err = isUserIsAllowed(loginMessage.User, loginMessage.Network, false)		if err != nil {			err = conn.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""))			if err != nil {				logger.Log(0, "error during message writing:", err.Error())			}			return		}		// Give the user the access token via Pass in the DB		if err = netcache.Set(stateStr, req); err != nil {			logger.Log(0, "machine failed to complete join on network,", loginMessage.Network, "-", err.Error())			return		}	} else { // handle SSO / OAuth		if auth_provider == nil {			err = conn.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""))			if err != nil {				logger.Log(0, "error during message writing:", err.Error())			}			return		}		redirectUrl = fmt.Sprintf("https://%s/api/oauth/register/%s", servercfg.GetAPIConnString(), stateStr)		err = conn.WriteMessage(messageType, []byte(redirectUrl))		if err != nil {			logger.Log(0, "error during message writing:", err.Error())		}	}	go func() {		for {			cachedReq, err := netcache.Get(stateStr)			if err != nil {				if strings.Contains(err.Error(), "expired") {					logger.Log(0, "timeout occurred while waiting for SSO on network", loginMessage.Network)					timeout <- true					break				}				continue			} else if cachedReq.Pass != "" {				logger.Log(0, "node SSO process completed for user", cachedReq.User, "on network", loginMessage.Network)				answer <- cachedReq.Pass				break			}			time.Sleep(500) // try it 2 times per second to see if auth is completed		}	}()	select {	case result := <-answer:		// a read from req.answerCh has occurred		err = conn.WriteMessage(messageType, []byte(result))		if err != nil {			logger.Log(0, "Error during message writing:", err.Error())		}	case <-timeout:		logger.Log(0, "Authentication server time out for a node on network", loginMessage.Network)		// the read from req.answerCh has timed out		err = conn.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""))		if err != nil {			logger.Log(0, "Error during message writing:", err.Error())		}	}	// The entry is not needed anymore, but we will let the producer to close it to avoid panic cases	if err = netcache.Del(stateStr); err != nil {		logger.Log(0, "failed to remove node SSO cache entry", err.Error())	}	// Cleanly close the connection by sending a close message and then	// waiting (with timeout) for the server to close the connection.	err = conn.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""))	if err != nil {		logger.Log(0, "write close:", err.Error())		return	}}
 |