浏览代码

Merge pull request #3092 from gravitl/master

Master
Abhishek K 1 年之前
父节点
当前提交
81ec688ee4
共有 8 个文件被更改,包括 154 次插入51 次删除
  1. 17 3
      .github/workflows/deletedroplets.yml
  2. 24 8
      logic/networks.go
  3. 11 10
      models/user_mgmt.go
  4. 28 12
      pro/auth/azure-ad.go
  5. 71 13
      pro/auth/github.go
  6. 1 1
      pro/auth/google.go
  7. 2 2
      pro/auth/oidc.go
  8. 0 2
      pro/controllers/users.go

+ 17 - 3
.github/workflows/deletedroplets.yml

@@ -37,11 +37,25 @@ jobs:
       - name: delete droplets
         if: success() || failure()
         run: |
-          sleep 15m
-          curl -X DELETE \
+          sleep 5m
+          response=$(curl -X DELETE \
             -H "Content-Type: application/json" \
             -H "Authorization: Bearer $DIGITALOCEAN_TOKEN" \
-            "https://api.digitalocean.com/v2/droplets?tag_name=$TAG"
+            -w "\n%{http_code}" \
+            "https://api.digitalocean.com/v2/droplets?tag_name=$TAG")
+          
+          status_code=$(echo "$response" | tail -n1)
+          body=$(echo "$response" | sed '$d')
+          
+          echo "Response body: $body"
+          echo "Status code: $status_code"
+          
+          if [ "$status_code" -eq 204 ]; then
+            echo "Droplets deleted successfully"
+          else
+            echo "Failed to delete droplets. Status code: $status_code"
+            exit 1
+          fi
         env:
           DIGITALOCEAN_TOKEN: ${{ secrets.DIGITALOCEAN_TOKEN }}
           TAG: ${{ github.event.workflow_run.id }}-${{ github.event.workflow_run.run_attempt }}

+ 24 - 8
logic/networks.go

@@ -42,19 +42,35 @@ func SetAllocatedIpMap() error {
 		pMap := map[string]net.IP{}
 		netName := v.NetID
 
+		//nodes
 		nodes, err := GetNetworkNodes(netName)
 		if err != nil {
 			slog.Error("could not load node for network", netName, "error", err.Error())
-			continue
-		}
-
-		for _, n := range nodes {
+		} else {
+			for _, n := range nodes {
 
-			if n.Address.IP != nil {
-				pMap[n.Address.IP.String()] = n.Address.IP
+				if n.Address.IP != nil {
+					pMap[n.Address.IP.String()] = n.Address.IP
+				}
+				if n.Address6.IP != nil {
+					pMap[n.Address6.IP.String()] = n.Address6.IP
+				}
 			}
-			if n.Address6.IP != nil {
-				pMap[n.Address6.IP.String()] = n.Address6.IP
+
+		}
+
+		//extClients
+		extClients, err := GetNetworkExtClients(netName)
+		if err != nil {
+			slog.Error("could not load extClient for network", netName, "error", err.Error())
+		} else {
+			for _, extClient := range extClients {
+				if extClient.Address != "" {
+					pMap[extClient.Address] = net.ParseIP(extClient.Address)
+				}
+				if extClient.Address6 != "" {
+					pMap[extClient.Address6] = net.ParseIP(extClient.Address6)
+				}
 			}
 		}
 

+ 11 - 10
models/user_mgmt.go

@@ -138,16 +138,17 @@ type UserGroup struct {
 
 // User struct - struct for Users
 type User struct {
-	UserName       string                                `json:"username" bson:"username" validate:"min=3,max=40,in_charset|email"`
-	Password       string                                `json:"password" bson:"password" validate:"required,min=5"`
-	IsAdmin        bool                                  `json:"isadmin" bson:"isadmin"` // 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"`
+	UserName                   string                                `json:"username" bson:"username" validate:"min=3,max=40,in_charset|email"`
+	ExternalIdentityProviderID string                                `json:"external_identity_provider_id"`
+	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 {

+ 28 - 12
pro/auth/azure-ad.go

@@ -3,6 +3,7 @@ package auth
 import (
 	"context"
 	"encoding/json"
+	"errors"
 	"fmt"
 	"io"
 	"net/http"
@@ -60,7 +61,7 @@ func handleAzureCallback(w http.ResponseWriter, r *http.Request) {
 	var content, err = getAzureUserInfo(rState, rCode)
 	if err != nil {
 		logger.Log(1, "error when getting user info from azure:", err.Error())
-		if strings.Contains(err.Error(), "invalid oauth state") {
+		if strings.Contains(err.Error(), "invalid oauth state") || strings.Contains(err.Error(), "failed to fetch user email from SSO state") {
 			handleOauthNotValid(w)
 			return
 		}
@@ -74,12 +75,23 @@ func handleAzureCallback(w http.ResponseWriter, r *http.Request) {
 		inviteExists = true
 	}
 	// check if user approval is already pending
-	if !inviteExists && logic.IsPendingUser(content.UserPrincipalName) {
+	if !inviteExists && logic.IsPendingUser(content.Email) {
 		handleOauthUserSignUpApprovalPending(w)
 		return
 	}
-
-	_, err = logic.GetUser(content.UserPrincipalName)
+	// if user exists with provider ID, convert them into email ID
+	user, err := logic.GetUser(content.UserPrincipalName)
+	if err == nil {
+		_, err := logic.GetUser(content.Email)
+		if err != nil {
+			user.UserName = content.Email
+			user.ExternalIdentityProviderID = content.UserPrincipalName
+			database.DeleteRecord(database.USERS_TABLE_NAME, content.UserPrincipalName)
+			d, _ := json.Marshal(user)
+			database.Insert(user.UserName, string(d), database.USERS_TABLE_NAME)
+		}
+	}
+	_, err = logic.GetUser(content.Email)
 	if err != nil {
 		if database.IsEmptyRecord(err) { // user must not exist, so try to make one
 			if inviteExists {
@@ -89,20 +101,20 @@ func handleAzureCallback(w http.ResponseWriter, r *http.Request) {
 					logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
 					return
 				}
-				user.UserName = content.UserPrincipalName // override username with azure id
+				user.ExternalIdentityProviderID = content.UserPrincipalName
 				if err = logic.CreateUser(&user); err != nil {
 					handleSomethingWentWrong(w)
 					return
 				}
 				logic.DeleteUserInvite(content.Email)
-				logic.DeletePendingUser(content.UserPrincipalName)
+				logic.DeletePendingUser(content.Email)
 			} else {
-				if !isEmailAllowed(content.UserPrincipalName) {
+				if !isEmailAllowed(content.Email) {
 					handleOauthUserNotAllowedToSignUp(w)
 					return
 				}
 				err = logic.InsertPendingUser(&models.User{
-					UserName: content.UserPrincipalName,
+					UserName: content.Email,
 				})
 				if err != nil {
 					handleSomethingWentWrong(w)
@@ -116,7 +128,7 @@ func handleAzureCallback(w http.ResponseWriter, r *http.Request) {
 			return
 		}
 	}
-	user, err := logic.GetUser(content.UserPrincipalName)
+	user, err = logic.GetUser(content.Email)
 	if err != nil {
 		handleOauthUserNotFound(w)
 		return
@@ -136,7 +148,7 @@ func handleAzureCallback(w http.ResponseWriter, r *http.Request) {
 	}
 	// send a netmaker jwt token
 	var authRequest = models.UserAuthParams{
-		UserName: content.UserPrincipalName,
+		UserName: content.Email,
 		Password: newPass,
 	}
 
@@ -146,8 +158,8 @@ func handleAzureCallback(w http.ResponseWriter, r *http.Request) {
 		return
 	}
 
-	logger.Log(1, "completed azure OAuth sigin in for", content.UserPrincipalName)
-	http.Redirect(w, r, servercfg.GetFrontendURL()+"/login?login="+jwt+"&user="+content.UserPrincipalName, http.StatusPermanentRedirect)
+	logger.Log(1, "completed azure OAuth sigin in for", content.Email)
+	http.Redirect(w, r, servercfg.GetFrontendURL()+"/login?login="+jwt+"&user="+content.Email, http.StatusPermanentRedirect)
 }
 
 func getAzureUserInfo(state string, code string) (*OAuthUser, error) {
@@ -187,6 +199,10 @@ func getAzureUserInfo(state string, code string) (*OAuthUser, error) {
 	if userInfo.Email == "" {
 		userInfo.Email = getUserEmailFromClaims(token.AccessToken)
 	}
+	if userInfo.Email == "" {
+		err = errors.New("failed to fetch user email from SSO state")
+		return userInfo, err
+	}
 	return userInfo, nil
 }
 

+ 71 - 13
pro/auth/github.go

@@ -3,6 +3,7 @@ package auth
 import (
 	"context"
 	"encoding/json"
+	"errors"
 	"fmt"
 	"io"
 	"net/http"
@@ -60,7 +61,7 @@ func handleGithubCallback(w http.ResponseWriter, r *http.Request) {
 	var content, err = getGithubUserInfo(rState, rCode)
 	if err != nil {
 		logger.Log(1, "error when getting user info from github:", err.Error())
-		if strings.Contains(err.Error(), "invalid oauth state") {
+		if strings.Contains(err.Error(), "invalid oauth state") || strings.Contains(err.Error(), "failed to fetch user email from SSO state") {
 			handleOauthNotValid(w)
 			return
 		}
@@ -74,11 +75,25 @@ func handleGithubCallback(w http.ResponseWriter, r *http.Request) {
 		inviteExists = true
 	}
 	// check if user approval is already pending
-	if !inviteExists && logic.IsPendingUser(content.Login) {
+	if !inviteExists && logic.IsPendingUser(content.Email) {
 		handleOauthUserSignUpApprovalPending(w)
 		return
 	}
-	_, err = logic.GetUser(content.Login)
+	// if user exists with provider ID, convert them into email ID
+	user, err := logic.GetUser(content.Login)
+	if err == nil {
+		// checks if user exists with email
+		_, err := logic.GetUser(content.Email)
+		if err != nil {
+			user.UserName = content.Email
+			user.ExternalIdentityProviderID = content.Login
+			database.DeleteRecord(database.USERS_TABLE_NAME, content.Login)
+			d, _ := json.Marshal(user)
+			database.Insert(user.UserName, string(d), database.USERS_TABLE_NAME)
+		}
+
+	}
+	_, err = logic.GetUser(content.Email)
 	if err != nil {
 		if database.IsEmptyRecord(err) { // user must not exist, so try to make one
 			if inviteExists {
@@ -88,20 +103,20 @@ func handleGithubCallback(w http.ResponseWriter, r *http.Request) {
 					logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
 					return
 				}
-				user.UserName = content.Login // overrides email with github id
+				user.ExternalIdentityProviderID = content.Login
 				if err = logic.CreateUser(&user); err != nil {
 					handleSomethingWentWrong(w)
 					return
 				}
 				logic.DeleteUserInvite(content.Email)
-				logic.DeletePendingUser(content.Login)
+				logic.DeletePendingUser(content.Email)
 			} else {
-				if !isEmailAllowed(content.Login) {
+				if !isEmailAllowed(content.Email) {
 					handleOauthUserNotAllowedToSignUp(w)
 					return
 				}
 				err = logic.InsertPendingUser(&models.User{
-					UserName: content.Login,
+					UserName: content.Email,
 				})
 				if err != nil {
 					handleSomethingWentWrong(w)
@@ -115,7 +130,7 @@ func handleGithubCallback(w http.ResponseWriter, r *http.Request) {
 			return
 		}
 	}
-	user, err := logic.GetUser(content.Login)
+	user, err = logic.GetUser(content.Email)
 	if err != nil {
 		handleOauthUserNotFound(w)
 		return
@@ -135,7 +150,7 @@ func handleGithubCallback(w http.ResponseWriter, r *http.Request) {
 	}
 	// send a netmaker jwt token
 	var authRequest = models.UserAuthParams{
-		UserName: content.Login,
+		UserName: content.Email,
 		Password: newPass,
 	}
 
@@ -145,11 +160,11 @@ func handleGithubCallback(w http.ResponseWriter, r *http.Request) {
 		return
 	}
 
-	logger.Log(1, "completed github OAuth sigin in for", content.Login)
-	http.Redirect(w, r, servercfg.GetFrontendURL()+"/login?login="+jwt+"&user="+content.Login, http.StatusPermanentRedirect)
+	logger.Log(1, "completed github OAuth sigin in for", content.Email)
+	http.Redirect(w, r, servercfg.GetFrontendURL()+"/login?login="+jwt+"&user="+content.Email, http.StatusPermanentRedirect)
 }
 
-func getGithubUserInfo(state string, code string) (*OAuthUser, error) {
+func getGithubUserInfo(state, code string) (*OAuthUser, error) {
 	oauth_state_string, isValid := logic.IsStateValid(state)
 	if (!isValid || state != oauth_state_string) && !isStateCached(state) {
 		return nil, fmt.Errorf("invalid oauth state")
@@ -187,7 +202,16 @@ func getGithubUserInfo(state string, code string) (*OAuthUser, error) {
 	}
 	userInfo.AccessToken = string(data)
 	if userInfo.Email == "" {
-		userInfo.Email = getUserEmailFromClaims(token.AccessToken)
+		// if user's email is not made public, get the info from the github emails api
+		logger.Log(2, "fetching user email from github api")
+		userInfo.Email, err = getGithubEmailsInfo(token.AccessToken)
+		if err != nil {
+			logger.Log(0, "failed to fetch user's email from github: ", err.Error())
+		}
+	}
+	if userInfo.Email == "" {
+		err = errors.New("failed to fetch user email from SSO state")
+		return userInfo, err
 	}
 	return userInfo, nil
 }
@@ -195,3 +219,37 @@ func getGithubUserInfo(state string, code string) (*OAuthUser, error) {
 func verifyGithubUser(token *oauth2.Token) bool {
 	return token.Valid()
 }
+
+func getGithubEmailsInfo(accessToken string) (string, error) {
+
+	var httpClient = &http.Client{}
+	var httpReq, reqErr = http.NewRequest("GET", "https://api.github.com/user/emails", nil)
+	if reqErr != nil {
+		return "", fmt.Errorf("failed to create request to GitHub")
+	}
+	httpReq.Header.Add("Accept", "application/vnd.github.v3+json")
+	httpReq.Header.Set("Authorization", "token "+accessToken)
+	response, err := httpClient.Do(httpReq)
+	if err != nil {
+		return "", fmt.Errorf("failed getting user info: %s", err.Error())
+	}
+	defer response.Body.Close()
+	contents, err := io.ReadAll(response.Body)
+	if err != nil {
+		return "", fmt.Errorf("failed reading response body: %s", err.Error())
+	}
+
+	emailsInfo := []interface{}{}
+	err = json.Unmarshal(contents, &emailsInfo)
+	if err != nil {
+		return "", err
+	}
+	for _, info := range emailsInfo {
+		emailInfoMap := info.(map[string]interface{})
+		if emailInfoMap["primary"].(bool) {
+			return emailInfoMap["email"].(string), nil
+		}
+
+	}
+	return "", errors.New("email not found")
+}

+ 1 - 1
pro/auth/google.go

@@ -90,7 +90,7 @@ func handleGoogleCallback(w http.ResponseWriter, r *http.Request) {
 					logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
 					return
 				}
-
+				user.ExternalIdentityProviderID = content.Email
 				if err = logic.CreateUser(&user); err != nil {
 					handleSomethingWentWrong(w)
 					return

+ 2 - 2
pro/auth/oidc.go

@@ -80,10 +80,9 @@ func handleOIDCCallback(w http.ResponseWriter, r *http.Request) {
 		handleOauthNotConfigured(w)
 		return
 	}
-
 	var inviteExists bool
 	// check if invite exists for User
-	in, err := logic.GetUserInvite(content.Login)
+	in, err := logic.GetUserInvite(content.Email)
 	if err == nil {
 		inviteExists = true
 	}
@@ -102,6 +101,7 @@ func handleOIDCCallback(w http.ResponseWriter, r *http.Request) {
 					logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
 					return
 				}
+				user.ExternalIdentityProviderID = content.Email
 				if err = logic.CreateUser(&user); err != nil {
 					handleSomethingWentWrong(w)
 					return

+ 0 - 2
pro/controllers/users.go

@@ -849,7 +849,6 @@ func getUserRemoteAccessGwsV1(w http.ResponseWriter, r *http.Request) {
 		return
 	}
 	userGwNodes := proLogic.GetUserRAGNodes(*user)
-	logger.Log(0, fmt.Sprintf("1. User Gw Nodes: %+v", userGwNodes))
 	for _, extClient := range allextClients {
 		node, ok := userGwNodes[extClient.IngressGatewayID]
 		if !ok {
@@ -885,7 +884,6 @@ func getUserRemoteAccessGwsV1(w http.ResponseWriter, r *http.Request) {
 			delete(userGwNodes, node.ID.String())
 		}
 	}
-	logger.Log(0, fmt.Sprintf("2. User Gw Nodes: %+v", userGwNodes))
 	// add remaining gw nodes to resp
 	for gwID := range userGwNodes {
 		node, err := logic.GetNodeByID(gwID)