123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167 |
- package auth
- import (
- "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 occured
- func 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
- }
- user, 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
- }
- accessToken, err := requestAccessKey(loginMessage.Network, 1, user.UserName)
- if err != nil {
- req.Pass = fmt.Sprintf("Error from the netmaker controller %s", err.Error())
- } else {
- req.Pass = fmt.Sprintf("AccessToken: %s", accessToken)
- }
- // 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
- }
- }
|